diff options
| author | Fuwn <[email protected]> | 2022-03-17 02:22:29 -0700 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2022-03-17 02:22:29 -0700 |
| commit | 421e019fe7049f3ef7dd28b1387b21413ba57a51 (patch) | |
| tree | b6051cffec0277df588dabb87c92fb10de3c8902 | |
| parent | docs(license): add for vergen (diff) | |
| download | cli-421e019fe7049f3ef7dd28b1387b21413ba57a51.tar.xz cli-421e019fe7049f3ef7dd28b1387b21413ba57a51.zip | |
feat: 0.1.0 :star:
| -rw-r--r-- | .github/workflows/check.yaml | 35 | ||||
| -rw-r--r-- | .github/workflows/release.yaml | 50 | ||||
| -rw-r--r-- | .gitignore | 7 | ||||
| -rw-r--r-- | .license_template | 17 | ||||
| -rw-r--r-- | Cargo.toml | 46 | ||||
| -rw-r--r-- | Makefile.toml | 38 | ||||
| -rw-r--r-- | README.rst | 93 | ||||
| -rw-r--r-- | build.rs | 19 | ||||
| -rw-r--r-- | rust-toolchain.toml | 2 | ||||
| -rw-r--r-- | rustfmt.toml | 30 | ||||
| -rw-r--r-- | src/export.rs | 96 | ||||
| -rw-r--r-- | src/main.rs | 303 |
12 files changed, 736 insertions, 0 deletions
diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml new file mode 100644 index 0000000..47f63a3 --- /dev/null +++ b/.github/workflows/check.yaml @@ -0,0 +1,35 @@ +name: Check ✅ + +on: + workflow_dispatch: + push: + paths: + - "*" + pull_request: + paths: + - "*" + +env: + CARGO_TERM_COLOR: always + +jobs: + check: + runs-on: ubuntu-latest + steps: + - name: Checkout 🛒 + uses: actions/checkout@v3 + + - name: Toolchain 🧰 + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly-2022-02-22 + components: rustfmt, clippy + override: true + + - name: Check ✅ + uses: actions-rs/cargo@v1 + continue-on-error: false + with: + command: check + args: --verbose diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..43df957 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,50 @@ +name: Release ⚾ + +on: + workflow_dispatch: + push: + tags: + - '*' + +jobs: + release: + runs-on: ubuntu-20.04 + steps: + - name: Checkout 🛒 + uses: actions/checkout@v2 + + - name: Toolchain 🧰 + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly-2022-02-22 + components: rustfmt, clippy + override: true + + - name: Build 🏗 + uses: actions-rs/cargo@v1 + continue-on-error: false + with: + command: build + args: --release + + - name: Create Release 🏉 + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ github.token }} + with: + tag_name: ${{ github.ref }} + release_name: ${{ github.ref }} + draft: false + prerelease: false + + - name: Upload Artifacts to Release 💎 + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ github.token }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./target/release/senpy + asset_name: senpy + asset_content_type: application/x-elf # x-msdownload for Windows diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..59a8c42 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +# Rust +target/ +Cargo.lock +**/*.rs.bk + +# IDE +/.idea/ diff --git a/.license_template b/.license_template new file mode 100644 index 0000000..f2af2b7 --- /dev/null +++ b/.license_template @@ -0,0 +1,17 @@ +// This file is part of senpy-cli <https://github.com/senpy-club/cli>. +// Copyright (C) {20\d{2}(-20\d{2})?} Fuwn <[email protected]> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// +// Copyright (C) {20\d{2}(-20\d{2})?} Fuwn <[email protected]> +// SPDX-License-Identifier: GPL-3.0-only
\ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..705d216 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,46 @@ +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[package] +name = "senpy-cli" +version = "0.1.0" +authors = ["Fuwn <[email protected]>"] +edition = "2021" +description = "Command-line interface to The Senpy Club API" +documentation = "https://docs.rs/senpy-cli/" +readme = "README.rst" +homepage = "https://github.com/senpy-club/cli" +repository = "https://github.com/senpy-club/cli" +license = "GPL-3.0-only" +keywords = ["rust", "anime", "senpy"] +categories = ["web-programming", "web-programming::http-server", "command-line-interface", "command-line-utilities"] + +[[bin]] +name = "senpy" +path = "src/main.rs" + +# Slower builds, faster executables +[profile.release] +lto = "fat" +codegen-units = 1 +# Optimize for size +opt-level = "s" + +[build-dependencies] +# Build variables +vergen = "7.0.0" + +# `Result` +anyhow = "1.0.56" + +[dependencies] +# CLI +clap = "3.1.6" + +# The Senpy Club API +senpy = "0.1.2" + +# Serialization +serde = "1.0.136" +serde_json = "1.0.79" +serde_yaml = "0.8.23" +serde_dhall = "0.11.0" diff --git a/Makefile.toml b/Makefile.toml new file mode 100644 index 0000000..f459978 --- /dev/null +++ b/Makefile.toml @@ -0,0 +1,38 @@ +# ------------ +# | Wrappers | +# ------------ +[tasks.fmt] +command = "cargo" +args = ["fmt"] +private = true + +[tasks.check] +command = "cargo" +args = ["check"] +private = true + +[tasks.clippy] +command = "cargo" +args = ["clippy"] +private = true + +# ------------- +# | Executors | +# ------------- +[tasks.checkf] +dependencies = ["fmt", "check"] + +[tasks.checkfc] +dependencies = ["fmt", "check", "clippy"] + +[tasks.docs] +workspace = false +toolchain = "nightly" +command = "cargo" +args = ["doc", "--open", "--document-private-items", "--no-deps"] + +[tasks.run] +workspace = false +dependencies = ["checkfc"] +command = "cargo" +args = ["run", "--bin", "senpy", "--", "${@}"] diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..446c710 --- /dev/null +++ b/README.rst @@ -0,0 +1,93 @@ +:code:`senpy-cli` +========= + +The Senpy Club CLI is a tool that provides quick access to The Senpy Club API +from the command-line! + +Get data **fast** from The Senpy Club API either as stdout or to a supported +file format. + +Quick links +^^^^^^^^^^^ + +.. raw:: html + + <p> + <a href="https://discord.com/invite/yWKgRT6"> + <img src="https://img.shields.io/discord/246524734718738442" + alt="Discord" /> + </a> + <a href="https://saythanks.io/to/[email protected]"> + <img src="https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg" + alt="Say Thanks" /> + </a> + <a href="LICENSE"> + <img src="https://img.shields.io/github/license/senpy-club/cli" + alt="License" /> + </a> + <a href="https://crates.io/crates/senpy-cli"> + <img src="https://img.shields.io/crates/v/senpy-cli.svg" + alt="Crate" /> + </a> + <a href="https://docs.rs/senpy-cli"> + <img src="https://docs.rs/senpy-cli/badge.svg" + alt="Documentation" /> + </a> + <a href="https://github.com/senpy-club/lci/actions/workflows/check.yaml"> + <img src="https://github.com/senpy-club/cli/actions/workflows/check.yaml/badge.svg?branch=main" + alt="Build Status" /> + </a> + </p> + +Installation +^^^^^^^^^^^^ + +Install from crates.io +---------------------- + +.. code-block:: shell + + $ cargo install senpy-cli --force + +Download from releases +---------------------- + +Alternatively, prebuilt binaries for x86_64-based Linux systems are available in +the `releases <https://github.com/senpy-club/cli/releases/latest>`_. If you are +using a different operating system or architecture such as macOS or Windows; +you'll have to build and install The Senpy Club CLI yourself! + +Install from self-compile +------------------------- + +.. code-block:: shell + + $ cargo install --git https://github.com/senpy-club/cli --branch main + +If you are building and installing yourself; you must have +`Rust <https://www.rust-lang.org/>`_ installed! + +Usage +^^^^^ + +The Senpy Club CLI allows you to export to stdout in the form of +ten-space-seperated columns or to a file in the JSON, YAML, or Dhall formats. + +Examples +-------- + +.. code-block:: shell + + $ senpy languages # Prints all available languages in a single column + $ senpy languages -t # Prints all available languages in one column and the fetch time in another + $ senpy languages -f languages.json # Exports all available languages to a JSON file + $ senpy languages -f languages.yaml # Exports all available languages to a YAML file + $ senpy languages -f languages.dhall # Exports all available languages to a Dhall file + $ senpy random | awk '{ print $1 }' # Prints the first column (language) from stdout on *nix-based environments + +Feel free to explore the rest of the available commands with :code:`senpy help`! + +License +^^^^^^^ + +`GNU General Public License v3.0 <https://github.com/senpy-club/api-worker/blob/main/LICENSE>`_ diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..5512a66 --- /dev/null +++ b/build.rs @@ -0,0 +1,19 @@ +// This file is part of senpy-cli <https://github.com/senpy-club/cli>. +// Copyright (C) 2022-2022 Fuwn <[email protected]> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// +// Copyright (C) 2022-2022 Fuwn <[email protected]> +// SPDX-License-Identifier: GPL-3.0-only + +fn main() -> anyhow::Result<()> { vergen::vergen(vergen::Config::default()) } diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..22a9829 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly-2022-02-22" diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..1dd4c90 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,30 @@ +condense_wildcard_suffixes = true +edition = "2021" +enum_discrim_align_threshold = 20 +# error_on_line_overflow = true +# error_on_unformatted = true +fn_single_line = true +force_multiline_blocks = true +format_code_in_doc_comments = true +format_macro_matchers = true +format_strings = true +imports_layout = "HorizontalVertical" +license_template_path = ".license_template" +max_width = 80 +match_arm_blocks = false +imports_granularity = "Crate" +newline_style = "Unix" +normalize_comments = true +normalize_doc_attributes = true +reorder_impl_items = true +group_imports = "StdExternalCrate" +reorder_modules = true +report_fixme = "Always" +# report_todo = "Always" +struct_field_align_threshold = 20 +struct_lit_single_line = false +tab_spaces = 2 +use_field_init_shorthand = true +use_try_shorthand = true +where_single_line = true +wrap_comments = true diff --git a/src/export.rs b/src/export.rs new file mode 100644 index 0000000..6d95b65 --- /dev/null +++ b/src/export.rs @@ -0,0 +1,96 @@ +// This file is part of senpy-cli <https://github.com/senpy-club/cli>. +// Copyright (C) 2022-2022 Fuwn <[email protected]> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// +// Copyright (C) 2022-2022 Fuwn <[email protected]> +// SPDX-License-Identifier: GPL-3.0-only + +use std::{ + fs::File, + io::{BufWriter, Write}, +}; + +use senpy::Random; + +pub trait Exporter { + fn random_to_file(_: &str, _: Random); + fn vec_to_file(_: &str, _: Vec<String>); +} + +pub struct JsonExporter; +pub struct YamlExporter; +pub struct DhallExporter; + +impl Exporter for JsonExporter { + fn random_to_file(filename: &str, random: Random) { + let writer = BufWriter::new(File::create(filename).unwrap()); + + serde_json::to_writer_pretty( + writer, + &serde_json::json!({ + "language": random.language, + "image": random.image, + }), + ) + .unwrap(); + } + + fn vec_to_file(filename: &str, languages: Vec<String>) { + let writer = BufWriter::new(File::create(filename).unwrap()); + + serde_json::to_writer_pretty(writer, &languages).unwrap(); + } +} + +impl Exporter for YamlExporter { + fn random_to_file(filename: &str, random: Random) { + let writer = BufWriter::new(File::create(filename).unwrap()); + + serde_yaml::to_writer(writer, &random).unwrap() + } + + fn vec_to_file(filename: &str, languages: Vec<String>) { + let writer = BufWriter::new(File::create(filename).unwrap()); + + serde_yaml::to_writer(writer, &languages).unwrap(); + } +} + +impl Exporter for DhallExporter { + fn random_to_file(filename: &str, random: Random) { + let mut writer = BufWriter::new(File::create(filename).unwrap()); + + writer + .write_all( + serde_dhall::serialize(&random) + .to_string() + .unwrap() + .as_bytes(), + ) + .unwrap(); + } + + fn vec_to_file(filename: &str, languages: Vec<String>) { + let mut writer = BufWriter::new(File::create(filename).unwrap()); + + writer + .write_all( + serde_dhall::serialize(&languages) + .to_string() + .unwrap() + .as_bytes(), + ) + .unwrap(); + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..96513f6 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,303 @@ +// This file is part of senpy-cli <https://github.com/senpy-club/cli>. +// Copyright (C) 2022-2022 Fuwn <[email protected]> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// +// Copyright (C) 2022-2022 Fuwn <[email protected]> +// SPDX-License-Identifier: GPL-3.0-only + +mod export; + +use clap::{Arg, Command}; + +use crate::export::Exporter; + +fn main() { + let matches = clap::Command::new(env!("CARGO_PKG_NAME")) + .version(&*format!( + "{}(1)-{}-({})-{}", + env!("CARGO_PKG_VERSION"), + env!("VERGEN_CARGO_PROFILE"), + env!("VERGEN_CARGO_TARGET_TRIPLE"), + env!("VERGEN_GIT_SHA").get(0..7).unwrap(), + )) + .author(env!("CARGO_PKG_AUTHORS")) + .subcommand_required(true) + .subcommands(vec![ + Command::new("languages") + .alias("langs") + .about("Prints all available languages") + .arg(Arg::new("time").long("time").short('t')) + .arg(Arg::new("file").long("file").short('f').takes_value(true)), + Command::new("language") + .alias("lang") + .arg_required_else_help(true) + .arg(Arg::new("language").required(true)) + .about("Prints all available images of a given language") + .arg(Arg::new("time").long("time").short('t')) + .arg(Arg::new("file").long("file").short('f').takes_value(true)), + Command::new("random") + .alias("rand") + .about("Prints a random language and image pair") + .arg(Arg::new("time").long("time").short('t')) + .arg(Arg::new("file").long("file").short('f').takes_value(true)), + ]) + .get_matches(); + + match matches.subcommand() { + Some(("languages", matches_languages)) => { + let time = std::time::Instant::now(); + let languages = senpy::languages(); + let time_taken = time.elapsed(); + + match languages { + Ok(languages) => { + let time_export = std::time::Instant::now(); + let mut exported = false; + + if matches_languages.is_present("file") { + if matches_languages.value_of("file").unwrap().contains("json") { + crate::export::JsonExporter::vec_to_file( + matches_languages.value_of("file").unwrap(), + languages, + ); + + exported = true; + } else if matches_languages + .value_of("file") + .unwrap() + .contains("yaml") + || matches_languages.value_of("file").unwrap().contains("yml") + { + crate::export::YamlExporter::vec_to_file( + matches_languages.value_of("file").unwrap(), + languages, + ); + + exported = true; + } else if matches_languages + .value_of("file") + .unwrap() + .contains("dhall") + { + crate::export::DhallExporter::vec_to_file( + matches_languages.value_of("file").unwrap(), + languages, + ); + + exported = true; + } + + if exported { + println!( + "time taken to export to file: {} ns", + time_export.elapsed().as_nanos(), + ); + } + } else { + // https://stackoverflow.com/a/30380640/14452787 + println!( + "{0: <10} {1: <10}", + "Languages", + if matches_languages.is_present("time") { + "Time" + } else { + "" + } + ); + + for (index, language) in languages.into_iter().enumerate() { + println!( + "{0: <10} {1: <10}", + language, + if matches_languages.is_present("time") && index == 0 { + time_taken.as_millis().to_string() + } else { + "".to_string() + } + ); + } + } + } + Err(e) => println!("{0: <10} {1: <10}", "Error", e), + } + } + Some(("language", matches_language)) => { + let time = std::time::Instant::now(); + let languages = senpy::languages(); + let time_taken = time.elapsed(); + + match languages { + Ok(languages) => { + let language = &languages + .into_iter() + .filter(|l| { + l.to_lowercase() + == matches_language + .value_of("language") + .unwrap() + .to_lowercase() + }) + .collect::<Vec<String>>()[0]; + let time_2 = std::time::Instant::now(); + let images = senpy::language(language.to_string().as_str()); + let time_taken_2 = time_2.elapsed(); + + match images { + Ok(images) => { + let time_export = std::time::Instant::now(); + let mut exported = false; + + if matches_language.is_present("file") { + if matches_language.value_of("file").unwrap().contains("json") { + crate::export::JsonExporter::vec_to_file( + matches_language.value_of("file").unwrap(), + images, + ); + + exported = true; + } else if matches_language + .value_of("file") + .unwrap() + .contains("yaml") + || matches_language.value_of("file").unwrap().contains("yml") + { + crate::export::YamlExporter::vec_to_file( + matches_language.value_of("file").unwrap(), + images, + ); + + exported = true; + } else if matches_language + .value_of("file") + .unwrap() + .contains("dhall") + { + crate::export::DhallExporter::vec_to_file( + matches_language.value_of("file").unwrap(), + images, + ); + + exported = true; + } + + if exported { + println!( + "time taken to export to file: {} ns", + time_export.elapsed().as_nanos(), + ); + } + } else { + // https://stackoverflow.com/a/30380640/14452787 + println!( + "{0: <10} {1: <10}", + "Images", + if matches_language.is_present("time") { + "Time" + } else { + "" + } + ); + + for (index, image) in images.into_iter().enumerate() { + println!( + "{0: <10} {1: <10}", + image, + if matches_language.is_present("time") && index == 0 { + (time_taken + time_taken_2).as_millis().to_string() + } else { + "".to_string() + } + ); + } + } + } + Err(e) => println!("{0: <10} {1: <10}", "Error", e), + } + } + Err(e) => println!("{0: <10} {1: <10}", "Error", e), + } + } + Some(("random", matches_random)) => { + let time = std::time::Instant::now(); + let random = senpy::random(); + let time_taken = time.elapsed(); + + match random { + Ok(image) => { + let time_export = std::time::Instant::now(); + let mut exported = false; + + if matches_random.is_present("file") { + if matches_random.value_of("file").unwrap().contains("json") { + crate::export::JsonExporter::random_to_file( + matches_random.value_of("file").unwrap(), + image, + ); + + exported = true; + } else if matches_random.value_of("file").unwrap().contains("yaml") + || matches_random.value_of("file").unwrap().contains("yml") + { + crate::export::YamlExporter::random_to_file( + matches_random.value_of("file").unwrap(), + image, + ); + + exported = true; + } else if matches_random.value_of("file").unwrap().contains("dhall") + { + crate::export::DhallExporter::random_to_file( + matches_random.value_of("file").unwrap(), + image, + ); + + exported = true; + } + + if exported { + println!( + "time taken to export to file: {} ns", + time_export.elapsed().as_nanos(), + ); + } + } else { + // https://stackoverflow.com/a/30380640/14452787 + println!( + "{0: <10} {1: <10} {2: <10}", + "Language", + "Image", + if matches_random.is_present("time") { + "Time" + } else { + "" + } + ); + println!( + "{0: <10} {1: <10} {2: <10}", + image.language, + image.image, + if matches_random.is_present("time") { + time_taken.as_millis().to_string() + } else { + "".to_string() + } + ); + } + } + Err(e) => println!("{0: <10} {1: <10}", "Error", e), + } + } + _ => unreachable!(), + } +} |