cbb/
lib.rs

1use std::ffi::OsString;
2use std::path::PathBuf;
3
4use regex::Regex;
5use structopt::StructOpt;
6
7#[derive(Debug, StructOpt)]
8#[structopt(rename_all = "kebab-case")]
9pub struct Opt {
10    /// Path of the comic folder to bind
11    ///
12    /// If the recursive option is set, consider the folder given as a library containing multiples
13    /// folders. It will not treat the given folder as a comic in itself.
14    // TODO: check if I really do this or not when implementing the recursive option
15    pub comic_folder: PathBuf,
16
17    /// Do a dry run of the program printing to stdout the action it would take
18    ///
19    /// Print how each file would be renamed.
20    #[structopt(long)]
21    pub dry_run: bool,
22
23    /// Set the pad you want to use
24    ///
25    /// If not set or set to a value inferior to the maximum pad of the comic, the value is
26    /// ignored.
27    #[structopt(long)]
28    pub pad: Option<usize>,
29
30    // TODO: implement the prefix
31    // /// Prefix to use for the files
32    // ///
33    // /// This is usefull when the images contains a number before their page number
34    // /// For example: when the title contains the name of the comic with its season: fooS02-42.png
35    // #[structopt(short, long)]
36    // pub prefix: &str,
37
38    // // TODO: implement the recursivity
39    // /// Recursively treat each child folder as a it's own comic to bind
40    // #[structopt(short, long)]
41    // pub recursive: bool,
42}
43
44impl Opt {
45    pub fn new<I, T>(args: I) -> Self
46    where
47        I: IntoIterator<Item = T>,
48        T: Into<OsString> + Clone,
49    {
50        Opt::from_iter(args)
51    }
52}
53
54#[derive(Debug)]
55struct Page {
56    prefix: String,
57    number: String,
58    suffix: String,
59}
60
61impl Page {
62    fn new(prefix: String, number: String, suffix: String) -> Page {
63        Page {
64            prefix,
65            number,
66            suffix,
67        }
68    }
69
70    fn original_filename(&self) -> String {
71        format!("{}{}{}", self.prefix, self.number, self.suffix)
72    }
73
74    fn new_filename(&self, pad: usize) -> String {
75        // format!("{:0pad$}-{}{}{}", number, self.prefix, self.number, self.suffix, pad = pad)
76        // TODO I think I will need this on in the end
77        format!(
78            "{}{:0pad$}{}",
79            self.prefix,
80            self.number.parse::<u32>().unwrap(),
81            self.suffix,
82            pad = pad
83        )
84    }
85}
86
87pub struct ComicBook {
88    pad: Option<usize>,
89    pages: Vec<Page>,
90}
91
92impl ComicBook {
93    pub fn new(files: Vec<String>, pad: Option<usize>) -> ComicBook {
94        let regex = Regex::new(r"(?P<prefix>\D*)(?P<number>\d*)(?P<suffix>.*)").unwrap();
95        ComicBook {
96            pad,
97            pages: files
98                .iter()
99                .map(|entry| {
100                    dbg!(entry.as_str());
101                    let caps = dbg!(regex.captures(entry.as_str())).unwrap();
102
103                    Page::new(
104                        String::from(caps.name("prefix").map_or("", |c| c.as_str())),
105                        String::from(&caps["number"]), // FIXME => ignore the file?
106                        String::from(caps.name("suffix").map_or("", |c| c.as_str())),
107                    )
108                })
109                .collect(),
110            //~ book: path.iter().map(|name| Page::new(name)).collect(),
111        }
112    }
113
114    pub fn bind<T>(&mut self, transform_page: T)
115    where
116        T: Fn(String, String),
117    {
118        let pad = self.get_pad_size().expect("get_pad_size"); // TODO have a nice error message (the user may habe specifyed a folder that does not contains cb pages)
119
120        for page in self.pages.iter() {
121            //if let Some(pos) = page.position {
122            let original_file = page.original_filename();
123            let new_file = page.new_filename(pad);
124
125            transform_page(original_file, new_file);
126            //}
127        }
128    }
129
130    fn get_pad_size(&self) -> Option<usize> {
131        let pad_pages = self
132            .pages
133            .iter()
134            .max_by_key(|x| x.number.len())?
135            .number
136            .len();
137
138        let pad = if let Some(pad_conf) = self.pad {
139            if pad_conf < pad_pages {
140                pad_pages
141            } else {
142                pad_conf
143            }
144        } else {
145            pad_pages
146        };
147        Some(pad)
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154    use std::path::Path;
155    //~ use std::sys_common::io::test::{TempDir, tmpdir};
156
157    #[test]
158    fn comic_book_creation() {
159        //~ let tmpdir = tmpdir();
160        //~ println!("{}", tmpdir);
161        //~ let comic = ComicBook::new(&[
162        //~ Path::new("01.png").to_path_buf(),
163        //~ Path::new("02.png").to_path_buf(),
164        //~ ]);
165
166        //~ println!("{:?}", comic.book);
167
168        assert!(false);
169        //~ assert_eq!(comic.book, [Page {filename: "01.png", position: None}, Page {filename: "02.png", position: None}]);
170    }
171
172    //~ #[test]
173    //~ fn detection_alpha() {
174    //~ let l = Unsorted(vec!["1", "2", "10"]);
175    //~ assert_eq!(l.detect_sort_type(), false);
176    //~ }
177
178    //~ #[test]
179    //~ fn detection_numerical() {
180    //~ let l = Unsorted(vec!["01", "02", "10"]);
181    //~ assert_eq!(l.detect_sort_type(), true);
182    //~ }
183}