twixter

A twtxt command line client in Rust

git clone git://git.shimmy1996.com/twixter.git
commit d809f22b81a6685d489625f71d8245a2034137c3
parent 33c61e698c63ddbbdb457c246485d2dcc634fbac
Author: Shimmy Xu <shimmy.xu@shimmy1996.com>
Date:   Fri,  6 Sep 2019 00:19:26 -0400

Implement pre_tweet_hook and post_tweet_hook

Diffstat:
MCargo.toml | 4++--
Mconfig.example | 9+++++++--
Msrc/main.rs | 99++++++++++++++++++++++++++++++++++++++++---------------------------------------
3 files changed, 59 insertions(+), 53 deletions(-)
diff --git a/Cargo.toml b/Cargo.toml
@@ -8,4 +8,5 @@ edition = "2018"
 chrono = "~0.4.8"
 clap = "~2.33.0"
 dirs = "~2.0.2"
-rust-ini = "~0.13.0"-
\ No newline at end of file
+rust-ini = "~0.13.0"
+strfmt = "0.1.6"
diff --git a/config.example b/config.example
@@ -2,5 +2,9 @@
 nick = user
 twtfile = ~/.local/share/twixter/twtxt.txt
 twturl = https://example.org/twtxt.txt
-scp_addr = user@example.org:~/public_html/twtxt.txt
-scp_port = 22
+pre_tweet_hook = "scp buckket@example.org:~/public_html/twtxt.txt {twtfile}"
+post_tweet_hook = "scp {twtfile} buckket@example.org:~/public_html/twtxt.txt"
+
+[following]
+bob = https://example.org/bob.txt
+alice = https://example.org/alice.txt+
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
@@ -1,18 +1,19 @@
 use std::collections::HashMap;
-use std::fs::{File, OpenOptions};
+use std::fs::OpenOptions;
 use std::io::prelude::*;
 use std::path::Path;
 use std::process::Command;
 
-use clap::{crate_version, App, Arg, SubCommand};
+use clap::{crate_version, App, Arg, ArgMatches, SubCommand};
+use strfmt::strfmt;
 
 #[derive(Debug)]
 struct Config {
     nick: String,
     twtfile: String,
     twturl: String,
-    scp_addr: String,
-    scp_port: String,
+    pre_tweet_hook: String,
+    post_tweet_hook: String,
     following: HashMap<String, String>,
 }
 
@@ -31,13 +32,16 @@ impl Config {
         *following
             .entry(twtxt_config["nick"].to_owned())
             .or_default() = twtxt_config["twturl"].to_owned();
+        // Parse hook commands.
+        let pre_tweet_hook = strfmt(&twtxt_config["pre_tweet_hook"], twtxt_config).unwrap();
+        let post_tweet_hook = strfmt(&twtxt_config["post_tweet_hook"], twtxt_config).unwrap();
 
         Config {
             nick: twtxt_config["nick"].to_owned(),
             twtfile: twtxt_config["twtfile"].to_owned(),
             twturl: twtxt_config["twturl"].to_owned(),
-            scp_addr: twtxt_config["scp_addr"].to_owned(),
-            scp_port: twtxt_config["scp_port"].to_owned(),
+            pre_tweet_hook: pre_tweet_hook,
+            post_tweet_hook: post_tweet_hook,
             following: following,
         }
     }
@@ -101,59 +105,56 @@ fn main() {
         std::fs::create_dir_all(twtfile_path).unwrap();
     }
 
-    // Fetch current twtfile.
-    fetch(&config);
-
-    // Add user post.
-    let content = command
-        .subcommand_matches("tweet")
-        .and_then(|subcommand| {
-            subcommand.args.get("content").and_then(|matched_arg| {
-                Some(
-                    matched_arg
-                        .vals
-                        .iter()
-                        .map(|os_string| os_string.clone().into_string().unwrap())
-                        .collect::<Vec<String>>()
-                        .join(" "),
-                )
-            })
+    // Parse subcommands.
+    match command.subcommand() {
+        ("tweet", Some(subcommand)) => tweet(&config, &subcommand),
+        _ => {}
+    }
+}
+
+fn tweet(config: &Config, subcommand: &ArgMatches) {
+    // Helper to run the tweet subcommand.
+
+    // Parse tweet content.
+    let content = subcommand
+        .args
+        .get("content")
+        .and_then(|matched_arg| {
+            Some(
+                matched_arg
+                    .vals
+                    .iter()
+                    .map(|os_string| os_string.clone().into_string().unwrap())
+                    .collect::<Vec<String>>()
+                    .join(" "),
+            )
         })
         .unwrap_or_default();
 
     if content == "" {
         eprintln!("Error: post content must not be empty");
     } else {
-        let post = compose(content);
-
-        let mut file = OpenOptions::new()
+        // Run pre tweet hook.
+        Command::new("sh")
+            .args(&["-c", &config.pre_tweet_hook])
+            .output()
+            .expect("Failed to run pre tweet hook");
+
+        // Write tweet.
+        OpenOptions::new()
             .append(true)
             .create(true)
-            .open(&config.twtfile)
-            .unwrap();
-
-        file.write(post.as_bytes())
+            .open(Path::new(&config.twtfile))
+            .unwrap()
+            .write(compose(content).as_bytes())
             .expect("Unable to write new post");
-    }
-
-    // Publish to remote.
-    publish(&config);
-}
 
-fn fetch(config: &Config) {
-    // Uses scp to fetch latest twtfile.
-    Command::new("scp")
-        .args(&["-P", &config.scp_port, &config.scp_addr, &config.twtfile])
-        .output()
-        .expect("Failed to pull!");
-}
-
-fn publish(config: &Config) {
-    // Publishes twtfile to remote.
-    Command::new("scp")
-        .args(&["-P", &config.scp_port, &config.twtfile, &config.scp_addr])
-        .output()
-        .expect("Failed to publish!");
+        // Run post tweet hook.
+        Command::new("sh")
+            .args(&["-c", &config.post_tweet_hook])
+            .output()
+            .expect("Failed to run post tweet hook");
+    }
 }
 
 fn compose(content: String) -> String {