Skip to content

Commit 2b6d0e7

Browse files
eitsupiclaude
andauthored
feat: accept all R-compatible CLI flags (#111)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent cc4a384 commit 2b6d0e7

8 files changed

Lines changed: 211 additions & 10 deletions

crates/arf-console/src/cli.rs

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,17 @@ use std::path::PathBuf;
1414
#[command(author, version, about, long_about = None)]
1515
pub struct Cli {
1616
/// R script file to execute (non-interactive mode)
17-
#[arg(value_hint = ValueHint::FilePath)]
17+
#[arg(value_hint = ValueHint::FilePath, conflicts_with = "file")]
1818
pub script: Option<PathBuf>,
1919

2020
/// Evaluate R expression and exit
2121
#[arg(short = 'e', long = "eval")]
2222
pub eval: Option<String>,
2323

24+
/// [R] Take input from FILE (same as positional SCRIPT argument)
25+
#[arg(short = 'f', long = "file", value_hint = ValueHint::FilePath, conflicts_with = "script", hide_short_help = true)]
26+
pub file: Option<PathBuf>,
27+
2428
/// Enable reprex mode (no prompt, output prefixed with #>)
2529
///
2630
/// Config: startup.mode.reprex
@@ -111,6 +115,56 @@ pub struct Cli {
111115
#[arg(long = "slave", hide = true)]
112116
pub slave: bool,
113117

118+
/// [R] Restore previously saved objects (opposite of --no-restore)
119+
#[arg(long = "restore", conflicts_with_all = ["no_restore", "no_restore_data"], hide = true)]
120+
pub restore: bool,
121+
122+
/// [R] Print more information about progress (no-op)
123+
#[arg(long = "verbose", hide = true)]
124+
pub verbose: bool,
125+
126+
/// [R] Specify encoding to be used for stdin (no-op)
127+
#[arg(long = "encoding", hide = true)]
128+
pub encoding: Option<String>,
129+
130+
/// [R] Set max number of connections to N
131+
#[arg(long = "max-connections", hide = true)]
132+
pub max_connections: Option<u32>,
133+
134+
/// [R] Set max size of protect stack to N
135+
#[arg(long = "max-ppsize", hide = true)]
136+
pub max_ppsize: Option<u32>,
137+
138+
/// [R] Set min number of fixed size obj's ("cons cells") to N
139+
#[arg(long = "min-nsize", hide = true)]
140+
pub min_nsize: Option<String>,
141+
142+
/// [R] Set vector heap minimum to N bytes; '4M' = 4 MegaB
143+
#[arg(long = "min-vsize", hide = true)]
144+
pub min_vsize: Option<String>,
145+
146+
/// [R] Run R through debugger NAME (no-op)
147+
#[arg(short = 'd', long = "debugger", hide = true)]
148+
pub debugger: Option<String>,
149+
150+
/// [R] Pass ARGS as arguments to the debugger (no-op)
151+
#[arg(long = "debugger-args", hide = true)]
152+
pub debugger_args: Option<String>,
153+
154+
/// [R] Use TYPE as GUI (no-op)
155+
#[arg(short = 'g', long = "gui", hide = true)]
156+
pub gui: Option<String>,
157+
158+
/// [R] Specify a sub-architecture (no-op)
159+
#[arg(long = "arch", hide = true)]
160+
pub arch: Option<String>,
161+
162+
/// [R] In R, skip the rest of the command line.
163+
/// arf accepts this flag for compatibility but does NOT consume trailing arguments;
164+
/// unknown flags after --args will still cause a parse error.
165+
#[arg(long = "args", hide = true, num_args = 0)]
166+
pub r_args_marker: bool,
167+
114168
/// [R] Don't use readline (no-op)
115169
#[arg(long = "no-readline", hide = true)]
116170
pub no_readline: bool,
@@ -326,6 +380,11 @@ impl Cli {
326380
Some(values)
327381
}
328382

383+
/// Returns the script file path from either `-f`/`--file` or the positional argument.
384+
pub fn script_file(&self) -> Option<&PathBuf> {
385+
self.script.as_ref().or(self.file.as_ref())
386+
}
387+
329388
/// Generate R initialization arguments based on CLI flags.
330389
///
331390
/// Returns a vector of R arguments like ["--quiet", "--no-save", "--no-restore"].
@@ -362,13 +421,27 @@ impl Cli {
362421
args.push("--no-save".to_string());
363422
}
364423

365-
if self.restore_data {
366-
args.push("--restore-data".to_string());
424+
if self.restore_data || self.restore {
425+
args.push("--restore".to_string());
367426
} else {
368427
args.push("--no-restore-data".to_string());
369428
}
370429
}
371430

431+
// Memory tuning flags - forward to R
432+
if let Some(n) = self.max_connections {
433+
args.push(format!("--max-connections={n}"));
434+
}
435+
if let Some(n) = self.max_ppsize {
436+
args.push(format!("--max-ppsize={n}"));
437+
}
438+
if let Some(ref n) = self.min_nsize {
439+
args.push(format!("--min-nsize={n}"));
440+
}
441+
if let Some(ref n) = self.min_vsize {
442+
args.push(format!("--min-vsize={n}"));
443+
}
444+
372445
// Always interactive (Unix only - Windows uses Rstart.r_interactive)
373446
#[cfg(unix)]
374447
args.push("--interactive".to_string());

crates/arf-console/src/completion/r_completer.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,13 +1004,19 @@ mod tests {
10041004
fn test_store_namespace_exports_evicts_expired() {
10051005
let mut completer = RCompleter::new();
10061006

1007-
// Insert an already-expired entry
1007+
// Insert an already-expired entry.
1008+
// Use checked_sub to avoid overflow on Windows where Instant is relative to boot time
1009+
// (system uptime may be shorter than cache duration in CI).
1010+
let expired_duration = RCompleter::NAMESPACE_CACHE_DURATION + Duration::from_secs(1);
1011+
let Some(expired_timestamp) = Instant::now().checked_sub(expired_duration) else {
1012+
// System uptime too short to create an expired entry; skip test
1013+
return;
1014+
};
10081015
completer.namespace_cache.insert(
10091016
"old_pkg::".to_string(),
10101017
NamespaceExportCache {
10111018
exports: vec!["old_func".to_string()],
1012-
timestamp: Instant::now()
1013-
- (RCompleter::NAMESPACE_CACHE_DURATION + Duration::from_secs(1)),
1019+
timestamp: expired_timestamp,
10141020
},
10151021
);
10161022

crates/arf-console/src/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ fn run() -> Result<()> {
6565
}
6666

6767
// Check if we're in script execution mode
68-
let script_mode = cli.eval.is_some() || cli.script.is_some();
68+
let script_mode = cli.eval.is_some() || cli.script_file().is_some();
6969

7070
if script_mode {
7171
// Script execution mode - no REPL, just run code and exit
@@ -635,7 +635,7 @@ fn run_script(cli: &Cli) -> Result<()> {
635635
// Get the code to execute
636636
let code = if let Some(eval_code) = &cli.eval {
637637
eval_code.clone()
638-
} else if let Some(script_path) = &cli.script {
638+
} else if let Some(script_path) = cli.script_file() {
639639
fs::read_to_string(script_path)
640640
.with_context(|| format!("Failed to read script file: {}", script_path.display()))?
641641
} else {

crates/arf-console/src/snapshots/arf__cli__tests__completions_bash.snap

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ _arf() {
108108

109109
case "${cmd}" in
110110
arf)
111-
opts="-e -c -q -h -V --eval --reprex --auto-format --config --no-banner --with-r-version --r-home --vanilla --quiet --no-save --save --no-restore --no-restore-data --restore-data --no-environ --no-site-file --no-init-file --interactive --no-echo --slave --no-readline --no-restore-history --no-auto-match --no-completion --history-dir --no-history --help --version [SCRIPT] completions config history help"
111+
opts="-e -f -c -q -d -g -h -V --eval --file --reprex --auto-format --config --no-banner --with-r-version --r-home --vanilla --quiet --no-save --save --no-restore --no-restore-data --restore-data --no-environ --no-site-file --no-init-file --interactive --no-echo --slave --restore --verbose --encoding --max-connections --max-ppsize --min-nsize --min-vsize --debugger --debugger-args --gui --arch --args --no-readline --no-restore-history --no-auto-match --no-completion --history-dir --no-history --help --version [SCRIPT] completions config history help"
112112
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
113113
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
114114
return 0
@@ -122,6 +122,36 @@ _arf() {
122122
COMPREPLY=($(compgen -f "${cur}"))
123123
return 0
124124
;;
125+
--file)
126+
local oldifs
127+
if [ -n "${IFS+x}" ]; then
128+
oldifs="$IFS"
129+
fi
130+
IFS=$'\n'
131+
COMPREPLY=($(compgen -f "${cur}"))
132+
if [ -n "${oldifs+x}" ]; then
133+
IFS="$oldifs"
134+
fi
135+
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
136+
compopt -o filenames
137+
fi
138+
return 0
139+
;;
140+
-f)
141+
local oldifs
142+
if [ -n "${IFS+x}" ]; then
143+
oldifs="$IFS"
144+
fi
145+
IFS=$'\n'
146+
COMPREPLY=($(compgen -f "${cur}"))
147+
if [ -n "${oldifs+x}" ]; then
148+
IFS="$oldifs"
149+
fi
150+
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
151+
compopt -o filenames
152+
fi
153+
return 0
154+
;;
125155
--config)
126156
local oldifs
127157
if [ -n "${IFS+x}" ]; then
@@ -163,6 +193,50 @@ _arf() {
163193
fi
164194
return 0
165195
;;
196+
--encoding)
197+
COMPREPLY=($(compgen -f "${cur}"))
198+
return 0
199+
;;
200+
--max-connections)
201+
COMPREPLY=($(compgen -f "${cur}"))
202+
return 0
203+
;;
204+
--max-ppsize)
205+
COMPREPLY=($(compgen -f "${cur}"))
206+
return 0
207+
;;
208+
--min-nsize)
209+
COMPREPLY=($(compgen -f "${cur}"))
210+
return 0
211+
;;
212+
--min-vsize)
213+
COMPREPLY=($(compgen -f "${cur}"))
214+
return 0
215+
;;
216+
--debugger)
217+
COMPREPLY=($(compgen -f "${cur}"))
218+
return 0
219+
;;
220+
-d)
221+
COMPREPLY=($(compgen -f "${cur}"))
222+
return 0
223+
;;
224+
--debugger-args)
225+
COMPREPLY=($(compgen -f "${cur}"))
226+
return 0
227+
;;
228+
--gui)
229+
COMPREPLY=($(compgen -f "${cur}"))
230+
return 0
231+
;;
232+
-g)
233+
COMPREPLY=($(compgen -f "${cur}"))
234+
return 0
235+
;;
236+
--arch)
237+
COMPREPLY=($(compgen -f "${cur}"))
238+
return 0
239+
;;
166240
--history-dir)
167241
COMPREPLY=()
168242
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then

