1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
use clap::{self, App, SubCommand, Arg, AppSettings};
use std::time::Duration;
use std::path::PathBuf;
use std::env::home_dir;
use std::str::FromStr;
use std::fs;
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub enum Subsystem {
Init {
force: bool,
},
AddUser {
verbose: bool,
},
QueueTweet {
file_to_load: Option<PathBuf>,
},
StartDaemon {
delay: Duration,
verbose: bool,
},
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct Options {
pub config_dir: (String, PathBuf),
pub subsystem: Subsystem,
}
impl Options {
pub fn parse() -> Options {
let matches = App::new("tweetr")
.version(crate_version!())
.author(crate_authors!())
.setting(AppSettings::ColoredHelp)
.setting(AppSettings::VersionlessSubcommands)
.setting(AppSettings::SubcommandRequiredElseHelp)
.about("tweetr is a platform that allows you to create and queue tweets to be shared when YOU want.\n\
You create content when you have time and then use FOSS and NOT pay whatever-ridiculous\n\
amount of $$$ for posting them automatically")
.arg(Arg::from_usage("-c --config-dir=[CONFIG_DIR] 'Directory containing configuration. Default: $HOME/.tweetr'")
.validator(Options::config_dir_validator))
.subcommand(SubCommand::with_name("init")
.about("Initialise global app data")
.arg(Arg::from_usage("-f --force 'Override current app configuration'")))
.subcommand(SubCommand::with_name("add-user")
.about("Add and authorise a user")
.arg(Arg::from_usage("-v --verbose 'Print more user data'")))
.subcommand(SubCommand::with_name("queue-tweet")
.about("Add a tweet to the queue")
.arg(Arg::from_usage("-f --file=[file] 'Load tweets from the specified file'").validator(Options::tweets_file_validator)))
.subcommand(SubCommand::with_name("start-daemon")
.about("Start the tweet-posting daemon")
.args(&[Arg::from_usage("-v --verbose 'Log all network requests'"),
Arg::from_usage("--delay=<delay> 'How long to wait between trying to post again [ms]'")
.default_value("60000")
.validator(Options::duration_validator)]))
.get_matches();
Options {
config_dir: match matches.value_of("config-dir") {
Some(dirs) => (dirs.to_string(), fs::canonicalize(dirs).unwrap()),
None => {
match home_dir() {
Some(mut hd) => {
hd = hd.canonicalize().unwrap();
hd.push(".tweetr");
fs::create_dir_all(&hd).unwrap();
("$HOME/.tweetr".to_string(), hd)
}
None => {
clap::Error {
message: "Couldn't automatically get home directory, please specify configuration directory with the -c option".to_string(),
kind: clap::ErrorKind::MissingRequiredArgument,
info: None,
}
.exit()
}
}
}
},
subsystem: match matches.subcommand() {
("init", Some(init_matches)) => Subsystem::Init { force: init_matches.is_present("force") },
("add-user", Some(add_user_matches)) => Subsystem::AddUser { verbose: add_user_matches.is_present("verbose") },
("queue-tweet", Some(queue_tweet_matches)) => {
Subsystem::QueueTweet { file_to_load: queue_tweet_matches.value_of("file").map(fs::canonicalize).map(Result::unwrap) }
}
("start-daemon", Some(start_daemon_matches)) => {
Subsystem::StartDaemon {
delay: Duration::from_millis(u64::from_str(start_daemon_matches.value_of("delay").unwrap()).unwrap()),
verbose: start_daemon_matches.is_present("verbose"),
}
}
_ => panic!("No subcommand passed"),
},
}
}
fn config_dir_validator(s: String) -> Result<(), String> {
fs::canonicalize(&s).map(|_| ()).map_err(|_| format!("Configuration directory \"{}\" not found", s))
}
fn tweets_file_validator(s: String) -> Result<(), String> {
fs::canonicalize(&s).map(|_| ()).map_err(|_| format!("File with tweets \"{}\" not found", s))
}
fn duration_validator(s: String) -> Result<(), String> {
u64::from_str(&s).map(|_| ()).map_err(|_| format!("\"{}\" is not a valid amount of milliseconds", s))
}
}