Last active
December 25, 2023 00:40
-
-
Save apainintheneck/2bd9bde17c7700eb429945ab80c70fa8 to your computer and use it in GitHub Desktop.
A blatant clone of the `choose` command written in awk.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env sh | |
awk ' | |
BEGIN { | |
TRUE = 1 | |
FALSE = 0 | |
arg_parse() | |
if(HELP) { | |
print_help() | |
exit(0) | |
} | |
} | |
{ | |
choice_list = "" | |
for(idx = 1; idx <= NF; ++idx) { | |
if(valid_choice(idx)) { | |
if(choice_list) | |
choice_list = choice_list OFS $idx | |
else | |
choice_list = $idx | |
} | |
} | |
if(choice_list) print choice_list | |
} | |
function arg_parse( idx, unused_args) { | |
idx = 2 # Skip the first arg since it will always be `--`. | |
# Parse options | |
for(; idx < ARGC; ++idx) { | |
if(ARGV[idx] == "-h" || ARGV[idx] == "--help") { | |
HELP = TRUE | |
} else if(ARGV[idx] == "-f" || ARGV[idx] == "--field-separator") { | |
if(CHARACTER_WISE) { | |
print "Error: The -c and -f options cannot be used together" | |
exit(1) | |
} | |
FS = ARGV[++idx] | |
puts FS | |
} else if(ARGV[idx] == "-i" || ARGV[idx] == "--input") { | |
INPUT_FILES[ARGV[++idx]] = TRUE | |
} else if(ARGV[idx] == "-o" || ARGV[idx] == "--output-field-separator") { | |
OFS = ARGV[++idx] | |
} else { | |
break | |
} | |
if(ARGC == idx) { | |
print "Error: Missing option parameter for: " ARGV[idx - 1] | |
exit(1) | |
} | |
} | |
# Parse choices | |
for(; idx < ARGC; ++idx) { | |
if(!parse_choice(ARGV[idx])) break | |
} | |
# Check for unused arguments | |
if(idx < ARGC) { | |
unused_args = ARGV[idx] | |
for(idx++; idx < ARGC; idx++) | |
unused_args = unused_args " " ARGV[idx] | |
print "Error: Unused arguments: " unused_args | |
exit(1) | |
} | |
# Add input files to ARGV | |
ARGC = 1 | |
for(file_name in INPUT_FILES) | |
ARGV[ARGC++] = file_name | |
} | |
function parse_choice(raw_choice, choice_start, choice_end, idx) { | |
if(raw_choice ~ /^(-)?[0-9]+$/) { | |
if(raw_choice >= 0) | |
POSITIVE_CHOICES[raw_choice + 1] = TRUE | |
else | |
NEGATIVE_CHOICES[raw_choice + 1] = TRUE | |
} else if(raw_choice ~ /^:(-)?[0-9]+$/) { | |
choice_end = substr(raw_choice, 2) | |
if(choice_end >= 0) { | |
choice_end++ | |
for(idx = 1; idx < choice_end; ++idx) | |
POSITIVE_CHOICES[idx] = TRUE | |
} else if(!MAX_CHOICE || MAX_NEGATIVE_CHOICE < choice_end) { | |
MAX_CHOICE = TRUE | |
MAX_NEGATIVE_CHOICE = choice_end + 0 | |
} | |
} else if(raw_choice ~ /^(-)?[0-9]+:$/) { | |
choice_start = substr(raw_choice, 1, length(raw_choice) - 1) | |
if(choice_start < 0) { | |
for(; 0 > choice_start; ++choice_start) { | |
NEGATIVE_CHOICES[choice_start + 1] = TRUE | |
} | |
} else if(!MIN_CHOICE || MIN_POSITIVE_CHOICE > choice_start + 1) { | |
MIN_CHOICE = TRUE | |
MIN_POSITIVE_CHOICE = choice_start + 1 | |
} | |
} else if(raw_choice ~ /^(-)?[0-9]+:(-)?[0-9]+$/) { | |
choice_start = substr(raw_choice, 1, index(raw_choice, ":") - 1) + 0 | |
choice_end = substr(raw_choice, index(raw_choice, ":") + 1) + 0 | |
if((choice_start < 0 && choice_end >= 0) || (choice_start >= 0 && choice_end < 0)) { | |
print "Error: Invalid choice: Ranges must be entirely positive or entirely negative: " raw_choice | |
exit(1) | |
} | |
for(; choice_start <= choice_end; ++choice_start) { | |
if(choice_start >= 0) | |
POSITIVE_CHOICES[choice_start + 1] = TRUE | |
else | |
NEGATIVE_CHOICES[choice_start + 1] = TRUE | |
} | |
} else { | |
return FALSE | |
} | |
return TRUE | |
} | |
function valid_choice(idx) { | |
if(POSITIVE_CHOICES[idx]) | |
return TRUE | |
else if(NEGATIVE_CHOICES[idx - NF]) | |
return TRUE | |
else if(MIN_CHOICE && idx >= MIN_POSITIVE_CHOICE) | |
return TRUE | |
else if(MAX_CHOICE && NF + MAX_NEGATIVE_CHOICE + 1 >= idx) | |
return TRUE | |
else | |
return FALSE | |
} | |
function print_help() { | |
print "tyranny" | |
print "" | |
print "A blatant `choose` clone. (See https://github.com/theryangeary/choose)" | |
print "" | |
print "USAGE:" | |
print " tyranny [OPTIONS] <choices>..." | |
print "" | |
print "OPTIONS:" | |
print " -f, --field-separator <field-separator>" | |
print " Specify field separator other than whitespace, using Awk `regex` syntax" | |
print " -i, --input <input> Input file" | |
print " -o, --output-field-separator <output-field-separator> Specify output field separator" | |
print " -h, --help Prints help information" | |
print "" | |
print "ARGS:" | |
print " <choices>... Fields to print. Either a or a:b where a and b are integers. The beginning or end" | |
print " of a range can be omitted, resulting in including the beginning or end of the line," | |
print " respectively. a:b is inclusive of b and they must be positive or both must be negative" | |
}' -- $@ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment