Changeset 83d90d2 in gignore


Ignore:
Timestamp:
19 Jan 2026, 19:49:46 (3 months ago)
Author:
Asher Mullaney <asher.mullaney@…>
Branches:
error_handling, file_locations, help, json_tlib, logging, man, multi_arg, trunk, visibility
Children:
1503595
Parents:
b6c2333
Message:

Refactoring complete.

Files:
1 added
3 edited

Legend:

Unmodified
Added
Removed
  • src/lib.rs

    rb6c2333 r83d90d2  
    1 #![warn(clippy::unwrap_used)]
    2 #![warn(clippy::all, clippy::pedantic, clippy::restriction)]
     1#![warn(clippy::unwrap_used, clippy::single_call_fn)]
    32
    4 mod template;
    5 
    6 use clap::{Subcommand, Parser};
     3pub mod template;
    74
    85use std::{
    9     fs::{self, File, OpenOptions,},
    10     io::Write,
     6    fs::{File, OpenOptions},
    117    error::Error,
    128    process::exit,
    13     io,
    149    path::Path,
    1510};
    1611
    17 trait HandleErr {
     12pub trait HandleErr {
    1813    type OkType;
    1914
     
    4540}
    4641
    47 #[derive(Parser)]
    48 #[command(about = ".gitignore manipulator tool.", long_about = None, version)]
    49 pub struct ClapArgs {
    50     #[command(subcommand)]
    51     pub operation: OperCommand,
    52 }
    5342
    54 #[derive(Subcommand)]
    55 pub enum OperCommand {
    56     /// Removes paths within .gitignore, or the file itself.
    57     Remove {
    58         /// Path to remove
    59         path: Option<String>,
    60     },
    61 
    62     /// Adds paths or .gitignore file.
    63     Add {
    64         /// Path to add
    65         path: Option<String>,
    66     },
    67 
    68     /// Lists items within .gitignore
    69     List {
    70         /// Print contents on one line, separated by whitespace.
    71         #[arg(long)]
    72         oneline: bool,
    73     },
    74 
    75     /// Add, remove, or use templates.
    76     Template {
    77         #[command(subcommand)]
    78         operation: TemplateCommand,
    79     },
    80 }
    81 
    82 #[derive(Subcommand)]
    83 pub enum TemplateCommand {
    84     /// Add template names and source files to the library
    85     Add {
    86         /// Name to add to library
    87         name: String,
    88         /// Source file to add to library
    89         source_file: String,
    90 
    91         #[arg(long, short)]
    92         /// Append template to .gitignore when using (rather than truncating)
    93         append: bool,
    94     },
    95 
    96     /// Remove templates from the library
    97     Remove {
    98         /// Name to remove from library
    99         name: String,
    100     },
    101 
    102     /// Use templates
    103     Use {
    104         /// Name to write to gitignore
    105         name: String,
    106     },
    107 
    108     /// List entries in library
    109     List {
    110         #[arg(long)]
    111         /// Print entries on one line
    112         oneline: bool
    113     },
    114    
    115     /// Clear template library
    116     Clear,
    117 }
    118 
    119 pub fn handle(args: &ClapArgs) {
    120     match &args.operation {
    121 
    122         OperCommand::Add {
    123             path
    124         } => {
    125             let Some(path) = path else {
    126                 File::create(".gitignore").handle_err("creating file");
    127 
    128                 std::process::exit(0);
    129             };
    130 
    131             if fs::read_to_string(".gitignore")
    132                 .handle_err("reading file")
    133                     .lines()
    134                     .collect::<Vec<&str>>()
    135                     .contains(&&path[..]) {
    136                         println!("That path already exists in .gitignore!");
    137                         exit(1);
    138             }
    139 
    140             let mut append_handle = open_append(".gitignore".as_ref()).handle_err("opening file for append");
    141 
    142             append_handle.write_all(format!("{}\n", path.trim()).as_bytes()).handle_err("writing to file");
    143         },
    144         OperCommand::Remove {
    145             path
    146         } => {
    147             let Some(path) = path else {
    148                 if fs::exists(".gitignore").handle_err("searching for file") {
    149                     loop {
    150                         println!("This will permanently remove .gitignore!");
    151                         println!("Do you really want to do this? y/N");
    152                         let mut s = String::new();
    153                         io::stdin()
    154                             .read_line(&mut s)
    155                             .handle_err("reading stdin");
    156 
    157                         match &s.trim().to_lowercase()[..] {
    158                             "" | "n" => break,
    159                             "y" => {
    160                                 println!("Removing .gitignore...");
    161 
    162                                 fs::remove_file(".gitignore").handle_err("removing file");
    163 
    164                                 exit(0);
    165                             }
    166                             _ => {},
    167                         }
    168                     }
    169                     exit(0);
    170                 } else {
    171                     println!("File is already removed.");
    172                     exit(1);
    173                 }
    174             };
    175 
    176             let contents = fs::read_to_string(".gitignore").handle_err("reading file");
    177 
    178             let mut lines: Vec<&str> =  contents.lines().collect();
    179 
    180             let mut extracted = lines.extract_if(.., |line| *line == path);
    181 
    182             if extracted.next().is_none() {
    183                 eprintln!("There was no match for `{path}`.");
    184                 exit(0);
    185             }
    186 
    187             drop(extracted);
    188 
    189             open_truncate(".gitignore".as_ref()).handle_err("opening file for truncate").write_all(b"").handle_err("writing to file");
    190 
    191             // Re-add lines to file, without [PATH].
    192             for line in lines {
    193                 open_append(".gitignore".as_ref()).handle_err("opening file for append")
    194                     .write_all(
    195                         format!("{line}\n").as_bytes()
    196                     ).handle_err("writing to file");
    197             }
    198         },
    199 
    200         OperCommand::List {
    201             oneline
    202         } => {
    203             let result = fs::read_to_string(".gitignore");
    204 
    205             if let Err(e) = result {
    206                 println!("Error reading file: {e}");
    207                 exit(1);
    208             }
    209 
    210             #[expect(clippy::missing_panics_doc)]
    211             #[expect(clippy::unwrap_used)]
    212             // unwrap() is okay here because result is checked for Err in a diverging if let
    213             // statement above. This means result will never be Err here.
    214             let result = result.unwrap();
    215 
    216             if *oneline {
    217                 for line in result.lines() {
    218                     print!("{} ", line.trim());
    219                 }
    220                 println!();
    221                 exit(0);
    222             }
    223 
    224             println!("{}", result.trim());
    225         },
    226 
    227         OperCommand::Template {
    228             operation: TemplateCommand::Add {
    229                 name,
    230                 source_file,
    231                 append,
    232             }
    233         } => {
    234             template::add(&template::Template::new(source_file.as_ref(), name, *append));
    235         }
    236 
    237         OperCommand::Template {
    238             operation: TemplateCommand::Remove {
    239                 name,
    240             }
    241         } => {
    242             template::remove(name);
    243         },
    244 
    245         OperCommand::Template {
    246             operation: TemplateCommand::List {
    247                 oneline,
    248             }
    249         } => {
    250             template::list(*oneline);
    251         },
    252 
    253         OperCommand::Template {
    254             operation: TemplateCommand::Use {
    255                 name,
    256             }
    257         } => {
    258             template::gitignore_write(name);
    259         },
    260 
    261         OperCommand::Template {
    262             operation: TemplateCommand::Clear
    263         } => {
    264             template::clear();
    265         }
    266     }
    267 }
    268 
    269 fn open_append(path: &Path) -> Result<File, Box<dyn Error>> {
     43pub fn open_append(path: &Path) -> Result<File, Box<dyn Error>> {
    27044    Ok(
    27145        OpenOptions::new()
     
    27549    )
    27650}
    277 fn open_truncate(path: &Path) -> Result<File, Box<dyn Error>> {
     51pub fn open_truncate(path: &Path) -> Result<File, Box<dyn Error>> {
    27852    Ok(
    27953        OpenOptions::new()
  • src/main.rs

    rb6c2333 r83d90d2  
    1 use clap::Parser;
    2 use gignore::ClapArgs;
     1use clap::{Parser, Subcommand};
     2use gignore::{
     3    open_append,
     4    open_truncate,
     5    HandleErr,
     6    template,
     7};
     8
     9use std::{
     10    fs::{self, File},
     11    io::Write,
     12    process::exit,
     13    io,
     14};
     15
     16
     17#[derive(Parser)]
     18#[command(about = ".gitignore manipulator tool.", long_about = None, version)]
     19struct ClapArgs {
     20    #[command(subcommand)]
     21    operation: OperCommand,
     22}
     23
     24#[derive(Subcommand)]
     25enum OperCommand {
     26    /// Removes paths within .gitignore, or the file itself.
     27    Remove {
     28        /// Path to remove
     29        path: Option<String>,
     30    },
     31
     32    /// Adds paths or .gitignore file.
     33    Add {
     34        /// Path to add
     35        path: Option<String>,
     36    },
     37
     38    /// Lists items within .gitignore
     39    List {
     40        /// Print contents on one line, separated by whitespace.
     41        #[arg(long)]
     42        oneline: bool,
     43    },
     44
     45    /// Add, remove, or use templates.
     46    Template {
     47        #[command(subcommand)]
     48        operation: TemplateCommand,
     49    },
     50}
     51
     52#[derive(Subcommand)]
     53enum TemplateCommand {
     54    /// Add template names and source files to the library
     55    Add {
     56        /// Name to add to library
     57        name: String,
     58        /// Source file to add to library
     59        source_file: String,
     60
     61        #[arg(long, short)]
     62        /// Append template to .gitignore when using (rather than truncating)
     63        append: bool,
     64    },
     65
     66    /// Remove templates from the library
     67    Remove {
     68        /// Name to remove from library
     69        name: String,
     70    },
     71
     72    /// Use templates
     73    Use {
     74        /// Name to write to gitignore
     75        name: String,
     76    },
     77
     78    /// List entries in library
     79    List {
     80        #[arg(long)]
     81        /// Print entries on one line
     82        oneline: bool
     83    },
     84   
     85    /// Clear template library
     86    Clear,
     87}
    388
    489fn main() {
    590    let args = ClapArgs::parse();
    691
    7     gignore::handle(&args);
    8 }
     92    match &args.operation {
     93
     94        OperCommand::Add {
     95            path
     96        } => {
     97            let Some(path) = path else {
     98                File::create(".gitignore").handle_err("creating file");
     99
     100                std::process::exit(0);
     101            };
     102
     103            if fs::read_to_string(".gitignore")
     104                .handle_err("reading file")
     105                    .lines()
     106                    .collect::<Vec<&str>>()
     107                    .contains(&&path[..]) {
     108                        println!("That path already exists in .gitignore!");
     109                        exit(1);
     110            }
     111
     112            let mut append_handle = open_append(".gitignore".as_ref()).handle_err("opening file for append");
     113
     114            append_handle.write_all(format!("{}\n", path.trim()).as_bytes()).handle_err("writing to file");
     115        },
     116        OperCommand::Remove {
     117            path
     118        } => {
     119            let Some(path) = path else {
     120                if fs::exists(".gitignore").handle_err("searching for file") {
     121                    loop {
     122                        println!("This will permanently remove .gitignore!");
     123                        println!("Do you really want to do this? y/N");
     124                        let mut s = String::new();
     125                        io::stdin()
     126                            .read_line(&mut s)
     127                            .handle_err("reading stdin");
     128
     129                        match &s.trim().to_lowercase()[..] {
     130                            "" | "n" => break,
     131                            "y" => {
     132                                println!("Removing .gitignore...");
     133
     134                                fs::remove_file(".gitignore").handle_err("removing file");
     135
     136                                exit(0);
     137                            }
     138                            _ => {},
     139                        }
     140                    }
     141                    exit(0);
     142                } else {
     143                    println!("File is already removed.");
     144                    exit(1);
     145                }
     146            };
     147
     148            let contents = fs::read_to_string(".gitignore").handle_err("reading file");
     149
     150            let mut lines: Vec<&str> =  contents.lines().collect();
     151
     152            let mut extracted = lines.extract_if(.., |line| *line == path);
     153
     154            if extracted.next().is_none() {
     155                eprintln!("There was no match for `{path}`.");
     156                exit(0);
     157            }
     158
     159            drop(extracted);
     160
     161            open_truncate(".gitignore".as_ref()).handle_err("opening file for truncate").write_all(b"").handle_err("writing to file");
     162
     163            // Re-add lines to file, without [PATH].
     164            for line in lines {
     165                open_append(".gitignore".as_ref()).handle_err("opening file for append")
     166                    .write_all(
     167                        format!("{line}\n").as_bytes()
     168                    ).handle_err("writing to file");
     169            }
     170        },
     171
     172        OperCommand::List {
     173            oneline
     174        } => {
     175            let result = fs::read_to_string(".gitignore");
     176
     177            if let Err(e) = result {
     178                println!("Error reading file: {e}");
     179                exit(1);
     180            }
     181
     182            #[expect(clippy::missing_panics_doc)]
     183            #[expect(clippy::unwrap_used)]
     184            // unwrap() is okay here because result is checked for Err in a diverging if let
     185            // statement above. This means result will never be Err here.
     186            let result = result.unwrap();
     187
     188            if *oneline {
     189                for line in result.lines() {
     190                    print!("{} ", line.trim());
     191                }
     192                println!();
     193                exit(0);
     194            }
     195
     196            println!("{}", result.trim());
     197        },
     198
     199        OperCommand::Template {
     200            operation: TemplateCommand::Add {
     201                name,
     202                source_file,
     203                append,
     204            }
     205        } => {
     206            template::add(&template::Template::new(source_file.as_ref(), name, *append));
     207        }
     208
     209        OperCommand::Template {
     210            operation: TemplateCommand::Remove {
     211                name,
     212            }
     213        } => {
     214            template::remove(name);
     215        },
     216
     217        OperCommand::Template {
     218            operation: TemplateCommand::List {
     219                oneline,
     220            }
     221        } => {
     222            template::list(*oneline);
     223        },
     224
     225        OperCommand::Template {
     226            operation: TemplateCommand::Use {
     227                name,
     228            }
     229        } => {
     230            template::gitignore_write(name);
     231        },
     232
     233        OperCommand::Template {
     234            operation: TemplateCommand::Clear
     235        } => {
     236            template::clear();
     237        }
     238    }
     239}
  • src/template.rs

    rb6c2333 r83d90d2  
    1 
    21use std::{
    32    path::{
Note: See TracChangeset for help on using the changeset viewer.