rustpython/
settings.rs

1use clap::{App, AppSettings, Arg, ArgMatches};
2use rustpython_vm::Settings;
3use std::{env, str::FromStr};
4
5pub enum RunMode {
6    ScriptInteractive(Option<String>, bool),
7    Command(String),
8    Module(String),
9    InstallPip(String),
10}
11
12pub fn opts_with_clap() -> (Settings, RunMode) {
13    let app = App::new("RustPython");
14    let matches = parse_arguments(app);
15    settings_from(&matches)
16}
17
18fn parse_arguments<'a>(app: App<'a, '_>) -> ArgMatches<'a> {
19    let app = app
20        .setting(AppSettings::TrailingVarArg)
21        .version(crate_version!())
22        .author(crate_authors!())
23        .about("Rust implementation of the Python language")
24        .usage("rustpython [OPTIONS] [-c CMD | -m MODULE | FILE] [PYARGS]...")
25        .arg(
26            Arg::with_name("script")
27                .required(false)
28                .allow_hyphen_values(true)
29                .multiple(true)
30                .value_name("script, args")
31                .min_values(1),
32        )
33        .arg(
34            Arg::with_name("c")
35                .short("c")
36                .takes_value(true)
37                .allow_hyphen_values(true)
38                .multiple(true)
39                .value_name("cmd, args")
40                .min_values(1)
41                .help("run the given string as a program"),
42        )
43        .arg(
44            Arg::with_name("m")
45                .short("m")
46                .takes_value(true)
47                .allow_hyphen_values(true)
48                .multiple(true)
49                .value_name("module, args")
50                .min_values(1)
51                .help("run library module as script"),
52        )
53        .arg(
54            Arg::with_name("install_pip")
55                .long("install-pip")
56                .takes_value(true)
57                .allow_hyphen_values(true)
58                .multiple(true)
59                .value_name("get-pip args")
60                .min_values(0)
61                .help("install the pip package manager for rustpython; \
62                        requires rustpython be build with the ssl feature enabled."
63                ),
64        )
65        .arg(
66            Arg::with_name("optimize")
67                .short("O")
68                .multiple(true)
69                .help("Optimize. Set __debug__ to false. Remove debug statements."),
70        )
71        .arg(
72            Arg::with_name("verbose")
73                .short("v")
74                .multiple(true)
75                .help("Give the verbosity (can be applied multiple times)"),
76        )
77        .arg(Arg::with_name("debug").short("d").help("Debug the parser."))
78        .arg(
79            Arg::with_name("quiet")
80                .short("q")
81                .help("Be quiet at startup."),
82        )
83        .arg(
84            Arg::with_name("inspect")
85                .short("i")
86                .help("Inspect interactively after running the script."),
87        )
88        .arg(
89            Arg::with_name("no-user-site")
90                .short("s")
91                .help("don't add user site directory to sys.path."),
92        )
93        .arg(
94            Arg::with_name("no-site")
95                .short("S")
96                .help("don't imply 'import site' on initialization"),
97        )
98        .arg(
99            Arg::with_name("dont-write-bytecode")
100                .short("B")
101                .help("don't write .pyc files on import"),
102        )
103        .arg(
104            Arg::with_name("safe-path")
105                .short("P")
106                .help("don’t prepend a potentially unsafe path to sys.path"),
107        )
108        .arg(
109            Arg::with_name("ignore-environment")
110                .short("E")
111                .help("Ignore environment variables PYTHON* such as PYTHONPATH"),
112        )
113        .arg(
114            Arg::with_name("isolate")
115                .short("I")
116                .help("isolate Python from the user's environment (implies -E and -s)"),
117        )
118        .arg(
119            Arg::with_name("implementation-option")
120                .short("X")
121                .takes_value(true)
122                .multiple(true)
123                .number_of_values(1)
124                .help("set implementation-specific option"),
125        )
126        .arg(
127            Arg::with_name("warning-control")
128                .short("W")
129                .takes_value(true)
130                .multiple(true)
131                .number_of_values(1)
132                .help("warning control; arg is action:message:category:module:lineno"),
133        )
134        .arg(
135            Arg::with_name("check-hash-based-pycs")
136                .long("check-hash-based-pycs")
137                .takes_value(true)
138                .number_of_values(1)
139                .default_value("default")
140                .help("always|default|never\ncontrol how Python invalidates hash-based .pyc files"),
141        )
142        .arg(
143            Arg::with_name("bytes-warning")
144                .short("b")
145                .multiple(true)
146                .help("issue warnings about using bytes where strings are usually expected (-bb: issue errors)"),
147        ).arg(
148            Arg::with_name("unbuffered")
149                .short("u")
150                .help(
151                    "force the stdout and stderr streams to be unbuffered; \
152                        this option has no effect on stdin; also PYTHONUNBUFFERED=x",
153                ),
154        );
155    #[cfg(feature = "flame-it")]
156    let app = app
157        .arg(
158            Arg::with_name("profile_output")
159                .long("profile-output")
160                .takes_value(true)
161                .help("the file to output the profiling information to"),
162        )
163        .arg(
164            Arg::with_name("profile_format")
165                .long("profile-format")
166                .takes_value(true)
167                .help("the profile format to output the profiling information in"),
168        );
169    app.get_matches()
170}
171
172/// Create settings by examining command line arguments and environment
173/// variables.
174fn settings_from(matches: &ArgMatches) -> (Settings, RunMode) {
175    let mut settings = Settings::default();
176    settings.isolated = matches.is_present("isolate");
177    settings.ignore_environment = matches.is_present("ignore-environment");
178    settings.interactive = !matches.is_present("c")
179        && !matches.is_present("m")
180        && (!matches.is_present("script") || matches.is_present("inspect"));
181    settings.bytes_warning = matches.occurrences_of("bytes-warning");
182    settings.import_site = !matches.is_present("no-site");
183
184    let ignore_environment = settings.ignore_environment || settings.isolated;
185
186    if !ignore_environment {
187        settings.path_list.extend(get_paths("RUSTPYTHONPATH"));
188        settings.path_list.extend(get_paths("PYTHONPATH"));
189    }
190
191    // Now process command line flags:
192    if matches.is_present("debug") || (!ignore_environment && env::var_os("PYTHONDEBUG").is_some())
193    {
194        settings.debug = true;
195    }
196
197    if matches.is_present("inspect")
198        || (!ignore_environment && env::var_os("PYTHONINSPECT").is_some())
199    {
200        settings.inspect = true;
201    }
202
203    if matches.is_present("optimize") {
204        settings.optimize = matches.occurrences_of("optimize").try_into().unwrap();
205    } else if !ignore_environment {
206        if let Ok(value) = get_env_var_value("PYTHONOPTIMIZE") {
207            settings.optimize = value;
208        }
209    }
210
211    if matches.is_present("verbose") {
212        settings.verbose = matches.occurrences_of("verbose").try_into().unwrap();
213    } else if !ignore_environment {
214        if let Ok(value) = get_env_var_value("PYTHONVERBOSE") {
215            settings.verbose = value;
216        }
217    }
218
219    if matches.is_present("no-user-site")
220        || matches.is_present("isolate")
221        || (!ignore_environment && env::var_os("PYTHONNOUSERSITE").is_some())
222    {
223        settings.user_site_directory = false;
224    }
225
226    if matches.is_present("quiet") {
227        settings.quiet = true;
228    }
229
230    if matches.is_present("dont-write-bytecode")
231        || (!ignore_environment && env::var_os("PYTHONDONTWRITEBYTECODE").is_some())
232    {
233        settings.write_bytecode = false;
234    }
235    if !ignore_environment && env::var_os("PYTHONINTMAXSTRDIGITS").is_some() {
236        settings.int_max_str_digits = match env::var("PYTHONINTMAXSTRDIGITS").unwrap().parse() {
237            Ok(digits) if digits == 0 || digits >= 640 => digits,
238            _ => {
239                error!("Fatal Python error: config_init_int_max_str_digits: PYTHONINTMAXSTRDIGITS: invalid limit; must be >= 640 or 0 for unlimited.\nPython runtime state: preinitialized");
240                std::process::exit(1);
241            }
242        };
243    }
244
245    if matches.is_present("safe-path")
246        || (!ignore_environment && env::var_os("PYTHONSAFEPATH").is_some())
247    {
248        settings.safe_path = true;
249    }
250
251    matches
252        .value_of("check-hash-based-pycs")
253        .unwrap_or("default")
254        .clone_into(&mut settings.check_hash_pycs_mode);
255
256    let mut dev_mode = false;
257    let mut warn_default_encoding = false;
258    if let Some(xopts) = matches.values_of("implementation-option") {
259        settings.xoptions.extend(xopts.map(|s| {
260            let mut parts = s.splitn(2, '=');
261            let name = parts.next().unwrap().to_owned();
262            let value = parts.next().map(ToOwned::to_owned);
263            if name == "dev" {
264                dev_mode = true
265            }
266            if name == "warn_default_encoding" {
267                warn_default_encoding = true
268            }
269            if name == "no_sig_int" {
270                settings.install_signal_handlers = false;
271            }
272            if name == "int_max_str_digits" {
273                settings.int_max_str_digits = match value.as_ref().unwrap().parse() {
274                    Ok(digits) if digits == 0 || digits >= 640 => digits,
275                    _ => {
276
277                    error!("Fatal Python error: config_init_int_max_str_digits: -X int_max_str_digits: invalid limit; must be >= 640 or 0 for unlimited.\nPython runtime state: preinitialized");
278                    std::process::exit(1);
279                    },
280                };
281            }
282            (name, value)
283        }));
284    }
285    settings.dev_mode = dev_mode;
286    if warn_default_encoding
287        || (!ignore_environment && env::var_os("PYTHONWARNDEFAULTENCODING").is_some())
288    {
289        settings.warn_default_encoding = true;
290    }
291
292    if dev_mode {
293        settings.warnoptions.push("default".to_owned())
294    }
295    if settings.bytes_warning > 0 {
296        let warn = if settings.bytes_warning > 1 {
297            "error::BytesWarning"
298        } else {
299            "default::BytesWarning"
300        };
301        settings.warnoptions.push(warn.to_owned());
302    }
303    if let Some(warnings) = matches.values_of("warning-control") {
304        settings.warnoptions.extend(warnings.map(ToOwned::to_owned));
305    }
306
307    let (mode, argv) = if let Some(mut cmd) = matches.values_of("c") {
308        let command = cmd.next().expect("clap ensure this exists");
309        let argv = std::iter::once("-c".to_owned())
310            .chain(cmd.map(ToOwned::to_owned))
311            .collect();
312        (RunMode::Command(command.to_owned()), argv)
313    } else if let Some(mut cmd) = matches.values_of("m") {
314        let module = cmd.next().expect("clap ensure this exists");
315        let argv = std::iter::once("PLACEHOLDER".to_owned())
316            .chain(cmd.map(ToOwned::to_owned))
317            .collect();
318        (RunMode::Module(module.to_owned()), argv)
319    } else if let Some(get_pip_args) = matches.values_of("install_pip") {
320        settings.isolated = true;
321        let mut args: Vec<_> = get_pip_args.map(ToOwned::to_owned).collect();
322        if args.is_empty() {
323            args.push("ensurepip".to_owned());
324            args.push("--upgrade".to_owned());
325            args.push("--default-pip".to_owned());
326        }
327        let installer = args[0].clone();
328        let mode = match installer.as_str() {
329            "ensurepip" | "get-pip" => RunMode::InstallPip(installer),
330            _ => panic!("--install-pip takes ensurepip or get-pip as first argument"),
331        };
332        (mode, args)
333    } else if let Some(argv) = matches.values_of("script") {
334        let argv: Vec<_> = argv.map(ToOwned::to_owned).collect();
335        let script = argv[0].clone();
336        (
337            RunMode::ScriptInteractive(Some(script), matches.is_present("inspect")),
338            argv,
339        )
340    } else {
341        (RunMode::ScriptInteractive(None, true), vec!["".to_owned()])
342    };
343
344    let hash_seed = match env::var("PYTHONHASHSEED") {
345        Ok(s) if s == "random" => Some(None),
346        Ok(s) => s.parse::<u32>().ok().map(Some),
347        Err(_) => Some(None),
348    };
349    settings.hash_seed = hash_seed.unwrap_or_else(|| {
350        error!("Fatal Python init error: PYTHONHASHSEED must be \"random\" or an integer in range [0; 4294967295]");
351        // TODO: Need to change to ExitCode or Termination
352        std::process::exit(1)
353    });
354
355    settings.argv = argv;
356
357    (settings, mode)
358}
359
360/// Get environment variable and turn it into integer.
361fn get_env_var_value(name: &str) -> Result<u8, std::env::VarError> {
362    env::var(name).map(|value| u8::from_str(&value).unwrap_or(1))
363}
364
365/// Helper function to retrieve a sequence of paths from an environment variable.
366fn get_paths(env_variable_name: &str) -> impl Iterator<Item = String> + '_ {
367    env::var_os(env_variable_name)
368        .into_iter()
369        .flat_map(move |paths| {
370            split_paths(&paths)
371                .map(|path| {
372                    path.into_os_string()
373                        .into_string()
374                        .unwrap_or_else(|_| panic!("{env_variable_name} isn't valid unicode"))
375                })
376                .collect::<Vec<_>>()
377        })
378}
379
380#[cfg(not(target_os = "wasi"))]
381pub(crate) use env::split_paths;
382#[cfg(target_os = "wasi")]
383pub(crate) fn split_paths<T: AsRef<std::ffi::OsStr> + ?Sized>(
384    s: &T,
385) -> impl Iterator<Item = std::path::PathBuf> + '_ {
386    use std::os::wasi::ffi::OsStrExt;
387    let s = s.as_ref().as_bytes();
388    s.split(|b| *b == b':')
389        .map(|x| std::ffi::OsStr::from_bytes(x).to_owned().into())
390}
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy