tabtab 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. data/History.txt +4 -0
  2. data/Manifest.txt +76 -0
  3. data/PostInstall.txt +10 -0
  4. data/README.rdoc +385 -0
  5. data/Rakefile +25 -0
  6. data/bin/install_tabtab +10 -0
  7. data/bin/tabtab +10 -0
  8. data/examples/tabtab.sh +7 -0
  9. data/features/aliases_for_completions.feature +23 -0
  10. data/features/development.feature +19 -0
  11. data/features/different_shells_installation.feature +26 -0
  12. data/features/discovered_gem_app_completions.feature +37 -0
  13. data/features/external_app_completions.feature +24 -0
  14. data/features/file_completions.feature +17 -0
  15. data/features/hide_short_flags.feature +19 -0
  16. data/features/steps/cli.rb +63 -0
  17. data/features/steps/common.rb +211 -0
  18. data/features/steps/completions.rb +15 -0
  19. data/features/steps/configuration.rb +20 -0
  20. data/features/steps/env.rb +17 -0
  21. data/features/steps/gems.rb +18 -0
  22. data/features/steps/shells.rb +3 -0
  23. data/lib/dev_definitions/gem.rb +54 -0
  24. data/lib/dev_definitions/rake.rb +23 -0
  25. data/lib/dev_definitions/script-generate.rb +8 -0
  26. data/lib/dev_definitions/script-server.rb +14 -0
  27. data/lib/install_tabtab/cli.rb +139 -0
  28. data/lib/tabtab.rb +10 -0
  29. data/lib/tabtab/cli.rb +116 -0
  30. data/lib/tabtab/completions.rb +6 -0
  31. data/lib/tabtab/completions/external.rb +39 -0
  32. data/lib/tabtab/completions/file.rb +23 -0
  33. data/lib/tabtab/completions/gems.rb +44 -0
  34. data/lib/tabtab/definitions.rb +28 -0
  35. data/lib/tabtab/definitions/base.rb +146 -0
  36. data/lib/tabtab/definitions/command.rb +47 -0
  37. data/lib/tabtab/definitions/default.rb +41 -0
  38. data/lib/tabtab/definitions/flag.rb +38 -0
  39. data/lib/tabtab/definitions/root.rb +70 -0
  40. data/lib/tabtab/framework_testing.rb +11 -0
  41. data/lib/tabtab/local_config.rb +16 -0
  42. data/lib/tabtab/test/assertions.rb +6 -0
  43. data/lib/tabtab_definitions/cucumber.rb +19 -0
  44. data/lib/tabtab_definitions/github.rb +50 -0
  45. data/lib/tabtab_definitions/newgem.rb +27 -0
  46. data/lib/tabtab_definitions/rails.rb +15 -0
  47. data/lib/tabtab_definitions/rubyforge.rb +17 -0
  48. data/script/console +10 -0
  49. data/script/destroy +14 -0
  50. data/script/generate +14 -0
  51. data/spec/definition_spec.rb +334 -0
  52. data/spec/external_spec.rb +38 -0
  53. data/spec/fixtures/bin/test_app +11 -0
  54. data/spec/fixtures/gems/multi_app/History.txt +2 -0
  55. data/spec/fixtures/gems/multi_app/Manifest.txt +7 -0
  56. data/spec/fixtures/gems/multi_app/Rakefile +25 -0
  57. data/spec/fixtures/gems/multi_app/bin/test_app +11 -0
  58. data/spec/fixtures/gems/multi_app/lib/multi_app.rb +6 -0
  59. data/spec/fixtures/gems/multi_app/lib/tabtab_definitions/some_app.rb +5 -0
  60. data/spec/fixtures/gems/multi_app/multi_app-0.0.1.gem +0 -0
  61. data/spec/fixtures/gems/multi_app/multi_app.gemspec +38 -0
  62. data/spec/fixtures/gems/my_app/History.txt +2 -0
  63. data/spec/fixtures/gems/my_app/Manifest.txt +7 -0
  64. data/spec/fixtures/gems/my_app/Rakefile +25 -0
  65. data/spec/fixtures/gems/my_app/bin/test_app +11 -0
  66. data/spec/fixtures/gems/my_app/lib/my_app.rb +6 -0
  67. data/spec/fixtures/gems/my_app/lib/tabtab_definitions.rb +5 -0
  68. data/spec/fixtures/gems/my_app/my_app-0.0.1.gem +0 -0
  69. data/spec/fixtures/gems/my_app/my_app.gemspec +38 -0
  70. data/spec/framework_testing_spec.rb +55 -0
  71. data/spec/install_tabtab_cli_spec.rb +139 -0
  72. data/spec/spec.opts +1 -0
  73. data/spec/spec_helper.rb +14 -0
  74. data/spec/tabtab_cli_spec.rb +145 -0
  75. data/tasks/rspec.rake +21 -0
  76. data/website/images/tabtab.png +0 -0
  77. metadata +167 -0
