-
-
Notifications
You must be signed in to change notification settings - Fork 102
Brace expansions #366
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Brace expansions #366
Changes from all commits
5aaddc4
5e40586
837b5e8
bfa2cc4
770d59d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| module Gitsh | ||
| module Arguments | ||
| class BraceExpansion | ||
| def initialize(options) | ||
| @options = options | ||
| end | ||
|
|
||
| def value(env) | ||
| options.flat_map { |option| option.value(env) } | ||
| end | ||
|
|
||
| def ==(other) | ||
| other.is_a?(self.class) && options == other.options | ||
| end | ||
|
|
||
| protected | ||
|
|
||
| attr_reader :options | ||
| end | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,7 +6,7 @@ def initialize(value) | |
| end | ||
|
|
||
| def value(_env) | ||
| raw_value | ||
| [raw_value] | ||
| end | ||
|
|
||
| def ==(other) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,7 @@ class Lexer < RLTK::Lexer | |
| '\\', # Escape character | ||
| '$', # Variable or sub-shell prefix | ||
| '(', ')', # Parentheses | ||
| '{', '}', # Braces (for expansion) | ||
| ]).freeze | ||
|
|
||
| SOFT_STRING_ESCAPABLES = CharacterClass.new([ | ||
|
|
@@ -24,6 +25,12 @@ class Lexer < RLTK::Lexer | |
| "'", # String terminator | ||
| ]).freeze | ||
|
|
||
| BRACE_EXPANSION_ESCAPABLES = CharacterClass.new([ | ||
| '\\', # Escape character | ||
| '{', '}', # Braces (for nested expansions) | ||
| ',', # Comma (for separating expansion values) | ||
| ]).freeze | ||
|
|
||
| class Environment < RLTK::Lexer::Environment | ||
| attr_reader :right_paren_stack | ||
|
|
||
|
|
@@ -89,6 +96,24 @@ def initialize(*args) | |
| rule(/\\/, :soft_string) { [:WORD, '\\'] } | ||
| rule(/"/, :soft_string) { pop_state } | ||
|
|
||
| [:default, :brace_expansion].each do |state| | ||
|
georgebrock marked this conversation as resolved.
|
||
| rule(/\{/, state) do | ||
| push_state(:brace_expansion) | ||
| :LEFT_BRACE | ||
| end | ||
| end | ||
| rule(/#{BRACE_EXPANSION_ESCAPABLES.to_negative_regexp}+/, :brace_expansion) do |t| | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Metrics/LineLength: Line is too long. [86/80] |
||
| [:WORD, t] | ||
| end | ||
| rule(/\\#{BRACE_EXPANSION_ESCAPABLES.to_regexp}/, :brace_expansion) do |t| | ||
| [:WORD, t[1]] | ||
| end | ||
| rule(/,/, :brace_expansion) { :COMMA } | ||
| rule(/\}/, :brace_expansion) do | ||
| pop_state | ||
| :RIGHT_BRACE | ||
| end | ||
|
|
||
| [:default, :soft_string].each do |state| | ||
| rule(/\$/, state) { push_state(:need_var_name) } | ||
| rule(/\$\{/, state) do | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -320,7 +320,9 @@ argument, the string delimiters | |
| the variable or sub-shell prefix | ||
| .Pf ( Ic $ Ns ), | ||
| parentheses | ||
| .Pf ( Ic ( ) Ns ), | ||
| .Pf ( Ic () Ns ), | ||
| braces | ||
| .Pf ( Ic {} Ns ), | ||
| and any character which would indicated the end of the argument | ||
| (spaces, | ||
| .Ic &|;# Ns ) | ||
|
|
@@ -353,6 +355,27 @@ but a single | |
| .Ic \e | ||
| will also be interpreted as a literal as long as it isn't followed by | ||
| a character that can be escaped in the current context. | ||
| . | ||
| .Sh ARGUMENT EXPANSION | ||
| Like many general-purposes shells, gitsh supports argument expansions. | ||
| .Pp | ||
| .Em Brace expansion | ||
| provides a short hand for consecutive arguments that are similar to each | ||
| other. A typical example is renaming a file, where portions of the name | ||
| remain the same. For example, the following two commands are equivalent: | ||
| .Bd -literal -offset indent | ||
| mv foo_{old,new}.txt | ||
| mv foo_old.txt foo_new.txt | ||
| .Ed | ||
| .Pp | ||
| Note that empty braces | ||
| .Pf ( Ic {} Ns ), | ||
| or braces containing no comma characters (like the Git arguments | ||
| .Ic @{u} | ||
| or | ||
| .Ic stash@{1} ) | ||
| are literals: no expansion occurs. | ||
| . | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we mention the special cases in here? |
||
| .Sh CONFIGURATION | ||
| The following | ||
| .Xr git-config 1 | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| require 'spec_helper' | ||
|
|
||
| describe 'Glob patterns in arguments' do | ||
| context 'brace expansion' do | ||
| it 'expands to include each option' do | ||
| GitshRunner.interactive do |gitsh| | ||
| gitsh.type(':echo h{i,o}p') | ||
|
|
||
| expect(gitsh).to output_no_errors | ||
| expect(gitsh).to output(/hip hop/) | ||
| end | ||
| end | ||
|
|
||
| it 'expands multiple expansions in one argument' do | ||
| GitshRunner.interactive do |gitsh| | ||
| gitsh.type(':echo 1{a,b}{x,y,z}') | ||
|
|
||
| expect(gitsh).to output_no_errors | ||
| expect(gitsh).to output(/1ax 1ay 1az 1bx 1by 1bz/) | ||
| end | ||
| end | ||
|
|
||
| it 'expands empty options' do | ||
| GitshRunner.interactive do |gitsh| | ||
| gitsh.type(':echo git{,,sh,,}') | ||
|
|
||
| expect(gitsh).to output_no_errors | ||
| expect(gitsh).to output(/git git gitsh git git/) | ||
| end | ||
| end | ||
|
|
||
| it 'expands nested expansions' do | ||
| GitshRunner.interactive do |gitsh| | ||
| gitsh.type(':echo f{{e,i,o}e,um}') | ||
|
|
||
| expect(gitsh).to output_no_errors | ||
| expect(gitsh).to output(/fee fie foe fum/) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 😄 |
||
| end | ||
| end | ||
|
|
||
| it 'supports escaped commas' do | ||
| GitshRunner.interactive do |gitsh| | ||
| gitsh.type(':echo 1{x,y\\,z}2') | ||
|
|
||
| expect(gitsh).to output_no_errors | ||
| expect(gitsh).to output(/1x2 1y,z2/) | ||
| end | ||
| end | ||
|
|
||
| it 'supports escaped braces' do | ||
| GitshRunner.interactive do |gitsh| | ||
| gitsh.type(':echo 1{x,\\{,\\}}2') | ||
|
|
||
| expect(gitsh).to output_no_errors | ||
| expect(gitsh).to output(/1x2 1{2 1}2/) | ||
| end | ||
| end | ||
|
|
||
| it 'treats empty braces as a literal argument' do | ||
| GitshRunner.interactive do |gitsh| | ||
| gitsh.type(':echo a{}') | ||
|
|
||
| expect(gitsh).to output_no_errors | ||
| expect(gitsh).to output(/a\{\}/) | ||
| end | ||
| end | ||
|
|
||
| it 'treats braces with one item and no comma as a literal argument' do | ||
| GitshRunner.interactive do |gitsh| | ||
| gitsh.type(':echo a{b}') | ||
|
|
||
| expect(gitsh).to output_no_errors | ||
| expect(gitsh).to output(/a\{b\}/) | ||
| end | ||
| end | ||
|
|
||
| it 'supports solo escaped braces' do | ||
| GitshRunner.interactive do |gitsh| | ||
| gitsh.type(':echo \\{') | ||
|
|
||
| expect(gitsh).to output_no_errors | ||
| expect(gitsh).to output(/\{/) | ||
|
|
||
| gitsh.type(':echo \\}') | ||
|
|
||
| expect(gitsh).to output_no_errors | ||
| expect(gitsh).to output(/\}/) | ||
| end | ||
| end | ||
| end | ||
| end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| require 'spec_helper' | ||
| require 'gitsh/arguments/brace_expansion' | ||
| require 'gitsh/arguments/string_argument' | ||
|
|
||
| describe Gitsh::Arguments::BraceExpansion do | ||
| describe '#value' do | ||
| it 'returns the values of its options' do | ||
| argument = described_class.new([ | ||
| string_argument('foo'), | ||
| string_argument('bar'), | ||
| ]) | ||
|
georgebrock marked this conversation as resolved.
|
||
|
|
||
| expect(argument.value(double(:env))).to eq ['foo', 'bar'] | ||
| end | ||
| end | ||
|
|
||
| describe '#==' do | ||
| it 'returns true when the options are equal' do | ||
| a1 = described_class.new(['a', 'b']) | ||
| a2 = described_class.new(['a', 'b']) | ||
|
|
||
| expect(a1).to eq a2 | ||
| end | ||
|
|
||
| it 'returns false when the options are not equal' do | ||
| a = described_class.new(['a', 'b']) | ||
| b = described_class.new(['c', 'd']) | ||
|
|
||
| expect(a).not_to eq b | ||
| end | ||
|
|
||
| it 'returns false when the other object has a different class' do | ||
| arg_a = described_class.new(['a', 'b']) | ||
| double_a = double(options: ['a', 'b']) | ||
|
|
||
| expect(arg_a).not_to eq double_a | ||
| end | ||
| end | ||
|
|
||
| def string_argument(string) | ||
| instance_double(Gitsh::Arguments::StringArgument, value: string) | ||
| end | ||
| end | ||
Uh oh!
There was an error while loading. Please reload this page.