toys-core 0.3.2 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -48,18 +48,21 @@ module Toys
48
48
  # @return [String] Converted string
49
49
  #
50
50
  def to_path_name(str)
51
- str.to_s.gsub(/([a-zA-Z])([A-Z])/) { |_m| "#{$1}_#{$2.downcase}" }.downcase
51
+ str = str.to_s.sub(/^_/, "").sub(/_$/, "").gsub(/_+/, "_")
52
+ while str.sub!(/([^_])([A-Z])/, "\\1_\\2") do end
53
+ str.downcase
52
54
  end
53
55
 
54
56
  ##
55
57
  # Convert the given string to a module name. Specifically, converts
56
- # to `UpperCamelCase`.
58
+ # to `UpperCamelCase`, and then to a symbol.
57
59
  #
58
60
  # @param [String,Symbol] str String to convert.
59
- # @return [String] Converted string
61
+ # @return [Symbol] Converted name
60
62
  #
61
63
  def to_module_name(str)
62
- str.to_s.gsub(/(^|_)([a-zA-Z0-9])/) { |_m| $2.upcase }
64
+ str = str.to_s.sub(/^_/, "").sub(/_$/, "").gsub(/_+/, "_")
65
+ str.to_s.gsub(/(^|_)([a-zA-Z])/) { |_m| $2.upcase }.to_sym
63
66
  end
64
67
 
65
68
  ##
@@ -77,7 +80,16 @@ module Toys
77
80
  #
78
81
  def lookup!(collection, name)
79
82
  require "toys/#{to_path_name(collection)}/#{to_path_name(name)}"
80
- ::Toys.const_get(to_module_name(collection)).const_get(to_module_name(name))
83
+ collection_sym = to_module_name(collection)
84
+ unless ::Toys.constants.include?(collection_sym)
85
+ raise ::NameError, "Module does not exist: Toys::#{collection_sym}"
86
+ end
87
+ collection_mod = ::Toys.const_get(collection_sym)
88
+ name_sym = to_module_name(name)
89
+ unless collection_mod.constants.include?(name_sym)
90
+ raise ::NameError, "Module does not exist: Toys::#{collection_sym}::#{name_sym}"
91
+ end
92
+ collection_mod.const_get(name_sym)
81
93
  end
82
94
 
83
95
  ##
@@ -37,6 +37,18 @@ module Toys
37
37
  # and related options.
38
38
  #
39
39
  class Usage
40
+ ##
41
+ # Default width of first column
42
+ # @return [Integer]
43
+ #
44
+ DEFAULT_LEFT_COLUMN_WIDTH = 32
45
+
46
+ ##
47
+ # Default indent
48
+ # @return [Integer]
49
+ #
50
+ DEFAULT_INDENT = 4
51
+
40
52
  ##
41
53
  # Create a usage helper given an execution context.
42
54
  #
@@ -44,22 +56,22 @@ module Toys
44
56
  # @return [Toys::Utils::Usage]
45
57
  #
46
58
  def self.from_context(context)
47
- new(context[Context::TOOL], context[Context::BINARY_NAME], context[Context::LOADER])
59
+ new(context[Context::TOOL], context[Context::LOADER], context[Context::BINARY_NAME])
48
60
  end
49
61
 
50
62
  ##
51
63
  # Create a usage helper.
52
64
  #
53
65
  # @param [Toys::Tool] tool The tool for which to generate documentation.
54
- # @param [String] binary_name The name of the binary. e.g. `"toys"`.
55
66
  # @param [Toys::Loader] loader A loader that can provide subcommands.
67
+ # @param [String] binary_name The name of the binary. e.g. `"toys"`.
56
68
  #
57
69
  # @return [Toys::Utils::Usage]
58
70
  #
59
- def initialize(tool, binary_name, loader)
71
+ def initialize(tool, loader, binary_name)
60
72
  @tool = tool
61
- @binary_name = binary_name
62
73
  @loader = loader
74
+ @binary_name = binary_name
63
75
  end