@@ -0,0 +1,6 @@
1
+ module TabTab::Completions
2
+ end
3
+
4
+ Dir[File.dirname(__FILE__) + "/completions/*.rb"].each do |path|
5
+ require path.gsub(/.rb$/, '')
6
+ end
@@ -0,0 +1,39 @@
1
+ class TabTab::Completions::External
2
+ attr_reader :global_config
3
+
4
+ def initialize(app_name, options_flag = '-h', global_config = {})
5
+ @app_name = app_name
6
+ @options_flag = options_flag
7
+ @global_config = global_config
8
+ end
9
+
10
+ def options_str
11
+ @options_str ||= `#{@app_name} #{@options_flag}`
12
+ end
13
+
14
+ # Returns list of long and short options for an application's --help display
15
+ # which was passed into initializer
16
+ def extract(_options_str = nil)
17
+ @options_str = _options_str if _options_str # hook for testing
18
+ @extract ||= begin
19
+ lines_containing_options = options_str.split(/\n/).grep(/^[\s\t]+-/)
20
+ all_options = lines_containing_options.inject([]) do |list, line|
21
+ list + line.scan(/(?:^\s+|,\s)(-[\w-]+)/).flatten
22
+ end
23
+ long_options = all_options.grep(/^--/).sort
24
+ short_options = hide_short_flags? ? [] : (all_options - long_options).sort
25
+ long_options + short_options
26
+ end
27
+ end
28
+
29
+ # Returns the sub-list of all options filtered by a common prefix
30
+ # e.g. if current +extract+ list is +['--help', '--extra', '-h', '-x']+
31
+ # then +starts_with('--')+ returns +['--help', '--extra']+
32
+ def starts_with(prefix)
33
+ extract.grep(/^#{prefix}/)
34
+ end
35
+
36
+ def hide_short_flags?
37
+ global_config[:shortflags] == 'disable'
38
+ end
39
+ end
@@ -0,0 +1,23 @@
1
+ class TabTab::Completions::File
2
+ attr_reader :file_path, :app_name, :current_token, :previous_token, :global_config
3
+
4
+ def initialize(file_path, app_name, current_token, previous_token, global_config = {})
5
+ @file_path = file_path
6
+ @app_name = app_name
7
+ @current_token = current_token
8
+ @previous_token = previous_token
9
+ @global_config = global_config
10
+ end
11
+
12
+ # Returns the sub-list of all options filtered by a common prefix
13
+ # e.g. if current +extract+ list is +['--help', '--extra', '-h', '-x']+
14
+ # then +starts_with('--')+ returns +['--help', '--extra']+
15
+ def extract
16
+ if File.exists?(file_path)
17
+ load file_path
18
+ TabTab::Definition[app_name].extract_completions(previous_token, current_token, global_config)
19
+ else
20
+ []
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,44 @@
1
+ class TabTab::Completions::Gem
2
+ attr_reader :gem_name, :app_name, :current_token, :previous_token, :global_config, :definitions_file
3
+
4
+ def initialize(gem_name, app_name, current_token, previous_token, global_config = {})
5
+ parse_gem_name_and_optional_explicit_file(gem_name)
6
+ @app_name = app_name
7
+ @current_token = current_token
8
+ @previous_token = previous_token
9
+ @global_config = global_config
10
+ end
11
+
12
+ # Returns the sub-list of all options filtered by a common prefix
13
+ # e.g. if current +extract+ list is +['--help', '--extra', '-h', '-x']+
14
+ # then +starts_with('--')+ returns +['--help', '--extra']+
15
+ def extract
16
+ require "tabtab/definitions"
17
+ load definitions_file
18
+ if TabTab::Definition[app_name]
19
+ TabTab::Definition[app_name].extract_completions(previous_token, current_token, global_config)
20
+ else
21
+ []
22
+ end
23
+ end
24
+
25
+ def load_gem_and_return_definitions_file
26
+ Dir[File.join(gem_root_path, '**', "tabtab_definitions.rb")].first
27
+ end
28
+
29
+ def parse_gem_name_and_optional_explicit_file(gem_name)
30
+ if gem_name =~ %r{^([\w\-_]*)/(.*)}
31
+ @gem_name, file_path = $1, $2
32
+ @definitions_file = File.join(gem_root_path, file_path)
33
+ else
34
+ @gem_name = gem_name
35
+ @definitions_file = load_gem_and_return_definitions_file
36
+ end
37
+ end
38
+
39
+ def gem_root_path
40
+ require "rubygems"
41
+ gem gem_name
42
+ $LOAD_PATH.grep(/#{gem_name}.*\/lib$/).first.gsub(/\/lib$/, '')
43
+ end
44
+ end
@@ -0,0 +1,28 @@
1
+ module TabTab::Definition
2
+ class InvalidDefinitionBlockArguments < Exception; end
3
+
4
+ class << self
5
+ attr_reader :registrations
6
+
7
+ def register(app_name, options={}, &block)
8
+ @registrations ||= {}
9
+ registrations[app_name] = Root.named(app_name, options, &block)
10
+ end
11
+
12
+ def [](app_name)
13
+ registrations[app_name]
14
+ end
15
+
16
+ def clear
17
+ @registrations = {}
18
+ end
19
+
20
+ def app_names
21
+ registrations.keys
22
+ end
23
+ end
24
+ end
25
+
26
+ Dir[File.dirname(__FILE__) + "/definitions/*.rb"].sort.each do |definition|
27
+ require definition.gsub(/.rb$/,'')
28
+ end
@@ -0,0 +1,146 @@
1
+ class TabTab::Definition::Base
2
+ attr_reader :parent, :contents, :definition_block
3
+
4
+ def initialize(parent, &block)
5
+ @parent = parent
6
+ @contents = []
7
+ @definition_block = block
8
+ yield_definition_block
9
+ end
10
+
11
+ # Example usage:
12
+ # c.flags :some_flag
13
+ # c.flags :some_flag, :s, "a description"
14
+ # c.flags :another_flag, "a description" do
15
+ # %w[possible values for the flag]
16
+ # end
17
+ # which map to example command-line expressions:
18
+ # myapp --some_flag
19
+ # myapp -s
20
+ # myapp --another_flag possible
21
+ # myapp --another_flag values
22
+ def flags(*flags_and_description, &block)
23
+ description = flags_and_description.pop if flags_and_description.last.is_a?(String)
24
+ flag_names = flags_and_description
25
+ contents << TabTab::Definition::Flag.new(self, flag_names, description, &block)
26
+ end
27
+ alias_method :flag, :flags
28
+
29
+ # Example usage:
30
+ # c.command :run
31
+ # c.command :run, "Runs the good stuff"
32
+ # c.command :choose, "Runs the good stuff", do
33
+ # %w[possible values for the commands argument]
34
+ # end
35
+ # c.command :set_speed, "Runs the good stuff", do |run|
36
+ # run.flags :a_flag
37
+ # run.command :slowly
38
+ # run.command :fast
39
+ # end
40
+ # which map to example command-line expressions:
41
+ # myapp run
42
+ # myapp choose possible
43
+ # myapp set_speed --a_flag fast
44
+ def command(name, description="", &block)
45
+ contents << TabTab::Definition::Command.new(self, name, description, &block)
46
+ end
47
+
48
+ # Find a direct child/contents definition that supports a given token
49
+ def [](token)
50
+ contents.find { |definition| definition.definition_type != :default && definition.matches_token?(token) }
51
+ end
52
+
53
+ # Find any child definition that supports a given token
54
+ def find_active_definition_for_last_token(last_token)
55
+ self[last_token] || contents.inject([]) do |mem, definition|
56
+ child = definition[last_token]
57
+ mem << child if child
58
+ mem
59
+ end.first
60
+ end
61
+
62
+
63
+ # How many tokens/parts of a command-line expression does this Flag consume
64
+ # By default, it is 1 token unless overridden by subclass
65
+ def tokens_consumed
66
+ 1
67
+ end
68
+
69
+ def filtered_completions(prefix)
70
+ completions_of_contents.grep(/^#{prefix}/)
71
+ end
72
+
73
+ def completions_of_contents
74
+ results = yield_result_block if contents.empty?
75
+ return results if results
76
+ contents.inject([]) do |mem, definition|
77
+ mem << definition.own_completions
78
+ mem
79
+ end.flatten
80
+ end
81
+
82
+ def own_completions
83
+ []
84
+ end
85
+
86
+ def definition_type?(def_type)
87
+ self.definition_type == def_type
88
+ end
89
+ #
90
+ # Test support
91
+ #
92
+
93
+ # Helper for test frameworks
94
+ def autocompletable?(cmd_line_or_tokens)
95
+ return false if cmd_line_or_tokens.nil?
96
+ tokens = cmd_line_or_tokens.is_a?(String) ? cmd_line_or_tokens.split(/\s/) : cmd_line_or_tokens
97
+ current, *remainder = tokens
98
+ return false unless matches_token?(current) # current cmd-line doesn't match this Definition
99
+ return true if remainder.empty?
100
+ if definition = find_child_definition_by_token(current, remainder)
101
+ return definition.autocompletable?(remainder)
102
+ end
103
+ yield_result_block
104
+ end
105
+
106
+ def yield_definition_block
107
+ if definition_block.nil?
108
+ return
109
+ elsif definition_block.arity == -1
110
+ # these blocks return a result/do lots of work - don't run them now
111
+ elsif definition_block.arity == 1
112
+ definition_block.call self
113
+ else
114
+ raise TabTab::Definition::InvalidDefinitionBlockArguments
115
+ end
116
+ end
117
+
118
+ def yield_result_block
119
+ if definition_block.nil? || definition_block.arity == 1
120
+ nil
121
+ elsif definition_block.arity == -1
122
+ definition_block.call
123
+ else
124
+ raise TabTab::Definition::InvalidDefinitionBlockArguments
125
+ end
126
+ end
127
+
128
+ # recursively ask parents, until finding root, for current_token being completed
129
+ def current_token
130
+ parent.current_token
131
+ end
132
+
133
+ protected
134
+
135
+ # To be implemented by subclasses.
136
+ # Determines if this definition matches the current token
137
+ def matches_token?(cmd_line_token)
138
+ false
139
+ end
140
+
141
+ def find_child_definition_by_token(cmd_line_token, remainder=nil)
142
+ contents.find do |definition|
143
+ definition.autocompletable?(remainder)
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,47 @@
1
+ module TabTab::Definition
2
+ class Command < Base
3
+ attr_reader :name, :description
4
+ def initialize(parent, name, description, &block)
5
+ @name = name.to_s
6
+ @description = description
7
+ super parent, &block
8
+ end
9
+
10
+ def definition_type
11
+ :command
12
+ end
13
+
14
+ # How many tokens/parts of a command-line expression does this Flag consume
15
+ # For example, the following consume 1 token:
16
+ # --simple
17
+ # -s
18
+ # The following consumes 2 tokens:
19
+ # --port 1234
20
+ def tokens_consumed
21
+ return 2 if definition_block
22
+ 1
23
+ end
24
+
25
+ def own_completions
26
+ [name]
27
+ end
28
+
29
+ # Example usage:
30
+ # c.default do
31
+ # %w[possible values following command name]
32
+ # end
33
+ # which map to example command-line expressions:
34
+ # myapp this_command possible
35
+ # myapp this_command value
36
+ # myapp this_command following
37
+ def default(description=nil, &block)
38
+ contents << TabTab::Definition::Default.new(self, description, &block)
39
+ end
40
+
41
+ # Determines if current token matches this command's name
42
+ def matches_token?(cmd_line_token)
43
+ cmd_line_token == name
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,41 @@
1
+ module TabTab::Definition
2
+ class Default < Base
3
+ attr_reader :description
4
+ def initialize(parent, description=nil, &definition_block)
5
+ @description = description
6
+ super parent, &definition_block
7
+ end
8
+
9
+ def definition_type
10
+ :default
11
+ end
12
+
13
+ def own_completions
14
+ yield_result_block
15
+ end
16
+
17
+ def yield_result_block
18
+ if definition_block.arity == -1
19
+ definition_block.call
20
+ elsif definition_block.arity == 1
21
+ definition_block.call(parent.current_token)
22
+ else
23
+ raise TabTab::Definition::InvalidDefinitionBlockArguments
24
+ end
25
+ end
26
+
27
+ # Determines if current token matches this command's name
28
+ def matches_token?(cmd_line_token)
29
+ results = yield_result_block
30
+ case results
31
+ when String
32
+ results == cmd_line_token.to_s
33
+ when Array
34
+ results.include?(cmd_line_token.to_s)
35
+ else
36
+ false
37
+ end
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,38 @@
1
+ module TabTab::Definition
2
+ class Flag < Base
3
+ attr_reader :definition, :flags, :description
4
+ def initialize(definition, flags, description, &block)
5
+ @flags = flags.map { |flag| flag.to_s }
6
+ @description = description
7
+ super definition, &block
8
+ end
9
+
10
+ def definition_type
11
+ :flag
12
+ end
13
+
14
+ # How many tokens/parts of a command-line expression does this Flag consume
15
+ # For example, the following consume 1 token:
16
+ # --simple
17
+ # -s
18
+ # The following consumes 2 tokens:
19
+ # --port 1234
20
+ def tokens_consumed
21
+ definition_block.nil? ? 1 : 2
22
+ end
23
+
24
+ # convert flags into --flag or -f based on length of value
25
+ def own_completions
26
+ flags.map do |flag|
27
+ flag.size > 1 ? "--#{flag}" : "-#{flag}"
28
+ end
29
+ end
30
+
31
+ # Determines if current token matches this command's flags
32
+ def matches_token?(cmd_line_token)
33
+ return false unless cmd_line_token
34
+ flags.include? cmd_line_token.gsub(/^-*/,'')
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,70 @@
1
+ module TabTab::Definition
2
+ class Root < Base
3
+ attr_reader :app_name, :current_token
4
+
5
+ def self.named(app_name, options = {}, &block)
6
+ self.new(app_name, options, &block)
7
+ end
8
+
9
+ def initialize(app_name, options = {}, &block)
10
+ @app_name = app_name
11
+ super(nil, &block)
12
+ import_help_flags(options[:import]) if options[:import]
13
+ end
14
+
15
+ def extract_completions(previous_token, current_token, global_config = {})
16
+ @current_token = current_token
17
+ @global_config = global_config
18
+ current = find_active_definition_for_last_token(previous_token) || self
19
+ current = (current.parent || self) if current.tokens_consumed == 1
20
+ completions = current.filtered_completions(current_token)
21
+ grouped = {:long => [], :short => [], :command => []}
22
+ grouped = completions.inject(grouped) do |mem, token|
23
+ if token =~ /^--/
24
+ mem[:long] << token
25
+ elsif token =~ /^-/
26
+ mem[:short] << token unless hide_short_flags?
27
+ else
28
+ mem[:command] << token
29
+ end
30
+ mem
31
+ end
32
+ grouped[:command].sort + grouped[:long].sort + grouped[:short].sort
33
+ end
34
+
35
+ def definition_type
36
+ :root
37
+ end
38
+
39
+ # Example usage:
40
+ # c.default do
41
+ # %w[possible values following command name]
42
+ # end
43
+ # which map to example command-line expressions:
44
+ # myapp this_command possible
45
+ # myapp this_command value
46
+ # myapp this_command following
47
+ def default(description=nil, &block)
48
+ contents << TabTab::Definition::Default.new(self, description, &block)
49
+ end
50
+
51
+ # Determines if current token matches the app name
52
+ def matches_token?(cmd_line_token)
53
+ cmd_line_token == app_name
54
+ end
55
+
56
+ def import_help_flags(help_flag)
57
+ help_flag = "--help" unless help_flag.is_a?(String)
58
+ imported_flags = TabTab::Completions::External.new(app_name, help_flag).extract
59
+ imported_flags.each do |flag|
60
+ flag.gsub!(/^-*/, '')
61
+ next unless flag.size > 0
62
+ self.flag(flag.to_sym)
63
+ end
64
+ end
65
+
66
+ def hide_short_flags?
67
+ @global_config[:shortflags] == 'disable'
68
+ end
69
+ end
70
+ end