From: "sampersand2 (Sam Westerman) via ruby-core" <ruby-core@ml.ruby-lang.org>
To: ruby-core@ml.ruby-lang.org
Cc: "sampersand2 (Sam Westerman)" <noreply@ruby-lang.org>
Subject: [ruby-core:120545] [Ruby master Feature#21015] Add in a `-g` flag, like `-s` but with a few more quality of life features
Date: Wed, 08 Jan 2025 01:50:12 +0000 (UTC) [thread overview]
Message-ID: <redmine.issue-21015.20250108015011.55433@ruby-lang.org> (raw)
In-Reply-To: <redmine.issue-21015.20250108015011.55433@ruby-lang.org>
Issue #21015 has been reported by sampersand2 (Sam Westerman).
----------------------------------------
Feature #21015: Add in a `-g` flag, like `-s` but with a few more quality of life features
https://bugs.ruby-lang.org/issues/21015
* Author: sampersand2 (Sam Westerman)
* Status: Open
----------------------------------------
Add in a `-g` flag, like `-s` but with a few more QOL features
## TL;DR
([PR](https://github.com/ruby/ruby/pull/12526).)
Ruby's `-s` flag has quite a few shortfalls that make it difficult to use when writing scripts . A new `-g` flag will fix it:
```shell
# Support `-abc` as a shorthand for `-a -b -c`
ruby -ge'p [$a, $b, $c]' -- -abc #=> [1, 1, 1]
# Support `-vvv` as a shorthand for `-v=3`
ruby -ge'p $v' -- -vvv #=> 3
# Support things other than strings:
ruby -ge'p [$n, $f]' -- -n90 -f=false #=> [90, false]
# long-forms don't have a leading `_`
ruby -ge'p $foo_bar' -- --foo-bar #=> true
```
I propose a `-g` flag to fix it.
# Background: What is `-s`
Ruby's `-s` flag is an extremely helpful feature when writing short little scripts to set config options; it automatically processes `ARGV` and removes leading "flags" for you, assigning them to global variables.
```ruby
#!/bin/ruby -s
# echo, ruby-style! If `-n` is given, no newline is printed
print ARGV.join(' '), ($n ? "" : "\n")
```
While `-s` is significantly less powerful than `OptionParse`, or even via just parsing options yourselves, it's **incredibly** useful when writing simple little scripts where the option parsing code would be longer than the script itself.
## Problem 1 (The big one): No support for chained short-form flags
The biggest problem with `-s` is that it doesn't let you concatenate short-form flags together: conventionally, `./myprogram -x -y -z` should be the same as `./myprogram -xyz`. However, if `./myprogram` were a ruby program using `-s`, you'd end up with the global variable `$xyz`.
Short scripts that use `-s` to parse options thus need to add the following code to handle all different permutations of `-x`, `-y`, and `-z`:
```ruby
# Handle all permutations of `-xyz`
$xyz || $xzy || $yxz || $yzx || $zxy || $zyx and $x=$y=$z=true
# Handle only two options given
$xy || $yx and $x=$y=true
$xz || $zx and $x=$z=true
$yz || $zy and $y=$z=true
```
Four flags becomes even worse:
```ruby
$wxyz || $wxzy || $wyxz || $wyzx || \
$wzxy || $wzyx || $xwyz || $xwzy || \
$xywz || $xyzw || $xzwy || $xzyw || \
$ywxz || $ywzx || $yxwz || $yxzw || \
$yzwx || $yzxw || $zwxy || $zwyx || \
$zxwy || $zxyw || $zywx || $zyxw and $w=$x=$y=$z=true
$wxy || $wyx || $xwy || $xyw || $ywx || $yxw and $w=$x=$y=true
$wxz || $wzx || $xwz || $xzw || $zwx || $zxw and $w=$x=$z=true
$wyz || $wzy || $ywz || $yzw || $zwy || $zyw and $w=$y=$z=true
$xyz || $xzy || $yxz || $yzx || $zxy || $zyx and $x=$y=$z=true
$wx || $xw and $w=$x=true
$wy || $yw and $w=$y=true
$wz || $zw and $w=$z=true
$xy || $yx and $x=$y=true
$xz || $zx and $x=$z=true
$yz || $zy and $y=$z=true
```
This is a huge problem for simple little scripts, as they're forced into an uncomfortable choice:
1. Use `OptionParser`, which is very much overkill for tiny scripts
2. Break from unix standards and require passing short-forms individually (i.e. `./program.rb -x -y -z`)
3. Do the cumbersome permutation checks as shown above
4. Give up on flags all together and use environment variables.
None of these options are great, *especially* because Ruby's all about programmer happiness and none of these options spark joy.
## Problem 2: All values are `Strings`
Less important than the short-form issue is that all values provided to flags become Strings (e.g. `ruby -se'p $foo' -- -foo=30` yields `"30"`). While this can be solved by `$foo = $foo&.to_i` somewhere early on, it's verbose:
```ruby
#!ruby -s
# Very simple benchmarking program
$log_level = $log_level&.to_i
$amount = $amount&.to_i
$timeout = $timeout&.to_i
$precision = $precision&.to_i
$amount.times do |iter|
start = Time.now
$log_level > 1 and puts "[iteration: #{iter}] running: #{ARGV}"
pipe = IO.popen ARGV
unless select [pipe], nil, nil, $timeout
warn "took too long!"
next
end
printf "%0.#{$precision}f", Time.now - start
end
```
Moreover, there's no way to create a falsey flag other than just omitting it, which can make interacting with `-s` scripts somewhat irritating:
```ruby
# Have to use `*[... ? ... : nil].compact`, otherwise
# we'd end up passing an empty string/nil as an arg
system("./myprogram.rb", *[enable_foo ? "--foo" : nil].compact)
```
A better solution would be to allow for `--foo=false` or `--foo=true`, and then parse the `false`/`true`.
## Problem 3 (Minor): Long-form options have a leading `_`
Long-form options, such as `--help`, have a leading `_` appended to them (to disambiguate them from `-help`). While it can be worked around (either `alias $help $_help` or just using `$_help` directly), it's irritating enough that I've been known to just force end-users to use `-help`. This diverges from the common "long-form options should have two dashes in front of them," further making ruby scripts with `-s` a bit awkward to use
## Problem 4 (Minor): Repeated flags aren't supported
Sometimes it's useful to know how many times a flag was repeated, such as `-vvv` to enable "very very verbose mode": Using `-s` you must do
```ruby
is_verbose = $v ? 1 : ($vv ? 2 : ($vvv ? 3 : ($vvvv ? 4 : ...)))
```
While I've yet to see a use-case for repeating a flag beyond three or four times, having to manually enumerate everything out is, again, a bit of a pain.
# Solution: Add a new `-g` flag, which supports all this
I propose the addition of a new command-line flag, `-g`, which adds in a new form of argument parsing that solves all these issues. [PR](https://github.com/ruby/ruby/pull/12526)
## Overview:
```shell
ruby -ge'p [$a, $b, $c]' -- -abc #=> [1, 1, 1]
ruby -ge'p $d' -- -d90 #=> 90
ruby -ge'p [$d, $e]' -- -d90e=foo #=> [90, "foo"]
ruby -ge'p [$hi, $foo_bar]' -- --hi --foo-bar=false #=> [true, false]
ruby -ge'p $x' -- -xxx #=> 3
# Putting it together now, lol.
ruby -g -e'p [$a, $b, $c, $d, $e, $world]' -- -abbcd90e=hello --world=false
# => [true, 2, true, 90, "hello", false]```
```
# Open Questions
## How should supplying both `-g` and `-s` work?
This is the first flag that'd directly conflict with another command-line flag, so what should be done when both `-g` and `-s` are given (such as `ruby -gs -e'p $x' -- -x=9`). Here's the options as I see them:
1. Use to last supplied flag (in the example `-s`). This'd act like how incompatible flags work in other utilities
2. Always use `-g`, as it can be considered a sort-of a "super set" of `-s`.
3. Emit a warning, and then do either `1.` or `2.`
4. Emit an error, and then do either `1.` or `2.`
I'm personally partial to emitting a warning on `-W2`, and then using the last supplied flag, however I could be convinced to any of the options
## Should `-x` do `$x = true` or `$x = 1`
I personally'd like `-x` to be `true`, just like `-s`. However, this conflicts with `-xx` yielding `2`, as `log if $x > 1` wouldn't work. Since `1` is truthy, I've defaulted `-x` to `1`, but I could be convinced to remove it (and even remove the entire `-xx` thing if there was a good argument.)
## Why introduce a new flag? Why name it `-g`?
I think adding in a new flag makes sense: `-g` is a modification of `-s`, so it naturally should be a new flag. (Attempting to shoehorn these options into `-s` would be a nightmare.)
I briefly considered using `-ss` as the flag name, to make it clear that it was a modification of `-s`, however that'd be the first two-character short flag and I didn't want to introduce that. (And, it'd be pretty ironic as a large portion of the impetus behind `-g` is to parse short flags, which `-ss` isn't :-P.)
As for `-g` specifically, I considered `-o` for "options" (nixed because most utilities use it for "output," and I didn't want the cognitive overload), and `-f` for "flags" (nixed because some utilities use it to mean "file," and ruby might use it in the future.) I picked `-g` because it's short for "globals" or "getflags".
# Alternatives
(TODO)
## Continue using `-s`
(TODO) not great, as shown above
# Prior art
(TODO) Perl has `-s`, and it works like Ruby's `-s`. IDK of any other languages which even attempt this.
--
https://bugs.ruby-lang.org/
______________________________________________
ruby-core mailing list -- ruby-core@ml.ruby-lang.org
To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/
next parent reply other threads:[~2025-01-08 1:50 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-01-08 1:50 sampersand2 (Sam Westerman) via ruby-core [this message]
2025-01-08 2:22 ` [ruby-core:120546] [Ruby master Feature#21015] [DRAFt] " nobu (Nobuyoshi Nakada) via ruby-core
2025-01-08 6:13 ` [ruby-core:120551] [Ruby master Feature#21015] " zenspider (Ryan Davis) via ruby-core
2025-01-09 10:15 ` [ruby-core:120565] " matz (Yukihiro Matsumoto) via ruby-core
2025-01-11 0:26 ` [ruby-core:120606] " sampersand2 (Sam Westerman) via ruby-core
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=redmine.issue-21015.20250108015011.55433@ruby-lang.org \
--to=ruby-core@ml.ruby-lang.org \
--cc=noreply@ruby-lang.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).