1use clap::{ArgGroup, Args, Parser, Subcommand};
2use colored::*;
3
4#[derive(Parser)]
5#[command(version, about, long_about = None)]
6#[command(propagate_version = true)]
7pub struct Cli {
8 #[command(subcommand)]
9 pub command: Commands,
10}
11
12#[derive(Subcommand)]
13pub enum Commands {
14 Seq(SeqArgs),
16
17 Sample(SampleArgs),
19 Size(SizeArgs),
21 Fqchk(FqchkArgs),
23 Comp(CompArgs),
25 }
28
29#[derive(Args)]
30pub struct FqchkArgs {
32 pub in_fq: String,
34 #[arg(short, long)]
35 pub out: Option<String>,
37 #[arg(short, long)]
38 pub quality_value: Option<u8>,
40 #[arg(short, long)]
41 pub ascii_base: Option<u8>,
43}
44
45#[derive(Args)]
55#[command(group(
56 ArgGroup::new("exclusive_group")
57 .args(["in_fq", "in_fa"])
58 .required(true)
59 .multiple(false)
60))]
61pub struct SizeArgs {
62 #[arg(short = 'I', long)]
63 pub in_fq: Option<String>,
65 #[arg(short = 'A', long)]
66 pub in_fa: Option<String>,
68}
69
70#[derive(Args)]
71#[command(group(
72 ArgGroup::new("exclusive_group")
73 .args(["in_fq", "in_fa"])
74 .required(true)
75 .multiple(false)
76))]
77pub struct CompArgs {
78 #[arg(short = 'I', long)]
79 pub in_fq: Option<String>,
81 #[arg(short = 'A', long)]
82 pub in_fa: Option<String>,
84 #[arg(short = 'u', long)]
85 pub exclude_masked: bool,
87 #[arg(short = 'r', long)]
88 pub in_bed: Option<String>,
90}
91
92#[derive(Args)]
93#[command(group(
94 ArgGroup::new("exclusive_group")
95 .args(["in_fq", "in_fa"])
96 .required(true)
97 .multiple(false)
98))]
99pub struct SampleArgs {
100 #[arg(short = 'I', long)]
101 pub in_fq: Option<String>,
103 #[arg(short = 'A', long)]
104 pub in_fa: Option<String>,
106 #[arg(short = 's', long)]
107 pub random_seed: Option<usize>,
109 #[arg(short = 'f', long, value_parser = validate_ratio)]
110 pub sample_fraction: Option<f64>,
112}
113
114#[derive(Args)]
115#[command(group(
116 ArgGroup::new("exclusive_group")
117 .args(["in_fq", "in_fa"])
118 .required(true)
119 .multiple(false)
120))]
121pub struct SeqArgs {
122 #[arg(short = 'I', long)]
123 pub in_fq: Option<String>,
125 #[arg(short = 'A', long)]
126 pub in_fa: Option<String>,
128
129 #[arg(short = 'L', long)]
130 pub mini_seq_length: Option<usize>,
132 #[arg(short = 'N', long)]
133 pub drop_ambigous_seq: bool,
135 #[arg(short = '1', long)]
136 pub output_odd: bool,
138 #[arg(short = '2', long)]
139 pub output_even: bool,
141
142 #[arg(short = 'r', long)]
143 pub reverse_complement: bool,
145 #[arg(short = 'R', long)]
146 pub both_complement: bool,
148 #[arg(long)]
149 pub output_fasta: bool,
151 #[arg(short = 'C', long)]
152 pub trim_header: bool,
154 #[arg(short = 'l', long)]
155 pub line_len: Option<usize>,
157
158 #[arg(short = 'Q', long)]
159 pub ascii_bases: Option<u8>,
161 #[arg(long)]
162 pub output_qual_33: bool,
164 #[arg(long)]
165 pub q_low: Option<u8>,
167 #[arg(long)]
168 pub q_high: Option<u8>,
170 #[arg(short = 'F', long)]
171 pub fake_fastq_quality: Option<char>,
173
174 #[arg(short = 'U', long)]
175 pub uppercases: bool,
179 #[arg(short = 'x', long)]
180 pub lowercases_to_char: bool,
182
183 #[arg(long)]
184 pub mask_char: Option<char>,
186 #[arg(short = 'M', long)]
187 pub mask_regions: Option<String>,
189 #[arg(long)]
190 pub mask_complement_region: bool,
192}
193pub fn valiation_seq_args(args: &SeqArgs) -> Result<(), std::io::Error> {
195 let mut errors = Vec::new();
196 if args.output_even && args.output_odd {
197 errors.push("--output-even-reads and --output-odd-reads can not be used together.");
198 }
199 if args.mask_complement_region && args.mask_regions.is_none() {
200 errors.push("--mask-complment-region requires --mask-regions.");
201 }
202 if args.lowercases_to_char && args.mask_char.is_none() {
203 errors.push("--lowercases-to-char requires --mask-char.");
204 }
205 if args.output_fasta && (args.output_qual_33 || args.fake_fastq_quality.is_some()) {
206 errors
207 .push("--output-fasta can not be used with --output-qual-33 or --fake-fastq-quality.");
208 }
209 if args.output_qual_33 && args.fake_fastq_quality.is_some() {
210 errors.push("--output-qual-33 and --fake-fastq-quality can not be used together.");
211 }
212 if args.reverse_complement && args.both_complement {
213 errors.push("--reverse-complement and --both-complement can not be used together.");
214 }
215 if !errors.is_empty() {
216 for error in errors {
217 eprintln!("{} {}", "error:".red().bold(), error);
218 }
219 std::process::exit(1);
220 }
221 Ok(())
222}
223
224fn validate_ratio(s: &str) -> Result<f64, String> {
225 let val: f64 = s
226 .parse()
227 .map_err(|_| "Must be a valid floating-point number".to_string())?;
228 if (0.0..=1.0).contains(&val) {
229 Ok(val)
230 } else {
231 Err("Value must be between 0.0 and 1.0 (inclusive)".to_string())
232 }
233}