1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
use clap::Parser;
use regex::Regex;

/// Represents the configuration options for PyTV.
#[derive(Debug)]
pub struct Config {
    /// The magic comment string used to identify template sections in the input file.
    pub magic_comment_str: String,
    /// The regular expression used to match template sections in the input file.
    pub template_re: Regex,
    /// Whether to run the Python script or not.
    pub run_python: bool,
    /// Whether to delete the Python script after running or not.
    pub delete_python: bool,
    /// The tab size used for parsing in the input file.
    pub tab_size: u32,
}

/// Represents the options for input and output file for PyTV.
#[derive(Debug, Default)]
pub struct FileOptions {
    /// The input file path.
    pub input: String,
    /// The output file path (optional).
    pub output: Option<String>,
}

impl Default for Config {
    /// Creates a new `Config` instance with default values.
    fn default() -> Self {
        Self::new(
            "!".to_string(),
            Self::default_template_re(),
            false,
            false,
            4,
        )
    }
}

/// Python Templated Verilog
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
    /// Input file
    #[arg(index = 1, value_name = "FILE")]
    input: String,
    /// Output file
    #[arg(short, long)]
    output: Option<String>,
    /// Run python (keep Python script)
    #[arg(
        short = 'r',
        long = "run-py",
        conflicts_with = "run_python_del",
        default_value = "false"
    )]
    run_python: bool,
    /// Run python (delete Python script)
    #[arg(
        short = 'R',
        long = "run-py-del",
        conflicts_with = "run_python",
        default_value = "false"
    )]
    run_python_del: bool,
    /// Tab size
    #[arg(short, long, default_value = "4", value_name = "INT")]
    tab_size: u32,
    /// Magic comment string (after "//")
    #[arg(short, long, default_value = "!", value_name = "STRING")]
    magic: String,
}

impl Config {
    /// Creates a new `Config` instance with the specified values.
    ///
    /// # Example
    /// ```
    /// use pytv::Config;
    /// use regex::Regex;
    /// let config = Config::new("!".to_string(), Regex::new(r"`([^`]+)`").unwrap(), false, false, 4);
    /// let default_config = Config::default();
    /// assert_eq!(config.magic_comment_str, default_config.magic_comment_str);
    /// ```
    pub fn new(
        magic_comment_str: String,
        template_re: Regex,
        run_python: bool,
        delete_python: bool,
        tab_size: u32,
    ) -> Config {
        Config {
            magic_comment_str,
            template_re,
            run_python,
            delete_python,
            tab_size,
        }
    }

    /// Parses the command line arguments and returns a tuple of `Config` and `FileOptions`.
    pub fn from_args() -> (Config, FileOptions) {
        let args = Args::parse();
        (
            Self::new(
                args.magic,
                Self::default_template_re(),
                args.run_python || args.run_python_del,
                args.run_python_del && !args.run_python,
                args.tab_size,
            ),
            FileOptions {
                input: args.input,
                output: args.output,
            },
        )
    }

    /// Returns the default regular expression used to match template sections in the input file.
    fn default_template_re() -> Regex {
        Regex::new(r"`([^`]+)`").unwrap()
    }
}