gli 2.1.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -88,5 +88,3 @@ Feature: The GLI executable works as intended
88
88
  Given the file "gli.rdoc" doesn't exist
89
89
  When I run `gli _doc`
90
90
  Then a file named "gli.rdoc" should exist
91
-
92
-
@@ -2,6 +2,17 @@ Given /^todo's bin directory is in my path/ do
2
2
  add_to_path(File.expand_path(File.join(File.dirname(__FILE__),'..','..','test','apps','todo','bin')))
3
3
  end
4
4
 
5
+ Given /^the todo app is coded to avoid sorted help commands$/ do
6
+ ENV['TODO_SORT_HELP'] = 'manually'
7
+ end
8
+
9
+ Given /^the todo app is coded to avoid wrapping text$/ do
10
+ ENV['TODO_WRAP_HELP_TEXT'] = 'never'
11
+ end
12
+
13
+ Given /^the todo app is coded to wrap text only for tty$/ do
14
+ ENV['TODO_WRAP_HELP_TEXT'] = 'tty_only'
15
+ end
5
16
 
6
17
  Given /^a clean home directory$/ do
7
18
  FileUtils.rm_rf File.join(ENV['HOME'],'gli_test_todo.rc')
@@ -24,6 +24,7 @@ Before do
24
24
  FileUtils.rm_rf new_home
25
25
  FileUtils.mkdir new_home
26
26
  ENV['HOME'] = new_home
27
+ FileUtils.cp 'gli.rdoc','gli.rdoc.orig'
27
28
  end
28
29
 
29
30
  After do |scenario|
@@ -34,6 +35,9 @@ After do |scenario|
34
35
  end
35
36
  ENV['PATH'] = @original_path.join(File::PATH_SEPARATOR)
36
37
  ENV['HOME'] = @original_home
38
+ ENV['TODO_SORT_HELP'] = nil
39
+ ENV['TODO_WRAP_HELP_TEXT'] = nil
40
+ FileUtils.mv 'gli.rdoc.orig','gli.rdoc'
37
41
  end
38
42
 
39
43
  def add_to_path(dir)
@@ -15,6 +15,9 @@ Feature: The todo app has a nice user interface
15
15
  NAME
16
16
  todo - Manages tasks
17
17
 
18
+ A test program that has a sophisticated UI that can be used to exercise a
19
+ lot of GLI's power
20
+
18
21
  SYNOPSIS
19
22
  todo [global options] command [command options] [arguments...]
20
23
 
@@ -85,8 +88,44 @@ Feature: The todo app has a nice user interface
85
88
  contexts
86
89
  """
87
90
 
88
- Scenario: Getting Help for a top level command of todo
89
- When I successfully run `todo help list`
91
+ Scenario: Getting Help with self-ordered commands
92
+ Given the todo app is coded to avoid sorted help commands
93
+ When I successfully run `todo help`
94
+ Then the output should contain:
95
+ """
96
+ NAME
97
+ todo - Manages tasks
98
+
99
+ A test program that has a sophisticated UI that can be used to exercise a
100
+ lot of GLI's power
101
+
102
+ SYNOPSIS
103
+ todo [global options] command [command options] [arguments...]
104
+
105
+ VERSION
106
+ 0.0.1
107
+
108
+ GLOBAL OPTIONS
109
+ --flag=arg - (default: none)
110
+ --help - Show this message
111
+ --[no-]otherswitch -
112
+ --[no-]switch -
113
+ --version -
114
+
115
+ COMMANDS
116
+ help - Shows a list of commands or help for one command
117
+ initconfig - Initialize the config file using current global options
118
+ create, new - Create a new task or context
119
+ list - List things, such as tasks or contexts
120
+ ls - LS things, such as tasks or contexts
121
+ first -
122
+ second -
123
+ chained -
124
+ chained2, ch2 -
125
+ """
126
+
127
+ Scenario Outline: Getting Help for a top level command of todo
128
+ When I successfully run `todo <help_invocation>`
90
129
  Then the output should contain:
91
130
  """
