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
172fn 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 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 std::process::exit(1)
353 });
354
355 settings.argv = argv;
356
357 (settings, mode)
358}
359
360fn 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
365fn 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}