crates/arf-console/src/snapshots/arf__cli__tests__completions_fish.snap

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ expression: completions
44
---
55
# Print an optspec for argparse to handle cmd's options that are independent of any subcommand.
66
function __fish_arf_global_optspecs
7-
string join \n e/eval= reprex auto-format c/config= no-banner with-r-version= r-home= vanilla q/quiet no-save save no-restore no-restore-data restore-data no-environ no-site-file no-init-file interactive no-echo slave no-readline no-restore-history no-auto-match no-completion history-dir= no-history h/help V/version
7+
string join \n e/eval= f/file= reprex auto-format c/config= no-banner with-r-version= r-home= vanilla q/quiet no-save save no-restore no-restore-data restore-data no-environ no-site-file no-init-file interactive no-echo slave restore verbose encoding= max-connections= max-ppsize= min-nsize= min-vsize= d/debugger= debugger-args= g/gui= arch= args no-readline no-restore-history no-auto-match no-completion history-dir= no-history h/help V/version
88
end
99

1010
function __fish_arf_needs_command
@@ -29,9 +29,19 @@ function __fish_arf_using_subcommand
2929
end
3030

3131
complete -c arf -n "__fish_arf_needs_command" -s e -l eval -d 'Evaluate R expression and exit' -r
32+
complete -c arf -n "__fish_arf_needs_command" -s f -l file -d '[R] Take input from FILE (same as positional SCRIPT argument)' -r -F
3233
complete -c arf -n "__fish_arf_needs_command" -s c -l config -d 'Path to configuration file' -r -F
3334
complete -c arf -n "__fish_arf_needs_command" -l with-r-version -d 'R version to use via rig (overrides r_source config)' -r
3435
complete -c arf -n "__fish_arf_needs_command" -l r-home -d 'Explicit R_HOME path (overrides r_source config)' -r -f -a "(__fish_complete_directories)"
36+
complete -c arf -n "__fish_arf_needs_command" -l encoding -d '[R] Specify encoding to be used for stdin (no-op)' -r
37+
complete -c arf -n "__fish_arf_needs_command" -l max-connections -d '[R] Set max number of connections to N' -r
38+
complete -c arf -n "__fish_arf_needs_command" -l max-ppsize -d '[R] Set max size of protect stack to N' -r
39+
complete -c arf -n "__fish_arf_needs_command" -l min-nsize -d '[R] Set min number of fixed size obj\'s ("cons cells") to N' -r
40+
complete -c arf -n "__fish_arf_needs_command" -l min-vsize -d '[R] Set vector heap minimum to N bytes; \'4M\' = 4 MegaB' -r
41+
complete -c arf -n "__fish_arf_needs_command" -s d -l debugger -d '[R] Run R through debugger NAME (no-op)' -r
42+
complete -c arf -n "__fish_arf_needs_command" -l debugger-args -d '[R] Pass ARGS as arguments to the debugger (no-op)' -r
43+
complete -c arf -n "__fish_arf_needs_command" -s g -l gui -d '[R] Use TYPE as GUI (no-op)' -r
44+
complete -c arf -n "__fish_arf_needs_command" -l arch -d '[R] Specify a sub-architecture (no-op)' -r
3545
complete -c arf -n "__fish_arf_needs_command" -l history-dir -d 'Custom history directory (overrides default XDG location)' -r -f -a "(__fish_complete_directories)"
3646
complete -c arf -n "__fish_arf_needs_command" -l reprex -d 'Enable reprex mode (no prompt, output prefixed with #>)'
3747
complete -c arf -n "__fish_arf_needs_command" -l auto-format -d 'Enable auto-formatting of R code in reprex mode (requires Air CLI)'
@@ -49,6 +59,9 @@ complete -c arf -n "__fish_arf_needs_command" -l no-init-file -d '[R] Don\'t rea
4959
complete -c arf -n "__fish_arf_needs_command" -l interactive -d '[R] Force R to run interactively (no-op, always interactive)'
5060
complete -c arf -n "__fish_arf_needs_command" -l no-echo -d '[R] Don\'t echo input (no-op, arf controls its own echo)'
5161
complete -c arf -n "__fish_arf_needs_command" -l slave -d '[R] Combine --quiet --no-save --no-restore (deprecated in R 4.0, use --no-echo)'
62+
complete -c arf -n "__fish_arf_needs_command" -l restore -d '[R] Restore previously saved objects (opposite of --no-restore)'
63+
complete -c arf -n "__fish_arf_needs_command" -l verbose -d '[R] Print more information about progress (no-op)'
64+
complete -c arf -n "__fish_arf_needs_command" -l args -d '[R] In R, skip the rest of the command line. arf accepts this flag for compatibility but does NOT consume trailing arguments; unknown flags after --args will still cause a parse error'
5265
complete -c arf -n "__fish_arf_needs_command" -l no-readline -d '[R] Don\'t use readline (no-op)'
5366
complete -c arf -n "__fish_arf_needs_command" -l no-restore-history -d '[R] Don\'t restore history (no-op)'
5467
complete -c arf -n "__fish_arf_needs_command" -l no-auto-match -d 'Disable auto-matching of brackets and quotes (for testing)'