64
76
 
65
77
  ##
@@ -71,27 +83,29 @@ module Toys
71
83
  # listing subcommands. Defaults to `nil` which finds all subcommands.
72
84
  # @param [Boolean] show_path If true, shows the path to the config file
73
85
  # containing the tool definition (if set). Defaults to false.
86
+ # @param [Integer] left_column_width Width of the first column. Default
87
+ # is {DEFAULT_LEFT_COLUMN_WIDTH}.
88
+ # @param [Integer] indent Indent width. Default is {DEFAULT_INDENT}.
74
89
  #
75
90
  # @return [String] A usage string.
76
91
  #
77
- def string(recursive: false, search: nil, show_path: false)
78
- optparse = ::OptionParser.new
79
- optparse.banner = @tool.includes_executor? ? tool_banner : group_banner
80
- unless @tool.effective_long_desc.empty?
81
- optparse.separator("")
82
- optparse.separator(@tool.effective_long_desc)
83
- end
84
- if show_path && @tool.definition_path
85
- optparse.separator("")
86
- optparse.separator("Defined in #{@tool.definition_path}")
87
- end
88
- add_switches(optparse)
92
+ def string(recursive: false, search: nil, show_path: false,
93
+ left_column_width: nil, indent: nil,
94
+ wrap_width: nil, right_column_wrap_width: nil)
95
+ left_column_width ||= DEFAULT_LEFT_COLUMN_WIDTH
96
+ indent ||= DEFAULT_INDENT
97
+ right_column_wrap_width ||= wrap_width - left_column_width - indent - 1 if wrap_width
98
+ lines = []
99
+ lines << (@tool.includes_executor? ? tool_banner : group_banner)
100
+ add_description(lines, wrap_width, show_path)
101
+ add_switches(lines, indent, left_column_width, right_column_wrap_width)
89
102
  if @tool.includes_executor?
90
- add_positional_arguments(optparse)
103
+ add_positional_arguments(lines, indent, left_column_width, right_column_wrap_width)
91
104
  else
92
- add_command_list(optparse, recursive, search)
105
+ add_command_list(lines, recursive, search, indent,
106
+ left_column_width, right_column_wrap_width)
93
107
  end
94
- optparse.to_s
108
+ lines.join("\n") + "\n"
95
109
  end
96
110
 
97
111
  private
@@ -118,40 +132,62 @@ module Toys
118
132
  # Returns the banner string for a group
119
133
  #
120
134
  def group_banner
121
- list = ["Usage:", @binary_name] +
122
- @tool.full_name +
123
- ["<command>", "<command-arguments...>"]
124
- list.join(" ")
135
+ banner = ["Usage:", @binary_name] +
136
+ @tool.full_name +
137
+ ["<command>", "<command-arguments...>"]
138
+ banner.join(" ")
139
+ end
140
+
141
+ def add_description(lines, wrap_width, show_path)
142
+ long_desc = @tool.effective_long_desc(wrap_width: wrap_width)
143
+ unless long_desc.empty?
144
+ lines << ""
145
+ lines.concat(long_desc)
146
+ end
147
+ if show_path && @tool.definition_path
148
+ lines << ""
149
+ lines << "Defined in #{@tool.definition_path}"
150
+ end
125
151
  end
126
152
 
127
153
  #
128
154
  # Add switches from the tool to the given optionparser. Causes the
129
155
  # optparser to generate documentation for those switches.
130
156
  #
131
- def add_switches(optparse)
157
+ def add_switches(lines, indent, left_column_width, right_column_wrap_width)
132
158
  return if @tool.switch_definitions.empty?
133
- optparse.separator("")
134
- optparse.separator("Options:")
159
+ lines << ""
160
+ lines << "Options:"
135
161
  @tool.switch_definitions.each do |switch|
136
- optparse.on(*switch.optparse_info)
162
+ add_switch(lines, switch, indent, left_column_width, right_column_wrap_width)
137
163
  end
