1use bpaf::*;
5use std::{ffi::OsString, path::PathBuf};
6
7#[derive(Debug, Clone, Default)]
8pub struct Perms {
9 read: bool,
10 write: bool,
11 exec: bool,
12}
13
14#[derive(Debug, Clone)]
15pub enum Perm {
16 All(Perms),
17 Any(Perms),
18 Exact(Perms),
19}
20
21#[derive(Debug, Clone)]
22#[allow(dead_code)]
23pub struct Options {
24 paths: Vec<PathBuf>,
25 exec: Option<Vec<OsString>>,
26 user: Option<String>,
27 perm: Option<Perm>,
28}
29
30fn user() -> impl Parser<Option<String>> {
32 let tag = literal("-user").anywhere();
34 let value = positional("USER").help("User name");
35 construct!(tag, value)
36 .adjacent()
37 .map(|pair| pair.1)
38 .optional()
39}
40
41fn exec() -> impl Parser<Option<Vec<OsString>>> {
43 let tag = literal("-exec")
44 .help("for every file find finds execute a separate shell command")
45 .anywhere();
46
47 let item = any::<OsString, _, _>("ITEM", |s| (s != ";").then_some(s))
48 .help("command with its arguments, find will replace {} with a file name")
49 .many();
50
51 let endtag = any::<String, _, _>(";", |s| (s == ";").then_some(()))
52 .help("anything after literal \";\" will be considered a regular option again");
53
54 construct!(tag, item, endtag)
55 .adjacent()
56 .map(|triple| triple.1)
57 .optional()
58}
59
60fn perm() -> impl Parser<Option<Perm>> {
62 fn parse_mode(input: &str) -> Result<Perms, String> {
63 let mut perms = Perms::default();
64 for c in input.chars() {
65 match c {
66 'r' => perms.read = true,
67 'w' => perms.write = true,
68 'x' => perms.exec = true,
69 _ => return Err(format!("{} is not a valid permission string", input)),
70 }
71 }
72 Ok(perms)
73 }
74
75 let tag = literal("-mode").anywhere();
76
77 let mode = any("MODE", Some)
80 .help("(perm | -perm | /perm), where perm is any subset of rwx characters, ex +rw")
81 .parse::<_, _, String>(|s: String| {
82 if let Some(m) = s.strip_prefix('-') {
83 Ok(Perm::All(parse_mode(m)?))
84 } else if let Some(m) = s.strip_prefix('/') {
85 Ok(Perm::Any(parse_mode(m)?))
86 } else {
87 Ok(Perm::Exact(parse_mode(&s)?))
88 }
89 });
90
91 construct!(tag, mode)
92 .adjacent()
93 .map(|pair| pair.1)
94 .optional()
95}
96
97pub fn options() -> OptionParser<Options> {
98 let paths = positional::<PathBuf>("PATH").many();
99
100 construct!(Options {
101 exec(),
102 user(),
103 perm(),
104 paths,
105 })
106 .to_options()
107}
108
109fn main() {
110 println!("{:#?}", options().run());
111}