Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_syntax/src/argv.rs

4.4 KiB, 1 run

created by r1870400018:62022, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! The driver between a process's command line and a syntax: `help`, `--help` and `--version`
2//! are answered here, so they never reach the caller's dispatch, and the rest is parsed as one
3//! message.
4use crate::{
5 core::SyntaxRef,
6 help::Page,
7 msg::Msg,
8};
9
10use oxedyne_fe2o3_core::prelude::*;
11
12use std::ffi::OsString;
13
14
15/// What a command line asked for.
16#[derive(Clone, Debug)]
17pub enum Parsed {
18 Run(Msg),
19 Help(Page),
20 Version,
21}
22
23/// How close a misspelt command or option must be to a real one to be suggested.
24pub const SIMILARITY_THRESHOLD: f64 = 0.4;
25
26/// Reads a process's arguments, `std::env::args_os()` or the like, program name first.
27///
28/// With no words, or `help`, `-h` or `--help` first, the answer is the command table.
29/// `help <command>` or `help <topic>` is that page, and `help --all` is everything. A
30/// command followed by `-h` or `--help` where an option could stand is the command's page;
31/// as an option's value, after a `--`, or among the words of a verbatim value, either is a
32/// word like any other. `--version` or `-V` alone is the version. Otherwise the words are a
33/// message.
34pub fn parse<I, S>(syntax: &SyntaxRef, args: I) -> Outcome<Parsed>
35 where
36 I: IntoIterator<Item=S>,
37 S: Into<OsString>,
38{
39 let words = res!(words(args));
40 read(syntax, words)
41}
42
43/// The words after the program name, each of which must be UTF-8. A word that is not is
44/// refused by position rather than panicking as `std::env::args` does.
45pub fn words<I, S>(args: I) -> Outcome<Vec<String>>
46 where
47 I: IntoIterator<Item=S>,
48 S: Into<OsString>,
49{
50 let mut out = Vec::new();
51 for (i, arg) in args.into_iter().enumerate().skip(1) {
52 match arg.into().into_string() {
53 Ok(s) => out.push(s),
54 Err(os) => return Err(err!(
55 "The word at position {} is not UTF-8 ('{}'), and command line words must \
56 be.", i, os.to_string_lossy();
57 Input, Invalid, UTF8)),
58 }
59 }
60 Ok(out)
61}
62
63/// As `parse`, for words already taken from the command line.
64pub fn read(syntax: &SyntaxRef, words: Vec<String>) -> Outcome<Parsed> {
65 let first = match words.first() {
66 Some(w) => w.as_str(),
67 None => return Ok(Parsed::Help(Page::Summary)),
68 };
69 if words.len() == 1 && (first == "--version" || first == "-V") {
70 return Ok(Parsed::Version);
71 }
72 if Msg::is_help_flag(first) && words.len() == 1 {
73 return Ok(Parsed::Help(Page::Summary));
74 }
75 if first == "help" && syntax.get_cmd("help").is_none() {
76 return match words.get(1).map(|w| w.as_str()) {
77 None => Ok(Parsed::Help(Page::Summary)),
78 Some("--all") => Ok(Parsed::Help(Page::All)),
79 Some(name) => {
80 if words.len() > 2 {
81 return Err(err!(
82 "The word '{}' at position 3 is more than help takes: help names one \
83 command or topic.", words[2];
84 Input, Excessive));
85 }
86 page_for(syntax, name)
87 },
88 };
89 }
90 // The parser says where a help flag stands, since only it knows whether the word is a value.
91 let msg = Msg::new(syntax.clone());
92 let msg = res!(msg.rx_argv(words, Some(SIMILARITY_THRESHOLD)));
93 if msg.end.help {
94 return Ok(Parsed::Help(match &msg.end.cmd {
95 Some(name) => Page::Command(name.clone()),
96 None => Page::Summary,
97 }));
98 }
99 Ok(Parsed::Run(msg))
100}
101
102fn page_for(syntax: &SyntaxRef, name: &str) -> Outcome<Parsed> {
103 if syntax.get_cmd(name).is_some() {
104 return Ok(Parsed::Help(Page::Command(name.to_string())));
105 }
106 if syntax.get_topic(name).is_some() {
107 return Ok(Parsed::Help(Page::Topic(name.to_string())));
108 }
109 let mut names: Vec<String> = syntax.cmds_in_order().into_iter()
110 .map(|c| c.config().name.clone()).collect();
111 for topic in &syntax.config().topics {
112 names.push(topic.name.clone());
113 }
114 Err(match Msg::closest(name, &names, SIMILARITY_THRESHOLD) {
115 Some(suggestion) => err!(
116 "Did you mean '{}'? There is no command or help topic '{}' in '{}'.",
117 suggestion, name, syntax.config().name;
118 Input, Invalid, Suggestion),
119 None => err!(
120 "There is no command or help topic '{}' in '{}'.",
121 name, syntax.config().name;
122 Input, Invalid),
123 })
124}