138
164
  end
139
165
 
166
+ #
167
+ # Add a single switch
168
+ #
169
+ def add_switch(lines, switch, indent, left_column_width, right_column_wrap_width)
170
+ switches_str = (switch.single_switch_syntax.map(&:str_without_value) +
171
+ switch.double_switch_syntax.map(&:str_without_value)).join(", ")
172
+ switches_str << switch.value_delim << switch.value_label if switch.value_label
173
+ switches_str = " #{switches_str}" if switch.single_switch_syntax.empty?
174
+ add_doc(lines, switches_str, switch.wrapped_docs(right_column_wrap_width),
175
+ indent, left_column_width)
176
+ end
177
+
140
178
  #
141
179
  # Add documentation for the tool's positional arguments, to the given
142
180
  # option parser.
143
181
  #
144
- def add_positional_arguments(optparse)
182
+ def add_positional_arguments(lines, indent, left_column_width, right_column_wrap_width)
145
183
  args_to_display = @tool.required_arg_definitions + @tool.optional_arg_definitions
146
184
  args_to_display << @tool.remaining_args_definition if @tool.remaining_args_definition
147
185
  return if args_to_display.empty?
148
- optparse.separator("")
149
- optparse.separator("Positional arguments:")
186
+ lines << ""
187
+ lines << "Positional arguments:"
150
188
  args_to_display.each do |arg_info|
151
- optparse.separator(" #{arg_info.canonical_name.ljust(31)} #{arg_info.doc.first}")
152
- (arg_info.doc[1..-1] || []).each do |d|
153
- optparse.separator(" #{d}")
154
- end
189
+ add_doc(lines, arg_info.canonical_name, arg_info.wrapped_docs(right_column_wrap_width),
190
+ indent, left_column_width)
155
191
  end
156
192
  end
157
193
 
@@ -159,23 +195,40 @@ module Toys
159
195
  # Add documentation for the tool's subcommands, to the given option
160
196
  # parser.
161
197
  #
162
- def add_command_list(optparse, recursive, search)
198
+ def add_command_list(lines, recursive, search, indent,
199
+ left_column_width, right_column_wrap_width)
163
200
  name_len = @tool.full_name.length
164
201
  subtools = find_commands(recursive, search)
165
202
  return if subtools.empty?
166
- optparse.separator("")
167
- if search
168
- optparse.separator("Commands with search term #{search.inspect}:")
169
- else
170
- optparse.separator("Commands:")
171
- end
203
+ lines << ""
204
+ lines << (search ? "Commands with search term #{search.inspect}:" : "Commands:")
172
205
  subtools.each do |subtool|
173
- tool_name = subtool.full_name.slice(name_len..-1).join(" ").ljust(31)
174
- if subtool.is_a?(Alias)
175
- optparse.separator(" #{tool_name} (Alias of #{subtool.display_target})")
206
+ tool_name = subtool.full_name.slice(name_len..-1).join(" ")
207
+ doc =
208
+ if subtool.is_a?(Alias)
209
+ ["(Alias of #{subtool.display_target})"]
210
+ else
211
+ subtool.effective_desc(wrap_width: right_column_wrap_width)
212
+ end
213
+ add_doc(lines, tool_name, doc, indent, left_column_width)
214
+ end
215
+ end
216
+
217
+ #
218
+ # Add a line with possible documentation strings.
219
+ #
220
+ def add_doc(lines, initial, doc, indent, left_column_width)
221
+ initial = "#{' ' * indent}#{initial.ljust(left_column_width)}"
222
+ remaining_doc =
223
+ if initial.size <= indent + left_column_width
224
+ lines << "#{initial} #{doc.first}"
225
+ doc[1..-1] || []
176
226
  else
177
- optparse.separator(" #{tool_name} #{subtool.effective_desc}")
227
+ lines << initial
228
+ doc
178
229
  end
230
+ remaining_doc.each do |d|
231
+ lines << "#{' ' * (indent + left_column_width)} #{d}"
179
232
  end