92
131
  NAME
@@ -111,6 +150,59 @@ Feature: The todo app has a nice user interface
111
150
  tasks - List tasks (default)
112
151
  """
113
152
 
153
+ Examples:
154
+ | help_invocation |
155
+ | help list |
156
+ | list -h |
157
+ | list --help |
158
+
159
+
160
+ Scenario: Getting Help without wrapping
161
+ Given the todo app is coded to avoid wrapping text
162
+ When I successfully run `todo help list`
163
+ Then the output should contain:
164
+ """
165
+ NAME
166
+ list - List things, such as tasks or contexts
167
+
168
+ SYNOPSIS
169
+ todo [global options] list [command options] [--flag arg] [-x arg] [tasks]
170
+ todo [global options] list [command options] [--otherflag arg] [-b] [-f|--foobar] contexts
171
+
172
+ DESCRIPTION
173
+ List a whole lot of things that you might be keeping track of in your overall todo list. This is your go-to place or finding all of the things that you might have stored in your todo databases.
174
+
175
+ COMMAND OPTIONS
176
+ -l, --[no-]long - Show long form
177
+
178
+ COMMANDS
179
+ contexts - List contexts
180
+ tasks - List tasks (default)
181
+ """
182
+
183
+ Scenario: Getting Help without wrapping
184
+ Given the todo app is coded to wrap text only for tty
185
+ When I successfully run `todo help list`
186
+ Then the output should contain:
187
+ """
188
+ NAME
189
+ list - List things, such as tasks or contexts
190
+
191
+ SYNOPSIS
192
+ todo [global options] list [command options] [--flag arg] [-x arg] [tasks]
193
+ todo [global options] list [command options] [--otherflag arg] [-b] [-f|--foobar] contexts
194
+
195
+ DESCRIPTION
196
+ List a whole lot of things that you might be keeping track of in your overall todo list. This is your go-to place or finding all of the things that you might have stored in your todo databases.
197
+
198
+ COMMAND OPTIONS
199
+ -l, --[no-]long - Show long form
200
+
201
+ COMMANDS
202
+ contexts - List contexts
203
+ tasks - List tasks (default)
204
+ """
205
+
114
206
  Scenario: Getting Help for a sub command of todo list
115
207
  When I successfully run `todo help list tasks`
116
208
  Then the output should contain:
data/gli.gemspec CHANGED
@@ -29,5 +29,6 @@ spec = Gem::Specification.new do |s|
29
29
  s.add_development_dependency('clean_test')
30
30
  s.add_development_dependency('aruba')
31
31
  s.add_development_dependency('sdoc')
32
+ s.add_development_dependency('faker','1.0.0')
32
33
  end
33
34
 
data/gli.rdoc CHANGED
@@ -1,13 +1,12 @@
1
1
  == gli - create scaffolding for a GLI-powered application
2
2
 
3
- v2.0.0.rc8
3
+ v2.1.0
4
4
 
5
5
  === Global Options
6
- === -r arg
6
+ === -r|--root arg
7
7
 
8
8
  Root dir of project
9
9
 
10
- [Aliases] --root
11
10
  [Default Value] .
12
11
  This is the directory where the project''s directory will be made, so if you
13
12
  specify a project name ''foo'' and the root dir of ''.'', the directory
@@ -18,46 +17,43 @@ Show this message
18
17
 
19
18
 
20
19
 
21
-
22
20
  === -n
23
21
  Dry run; dont change the disk
24
22
 
25
23
 
26
24
 
27
-
28
25
  === -v
29
26
  Be verbose
30
27
 
31
28
 
32
29
 
33
-
34
30
  === --version
35
31
 
36
32
 
37
33
 
38
34
 
39
-
40
35
  === Commands
41
- ==== help command
36
+ ==== Command: <tt>help command</tt>
42
37
  Shows a list of commands or help for one command
43
38
 
44
-
45
39
  Gets help for the application or its commands. Can also list the commands in a way helpful to creating a bash-style completion function
46
- ==== init project_name [command[ command]*]
47
- Create a new GLI-based project
40
+ ===== Options
41
+ ===== -c
42
+ List commands one per line, to assist with shell completion
48
43
 
49
- [Aliases] scaffold
44
+
45
+
46
+ ==== Command: <tt>init|scaffold project_name [command[ command]*]</tt>
47
+ Create a new GLI-based project
50
48
 
51
49
  This will create a scaffold command line project that uses GLI
52
50
  for command line processing. Specifically, this will create
53
51
  an executable ready to go, as well as a lib and test directory, all
54
52
  inside the directory named for your project
55
53
  ===== Options
56
- ===== -e
54
+ ===== -e|--[no-]ext
57
55
  Create an ext dir
58
56
 
59
- [Aliases] --[no-]ext
60
-
61
57
 
62
58
 
63
59
  ===== --[no-]force
@@ -65,16 +61,13 @@ Overwrite/ignore existing files and directories
65
61
 
66
62
 
67
63
 
68
-
69
64
  ===== --notest
70
65
  Do not create a test or features dir
71
66
 
72
67
 
73
68
 
74
-
75
69
  ===== --[no-]rvmrc
76
70
  Create an .rvmrc based on your current RVM setup
77
71
 
78
72
 
79
73
 
80
-
data/lib/gli/app.rb CHANGED
@@ -23,7 +23,7 @@ module GLI
23
23
  $LOAD_PATH.each do |load_path|
24
24
  commands_path = File.join(load_path,path)
25
25
  if File.exists? commands_path
26
- Dir.entries(commands_path).each do |entry|
26
+ Dir.entries(commands_path).sort.each do |entry|
27
27
  file = File.join(commands_path,entry)
28
28
  if file =~ /\.rb$/
29
29
  require file
@@ -44,6 +44,17 @@ module GLI
44
44
  @program_desc
45
45
  end
46
46
 
47
+ # Provide a longer description of the program. This can be as long as needed, and use double-newlines
48
+ # for paragraphs. This will show up in the help output.
49
+ #
50
+ # description:: A String for the description
51
+ def program_long_desc(description=nil)
52
+ if description
53
+ @program_long_desc = description
54
+ end
55
+ @program_long_desc
56
+ end
57
+
47
58
  # Use this if the following command should not have the pre block executed.
48
59
  # By default, the pre block is executed before each command and can result in
49
60
  # aborting the call. Using this will avoid that behavior for the following command
@@ -77,6 +88,7 @@ module GLI
77
88
  @config_file = File.join(File.expand_path(ENV['HOME']),filename)
78
89
  end
79
90
  commands[:initconfig] = InitConfig.new(@config_file,commands,flags,switches)
91
+ @commands_declaration_order << commands[:initconfig]
80
92
  @config_file
81
93
  end
82
94
 
@@ -195,8 +207,8 @@ module GLI
195
207
 
196
208
  # Exit now, showing the user help for the command they executed. Use #exit_now! to just show the error message
197
209
  #
198
- # message:: message to indicate how the user has messed up the CLI invocation
199
- def help_now!(message)
210
+ # message:: message to indicate how the user has messed up the CLI invocation or nil to just simply show help
211
+ def help_now!(message=nil)
200
212
  exception = OptionParser::ParseError.new(message)
201
213
  class << exception
202
214
  def exit_code; 64; end
@@ -204,6 +216,25 @@ module GLI
204
216
  raise exception
205
217
  end
206
218
 
219
+ # Control how help commands are sorted. By default, the commands are sorted alphabetically.
220
+ #
221
+ # sort_type:: How you want help commands sorted:
222
+ # +:manually+:: help commands are ordered in the order declared.
223
+ # +:alpha+:: sort alphabetically (default)
224
+ def sort_help(sort_type)
225
+ @help_sort_type = sort_type
226
+ end
227
+
228
+ # Set how help text is wrapped.
229
+ #
230
+ # wrap_type:: Symbol indicating how you'd like text wrapped:
231
+ # +:to_terminal+:: Wrap text based on the width of the terminal (default)
232
+ # +:never+:: Do not wrap text at all. This will bring all help content onto one line, removing any newlines
233
+ # +:tty_only+:: Wrap like +:to_terminal+ if this output is going to a TTY, otherwise don't wrap (like +:never+)
234
+ def wrap_help_text(wrap_type)
235
+ @help_text_wrap_type = wrap_type
236
+ end
237
+
207
238
  def program_name(override=nil) #:nodoc:
208
239
  warn "#program_name has been deprecated"
209
240
  end
@@ -15,6 +15,7 @@ module GLI
15
15
  switches.clear
16
16
  flags.clear
17
17
  @commands = nil
18
+ @commands_declaration_order = []
18
19
  @version = nil
19
20
  @config_file = nil
20
21
  @use_openstruct = false
@@ -27,8 +28,13 @@ module GLI
27
28
  clear_nexts
28
29
  end
29
30
 
31
+ # Get an array of commands, ordered by when they were declared
32
+ def commands_declaration_order # :nodoc:
33
+ @commands_declaration_order
34
+ end
35
+
30
36
  # Get the version string
31
- def version_string #:nodoc
37
+ def version_string #:nodoc:
32
38
  @version
33
39
  end
34
40
 
@@ -119,7 +125,13 @@ module GLI
119
125
  end
120
126
 
121
127
  def commands # :nodoc:
122
- @commands ||= { :help => GLI::Commands::Help.new(self), :_doc => GLI::Commands::Doc.new(self) }
128
+ if !@commands
129
+ @commands = { :help => GLI::Commands::Help.new(self), :_doc => GLI::Commands::Doc.new(self) }
130
+ @commands_declaration_order ||= []
131
+ @commands_declaration_order << @commands[:help]
132
+ @commands_declaration_order << @commands[:_doc]
133
+ end
134
+ @commands
123
135
  end
124
136
 
125
137
  def pre_block
@@ -137,6 +149,14 @@ module GLI
137
149
  @around_blocks || []
138
150
  end
139
151
 
152
+ def help_sort_type
153
+ @help_sort_type || :alpha
154
+ end
155
+
156
+ def help_text_wrap_type
157
+ @help_text_wrap_type || :to_terminal
158
+ end
159
+
140
160
  # Sets the default values for flags based on the configuration
141
161
  def override_defaults_based_on_config(config)
142
162
  override_default(flags,config)
@@ -167,9 +187,8 @@ module GLI
167
187
 
168
188
  def handle_exception(ex,command)
169
189
  if regular_error_handling?(ex)
170
- stderr.puts error_message(ex)
190
+ output_error_message(ex)
171
191
  if ex.kind_of?(OptionParser::ParseError) || ex.kind_of?(BadCommandLine)
172
- stderr.puts
173
192
  commands[:help] and commands[:help].execute({},{},command.nil? ? [] : [command.name.to_s])
174
193
  end
175
194
  end
@@ -180,6 +199,17 @@ module GLI
180
199
  ex.exit_code
181
200
  end
182
201
 
202
+ def output_error_message(ex)
203
+ stderr.puts error_message(ex) unless no_message_given?(ex)
204
+ if ex.kind_of?(OptionParser::ParseError) || ex.kind_of?(BadCommandLine)
205
+ stderr.puts unless no_message_given?(ex)
206
+ end
207
+ end
208
+
209
+ def no_message_given?(ex)
210
+ ex.message == ex.class.name
211
+ end
212
+
183
213
  # Possibly returns a copy of the passed-in Hash as an instance of GLI::Option.
184
214
  # By default, it will *not*. However by putting <tt>use_openstruct true</tt>
185
215
  # in your CLI definition, it will
data/lib/gli/command.rb CHANGED
@@ -50,6 +50,7 @@ module GLI
50
50
  @skips_pre = options[:skips_pre]
51
51
  @skips_post = options[:skips_post]
52
52
  @skips_around = options[:skips_around]
53
+ @commands_declaration_order = []
53
54
  clear_nexts
54
55
  end
55
56
 
@@ -132,6 +133,13 @@ module GLI
132
133
  @default_desc = desc
133
134
  end
134
135
 
136
+ # Returns true if this command has the given option defined
137
+ def has_option?(option) #:nodoc:
138
+ option = option.gsub(/^\-+/,'')
139
+ ((flags.values.map { |_| [_.name,_.aliases] }) +
140
+ (switches.values.map { |_| [_.name,_.aliases] })).flatten.map(&:to_s).include?(option)
141
+ end
142
+
135
143
  def self.name_as_string(name,negatable=false) #:nodoc:
136
144
  name.to_s
137
145
  end
@@ -44,6 +44,11 @@ module GLI
44
44
  all_forms
45
45
  end
46
46
 
47
+ # Get an array of commands, ordered by when they were declared
48
+ def commands_declaration_order # :nodoc:
49
+ @commands_declaration_order
50
+ end
51
+
47
52
  def flag(*names)
48
53
  new_flag = if parent.kind_of? Command
49
54
  parent.flag(*names)
@@ -32,7 +32,8 @@ module GLI
32
32
  # Generates documentation using the listener
33
33
  def document(document_listener)
34
34
  document_listener.beginning
35
- document_listener.program_desc(@app.program_desc)
35
+ document_listener.program_desc(@app.program_desc) unless @app.program_desc.nil?
36
+ document_listener.program_long_desc(@app.program_long_desc) unless @app.program_long_desc.nil?
36
37
  document_listener.version(@app.version_string)
37
38
  if any_options?(@app)
38
39
  document_listener.options
@@ -66,6 +67,11 @@ module GLI
66
67
  abstract!
67
68
  end
68
69
 
70
+ # Gives you the program long description
71
+ def program_long_desc(desc)
72
+ abstract!
73
+ end
74
+
69
75
  # Gives you the program version
70
76
  def version(version)
71
77
  abstract!
@@ -107,7 +113,7 @@ module GLI
107
113
  end
108
114
 
109
115
  # Gives you a command in the current context and creates a new context of this command
110
- def command(name,aliases,desc,long_desc,arg_name)
116
+ def command(name,aliases,desc,long_desc,arg_name,arg_options)
111
117
  abstract!
112
118
  end
113
119
 
@@ -136,11 +142,7 @@ module GLI
136
142
 
137
143
  def document_commands(document_listener,context)
138
144
  context.commands.values.reject {|_| _.nodoc }.sort(&by_name).each do |command|
139
- document_listener.command(command.name,
140
- Array(command.aliases),
141
- command.description,
142
- command.long_description,
143
- command.arguments_description)
145
+ call_command_method_being_backwards_compatible(document_listener,command)
144
146
  document_listener.options if any_options?(command)
145
147
  document_flags_and_switches(document_listener,command_flags(command),command_switches(command))
146
148
  document_listener.end_options if any_options?(command)
@@ -152,6 +154,18 @@ module GLI
152
154
  document_listener.default_command(context.get_default_command)
153
155
  end
154
156
 
157
+ def call_command_method_being_backwards_compatible(document_listener,command)
158
+ command_args = [command.name,
159
+ Array(command.aliases),
160
+ command.description,
161
+ command.long_description,
162
+ command.arguments_description]
163
+ if document_listener.method(:command).arity == 6
164
+ command_args << command.arguments_options
165
+ end
166
+ document_listener.command(*command_args)
167
+ end
168
+
155
169
  def by_name
156
170
  lambda { |a,b| a.name.to_s <=> b.name.to_s }
157
171
  end
@@ -170,7 +184,7 @@ module GLI
170
184
  Array(flag.aliases),
171
185
  flag.description,
172
186
  flag.long_description,
173
- flag.default_value,
187
+ flag.safe_default_value,
174
188
  flag.argument_name,
175
189
  flag.must_match,
176
190
  flag.type)