crates/arf-console/src/snapshots/arf__cli__tests__completions_powershell.snap

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,23 @@ Register-ArgumentCompleter -Native -CommandName 'arf' -ScriptBlock {
2727
'arf' {
2828
[CompletionResult]::new('-e', '-e', [CompletionResultType]::ParameterName, 'Evaluate R expression and exit')
2929
[CompletionResult]::new('--eval', '--eval', [CompletionResultType]::ParameterName, 'Evaluate R expression and exit')
30+
[CompletionResult]::new('-f', '-f', [CompletionResultType]::ParameterName, '[R] Take input from FILE (same as positional SCRIPT argument)')
31+
[CompletionResult]::new('--file', '--file', [CompletionResultType]::ParameterName, '[R] Take input from FILE (same as positional SCRIPT argument)')
3032
[CompletionResult]::new('-c', '-c', [CompletionResultType]::ParameterName, 'Path to configuration file')
3133
[CompletionResult]::new('--config', '--config', [CompletionResultType]::ParameterName, 'Path to configuration file')
3234
[CompletionResult]::new('--with-r-version', '--with-r-version', [CompletionResultType]::ParameterName, 'R version to use via rig (overrides r_source config)')
3335
[CompletionResult]::new('--r-home', '--r-home', [CompletionResultType]::ParameterName, 'Explicit R_HOME path (overrides r_source config)')
36+
[CompletionResult]::new('--encoding', '--encoding', [CompletionResultType]::ParameterName, '[R] Specify encoding to be used for stdin (no-op)')
37+
[CompletionResult]::new('--max-connections', '--max-connections', [CompletionResultType]::ParameterName, '[R] Set max number of connections to N')
38+
[CompletionResult]::new('--max-ppsize', '--max-ppsize', [CompletionResultType]::ParameterName, '[R] Set max size of protect stack to N')
39+
[CompletionResult]::new('--min-nsize', '--min-nsize', [CompletionResultType]::ParameterName, '[R] Set min number of fixed size obj''s ("cons cells") to N')
40+
[CompletionResult]::new('--min-vsize', '--min-vsize', [CompletionResultType]::ParameterName, '[R] Set vector heap minimum to N bytes; ''4M'' = 4 MegaB')
41+
[CompletionResult]::new('-d', '-d', [CompletionResultType]::ParameterName, '[R] Run R through debugger NAME (no-op)')
42+
[CompletionResult]::new('--debugger', '--debugger', [CompletionResultType]::ParameterName, '[R] Run R through debugger NAME (no-op)')
43+
[CompletionResult]::new('--debugger-args', '--debugger-args', [CompletionResultType]::ParameterName, '[R] Pass ARGS as arguments to the debugger (no-op)')
44+
[CompletionResult]::new('-g', '-g', [CompletionResultType]::ParameterName, '[R] Use TYPE as GUI (no-op)')
45+
[CompletionResult]::new('--gui', '--gui', [CompletionResultType]::ParameterName, '[R] Use TYPE as GUI (no-op)')
46+
[CompletionResult]::new('--arch', '--arch', [CompletionResultType]::ParameterName, '[R] Specify a sub-architecture (no-op)')
3447
[CompletionResult]::new('--history-dir', '--history-dir', [CompletionResultType]::ParameterName, 'Custom history directory (overrides default XDG location)')
3548
[CompletionResult]::new('--reprex', '--reprex', [CompletionResultType]::ParameterName, 'Enable reprex mode (no prompt, output prefixed with #>)')
3649
[CompletionResult]::new('--auto-format', '--auto-format', [CompletionResultType]::ParameterName, 'Enable auto-formatting of R code in reprex mode (requires Air CLI)')
@@ -49,6 +62,9 @@ Register-ArgumentCompleter -Native -CommandName 'arf' -ScriptBlock {
4962
[CompletionResult]::new('--interactive', '--interactive', [CompletionResultType]::ParameterName, '[R] Force R to run interactively (no-op, always interactive)')
5063
[CompletionResult]::new('--no-echo', '--no-echo', [CompletionResultType]::ParameterName, '[R] Don''t echo input (no-op, arf controls its own echo)')
5164
[CompletionResult]::new('--slave', '--slave', [CompletionResultType]::ParameterName, '[R] Combine --quiet --no-save --no-restore (deprecated in R 4.0, use --no-echo)')
65+
[CompletionResult]::new('--restore', '--restore', [CompletionResultType]::ParameterName, '[R] Restore previously saved objects (opposite of --no-restore)')
66+
[CompletionResult]::new('--verbose', '--verbose', [CompletionResultType]::ParameterName, '[R] Print more information about progress (no-op)')
67+
[CompletionResult]::new('--args', '--args', [CompletionResultType]::ParameterName, '[R] In R, skip the rest of the command line. arf accepts this flag for compatibility but does NOT consume trailing arguments; unknown flags after --args will still cause a parse error')
5268
[CompletionResult]::new('--no-readline', '--no-readline', [CompletionResultType]::ParameterName, '[R] Don''t use readline (no-op)')
5369
[CompletionResult]::new('--no-restore-history', '--no-restore-history', [CompletionResultType]::ParameterName, '[R] Don''t restore history (no-op)')
5470
[CompletionResult]::new('--no-auto-match', '--no-auto-match', [CompletionResultType]::ParameterName, 'Disable auto-matching of brackets and quotes (for testing)')

0 commit comments

Comments
 (0)