Browse Source

Implement subcommand inferring based on calling name with feature flag

See issue timvisee/ffsend#24
timvisee 6 years ago
parent
commit
a6deb9155b
5 changed files with 109 additions and 15 deletions
  1. 7 4
      Cargo.toml
  2. 42 10
      README.md
  3. 39 1
      src/cmd/handler.rs
  4. 19 0
      src/config.rs
  5. 2 0
      src/util.rs

+ 7 - 4
Cargo.toml

@@ -50,7 +50,7 @@ name = "ffsend"
 path = "src/main.rs"
 path = "src/main.rs"
 
 
 [features]
 [features]
-default = ["archive", "clipboard", "history", "send2", "send3", "qrcode", "urlshorten"]
+default = ["archive", "clipboard", "history", "infer-command", "qrcode", "send2", "send3", "urlshorten"]
 
 
 # Compile with file archiving support
 # Compile with file archiving support
 archive = ["tar"]
 archive = ["tar"]
@@ -58,9 +58,6 @@ archive = ["tar"]
 # Compile with file history support
 # Compile with file history support
 history = []
 history = []
 
 
-# Compile without colored output support
-no-color = ["colored/no-color"]
-
 # Support for Firefox Send v2
 # Support for Firefox Send v2
 send2 = ["ffsend-api/send2"]
 send2 = ["ffsend-api/send2"]
 
 
@@ -73,6 +70,12 @@ qrcode = ["qr2term"]
 # Support for shortening share URLs
 # Support for shortening share URLs
 urlshorten = ["urlshortener"]
 urlshorten = ["urlshortener"]
 
 
+# Support for inferring subcommand when linking binary
+infer-command = []
+
+# Compile without colored output support
+no-color = ["colored/no-color"]
+
 [dependencies]
 [dependencies]
 chbs = "0.0.8"
 chbs = "0.0.8"
 chrono = "0.4"
 chrono = "0.4"

+ 42 - 10
README.md

@@ -388,16 +388,17 @@ Different use flags are available for `ffsend` to toggle whether to include
 various features.
 various features.
 The following features are available, some of which are enabled by default:
 The following features are available, some of which are enabled by default:
 
 
-| Feature     | Enabled | Description                                                |
-| :---------: | :-----: | :--------------------------------------------------------- |
-| `send2`     | Default | Support for Firefox Send v2 servers                        |
-| `send3`     | Default | Support for Firefox Send v3 servers                        |
-| `clipboard` | Default | Support for copying links to the clipboard                 |
-| `history`   | Default | Support for tracking files in history                      |
-| `archive`   | Default | Support for archiving and extracting uploads and downloads |
-| `qrcode`    | Default | Support for rendering a QR code for a share URL            |
-| `urlshorten`| Default | Support for shortening share URLs                          |
-| `no-color`  |         | Compile without color support in error and help messages   |
+| Feature        | Enabled | Description                                                |
+| :------------: | :-----: | :--------------------------------------------------------- |
+| `send2`        | Default | Support for Firefox Send v2 servers                        |
+| `send3`        | Default | Support for Firefox Send v3 servers                        |
+| `clipboard`    | Default | Support for copying links to the clipboard                 |
+| `history`      | Default | Support for tracking files in history                      |
+| `archive`      | Default | Support for archiving and extracting uploads and downloads |
+| `qrcode`       | Default | Support for rendering a QR code for a share URL            |
+| `urlshorten`   | Default | Support for shortening share URLs                          |
+| `infer-command`| Default | Support for inferring subcommand based on binary name      |
+| `no-color`     |         | Compile without color support in error and help messages   |
 
 
 To enable features during building or installation, specify them with
 To enable features during building or installation, specify them with
 `--features <features...>` when using `cargo`.
 `--features <features...>` when using `cargo`.
@@ -454,6 +455,37 @@ empty.
 At this time, no configuration or _dotfile_ file support is available.
 At this time, no configuration or _dotfile_ file support is available.
 This will be something added in a later release.
 This will be something added in a later release.
 
 