180
233
  end
181
234
 
@@ -187,8 +240,9 @@ module Toys
187
240
  return subtools if search.nil? || search.empty?
188
241
  regex = Regexp.new("(^|\\s)#{Regexp.escape(search)}(\\s|$)", Regexp::IGNORECASE)
189
242
  subtools.find_all do |tool|
190
- regex =~ tool.display_name || regex =~ tool.effective_desc ||
191
- regex =~ tool.effective_long_desc
243
+ regex =~ tool.display_name ||
244
+ tool.effective_desc.find { |d| regex =~ d } ||
245
+ tool.effective_long_desc.find { |d| regex =~ d }
192
246
  end
193
247
  end
194
248
  end
@@ -0,0 +1,92 @@
1
+ # Copyright 2018 Daniel Azuma
2
+ #
3
+ # All rights reserved.
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # * Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ # * Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ # * Neither the name of the copyright holder, nor the names of any other
14
+ # contributors to this software, may be used to endorse or promote products
15
+ # derived from this software without specific prior written permission.
16
+ #
17
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
+ # POSSIBILITY OF SUCH DAMAGE.
28
+ ;
29
+
30
+ module Toys
31
+ module Utils
32
+ ##
33
+ # A string intended to be wrapped.
34
+ #
35
+ class WrappableString
36
+ ##
37
+ # Create a wrapped string.
38
+ # @param [String] string The string.
39
+ #
40
+ def initialize(string = "")
41
+ @string = string
42
+ end
43
+
44
+ ##
45
+ # Returns the string.
46
+ # @return [String]
47
+ #
48
+ attr_reader :string
49
+
50
+ ##
51
+ # Returns the string.
52
+ # @return [String]
53
+ #
54
+ def to_s
55
+ string
56
+ end
57
+
58
+ ## @private
59
+ def ==(other)
60
+ other.is_a?(WrappableString) ? other.string == string : false
61
+ end
62
+ alias eql? ==
63
+
64
+ ## @private
65
+ def hash
66
+ string.hash
67
+ end
68
+
69
+ ##
70
+ # Wraps the string to the given width.
71
+ #
72
+ # @param [Integer] width Width in characters.
73
+ # @return [Array<String>] Wrapped lines
74
+ #
75
+ def wrap(width)
76
+ lines = []
77
+ str = string.gsub(/\s/, " ").sub(/^\s+/, "")
78
+ until str.empty?
79
+ i = str.index(/\S(\s|$)/) + 1
80
+ loop do
81
+ next_i = str.index(/\S(\s|$)/, i)
82
+ break if next_i.nil? || next_i >= width
83
+ i = next_i + 1
84
+ end
85
+ lines << str[0, i]
86
+ str = str[i..-1].sub(/^\s+/, "")
87
+ end
88
+ lines
89
+ end
90
+ end
91
+ end
92
+ end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: toys-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Azuma
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-05-07 00:00:00.000000000 Z
11
+ date: 2018-05-10 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: highline
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: minitest
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -100,7 +114,9 @@ files:
100
114
  - lib/toys/errors.rb
101
115
  - lib/toys/helpers.rb
102
116
  - lib/toys/helpers/exec.rb
103
- - lib/toys/helpers/file_utils.rb
117
+ - lib/toys/helpers/fileutils.rb
118
+ - lib/toys/helpers/highline.rb
119
+ - lib/toys/helpers/spinner.rb
104
120
  - lib/toys/loader.rb
105
121
  - lib/toys/middleware.rb
106
122
  - lib/toys/middleware/add_verbosity_switches.rb
@@ -119,6 +135,7 @@ files:
119
135
  - lib/toys/tool.rb
120
136
  - lib/toys/utils/module_lookup.rb
121
137
  - lib/toys/utils/usage.rb
138
+ - lib/toys/utils/wrappable_string.rb
122
139
  homepage: https://github.com/dazuma/toys
123
140
  licenses:
124
141
  - BSD-3-Clause