-
-
Notifications
You must be signed in to change notification settings - Fork 103
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
Tab completion NFA DSL #310
Conversation
rule(/\s+/) { } | ||
|
||
rule(/#/) { push_state :comment } | ||
rule(/[^\n]+/, :comment) { } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Space inside empty braces detected.
rule(/\(/) { :LEFT_PAREN } | ||
rule(/\)/) { :RIGHT_PAREN } | ||
rule(/\s*\n\s*\n/) { :BLANK } | ||
rule(/\s+/) { } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Space inside empty braces detected.
|
||
def build(start_state, options = {}) | ||
with_optional_end_state(options) do | ||
parts.inject(start_state) do |state, part| |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prefer reduce over inject.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But that's not what I'm here to tell you about.
I'm here to talk about the draft.
They got a buildin' down in New York City called Whitehall Street, where you
Walk in, you get injected, inspected, detected, infected, neglected and
Selected!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hound config updated.
it 'delegates to the various rule factories' do | ||
start_state = double(:start_state) | ||
rule_factory_1 = stub_rule_factory | ||
rule_factory_2 = stub_rule_factory |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use normalcase for variable numbers.
describe '#build' do | ||
it 'delegates to the various rule factories' do | ||
start_state = double(:start_state) | ||
rule_factory_1 = stub_rule_factory |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use normalcase for variable numbers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hound config updated.
result = described_class.parse(tokens( | ||
[:WORD, 'push'], [:BLANK], | ||
[:WORD, 'pull'], [:BLANK], [:BLANK], | ||
[:WORD, 'fetch'], [:EOS], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid comma after the last parameter of a method call, unless each item is on its own line.
|
||
it 'parses multiple rules in the same input' do | ||
result = described_class.parse(tokens( | ||
[:WORD, 'push'], [:BLANK], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indent the first parameter one step more than tokens(.
it 'parses rules combining the pipe operator and a post-fix operator' do | ||
result = parse_single_rule(tokens( | ||
[:LEFT_PAREN], [:WORD, 'add'], [:OR], [:WORD, 'commit'], [:RIGHT_PAREN], | ||
[:PLUS], [:EOS], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid comma after the last parameter of a method call, unless each item is on its own line.
|
||
it 'recognises parentheses' do | ||
expect('(add|commit)').to produce_tokens [ | ||
'LEFT_PAREN', 'WORD(add)', 'OR', 'WORD(commit)', 'RIGHT_PAREN', 'EOS', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid comma after the last item of an array, unless each item is on its own line.
|
||
it 'recognises the pipe operator' do | ||
expect('stash pop|drop').to produce_tokens [ | ||
'WORD(stash)', 'WORD(pop)', 'OR', 'WORD(drop)', 'EOS', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid comma after the last item of an array, unless each item is on its own line.
end_state_1 = double(:end_state_1) | ||
part_1 = double(:factory, build: end_state_1) | ||
end_state_2 = double(:end_state_2, add_free_transition: nil) | ||
part_2 = double(:factory, build: end_state_2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use normalcase for variable numbers.
end_state = double(:end_state) | ||
end_state_1 = double(:end_state_1) | ||
part_1 = double(:factory, build: end_state_1) | ||
end_state_2 = double(:end_state_2, add_free_transition: nil) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use normalcase for variable numbers.
start_state = double(:start_state) | ||
end_state = double(:end_state) | ||
end_state_1 = double(:end_state_1) | ||
part_1 = double(:factory, build: end_state_1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use normalcase for variable numbers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't love GITSH_CONFIG_DIRECTORY
, but I also don't want to make you pass a string through a series of method calls.
Overall: LGTM!
factory = Parser.parse(tokens, gitsh_env: env) | ||
factory.build(start_state) | ||
rescue Errno::ENOENT | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The silence here makes me suspect that we want a conditional elsewhere instead. I feel like DSL.load
should complain about a missing file, but that DSL.load
should only be called if we want such a complaint ... if that makes sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could move the rescue up a level to the Gitsh::TabCompletion::AutomatonFactory
object?
I don't think I want a missing configuration file to cause an error that the user sees, but maybe it should—without the file there will be no tab completion.
|
||
def build(start_state, options = {}) | ||
with_optional_end_state(options) do | ||
parts.inject(start_state) do |state, part| |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But that's not what I'm here to tell you about.
I'm here to talk about the draft.
They got a buildin' down in New York City called Whitehall Street, where you
Walk in, you get injected, inspected, detected, infected, neglected and
Selected!
man/man1/gitsh.1.in
Outdated
@@ -406,6 +406,13 @@ The | |||
.Pa .gitshrc | |||
file will not be loaded for non-interactive sessions, e.g. when gitsh is | |||
invoked with the path to a script file. | |||
.It Pa @prefix@/etc/gitsh/completions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe @pkgsysconfdir@
instead of @prefix@
is more accurate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call.
man/man5/gitsh_completions.5.in
Outdated
file consists of rules defining the arguments that are expected | ||
by various commands. | ||
.Xr gitsh 1 | ||
uses the rules to match the input before the word being completed, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably no comma.
man/man5/gitsh_completions.5.in
Outdated
.Pp | ||
Each rule is separated from other rules by a blank line. | ||
.Pp | ||
The rule specifies the name of the command, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No comma.
def stub_text_matcher(text) | ||
klass = Gitsh::TabCompletion::Matchers::TextMatcher | ||
matcher = instance_double(klass) | ||
allow(klass).to receive(:new).with(text).and_return(matcher) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#with
makes this into a mock instead of a stub, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I never get those distinctions right, but you're probably right.
end | ||
|
||
def write_temp_completions_file(completions) | ||
temp_file('gitsh_completions', completions).path |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How's this work -- do these files get cleaned up?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. It uses the Ruby standard library's Tempfile
class behind the scenes, so it should be cleaned up when the object is garbage collected.
It might not be too bad: the file it's current defined in would pass it to |
Thinking about this configuration thing some more: we already pass the |
it 'builds a graph of automaton states by reading the given file' do | ||
path = write_temp_completions_file([ | ||
'stash (apply|drop|pop|show)', | ||
].join("\n")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Put a comma after the last parameter of a multiline method call.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Err … this isn't quite what I intended. Rubocop seems to struggle with nested multiline things.
[:WORD, 'push'], [:BLANK], | ||
[:WORD, 'pull'], [:BLANK], [:BLANK], | ||
[:WORD, 'fetch'], [:EOS], | ||
), gitsh_env: double(:env)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Put a comma after the last parameter of a multiline method call.
result = parse_single_rule(tokens( | ||
[:LEFT_PAREN], [:WORD, 'add'], [:OR], [:WORD, 'commit'], [:RIGHT_PAREN], | ||
[:PLUS], [:EOS], | ||
)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Put a comma after the last parameter of a multiline method call.
result = parse_single_rule(tokens( | ||
[:LEFT_PAREN], [:WORD, 'stash'], [:WORD, 'pop'], [:OR], | ||
[:WORD, 'add'], [:RIGHT_PAREN], [:EOS], | ||
)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Put a comma after the last parameter of a multiline method call.
result = parse_single_rule(tokens( | ||
[:LEFT_PAREN], [:WORD, 'commit'], [:OR], | ||
[:WORD, 'add'], [:OR], [:VAR, 'path'], [:RIGHT_PAREN], [:EOS], | ||
)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Put a comma after the last parameter of a multiline method call.
it 'parses rules with multiple words and variables' do | ||
result = parse_single_rule(tokens( | ||
[:WORD, 'stash'], [:WORD, 'pop'], [:VAR, 'revision'], [:EOS], | ||
)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Put a comma after the last parameter of a multiline method call.
@@ -4,6 +4,9 @@ module Gitsh | |||
module TabCompletion | |||
module Matchers | |||
class PathMatcher < BaseMatcher | |||
def initialize(_env) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Put empty method definitions on a single line.
a867127
to
39dc722
Compare
lib/gitsh/environment.rb
Outdated
@@ -6,8 +6,9 @@ | |||
module Gitsh | |||
class Environment | |||
DEFAULT_GIT_COMMAND = '/usr/bin/env git'.freeze | |||
DEFAULT_CONFIG_DIRECTORY = '/usr/local/etc/gitsh' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Freeze mutable objects assigned to constants.
@mike-burns I think 3a76b3f addresses your concerns about the global constant. |
Sure does! LGTM. |
This commit makes the Rubocop config used by Hound more consistent with the existing project style.
This commit introduces a domain specific language (DSL) to define the state graph for the tab completion automaton. It also replaces the hard-coded state graph with an equivalent graph loaded from a configuration file. The details of the DSL are given in the gitsh_completions(5) manual page which is added in this commit. The DSL will need to be expanded somewhat to do everything we want the tab completion system to be able to do, but this is a good step in that direction.
c3a8d2e
to
69ae4f2
Compare
#296 introduced a non-deterministic finite automaton as the basis of the tab completion system. This PR is the next step in my grand plan for tab completion: it introduces a DSL for loading the NFA's state graph from a configuration file.
The DSL format is described in the
gitsh_completions.5
manual page, which is added in this PR.The configuration file included in this PR (
etc/completions
) results in a graph very similar to the one that was hard coded prior to the introduction of the DSL. Subsequent PRs will add features to the DSL so it can be used for more complex graphs that produce better tab completion behaviour.Note that I'm not using DSL in the sense it's usually used with Ruby: this language has a lexer and a parser, it's not just a pretty looking Ruby API.