+### Binary for each subcommand: `ffput`, `ffget`
+`ffsend` supports having a separate binary for a single subcommand, such as
+having `ffput` and `ffget` just for uploading and downloading through `ffsend`.
+This allows simple commands like:
+```bash
+ffput my-file.txt
+ffget https://send.firefox.com/#sample-share-url
+```
+
+This works for a predefined list of binary names:
+* `ffput` -> `ffsend upload ...`
+* `ffget` -> `ffsend download ...`
+* `ffdel` -> `ffsend delete ...`
+* _This list is defined in [`src/config.rs`](./src/config.rs) as `INFER_COMMANDS`_
+
+Symbolic or hard links may be created having these names for this functionality
+(similar to [`busybox`](https://en.wikipedia.org/wiki/BusyBox#Single_binary)),
+so you don't have to clone the `ffsend` binary for each subcommand.  
+On Linux and macOS you can use the following commands for setting up these
+symbolic links in the current directory:
+```bash
+ln -s $(which ffsend) ./ffput
+ln -s $(which ffsend) ./ffget
+```
+
+Support for this feature is only available when `ffsend` is compiled with the
+[`infer-command`](#compile-features--use-flags) feature flag.
+This is usually enabled by default.
+To verify support is available with an existing installation, make sure the
+feature is listed when invoking `ffsend debug`.
+
 ## Security
 ## Security
 In short; the `ffsend` tool and the [Send][send] service can be considered
 In short; the `ffsend` tool and the [Send][send] service can be considered
 secure, and may be used to share sensitive files. Note though that the
 secure, and may be used to share sensitive files. Note though that the

+ 39 - 1
src/cmd/handler.rs

@@ -1,5 +1,8 @@
 extern crate directories;
 extern crate directories;
 
 
+#[cfg(feature = "infer-command")]
+use std::ffi::OsString;
+
 use clap::{App, AppSettings, Arg, ArgMatches};
 use clap::{App, AppSettings, Arg, ArgMatches};
 
 
 use super::arg::{ArgApi, CmdArg};
 use super::arg::{ArgApi, CmdArg};
@@ -15,9 +18,13 @@ use super::subcmd::{
     CmdDebug, CmdDelete, CmdDownload, CmdExists, CmdInfo, CmdParams, CmdPassword, CmdUpload,
     CmdDebug, CmdDelete, CmdDownload, CmdExists, CmdInfo, CmdParams, CmdPassword, CmdUpload,
     CmdVersion,
     CmdVersion,
 };
 };
+#[cfg(feature = "infer-command")]
+use crate::config::INFER_COMMANDS;
 use crate::config::{CLIENT_TIMEOUT, CLIENT_TRANSFER_TIMEOUT};
 use crate::config::{CLIENT_TIMEOUT, CLIENT_TRANSFER_TIMEOUT};
 #[cfg(feature = "history")]
 #[cfg(feature = "history")]
 use crate::util::app_history_file_path_string;
 use crate::util::app_history_file_path_string;
+#[cfg(feature = "infer-command")]
+use crate::util::bin_name;
 
 
 #[cfg(feature = "history")]
 #[cfg(feature = "history")]
 lazy_static! {
 lazy_static! {
@@ -188,9 +195,40 @@ impl<'a: 'b, 'b> Handler<'a> {
 
 
     /// Parse CLI arguments.
     /// Parse CLI arguments.
     pub fn parse() -> Handler<'a> {
     pub fn parse() -> Handler<'a> {
+        // Obtain the program arguments
+        #[allow(unused_mut)]
+        let mut args: Vec<_> = ::std::env::args_os().collect();
+
+        // Infer subcommand based on binary name
+        #[cfg(feature = "infer-command")]
+        Self::infer_subcommand(&mut args);
+
         // Build the application CLI definition, get the matches
         // Build the application CLI definition, get the matches
         Handler {
         Handler {
-            matches: Handler::build().get_matches(),
+            matches: Handler::build().get_matches_from(args),
+        }
+    }
+
+    /// Infer subcommand when the binary has a predefined name,
+    /// modifying the given program arguments.
+    ///
+    /// If no subcommand could be inferred, the `args` list leaves unchanged.
+    /// See `crate::config::INFER_COMMANDS` for a list of commands.
+    ///
+    /// When the `ffsend` binary is called with such a name, the corresponding subcommand is
+    /// automatically inserted as argument. This also works when calling binaries through symbolic
+    /// or hard links.
+    #[cfg(feature = "infer-command")]
+    fn infer_subcommand(args: &mut Vec<OsString>) {
+        // Get the name of the called binary
+        let name = bin_name();
+
+        // Infer subcommands
+        for (bin, subcmd) in INFER_COMMANDS.iter() {
+            if &name == bin {
+                args.insert(1, subcmd.into());
+                break;
+            }
         }
         }
     }
     }
 
 

+ 19 - 0
src/config.rs

@@ -1,3 +1,6 @@
+#[cfg(feature = "infer-command")]
+use std::collections::HashMap;
+
 use ffsend_api::api::{DesiredVersion, Version};
 use ffsend_api::api::{DesiredVersion, Version};
 
 
 /// The timeout for the Send client for generic requests, `0` to disable.
 /// The timeout for the Send client for generic requests, `0` to disable.
@@ -15,3 +18,19 @@ pub const API_VERSION_DESIRED_DEFAULT: DesiredVersion = DesiredVersion::Assume(A
 pub const API_VERSION_ASSUME: Version = Version::V3;
 pub const API_VERSION_ASSUME: Version = Version::V3;
 #[cfg(not(feature = "send3"))]
 #[cfg(not(feature = "send3"))]
 pub const API_VERSION_ASSUME: Version = Version::V2;
 pub const API_VERSION_ASSUME: Version = Version::V2;
+
+#[cfg(feature = "infer-command")]
+lazy_static! {
+    /// Hashmap holding binary names to infer subcommands for.
+    ///
+    /// When the `ffsend` binary is called with such a name, the corresponding subcommand is
+    /// automatically inserted as argument. This also works when calling binaries through symbolic
+    /// or hard links.
+    pub static ref INFER_COMMANDS: HashMap<&'static str, &'static str> = {
+        let mut m = HashMap::new();
+        m.insert("ffput", "upload");
+        m.insert("ffget", "download");
+        m.insert("ffdel", "delete");
+        m
+    };
+}

+ 2 - 0
src/util.rs

@@ -849,6 +849,8 @@ pub fn features_list() -> Vec<&'static str> {
     features.push("qrcode");
     features.push("qrcode");
     #[cfg(feature = "urlshorten")]
     #[cfg(feature = "urlshorten")]
     features.push("urlshorten");
     features.push("urlshorten");
+    #[cfg(feature = "infer-command")]
+    features.push("infer-command");
     #[cfg(feature = "no-qcolor")]
     #[cfg(feature = "no-qcolor")]
     features.push("no-color");
     features.push("no-color");
     #[cfg(feature = "send2")]
     #[cfg(feature = "send2")]