GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Docs.rs
  • rustpython-0.6.0
  • Permalink
  • Docs.rs crate page
  • MIT
  • Repository
  • crates.io
  • Source
  • windelbouwman
  • github:rustpython:packaging
  • dirs ^6 normal
  • env_logger ^0.11 normal
  • flame ^0.2.2 normal optional
  • flamescope ^0.1.2 normal optional

This view

  • Original page
  • New address

GeezerVison is showing docs.rs — settings.rs - source

rustpython/
settings.rs

1use core::cmp;
2use core::num::NonZeroI32;
3use core::str::FromStr;
4use lexopt::Arg::{Long, Short, Value};
5use lexopt::ValueExt;
6use rustpython_vm::{Settings, vm::CheckHashPycsMode};
7use std::env;
8
9pub enum RunMode {
10    Script(String),
11    Command(String),
12    Module(String),
13    InstallPip(InstallPipMode),
14    Repl,
15}
16
17pub enum InstallPipMode {
18    /// Install pip using the ensurepip pip module. This has a higher chance of
19    /// success, but may not install the latest version of pip.
20    Ensurepip,
21    /// Install pip using the get-pip.py script, which retrieves the latest pip version.
22    /// This can be broken due to incompatibilities with cpython.
23    GetPip,
24}
25
26impl FromStr for InstallPipMode {
27    type Err = &'static str;
28
29    fn from_str(s: &str) -> Result<Self, Self::Err> {
30        match s {
31            "ensurepip" => Ok(Self::Ensurepip),
32            "get-pip" => Ok(Self::GetPip),
33            _ => Err("--install-pip takes ensurepip or get-pip as first argument"),
34        }
35    }
36}
37
38#[derive(Default)]
39struct CliArgs {
40    bytes_warning: u8,
41    dont_write_bytecode: bool,
42    debug: u8,
43    ignore_environment: bool,
44    inspect: bool,
45    isolate: bool,
46    optimize: u8,
47    safe_path: bool,
48    quiet: bool,
49    random_hash_seed: bool,
50    no_user_site: bool,
51    no_site: bool,
52    unbuffered: bool,
53    verbose: u8,
54    warning_control: Vec<String>,
55    implementation_option: Vec<String>,
56    check_hash_based_pycs: CheckHashPycsMode,
57
58    #[cfg(feature = "flame-it")]
59    profile_output: Option<std::ffi::OsString>,
60    #[cfg(feature = "flame-it")]
61    profile_format: Option<String>,
62}
63
64const USAGE_STRING: &str = "\
65usage: {PROG} [option] ... [-c cmd | -m mod | file | -] [arg] ...
66Options (and corresponding environment variables):
67-b     : issue warnings about converting bytes/bytearray to str and comparing
68         bytes/bytearray with str or bytes with int. (-bb: issue errors)
69-B     : don't write .pyc files on import; also PYTHONDONTWRITEBYTECODE=x
70-c cmd : program passed in as string (terminates option list)
71-d     : turn on parser debugging output (for experts only, only works on
72         debug builds); also PYTHONDEBUG=x
73-E     : ignore PYTHON* environment variables (such as PYTHONPATH)
74-h     : print this help message and exit (also -? or --help)
75-i     : inspect interactively after running script; forces a prompt even
76         if stdin does not appear to be a terminal; also PYTHONINSPECT=x
77-I     : isolate Python from the user's environment (implies -E and -s)
78-m mod : run library module as a script (terminates option list)
79-O     : remove assert and __debug__-dependent statements; add .opt-1 before
80         .pyc extension; also PYTHONOPTIMIZE=x
81-OO    : do -O changes and also discard docstrings; add .opt-2 before
82         .pyc extension
83-P     : don't prepend a potentially unsafe path to sys.path; also
84         PYTHONSAFEPATH
85-q     : don't print version and copyright messages on interactive startup
86-s     : don't add user site directory to sys.path; also PYTHONNOUSERSITE=x
87-S     : don't imply 'import site' on initialization
88-u     : force the stdout and stderr streams to be unbuffered;
89         this option has no effect on stdin; also PYTHONUNBUFFERED=x
90-v     : verbose (trace import statements); also PYTHONVERBOSE=x
91         can be supplied multiple times to increase verbosity
92-V     : print the Python version number and exit (also --version)
93         when given twice, print more information about the build
94-W arg : warning control; arg is action:message:category:module:lineno
95         also PYTHONWARNINGS=arg
96-x     : skip first line of source, allowing use of non-Unix forms of #!cmd
97-X opt : set implementation-specific option
98--check-hash-based-pycs always|default|never:
99         control how Python invalidates hash-based .pyc files
100--help-env: print help about Python environment variables and exit
101--help-xoptions: print help about implementation-specific -X options and exit
102--help-all: print complete help information and exit
103
104RustPython extensions:
105
106
107Arguments:
108file   : program read from script file
109-      : program read from stdin (default; interactive mode if a tty)
110arg ...: arguments passed to program in sys.argv[1:]
111";
112
113fn parse_args() -> Result<(CliArgs, RunMode, Vec<String>), lexopt::Error> {
114    let mut args = CliArgs::default();
115    let mut parser = lexopt::Parser::from_env();
116    fn argv(argv0: String, mut parser: lexopt::Parser) -> Result<Vec<String>, lexopt::Error> {
117        core::iter::once(Ok(argv0))
118            .chain(parser.raw_args()?.map(|arg| arg.string()))
119            .collect()
120    }
121    while let Some(arg) = parser.next()? {
122        match arg {
123            Short('b') => args.bytes_warning += 1,
124            Short('B') => args.dont_write_bytecode = true,
125            Short('c') => {
126                let cmd = parser.value()?.string()?;
127                return Ok((
128                    args,
129                    RunMode::Command(dedent(&cmd)),
130                    argv("-c".to_owned(), parser)?,
131                ));
132            }
133            Short('d') => args.debug += 1,
134            Short('E') => args.ignore_environment = true,
135            Short('h' | '?') | Long("help") => help(parser),
136            Short('i') => args.inspect = true,
137            Short('I') => args.isolate = true,
138            Short('m') => {
139                let module = parser.value()?.string()?;
140                let argv = argv("-m".to_owned(), parser)?;
141                return Ok((args, RunMode::Module(module), argv));
142            }
143            Short('O') => args.optimize += 1,
144            Short('P') => args.safe_path = true,
145            Short('q') => args.quiet = true,
146            Short('R') => args.random_hash_seed = true,
147            Short('S') => args.no_site = true,
148            Short('s') => args.no_user_site = true,
149            Short('u') => args.unbuffered = true,
150            Short('v') => args.verbose += 1,
151            Short('V') | Long("version") => version(),
152            Short('W') => args.warning_control.push(parser.value()?.string()?),
153            // TODO: Short('x') =>
154            Short('X') => args.implementation_option.push(parser.value()?.string()?),
155
156            Long("check-hash-based-pycs") => {
157                args.check_hash_based_pycs = parser.value()?.parse()?
158            }
159
160            // TODO: make these more specific
161            Long("help-env") => help(parser),
162            Long("help-xoptions") => help(parser),
163            Long("help-all") => help(parser),
164
165            #[cfg(feature = "flame-it")]
166            Long("profile-output") => args.profile_output = Some(parser.value()?),
167            #[cfg(feature = "flame-it")]
168            Long("profile-format") => args.profile_format = Some(parser.value()?.string()?),
169
170            Long("install-pip") => {
171                let (mode, argv) = if let Some(val) = parser.optional_value() {
172                    (val.parse()?, vec![val.string()?])
173                } else if let Ok(argv0) = parser.value() {
174                    let mode = argv0.parse()?;
175                    (mode, argv(argv0.string()?, parser)?)
176                } else {
177                    (
178                        InstallPipMode::Ensurepip,
179                        ["ensurepip", "--upgrade", "--default-pip"]
180                            .map(str::to_owned)
181                            .into(),
182                    )
183                };
184                return Ok((args, RunMode::InstallPip(mode), argv));
185            }
186            Value(script_name) => {
187                let script_name = script_name.string()?;
188                let mode = if script_name == "-" {
189                    RunMode::Repl
190                } else {
191                    RunMode::Script(script_name.clone())
192                };
193                return Ok((args, mode, argv(script_name, parser)?));
194            }
195            _ => return Err(arg.unexpected()),
196        }
197    }
198    Ok((args, RunMode::Repl, vec![]))
199}
200
201fn help(parser: lexopt::Parser) -> ! {
202    let usage = USAGE_STRING.replace("{PROG}", parser.bin_name().unwrap_or("rustpython"));
203    print!("{usage}");
204    std::process::exit(0);
205}
206
207fn version() -> ! {
208    println!("Python {}", rustpython_vm::version::RUSTPYTHON_VERSION);
209    std::process::exit(0);
210}
211
212/// Create settings by examining command line arguments and environment
213/// variables.
214pub fn parse_opts() -> Result<(Settings, RunMode), lexopt::Error> {
215    let (args, mode, argv) = parse_args()?;
216
217    let mut settings = Settings::default();
218    settings.isolated = args.isolate;
219    settings.ignore_environment = settings.isolated || args.ignore_environment;
220    settings.bytes_warning = args.bytes_warning.into();
221    settings.import_site = !args.no_site;
222
223    let ignore_environment = settings.ignore_environment;
224
225    if !ignore_environment {
226        settings.path_list.extend(get_paths("RUSTPYTHONPATH"));
227        settings.path_list.extend(get_paths("PYTHONPATH"));
228    }
229
230    // Now process command line flags:
231
232    let get_env = |env| (!ignore_environment).then(|| env::var_os(env)).flatten();
233
234    let env_count = |env| {
235        get_env(env).filter(|v| !v.is_empty()).map_or(0, |val| {
236            val.to_str().and_then(|v| v.parse::<u8>().ok()).unwrap_or(1)
237        })
238    };
239
240    settings.optimize = cmp::max(args.optimize, env_count("PYTHONOPTIMIZE"));
241    settings.verbose = cmp::max(args.verbose, env_count("PYTHONVERBOSE"));
242    settings.debug = cmp::max(args.debug, env_count("PYTHONDEBUG"));
243
244    let env_bool = |env| get_env(env).is_some_and(|v| !v.is_empty());
245
246    settings.user_site_directory =
247        !(settings.isolated || args.no_user_site || env_bool("PYTHONNOUSERSITE"));
248    settings.quiet = args.quiet;
249    settings.write_bytecode = !(args.dont_write_bytecode || env_bool("PYTHONDONTWRITEBYTECODE"));
250    settings.safe_path = settings.isolated || args.safe_path || env_bool("PYTHONSAFEPATH");
251    settings.inspect = args.inspect || env_bool("PYTHONINSPECT");
252    settings.interactive = args.inspect;
253    settings.buffered_stdio = !args.unbuffered;
254
255    if let Some(val) = get_env("PYTHONINTMAXSTRDIGITS") {
256        settings.int_max_str_digits = match val.to_str().and_then(|s| s.parse().ok()) {
257            Some(digits @ (0 | 640..)) => digits,
258            _ => {
259                error!(
260                    "Fatal Python error: config_init_int_max_str_digits: PYTHONINTMAXSTRDIGITS: invalid limit; must be >= 640 or 0 for unlimited.\nPython runtime state: preinitialized"
261                );
262                std::process::exit(1);
263            }
264        };
265    }
266
267    if let Some(val) = get_env("PYTHON_CPU_COUNT") {
268        settings.cpu_count = match parse_cpu_count(val.to_str()) {
269            Ok(cpu_count) => cpu_count,
270            Err(()) => {
271                error!(
272                    "Fatal Python error: config_init_cpu_count: \
273                     -X cpu_count=n option: n is missing or an invalid number, \
274                     n must be greater than 0\n\
275                     Python runtime state: preinitialized"
276                );
277                std::process::exit(1);
278            }
279        };
280    }
281
282    settings.check_hash_pycs_mode = args.check_hash_based_pycs;
283
284    if let Some(val) = get_env("PYTHONUTF8")
285        && let Some(val_str) = val.to_str()
286        && !val_str.is_empty()
287    {
288        settings.utf8_mode = match val_str {
289            "1" => 1,
290            "0" => 0,
291            _ => {
292                error!(
293                    "Fatal Python error: config_init_utf8_mode: \
294                     PYTHONUTF8=N: N is missing or invalid\n\
295                     Python runtime state: preinitialized"
296                );
297                std::process::exit(1);
298            }
299        };
300    }
301
302    let xopts = args.implementation_option.into_iter().map(|s| {
303        let (name, value) = match s.split_once('=') {
304            Some((name, value)) => (name.to_owned(), Some(value)),
305            None => (s, None),
306        };
307        match &*name {
308            "dev" => settings.dev_mode = true,
309            "faulthandler" => settings.faulthandler = true,
310            "warn_default_encoding" => settings.warn_default_encoding = true,
311            "utf8" => {
312                settings.utf8_mode = match value {
313                    None => 1,
314                    Some("1") => 1,
315                    Some("0") => 0,
316                    _ => {
317                        error!(
318                            "Fatal Python error: config_init_utf8_mode: \
319                             -X utf8=n: n is missing or invalid\n\
320                             Python runtime state: preinitialized"
321                        );
322                        std::process::exit(1);
323                    }
324                };
325            }
326            "no_sig_int" => settings.install_signal_handlers = false,
327            "no_debug_ranges" => settings.code_debug_ranges = false,
328            "cpu_count" => {
329                settings.cpu_count = match parse_cpu_count(value) {
330                    Ok(cpu_count) => cpu_count,
331                    Err(()) => {
332                        error!(
333                            "Fatal Python error: config_init_cpu_count: \
334                             -X cpu_count=n option: n is missing or an invalid \
335                             number, n must be greater than 0\n\
336                             Python runtime state: preinitialized"
337                        );
338                        std::process::exit(1);
339                    }
340                };
341            }
342            "int_max_str_digits" => {
343                settings.int_max_str_digits = match value.and_then(|value| value.parse().ok()) {
344                    Some(digits) if digits == 0 || digits >= 640 => digits,
345                    _ => {
346                        error!(
347                            "Fatal Python error: config_init_int_max_str_digits: \
348                             -X int_max_str_digits: \
349                             invalid limit; must be >= 640 or 0 for unlimited.\n\
350                             Python runtime state: preinitialized"
351                        );
352                        std::process::exit(1);
353                    }
354                };
355            }
356            "thread_inherit_context" => {
357                settings.thread_inherit_context = match value {
358                    Some("1") => true,
359                    Some("0") => false,
360                    _ => {
361                        error!(
362                            "Fatal Python error: config_init_thread_inherit_context: \
363                             -X thread_inherit_context=n: n is missing or invalid\n\
364                             Python runtime state: preinitialized"
365                        );
366                        std::process::exit(1);
367                    }
368                };
369            }
370            _ => {}
371        }
372        (name, value.map(str::to_owned))
373    });
374    settings.xoptions.extend(xopts);
375
376    // Resolve utf8_mode if not explicitly set by PYTHONUTF8 or -X utf8.
377    // Default to UTF-8 mode since RustPython's locale encoding detection
378    // is incomplete. Users can set PYTHONUTF8=0 or -X utf8=0 to disable.
379    if settings.utf8_mode < 0 {
380        settings.utf8_mode = 1;
381    }
382
383    settings.warn_default_encoding =
384        settings.warn_default_encoding || env_bool("PYTHONWARNDEFAULTENCODING");
385    settings.faulthandler = settings.faulthandler || env_bool("PYTHONFAULTHANDLER");
386    if env_bool("PYTHONNODEBUGRANGES") {
387        settings.code_debug_ranges = false;
388    }
389    if let Some(val) = get_env("PYTHON_THREAD_INHERIT_CONTEXT") {
390        settings.thread_inherit_context = match val.to_str() {
391            Some("1") => true,
392            Some("0") => false,
393            _ => {
394                error!(
395                    "Fatal Python error: config_init_thread_inherit_context: \
396                     PYTHON_THREAD_INHERIT_CONTEXT=N: N is missing or invalid\n\
397                     Python runtime state: preinitialized"
398                );
399                std::process::exit(1);
400            }
401        };
402    }
403
404    // Parse PYTHONIOENCODING=encoding[:errors]
405    if let Some(val) = get_env("PYTHONIOENCODING")
406        && let Some(val_str) = val.to_str()
407        && !val_str.is_empty()
408    {
409        if let Some((enc, err)) = val_str.split_once(':') {
410            if !enc.is_empty() {
411                settings.stdio_encoding = Some(enc.to_owned());
412            }
413            if !err.is_empty() {
414                settings.stdio_errors = Some(err.to_owned());
415            }
416        } else {
417            settings.stdio_encoding = Some(val_str.to_owned());
418        }
419    }
420
421    if settings.dev_mode {
422        settings.warnoptions.push("default".to_owned());
423        settings.faulthandler = true;
424    }
425    if settings.bytes_warning > 0 {
426        let warn = if settings.bytes_warning > 1 {
427            "error::BytesWarning"
428        } else {
429            "default::BytesWarning"
430        };
431        settings.warnoptions.push(warn.to_owned());
432    }
433    if let Some(val) = get_env("PYTHONWARNINGS")
434        && let Some(val_str) = val.to_str()
435        && !val_str.is_empty()
436    {
437        for warning in val_str.split(',') {
438            let warning = warning.trim();
439            if !warning.is_empty() {
440                settings.warnoptions.push(warning.to_owned());
441            }
442        }
443    }
444    settings.warnoptions.extend(args.warning_control);
445
446    settings.hash_seed = match (!args.random_hash_seed)
447        .then(|| get_env("PYTHONHASHSEED"))
448        .flatten()
449    {
450        Some(s) if s == "random" || s.is_empty() => None,
451        Some(s) => {
452            let seed = s.parse_with(|s| {
453                s.parse::<u32>().map_err(|_| {
454                    "Fatal Python init error: PYTHONHASHSEED must be \
455                    \"random\" or an integer in range [0; 4294967295]"
456                })
457            })?;
458            Some(seed)
459        }
460        None => None,
461    };
462
463    settings.argv = argv;
464
465    #[cfg(feature = "flame-it")]
466    {
467        settings.profile_output = args.profile_output;
468        settings.profile_format = args.profile_format;
469    }
470
471    Ok((settings, mode))
472}
473
474/// `-X cpu_count` / `PYTHON_CPU_COUNT`: a count above zero, or `"default"`.
475/// `Ok(None)` leaves the count to the host. Zero, negatives, and values above
476/// `i32::MAX` are rejected.
477/// = config_init_cpu_count
478fn parse_cpu_count(value: Option<&str>) -> Result<Option<NonZeroI32>, ()> {
479    match value {
480        None => Err(()),
481        Some("default") => Ok(None),
482        Some(number) => number
483            .parse()
484            .ok()
485            .filter(|count: &NonZeroI32| count.get() > 0)
486            .map(Some)
487            .ok_or(()),
488    }
489}
490
491fn get_paths(env_variable_name: &str) -> impl Iterator<Item = String> + '_ {
492    env::var_os(env_variable_name)
493        .filter(|v| !v.is_empty())
494        .into_iter()
495        .flat_map(move |paths| {
496            split_paths(&paths)
497                .map(|path| {
498                    path.into_os_string()
499                        .into_string()
500                        .unwrap_or_else(|_| panic!("{env_variable_name} isn't valid unicode"))
501                })
502                .collect::<Vec<_>>()
503        })
504}
505
506#[cfg(not(target_os = "wasi"))]
507pub(crate) use env::split_paths;
508#[cfg(target_os = "wasi")]
509pub(crate) fn split_paths<T: AsRef<std::ffi::OsStr> + ?Sized>(
510    s: &T,
511) -> impl Iterator<Item = std::path::PathBuf> + '_ {
512    let s = s.as_ref().as_encoded_bytes();
513    s.split(|b| *b == b':').map(|x| {
514        unsafe { std::ffi::OsStr::from_encoded_bytes_unchecked(x) }
515            .to_owned()
516            .into()
517    })
518}
519
520/// Remove common whitespace prefix from all lines in a string.
521///
522/// This is like textwrap.dedent, and is used to process -c's code
523/// argument.  It's different from ruff's dedent, which does not
524/// distinguish between tab and space characters when dedenting.
525fn dedent(input: &str) -> String {
526    let mut prefix: Option<String> = None;
527    let isspace = |c| c == ' ' || c == '\t';
528
529    // All-whitespace lines become empty.
530    let deblanked: Vec<&str> = input
531        .lines()
532        .map(|line| if line.chars().all(isspace) { "" } else { line })
533        .collect();
534
535    // Find maximum common whitespace prefix, if any.
536    for line in &deblanked {
537        if line.is_empty() {
538            continue;
539        }
540        if let Some(ref mut pstr) = prefix {
541            for (i, (c, pc)) in line.chars().zip(pstr.chars()).enumerate() {
542                // It's okay if `line` is shorter than `pstr`.  At
543                // least one char in `line` must be non-whitespace,
544                // and if `line` is shorter than `pstr`, this
545                // non-whitespace char will be compared to a
546                // whitespace char, and loop will terminate.
547                if c != pc {
548                    pstr.truncate(i);
549                    break;
550                }
551            }
552        } else {
553            let mut pstr = String::new();
554            for c in line.chars() {
555                if isspace(c) {
556                    pstr.push(c);
557                } else {
558                    break;
559                }
560            }
561            prefix = Some(pstr);
562            continue;
563        }
564    }
565
566    if let Some(pstr) = prefix {
567        // Strip common prefix.
568        deblanked
569            .iter()
570            .map(|line| {
571                if line.is_empty() {
572                    String::from("")
573                } else {
574                    // All non-empty lines start with pstr, must be at
575                    // least pstr.len() long.
576                    String::from(line.get(pstr.len()..).unwrap())
577                }
578            })
579            .collect::<Vec<_>>()
580            .join("\n")
581    } else {
582        // No prefix found: all lines blank.
583        deblanked.join("\n")
584    }
585}
You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.