1use std::collections::HashMap;
6
7use std::collections::{HashSet, VecDeque};
8
9#[allow(dead_code)]
11#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
12pub enum BashLogLevel {
13 Debug,
14 Info,
15 Warn,
16 Error,
17 Fatal,
18}
19#[allow(dead_code)]
20impl BashLogLevel {
21 pub fn name(&self) -> &'static str {
23 match self {
24 BashLogLevel::Debug => "DEBUG",
25 BashLogLevel::Info => "INFO",
26 BashLogLevel::Warn => "WARN",
27 BashLogLevel::Error => "ERROR",
28 BashLogLevel::Fatal => "FATAL",
29 }
30 }
31 pub fn ansi_color(&self) -> &'static str {
33 match self {
34 BashLogLevel::Debug => "\\033[0;36m",
35 BashLogLevel::Info => "\\033[0;32m",
36 BashLogLevel::Warn => "\\033[0;33m",
37 BashLogLevel::Error => "\\033[0;31m",
38 BashLogLevel::Fatal => "\\033[1;31m",
39 }
40 }
41 pub fn is_stderr(&self) -> bool {
43 matches!(self, BashLogLevel::Error | BashLogLevel::Fatal)
44 }
45}
46#[allow(dead_code)]
48#[derive(Debug, Clone)]
49pub struct BashLogger {
50 pub timestamps: bool,
52 pub color: bool,
54 pub min_level: BashLogLevel,
56 pub log_file: Option<std::string::String>,
58}
59#[allow(dead_code)]
60impl BashLogger {
61 pub fn new() -> Self {
63 BashLogger {
64 timestamps: false,
65 color: false,
66 min_level: BashLogLevel::Info,
67 log_file: None,
68 }
69 }
70 pub fn with_timestamps(mut self) -> Self {
72 self.timestamps = true;
73 self
74 }
75 pub fn with_color(mut self) -> Self {
77 self.color = true;
78 self
79 }
80 pub fn with_min_level(mut self, level: BashLogLevel) -> Self {
82 self.min_level = level;
83 self
84 }
85 pub fn with_log_file(mut self, path: &str) -> Self {
87 self.log_file = Some(path.to_string());
88 self
89 }
90 pub fn emit_framework(&self) -> std::string::String {
92 let mut out = std::string::String::new();
93 out.push_str("# ---- Logging framework ----\n");
94 out.push_str(&format!(
95 "readonly _LOG_LEVEL_MIN={}\n",
96 self.min_level as u8
97 ));
98 out.push_str("readonly _LOG_DEBUG=0 _LOG_INFO=1 _LOG_WARN=2 _LOG_ERROR=3 _LOG_FATAL=4\n");
99 out.push_str("_log() {\n");
100 out.push_str(" local level=$1 level_name=$2 message=$3\n");
101 out.push_str(" [[ $level -lt $_LOG_LEVEL_MIN ]] && return 0\n");
102 if self.timestamps {
103 out.push_str(" local ts; ts=$(date '+%Y-%m-%dT%H:%M:%S')\n");
104 }
105 let msg_prefix = if self.timestamps {
106 "\"[$ts][$level_name] $message\""
107 } else {
108 "\"[$level_name] $message\""
109 };
110 if self.color {
111 out.push_str(" local color reset='\\033[0m'\n");
112 out.push_str(" case $level_name in\n");
113 out.push_str(" DEBUG) color='\\033[0;36m' ;;\n");
114 out.push_str(" INFO) color='\\033[0;32m' ;;\n");
115 out.push_str(" WARN) color='\\033[0;33m' ;;\n");
116 out.push_str(" ERROR|FATAL) color='\\033[0;31m' ;;\n");
117 out.push_str(" *) color='' ;;\n");
118 out.push_str(" esac\n");
119 if self.timestamps {
120 out.push_str(" local formatted=\"${color}[$ts][$level_name]${reset} $message\"\n");
121 } else {
122 out.push_str(" local formatted=\"${color}[$level_name]${reset} $message\"\n");
123 }
124 out.push_str(" if [[ $level -ge $_LOG_ERROR ]]; then\n");
125 out.push_str(" echo -e \"$formatted\" >&2\n");
126 out.push_str(" else\n");
127 out.push_str(" echo -e \"$formatted\"\n");
128 out.push_str(" fi\n");
129 } else {
130 out.push_str(&format!(" if [[ $level -ge $_LOG_ERROR ]]; then\n"));
131 out.push_str(&format!(" echo {} >&2\n", msg_prefix));
132 out.push_str(" else\n");
133 out.push_str(&format!(" echo {}\n", msg_prefix));
134 out.push_str(" fi\n");
135 }
136 if let Some(log_file) = &self.log_file {
137 if self.timestamps {
138 out.push_str(&format!(
139 " echo \"[$ts][$level_name] $message\" >> \"{}\"\n",
140 log_file
141 ));
142 } else {
143 out.push_str(&format!(
144 " echo \"[$level_name] $message\" >> \"{}\"\n",
145 log_file
146 ));
147 }
148 }
149 out.push_str("}\n\n");
150 for (fn_name, level_const, level_name) in &[
151 ("log_debug", "_LOG_DEBUG", "DEBUG"),
152 ("log_info", "_LOG_INFO", "INFO"),
153 ("log_warn", "_LOG_WARN", "WARN"),
154 ("log_error", "_LOG_ERROR", "ERROR"),
155 ("log_fatal", "_LOG_FATAL", "FATAL"),
156 ] {
157 out.push_str(&format!(
158 "{fn_name}() {{ _log ${level_const} \"{level_name}\" \"$1\"; }}\n"
159 ));
160 }
161 out.push('\n');
162 out
163 }
164}
165#[allow(dead_code)]
166#[derive(Debug, Clone)]
167pub struct BashAnalysisCache {
168 pub(super) entries: std::collections::HashMap<String, BashCacheEntry>,
169 pub(super) max_size: usize,
170 pub(super) hits: u64,
171 pub(super) misses: u64,
172}
173impl BashAnalysisCache {
174 #[allow(dead_code)]
175 pub fn new(max_size: usize) -> Self {
176 BashAnalysisCache {
177 entries: std::collections::HashMap::new(),
178 max_size,
179 hits: 0,
180 misses: 0,
181 }
182 }
183 #[allow(dead_code)]
184 pub fn get(&mut self, key: &str) -> Option<&BashCacheEntry> {
185 if self.entries.contains_key(key) {
186 self.hits += 1;
187 self.entries.get(key)
188 } else {
189 self.misses += 1;
190 None
191 }
192 }
193 #[allow(dead_code)]
194 pub fn insert(&mut self, key: String, data: Vec<u8>) {
195 if self.entries.len() >= self.max_size {
196 if let Some(oldest) = self.entries.keys().next().cloned() {
197 self.entries.remove(&oldest);
198 }
199 }
200 self.entries.insert(
201 key.clone(),
202 BashCacheEntry {
203 key,
204 data,
205 timestamp: 0,
206 valid: true,
207 },
208 );
209 }
210 #[allow(dead_code)]
211 pub fn invalidate(&mut self, key: &str) {
212 if let Some(entry) = self.entries.get_mut(key) {
213 entry.valid = false;
214 }
215 }
216 #[allow(dead_code)]
217 pub fn clear(&mut self) {
218 self.entries.clear();
219 }
220 #[allow(dead_code)]
221 pub fn hit_rate(&self) -> f64 {
222 let total = self.hits + self.misses;
223 if total == 0 {
224 return 0.0;
225 }
226 self.hits as f64 / total as f64
227 }
228 #[allow(dead_code)]
229 pub fn size(&self) -> usize {
230 self.entries.len()
231 }
232}
233#[allow(dead_code)]
234pub struct BashConstantFoldingHelper;
235impl BashConstantFoldingHelper {
236 #[allow(dead_code)]
237 pub fn fold_add_i64(a: i64, b: i64) -> Option<i64> {
238 a.checked_add(b)
239 }
240 #[allow(dead_code)]
241 pub fn fold_sub_i64(a: i64, b: i64) -> Option<i64> {
242 a.checked_sub(b)
243 }
244 #[allow(dead_code)]
245 pub fn fold_mul_i64(a: i64, b: i64) -> Option<i64> {
246 a.checked_mul(b)
247 }
248 #[allow(dead_code)]
249 pub fn fold_div_i64(a: i64, b: i64) -> Option<i64> {
250 if b == 0 {
251 None
252 } else {
253 a.checked_div(b)
254 }
255 }
256 #[allow(dead_code)]
257 pub fn fold_add_f64(a: f64, b: f64) -> f64 {
258 a + b
259 }
260 #[allow(dead_code)]
261 pub fn fold_mul_f64(a: f64, b: f64) -> f64 {
262 a * b
263 }
264 #[allow(dead_code)]
265 pub fn fold_neg_i64(a: i64) -> Option<i64> {
266 a.checked_neg()
267 }
268 #[allow(dead_code)]
269 pub fn fold_not_bool(a: bool) -> bool {
270 !a
271 }
272 #[allow(dead_code)]
273 pub fn fold_and_bool(a: bool, b: bool) -> bool {
274 a && b
275 }
276 #[allow(dead_code)]
277 pub fn fold_or_bool(a: bool, b: bool) -> bool {
278 a || b
279 }
280 #[allow(dead_code)]
281 pub fn fold_shl_i64(a: i64, b: u32) -> Option<i64> {
282 a.checked_shl(b)
283 }
284 #[allow(dead_code)]
285 pub fn fold_shr_i64(a: i64, b: u32) -> Option<i64> {
286 a.checked_shr(b)
287 }
288 #[allow(dead_code)]
289 pub fn fold_rem_i64(a: i64, b: i64) -> Option<i64> {
290 if b == 0 {
291 None
292 } else {
293 Some(a % b)
294 }
295 }
296 #[allow(dead_code)]
297 pub fn fold_bitand_i64(a: i64, b: i64) -> i64 {
298 a & b
299 }
300 #[allow(dead_code)]
301 pub fn fold_bitor_i64(a: i64, b: i64) -> i64 {
302 a | b
303 }
304 #[allow(dead_code)]
305 pub fn fold_bitxor_i64(a: i64, b: i64) -> i64 {
306 a ^ b
307 }
308 #[allow(dead_code)]
309 pub fn fold_bitnot_i64(a: i64) -> i64 {
310 !a
311 }
312}
313#[derive(Debug, Clone, PartialEq)]
315pub struct BashHereDoc {
316 pub delimiter: std::string::String,
318 pub strip_tabs: bool,
320 pub no_expand: bool,
322 pub content: Vec<std::string::String>,
324}
325impl BashHereDoc {
326 pub fn new(
328 delimiter: impl Into<std::string::String>,
329 content: Vec<std::string::String>,
330 ) -> Self {
331 BashHereDoc {
332 delimiter: delimiter.into(),
333 strip_tabs: false,
334 no_expand: false,
335 content,
336 }
337 }
338}
339#[derive(Debug, Clone, PartialEq, Eq, Hash)]
341pub enum BashVar {
342 Local(std::string::String),
344 Global(std::string::String),
346 Env(std::string::String),
348 Readonly(std::string::String),
350 Integer(std::string::String),
352 Array(std::string::String),
354 AssocArray(std::string::String),
356 NameRef(std::string::String),
358}
359impl BashVar {
360 pub fn name(&self) -> &str {
362 match self {
363 BashVar::Local(n)
364 | BashVar::Global(n)
365 | BashVar::Env(n)
366 | BashVar::Readonly(n)
367 | BashVar::Integer(n)
368 | BashVar::Array(n)
369 | BashVar::AssocArray(n)
370 | BashVar::NameRef(n) => n.as_str(),
371 }
372 }
373 pub fn is_exported(&self) -> bool {
375 matches!(self, BashVar::Env(_))
376 }
377 pub fn is_readonly(&self) -> bool {
379 matches!(self, BashVar::Readonly(_))
380 }
381}
382#[derive(Debug, Clone, PartialEq)]
384pub enum BashCondition {
385 FileExists(BashExpr),
387 IsFile(BashExpr),
389 IsDir(BashExpr),
391 NonEmpty(BashExpr),
393 Empty(BashExpr),
395 StrEq(BashExpr, BashExpr),
397 StrNe(BashExpr, BashExpr),
399 StrLt(BashExpr, BashExpr),
401 ArithLt(std::string::String, std::string::String),
403 ArithEq(std::string::String, std::string::String),
405 And(Box<BashCondition>, Box<BashCondition>),
407 Or(Box<BashCondition>, Box<BashCondition>),
409 Not(Box<BashCondition>),
411 Raw(std::string::String),
413}
414#[allow(dead_code)]
415#[derive(Debug, Clone)]
416pub struct BashDepGraph {
417 pub(super) nodes: Vec<u32>,
418 pub(super) edges: Vec<(u32, u32)>,
419}
420impl BashDepGraph {
421 #[allow(dead_code)]
422 pub fn new() -> Self {
423 BashDepGraph {
424 nodes: Vec::new(),
425 edges: Vec::new(),
426 }
427 }
428 #[allow(dead_code)]
429 pub fn add_node(&mut self, id: u32) {
430 if !self.nodes.contains(&id) {
431 self.nodes.push(id);
432 }
433 }
434 #[allow(dead_code)]
435 pub fn add_dep(&mut self, dep: u32, dependent: u32) {
436 self.add_node(dep);
437 self.add_node(dependent);
438 self.edges.push((dep, dependent));
439 }
440 #[allow(dead_code)]
441 pub fn dependents_of(&self, node: u32) -> Vec<u32> {
442 self.edges
443 .iter()
444 .filter(|(d, _)| *d == node)
445 .map(|(_, dep)| *dep)
446 .collect()
447 }
448 #[allow(dead_code)]
449 pub fn dependencies_of(&self, node: u32) -> Vec<u32> {
450 self.edges
451 .iter()
452 .filter(|(_, dep)| *dep == node)
453 .map(|(d, _)| *d)
454 .collect()
455 }
456 #[allow(dead_code)]
457 pub fn topological_sort(&self) -> Vec<u32> {
458 let mut in_degree: std::collections::HashMap<u32, u32> = std::collections::HashMap::new();
459 for &n in &self.nodes {
460 in_degree.insert(n, 0);
461 }
462 for (_, dep) in &self.edges {
463 *in_degree.entry(*dep).or_insert(0) += 1;
464 }
465 let mut queue: std::collections::VecDeque<u32> = self
466 .nodes
467 .iter()
468 .filter(|&&n| in_degree[&n] == 0)
469 .copied()
470 .collect();
471 let mut result = Vec::new();
472 while let Some(node) = queue.pop_front() {
473 result.push(node);
474 for dep in self.dependents_of(node) {
475 let cnt = in_degree.entry(dep).or_insert(0);
476 *cnt = cnt.saturating_sub(1);
477 if *cnt == 0 {
478 queue.push_back(dep);
479 }
480 }
481 }
482 result
483 }
484 #[allow(dead_code)]
485 pub fn has_cycle(&self) -> bool {
486 self.topological_sort().len() < self.nodes.len()
487 }
488}
489#[allow(dead_code)]
491#[derive(Debug, Clone)]
492pub struct BashJobManager {
493 pub max_jobs: usize,
495 pub wait_on_exit: bool,
497 pub pids_var: std::string::String,
499}
500#[allow(dead_code)]
501impl BashJobManager {
502 pub fn new(max_jobs: usize) -> Self {
504 BashJobManager {
505 max_jobs,
506 wait_on_exit: true,
507 pids_var: "PIDS".to_string(),
508 }
509 }
510 pub fn with_pids_var(mut self, var: &str) -> Self {
512 self.pids_var = var.to_string();
513 self
514 }
515 pub fn emit_framework(&self) -> std::string::String {
517 let mut out = std::string::String::new();
518 out.push_str("# ---- Job manager ----\n");
519 out.push_str(&format!("declare -a {}\n", self.pids_var));
520 out.push_str(&format!("readonly _MAX_JOBS={}\n\n", self.max_jobs));
521 out.push_str("_wait_jobs() {\n");
522 out.push_str(&format!(" local -n _pids={}\n", self.pids_var));
523 out.push_str(" local _failed=0\n");
524 out.push_str(" for _pid in \"${_pids[@]:-}\"; do\n");
525 out.push_str(" wait \"$_pid\" || (( _failed++ ))\n");
526 out.push_str(" done\n");
527 out.push_str(&format!(" {}=()\n", self.pids_var));
528 out.push_str(" return $_failed\n");
529 out.push_str("}\n\n");
530 out.push_str("run_job() {\n");
531 out.push_str(" local _cmd=\"$@\"\n");
532 out.push_str(&format!(" local -n _pids={}\n", self.pids_var));
533 out.push_str(" while [[ ${#_pids[@]} -ge $_MAX_JOBS ]]; do\n");
534 out.push_str(" _wait_jobs || true\n");
535 out.push_str(" done\n");
536 out.push_str(" eval \"$_cmd\" &\n");
537 out.push_str(" _pids+=(\"$!\")\n");
538 out.push_str("}\n\n");
539 if self.wait_on_exit {
540 out.push_str("trap '_wait_jobs' EXIT\n\n");
541 }
542 out
543 }
544}
545#[allow(dead_code)]
546#[derive(Debug, Clone)]
547pub struct BashWorklist {
548 pub(super) items: std::collections::VecDeque<u32>,
549 pub(super) in_worklist: std::collections::HashSet<u32>,
550}
551impl BashWorklist {
552 #[allow(dead_code)]
553 pub fn new() -> Self {
554 BashWorklist {
555 items: std::collections::VecDeque::new(),
556 in_worklist: std::collections::HashSet::new(),
557 }
558 }
559 #[allow(dead_code)]
560 pub fn push(&mut self, item: u32) -> bool {
561 if self.in_worklist.insert(item) {
562 self.items.push_back(item);
563 true
564 } else {
565 false
566 }
567 }
568 #[allow(dead_code)]
569 pub fn pop(&mut self) -> Option<u32> {
570 let item = self.items.pop_front()?;
571 self.in_worklist.remove(&item);
572 Some(item)
573 }
574 #[allow(dead_code)]
575 pub fn is_empty(&self) -> bool {
576 self.items.is_empty()
577 }
578 #[allow(dead_code)]
579 pub fn len(&self) -> usize {
580 self.items.len()
581 }
582 #[allow(dead_code)]
583 pub fn contains(&self, item: u32) -> bool {
584 self.in_worklist.contains(&item)
585 }
586}
587#[allow(dead_code)]
589#[derive(Debug, Clone)]
590pub struct BashTemplate {
591 pub template: std::string::String,
593 pub vars: HashMap<std::string::String, std::string::String>,
595}
596#[allow(dead_code)]
597impl BashTemplate {
598 pub fn new(template: &str) -> Self {
600 BashTemplate {
601 template: template.to_string(),
602 vars: HashMap::new(),
603 }
604 }
605 pub fn set(mut self, key: &str, value: &str) -> Self {
607 self.vars.insert(key.to_string(), value.to_string());
608 self
609 }
610 pub fn render(&self) -> std::string::String {
612 let mut result = self.template.clone();
613 for (key, val) in &self.vars {
614 let placeholder = format!("{{{{{}}}}}", key);
615 result = result.replace(&placeholder, val);
616 }
617 result
618 }
619 pub fn emit_bash_render(&self) -> std::string::String {
621 let mut out = std::string::String::new();
622 out.push_str("_render_template() {\n");
623 out.push_str(" local _template=\"$1\"\n");
624 for (key, _val) in &self.vars {
625 out.push_str(&format!(
626 " _template=\"${{_template//\\\"{{{{{}}}}}\\\"/${{{}}}}}\"\n",
627 key,
628 key.to_uppercase()
629 ));
630 }
631 out.push_str(" echo \"$_template\"\n");
632 out.push_str("}\n");
633 out
634 }
635}
636#[derive(Debug, Clone, PartialEq)]
638pub enum BashStatement {
639 Assign(std::string::String, BashExpr),
641 Local(std::string::String, Option<BashExpr>),
643 Export(std::string::String, BashExpr),
645 Readonly(std::string::String, BashExpr),
647 Declare(std::string::String, std::string::String, Option<BashExpr>),
649 Cmd(std::string::String),
651 If {
653 cond: BashCondition,
654 then: Vec<BashStatement>,
655 elifs: Vec<(BashCondition, Vec<BashStatement>)>,
656 else_: Option<Vec<BashStatement>>,
657 },
658 While {
660 cond: BashCondition,
661 body: Vec<BashStatement>,
662 },
663 For {
665 var: std::string::String,
666 in_: Vec<BashExpr>,
667 body: Vec<BashStatement>,
668 },
669 ForArith {
671 init: std::string::String,
672 cond: std::string::String,
673 incr: std::string::String,
674 body: Vec<BashStatement>,
675 },
676 Case {
678 expr: BashExpr,
679 arms: Vec<(std::string::String, Vec<BashStatement>)>,
680 },
681 Call(std::string::String, Vec<BashExpr>),
683 Return(Option<u8>),
685 Break,
687 Continue,
689 Echo(BashExpr),
691 Printf(std::string::String, Vec<BashExpr>),
693 Read(Vec<std::string::String>),
695 Exit(u8),
697 Raw(std::string::String),
699 Pipe(Vec<std::string::String>),
701 Trap(std::string::String, std::string::String),
703 Source(std::string::String),
705}
706#[allow(dead_code)]
708#[derive(Debug, Clone, PartialEq)]
709pub struct BashHeredoc {
710 pub tag: std::string::String,
712 pub strip_tabs: bool,
714 pub quoted: bool,
716 pub lines: Vec<std::string::String>,
718 pub fd: Option<u8>,
720}
721#[allow(dead_code)]
722impl BashHeredoc {
723 pub fn new(tag: &str) -> Self {
725 BashHeredoc {
726 tag: tag.to_string(),
727 strip_tabs: false,
728 quoted: false,
729 lines: vec![],
730 fd: None,
731 }
732 }
733 pub fn strip_tabs(mut self) -> Self {
735 self.strip_tabs = true;
736 self
737 }
738 pub fn quoted(mut self) -> Self {
740 self.quoted = true;
741 self
742 }
743 pub fn redirect_fd(mut self, fd: u8) -> Self {
745 self.fd = Some(fd);
746 self
747 }
748 pub fn line(mut self, s: &str) -> Self {
750 self.lines.push(s.to_string());
751 self
752 }
753 pub fn emit(&self) -> std::string::String {
755 let arrow = if self.strip_tabs { "<<-" } else { "<<" };
756 let tag = if self.quoted {
757 format!("'{}'", self.tag)
758 } else {
759 self.tag.clone()
760 };
761 let fd_str = self.fd.map(|fd| format!("{}&", fd)).unwrap_or_default();
762 let mut out = format!("{}{}{}\\n", fd_str, arrow, tag);
763 for l in &self.lines {
764 out.push_str(l);
765 out.push_str("\\n");
766 }
767 out.push_str(&self.tag);
768 out
769 }
770}
771#[allow(dead_code)]
773#[derive(Debug, Clone)]
774pub struct BashArgParser {
775 pub prog_name: std::string::String,
777 pub options: Vec<BashCliOption>,
779 pub positionals: Vec<std::string::String>,
781 pub description: std::string::String,
783}
784#[allow(dead_code)]
785impl BashArgParser {
786 pub fn new(prog_name: &str, description: &str) -> Self {
788 BashArgParser {
789 prog_name: prog_name.to_string(),
790 options: vec![],
791 positionals: vec![],
792 description: description.to_string(),
793 }
794 }
795 pub fn add_option(mut self, opt: BashCliOption) -> Self {
797 self.options.push(opt);
798 self
799 }
800 pub fn add_positional(mut self, name: &str) -> Self {
802 self.positionals.push(name.to_string());
803 self
804 }
805 pub fn emit_usage(&self) -> std::string::String {
807 let mut out = std::string::String::new();
808 out.push_str("usage() {\n");
809 out.push_str(&format!(" echo \"Usage: {} [OPTIONS]", self.prog_name));
810 for pos in &self.positionals {
811 out.push_str(&format!(" <{}>", pos));
812 }
813 out.push_str("\"\n");
814 out.push_str(&format!(" echo \"{}\"\n", self.description));
815 out.push_str(" echo \"\"\n");
816 out.push_str(" echo \"Options:\"\n");
817 out.push_str(" echo \" -h, --help Show this help message\"\n");
818 for opt in &self.options {
819 let short_str = opt
820 .short
821 .map(|c| format!("-{}, ", c))
822 .unwrap_or_else(|| " ".to_string());
823 let arg_meta = if opt.has_arg { " <VALUE>" } else { "" };
824 let req_str = if opt.required { " (required)" } else { "" };
825 let def_str = opt
826 .default
827 .as_ref()
828 .map(|d| format!(" [default: {}]", d))
829 .unwrap_or_default();
830 out.push_str(&format!(
831 " {}--{}{:<12}{}{}{}\n",
832 short_str, opt.long, arg_meta, opt.help, req_str, def_str
833 ));
834 }
835 out.push_str("}\n");
836 out
837 }
838 pub fn emit_parse_block(&self) -> std::string::String {
840 let mut out = std::string::String::new();
841 for opt in &self.options {
842 if let Some(default) = &opt.default {
843 out.push_str(&format!("{}={}\n", opt.var_name.to_uppercase(), default));
844 }
845 }
846 out.push('\n');
847 out.push_str("while [[ $# -gt 0 ]]; do\n");
848 out.push_str(" case \"$1\" in\n");
849 out.push_str(" -h|--help) usage; exit 0 ;;\n");
850 for opt in &self.options {
851 let short_pat = opt.short.map(|c| format!("-{}|", c)).unwrap_or_default();
852 if opt.has_arg {
853 out.push_str(&format!(
854 " {}--{})\n {}=\"${{2:-}}\"\n shift 2\n ;;\n",
855 short_pat,
856 opt.long,
857 opt.var_name.to_uppercase()
858 ));
859 } else {
860 out.push_str(&format!(
861 " {}--{}) {}=true; shift ;;\n",
862 short_pat,
863 opt.long,
864 opt.var_name.to_uppercase()
865 ));
866 }
867 }
868 out.push_str(" --) shift; break ;;\n");
869 out.push_str(" -*) echo \"Unknown option: $1\" >&2; usage; exit 1 ;;\n");
870 out.push_str(" *) break ;;\n");
871 out.push_str(" esac\n");
872 out.push_str("done\n\n");
873 for opt in &self.options {
874 if opt.required {
875 out.push_str(
876 &format!(
877 "if [[ -z \"${{{}:-}}\" ]]; then\n echo \"Error: {} is required\" >&2\n usage\n exit 1\nfi\n",
878 opt.var_name.to_uppercase(), opt.var_name.to_uppercase()
879 ),
880 );
881 }
882 }
883 for (i, pos) in self.positionals.iter().enumerate() {
884 out.push_str(&format!(
885 "readonly {}=\"${{{}:-}}\"\n",
886 pos.to_uppercase(),
887 i + 1
888 ));
889 }
890 out
891 }
892}
893#[allow(dead_code)]
895#[derive(Debug, Clone)]
896pub struct BashCliOption {
897 pub long: std::string::String,
899 pub short: Option<char>,
901 pub var_name: std::string::String,
903 pub default: Option<std::string::String>,
905 pub has_arg: bool,
907 pub help: std::string::String,
909 pub required: bool,
911}
912#[allow(dead_code)]
913impl BashCliOption {
914 pub fn flag(long: &str, short: Option<char>, var_name: &str, help: &str) -> Self {
916 BashCliOption {
917 long: long.to_string(),
918 short,
919 var_name: var_name.to_string(),
920 default: Some("false".to_string()),
921 has_arg: false,
922 help: help.to_string(),
923 required: false,
924 }
925 }
926 pub fn arg(
928 long: &str,
929 short: Option<char>,
930 var_name: &str,
931 default: Option<&str>,
932 help: &str,
933 ) -> Self {
934 BashCliOption {
935 long: long.to_string(),
936 short,
937 var_name: var_name.to_string(),
938 default: default.map(|s| s.to_string()),
939 has_arg: true,
940 help: help.to_string(),
941 required: false,
942 }
943 }
944 pub fn required(mut self) -> Self {
946 self.required = true;
947 self
948 }
949}
950#[allow(dead_code)]
951#[derive(Debug, Clone, Default)]
952pub struct BashPassStats {
953 pub total_runs: u32,
954 pub successful_runs: u32,
955 pub total_changes: u64,
956 pub time_ms: u64,
957 pub iterations_used: u32,
958}
959impl BashPassStats {
960 #[allow(dead_code)]
961 pub fn new() -> Self {
962 Self::default()
963 }
964 #[allow(dead_code)]
965 pub fn record_run(&mut self, changes: u64, time_ms: u64, iterations: u32) {
966 self.total_runs += 1;
967 self.successful_runs += 1;
968 self.total_changes += changes;
969 self.time_ms += time_ms;
970 self.iterations_used = iterations;
971 }
972 #[allow(dead_code)]
973 pub fn average_changes_per_run(&self) -> f64 {
974 if self.total_runs == 0 {
975 return 0.0;
976 }
977 self.total_changes as f64 / self.total_runs as f64
978 }
979 #[allow(dead_code)]
980 pub fn success_rate(&self) -> f64 {
981 if self.total_runs == 0 {
982 return 0.0;
983 }
984 self.successful_runs as f64 / self.total_runs as f64
985 }
986 #[allow(dead_code)]
987 pub fn format_summary(&self) -> String {
988 format!(
989 "Runs: {}/{}, Changes: {}, Time: {}ms",
990 self.successful_runs, self.total_runs, self.total_changes, self.time_ms
991 )
992 }
993}
994#[allow(dead_code)]
995#[derive(Debug, Clone)]
996pub struct BashDominatorTree {
997 pub idom: Vec<Option<u32>>,
998 pub dom_children: Vec<Vec<u32>>,
999 pub dom_depth: Vec<u32>,
1000}
1001impl BashDominatorTree {
1002 #[allow(dead_code)]
1003 pub fn new(size: usize) -> Self {
1004 BashDominatorTree {
1005 idom: vec![None; size],
1006 dom_children: vec![Vec::new(); size],
1007 dom_depth: vec![0; size],
1008 }
1009 }
1010 #[allow(dead_code)]
1011 pub fn set_idom(&mut self, node: usize, idom: u32) {
1012 self.idom[node] = Some(idom);
1013 }
1014 #[allow(dead_code)]
1015 pub fn dominates(&self, a: usize, b: usize) -> bool {
1016 if a == b {
1017 return true;
1018 }
1019 let mut cur = b;
1020 loop {
1021 match self.idom[cur] {
1022 Some(parent) if parent as usize == a => return true,
1023 Some(parent) if parent as usize == cur => return false,
1024 Some(parent) => cur = parent as usize,
1025 None => return false,
1026 }
1027 }
1028 }
1029 #[allow(dead_code)]
1030 pub fn depth(&self, node: usize) -> u32 {
1031 self.dom_depth.get(node).copied().unwrap_or(0)
1032 }
1033}
1034#[derive(Debug, Clone, PartialEq)]
1036pub enum BashExpr {
1037 Var(BashVar),
1039 Lit(std::string::String),
1041 DQuoted(std::string::String),
1043 CmdSubst(std::string::String),
1045 ArithExpr(std::string::String),
1047 ProcSubst {
1049 is_input: bool,
1050 cmd: std::string::String,
1051 },
1052 ArrayElem(std::string::String, Box<BashExpr>),
1054 ArrayLen(std::string::String),
1056 ArrayAll(std::string::String),
1058 AssocElem(std::string::String, Box<BashExpr>),
1060 Default(std::string::String, Box<BashExpr>),
1062 AssignDefault(std::string::String, Box<BashExpr>),
1064 Substring(std::string::String, usize, Option<usize>),
1066 StringLen(std::string::String),
1068 StripPrefix(std::string::String, std::string::String),
1070 StripSuffix(std::string::String, std::string::String),
1072 UpperCase(std::string::String),
1074 LowerCase(std::string::String),
1075 LastStatus,
1077 ShellPid,
1079 ScriptName,
1081 Positional(usize),
1083 AllArgs,
1085 ArgCount,
1087 Concat(Box<BashExpr>, Box<BashExpr>),
1089}
1090#[allow(dead_code)]
1091#[derive(Debug, Clone, PartialEq)]
1092pub enum BashPassPhase {
1093 Analysis,
1094 Transformation,
1095 Verification,
1096 Cleanup,
1097}
1098impl BashPassPhase {
1099 #[allow(dead_code)]
1100 pub fn name(&self) -> &str {
1101 match self {
1102 BashPassPhase::Analysis => "analysis",
1103 BashPassPhase::Transformation => "transformation",
1104 BashPassPhase::Verification => "verification",
1105 BashPassPhase::Cleanup => "cleanup",
1106 }
1107 }
1108 #[allow(dead_code)]
1109 pub fn is_modifying(&self) -> bool {
1110 matches!(self, BashPassPhase::Transformation | BashPassPhase::Cleanup)
1111 }
1112}
1113#[derive(Debug, Clone)]
1115pub struct BashScript {
1116 pub shebang: std::string::String,
1118 pub set_flags: Vec<std::string::String>,
1120 pub traps: Vec<(std::string::String, std::string::String)>,
1122 pub globals: Vec<(std::string::String, std::string::String)>,
1124 pub functions: Vec<BashFunction>,
1126 pub main: Vec<std::string::String>,
1128}
1129impl BashScript {
1130 pub fn new() -> Self {
1132 BashScript {
1133 shebang: "#!/usr/bin/env bash".to_string(),
1134 set_flags: vec!["-euo".to_string(), "pipefail".to_string()],
1135 traps: vec![],
1136 globals: vec![],
1137 functions: vec![],
1138 main: vec![],
1139 }
1140 }
1141 pub fn lenient() -> Self {
1143 BashScript {
1144 shebang: "#!/usr/bin/env bash".to_string(),
1145 set_flags: vec![],
1146 traps: vec![],
1147 globals: vec![],
1148 functions: vec![],
1149 main: vec![],
1150 }
1151 }
1152}
1153#[allow(dead_code)]
1154#[derive(Debug, Clone)]
1155pub struct BashPassConfig {
1156 pub phase: BashPassPhase,
1157 pub enabled: bool,
1158 pub max_iterations: u32,
1159 pub debug_output: bool,
1160 pub pass_name: String,
1161}
1162impl BashPassConfig {
1163 #[allow(dead_code)]
1164 pub fn new(name: impl Into<String>, phase: BashPassPhase) -> Self {
1165 BashPassConfig {
1166 phase,
1167 enabled: true,
1168 max_iterations: 10,
1169 debug_output: false,
1170 pass_name: name.into(),
1171 }
1172 }
1173 #[allow(dead_code)]
1174 pub fn disabled(mut self) -> Self {
1175 self.enabled = false;
1176 self
1177 }
1178 #[allow(dead_code)]
1179 pub fn with_debug(mut self) -> Self {
1180 self.debug_output = true;
1181 self
1182 }
1183 #[allow(dead_code)]
1184 pub fn max_iter(mut self, n: u32) -> Self {
1185 self.max_iterations = n;
1186 self
1187 }
1188}
1189#[allow(dead_code)]
1190pub struct BashPassRegistry {
1191 pub(super) configs: Vec<BashPassConfig>,
1192 pub(super) stats: std::collections::HashMap<String, BashPassStats>,
1193}
1194impl BashPassRegistry {
1195 #[allow(dead_code)]
1196 pub fn new() -> Self {
1197 BashPassRegistry {
1198 configs: Vec::new(),
1199 stats: std::collections::HashMap::new(),
1200 }
1201 }
1202 #[allow(dead_code)]
1203 pub fn register(&mut self, config: BashPassConfig) {
1204 self.stats
1205 .insert(config.pass_name.clone(), BashPassStats::new());
1206 self.configs.push(config);
1207 }
1208 #[allow(dead_code)]
1209 pub fn enabled_passes(&self) -> Vec<&BashPassConfig> {
1210 self.configs.iter().filter(|c| c.enabled).collect()
1211 }
1212 #[allow(dead_code)]
1213 pub fn get_stats(&self, name: &str) -> Option<&BashPassStats> {
1214 self.stats.get(name)
1215 }
1216 #[allow(dead_code)]
1217 pub fn total_passes(&self) -> usize {
1218 self.configs.len()
1219 }
1220 #[allow(dead_code)]
1221 pub fn enabled_count(&self) -> usize {
1222 self.enabled_passes().len()
1223 }
1224 #[allow(dead_code)]
1225 pub fn update_stats(&mut self, name: &str, changes: u64, time_ms: u64, iter: u32) {
1226 if let Some(stats) = self.stats.get_mut(name) {
1227 stats.record_run(changes, time_ms, iter);
1228 }
1229 }
1230}
1231#[allow(dead_code)]
1232#[derive(Debug, Clone)]
1233pub struct BashCacheEntry {
1234 pub key: String,
1235 pub data: Vec<u8>,
1236 pub timestamp: u64,
1237 pub valid: bool,
1238}
1239#[derive(Debug, Clone, PartialEq)]
1241pub struct BashFunction {
1242 pub name: std::string::String,
1244 pub local_vars: Vec<std::string::String>,
1246 pub body: Vec<std::string::String>,
1248 pub description: Option<std::string::String>,
1250}
1251impl BashFunction {
1252 pub fn new(name: impl Into<std::string::String>, body: Vec<std::string::String>) -> Self {
1254 BashFunction {
1255 name: name.into(),
1256 local_vars: vec![],
1257 body,
1258 description: None,
1259 }
1260 }
1261 pub fn with_locals(
1263 name: impl Into<std::string::String>,
1264 local_vars: Vec<std::string::String>,
1265 body: Vec<std::string::String>,
1266 ) -> Self {
1267 BashFunction {
1268 name: name.into(),
1269 local_vars,
1270 body,
1271 description: None,
1272 }
1273 }
1274}
1275pub struct BashBackend {
1277 pub(super) indent: std::string::String,
1279 pub(super) mangle_cache: HashMap<std::string::String, std::string::String>,
1281 pub(super) spacing: bool,
1283}
1284impl BashBackend {
1285 pub fn new() -> Self {
1287 BashBackend {
1288 indent: " ".to_string(),
1289 mangle_cache: HashMap::new(),
1290 spacing: true,
1291 }
1292 }
1293 pub fn compact() -> Self {
1295 BashBackend {
1296 indent: " ".to_string(),
1297 mangle_cache: HashMap::new(),
1298 spacing: false,
1299 }
1300 }
1301 pub fn emit_var_decl(&self, var: &BashVar, value: Option<&str>) -> std::string::String {
1303 let val_s = value.map(|v| format!("={}", v)).unwrap_or_default();
1304 match var {
1305 BashVar::Local(n) => format!("local {}{}", n, val_s),
1306 BashVar::Global(n) => format!("{}{}", n, val_s),
1307 BashVar::Env(n) => format!("export {}{}", n, val_s),
1308 BashVar::Readonly(n) => format!("readonly {}{}", n, val_s),
1309 BashVar::Integer(n) => format!("declare -i {}{}", n, val_s),
1310 BashVar::Array(n) => format!("declare -a {}{}", n, val_s),
1311 BashVar::AssocArray(n) => format!("declare -A {}{}", n, val_s),
1312 BashVar::NameRef(n) => format!("declare -n {}{}", n, val_s),
1313 }
1314 }
1315 pub fn emit_var(&self, var: &BashVar) -> std::string::String {
1317 format!("{}", var)
1318 }
1319 pub fn emit_expr(&self, expr: &BashExpr) -> std::string::String {
1321 format!("{}", expr)
1322 }
1323 pub fn emit_condition(&self, cond: &BashCondition) -> std::string::String {
1325 format!("{}", cond)
1326 }
1327 pub fn mangle_name(&self, name: &str) -> std::string::String {
1332 if let Some(cached) = self.mangle_cache.get(name) {
1333 return cached.clone();
1334 }
1335 let mut result = std::string::String::new();
1336 let mut prev_special = false;
1337 for (i, c) in name.chars().enumerate() {
1338 match c {
1339 'a'..='z' | 'A'..='Z' | '_' => {
1340 result.push(c);
1341 prev_special = false;
1342 }
1343 '0'..='9' => {
1344 if i == 0 {
1345 result.push('_');
1346 }
1347 result.push(c);
1348 prev_special = false;
1349 }
1350 '.' | ':' => {
1351 if !prev_special {
1352 result.push_str("__");
1353 }
1354 prev_special = true;
1355 }
1356 '\'' | '-' => {
1357 if !prev_special {
1358 result.push('_');
1359 }
1360 prev_special = true;
1361 }
1362 _ => {
1363 if !prev_special {
1364 result.push_str(&format!("_u{:04X}_", c as u32));
1365 }
1366 prev_special = true;
1367 }
1368 }
1369 }
1370 if result.is_empty() {
1371 result.push('_');
1372 }
1373 let builtins = [
1374 "alias",
1375 "bg",
1376 "bind",
1377 "break",
1378 "builtin",
1379 "caller",
1380 "cd",
1381 "command",
1382 "compgen",
1383 "complete",
1384 "compopt",
1385 "continue",
1386 "declare",
1387 "dirs",
1388 "disown",
1389 "echo",
1390 "enable",
1391 "eval",
1392 "exec",
1393 "exit",
1394 "export",
1395 "false",
1396 "fc",
1397 "fg",
1398 "getopts",
1399 "hash",
1400 "help",
1401 "history",
1402 "if",
1403 "jobs",
1404 "kill",
1405 "let",
1406 "local",
1407 "logout",
1408 "mapfile",
1409 "popd",
1410 "printf",
1411 "pushd",
1412 "pwd",
1413 "read",
1414 "readarray",
1415 "readonly",
1416 "return",
1417 "select",
1418 "set",
1419 "shift",
1420 "shopt",
1421 "source",
1422 "suspend",
1423 "test",
1424 "time",
1425 "times",
1426 "trap",
1427 "true",
1428 "type",
1429 "typeset",
1430 "ulimit",
1431 "umask",
1432 "unalias",
1433 "unset",
1434 "until",
1435 "wait",
1436 "while",
1437 ];
1438 if builtins.contains(&result.as_str()) {
1439 result.push_str("__ox");
1440 }
1441 result
1442 }
1443 pub fn emit_function(&self, func: &BashFunction) -> std::string::String {
1445 let mut out = std::string::String::new();
1446 if let Some(desc) = &func.description {
1447 out.push_str(&format!("# {}\n", desc));
1448 }
1449 out.push_str(&format!("{}() {{\n", func.name));
1450 for local in &func.local_vars {
1451 out.push_str(&format!("{}local {}\n", self.indent, local));
1452 }
1453 if !func.local_vars.is_empty() && !func.body.is_empty() {
1454 out.push('\n');
1455 }
1456 for line in &func.body {
1457 if line.is_empty() {
1458 out.push('\n');
1459 } else {
1460 out.push_str(&format!("{}{}\n", self.indent, line));
1461 }
1462 }
1463 out.push_str("}\n");
1464 out
1465 }
1466 pub fn emit_heredoc(&self, heredoc: &BashHereDoc) -> std::string::String {
1468 format!("{}", heredoc)
1469 }
1470 pub fn emit_script(&self, script: &BashScript) -> std::string::String {
1472 let mut out = std::string::String::new();
1473 out.push_str(&format!("{}\n", script.shebang));
1474 if !script.set_flags.is_empty() {
1475 out.push_str(&format!("set {}\n", script.set_flags.join(" ")));
1476 }
1477 out.push('\n');
1478 for (signal, handler) in &script.traps {
1479 out.push_str(&format!("trap '{}' {}\n", handler, signal));
1480 }
1481 if !script.traps.is_empty() {
1482 out.push('\n');
1483 }
1484 for (name, value) in &script.globals {
1485 out.push_str(&format!("readonly {}={}\n", name, value));
1486 }
1487 if !script.globals.is_empty() {
1488 out.push('\n');
1489 }
1490 for func in &script.functions {
1491 out.push_str(&self.emit_function(func));
1492 if self.spacing {
1493 out.push('\n');
1494 }
1495 }
1496 if !script.main.is_empty() {
1497 if !script.functions.is_empty() {
1498 out.push_str("# --- main ---\n");
1499 }
1500 for line in &script.main {
1501 out.push_str(line);
1502 out.push('\n');
1503 }
1504 }
1505 out
1506 }
1507 pub fn emit_array_assign(&self, name: &str, elems: &[BashExpr]) -> std::string::String {
1509 let elems_s: Vec<std::string::String> = elems.iter().map(|e| format!("{}", e)).collect();
1510 format!("{}=({})", name, elems_s.join(" "))
1511 }
1512 pub fn emit_assoc_array_assign(
1514 &self,
1515 name: &str,
1516 pairs: &[(std::string::String, std::string::String)],
1517 ) -> std::string::String {
1518 let mut out = format!("declare -A {}\n", name);
1519 for (k, v) in pairs {
1520 out.push_str(&format!("{}[{}]={}\n", name, k, v));
1521 }
1522 out
1523 }
1524 pub fn emit_if(
1526 &self,
1527 cond: &BashCondition,
1528 then: &[&str],
1529 else_: Option<&[&str]>,
1530 ) -> std::string::String {
1531 let mut out = format!("if {}; then\n", cond);
1532 for line in then {
1533 out.push_str(&format!("{}{}\n", self.indent, line));
1534 }
1535 if let Some(else_body) = else_ {
1536 out.push_str("else\n");
1537 for line in else_body {
1538 out.push_str(&format!("{}{}\n", self.indent, line));
1539 }
1540 }
1541 out.push_str("fi\n");
1542 out
1543 }
1544 pub fn emit_for_in(&self, var: &str, items: &[BashExpr], body: &[&str]) -> std::string::String {
1546 let items_s: Vec<std::string::String> = items.iter().map(|e| format!("{}", e)).collect();
1547 let mut out = format!("for {} in {}; do\n", var, items_s.join(" "));
1548 for line in body {
1549 out.push_str(&format!("{}{}\n", self.indent, line));
1550 }
1551 out.push_str("done\n");
1552 out
1553 }
1554 pub fn emit_while(&self, cond: &BashCondition, body: &[&str]) -> std::string::String {
1556 let mut out = format!("while {}; do\n", cond);
1557 for line in body {
1558 out.push_str(&format!("{}{}\n", self.indent, line));
1559 }
1560 out.push_str("done\n");
1561 out
1562 }
1563 pub fn emit_case(&self, expr: &BashExpr, arms: &[(&str, Vec<&str>)]) -> std::string::String {
1565 let mut out = format!("case {} in\n", expr);
1566 for (pattern, body) in arms {
1567 out.push_str(&format!("{}{})\n", self.indent, pattern));
1568 for line in body {
1569 out.push_str(&format!("{}{}{}\n", self.indent, self.indent, line));
1570 }
1571 out.push_str(&format!("{};;\n", self.indent));
1572 }
1573 out.push_str("esac\n");
1574 out
1575 }
1576}
1577#[allow(dead_code)]
1578#[derive(Debug, Clone)]
1579pub struct BashLivenessInfo {
1580 pub live_in: Vec<std::collections::HashSet<u32>>,
1581 pub live_out: Vec<std::collections::HashSet<u32>>,
1582 pub defs: Vec<std::collections::HashSet<u32>>,
1583 pub uses: Vec<std::collections::HashSet<u32>>,
1584}
1585impl BashLivenessInfo {
1586 #[allow(dead_code)]
1587 pub fn new(block_count: usize) -> Self {
1588 BashLivenessInfo {
1589 live_in: vec![std::collections::HashSet::new(); block_count],
1590 live_out: vec![std::collections::HashSet::new(); block_count],
1591 defs: vec![std::collections::HashSet::new(); block_count],
1592 uses: vec![std::collections::HashSet::new(); block_count],
1593 }
1594 }
1595 #[allow(dead_code)]
1596 pub fn add_def(&mut self, block: usize, var: u32) {
1597 if block < self.defs.len() {
1598 self.defs[block].insert(var);
1599 }
1600 }
1601 #[allow(dead_code)]
1602 pub fn add_use(&mut self, block: usize, var: u32) {
1603 if block < self.uses.len() {
1604 self.uses[block].insert(var);
1605 }
1606 }
1607 #[allow(dead_code)]
1608 pub fn is_live_in(&self, block: usize, var: u32) -> bool {
1609 self.live_in
1610 .get(block)
1611 .map(|s| s.contains(&var))
1612 .unwrap_or(false)
1613 }
1614 #[allow(dead_code)]
1615 pub fn is_live_out(&self, block: usize, var: u32) -> bool {
1616 self.live_out
1617 .get(block)
1618 .map(|s| s.contains(&var))
1619 .unwrap_or(false)
1620 }
1621}
1622#[allow(dead_code)]
1624#[derive(Debug, Clone, PartialEq)]
1625pub struct BashTrap {
1626 pub signal: std::string::String,
1628 pub handler: std::string::String,
1630 pub reset: bool,
1632}
1633#[allow(dead_code)]
1634impl BashTrap {
1635 pub fn new(signal: &str, handler: &str) -> Self {
1637 BashTrap {
1638 signal: signal.to_string(),
1639 handler: handler.to_string(),
1640 reset: false,
1641 }
1642 }
1643 pub fn on_exit(handler: &str) -> Self {
1645 BashTrap::new("EXIT", handler)
1646 }
1647 pub fn on_err(handler: &str) -> Self {
1649 BashTrap::new("ERR", handler)
1650 }
1651 pub fn on_int(handler: &str) -> Self {
1653 BashTrap::new("INT", handler)
1654 }
1655 pub fn on_term(handler: &str) -> Self {
1657 BashTrap::new("TERM", handler)
1658 }
1659 pub fn reset(signal: &str) -> Self {
1661 BashTrap {
1662 signal: signal.to_string(),
1663 handler: "-".to_string(),
1664 reset: true,
1665 }
1666 }
1667 pub fn emit(&self) -> std::string::String {
1669 format!("trap '{}' {}", self.handler, self.signal)
1670 }
1671}