toys-core 0.3.3 → 0.3.4

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.
@@ -0,0 +1,105 @@
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
+ require "highline"
31
+
32
+ module Toys
33
+ module Utils
34
+ ##
35
+ # Something that outputs lines to the console or log.
36
+ #
37
+ class LineOutput
38
+ ##
39
+ # Create a line output.
40
+ #
41
+ # @param [IO,Logger,nil] sink Where to write lines.
42
+ #
43
+ def initialize(sink, log_level: ::Logger::INFO, styled: nil)
44
+ @sink = sink
45
+ @log_level = sink.is_a?(::Logger) ? log_level : nil
46
+ @styled =
47
+ if styled.nil?
48
+ sink.respond_to?(:tty?) && sink.tty?
49
+ else
50
+ styled ? true : false
51
+ end
52
+ end
53
+
54
+ ##
55
+ # Where to write lines
56
+ # @return [IO,Logger,nil]
57
+ #
58
+ attr_reader :sink
59
+
60
+ ##
61
+ # Whether output is styled
62
+ # @return [Boolean]
63
+ #
64
+ attr_reader :styled
65
+
66
+ ##
67
+ # If the sink is a Logger, the level to log, otherwise `nil`.
68
+ # @return [Integer,nil]
69
+ #
70
+ attr_reader :log_level
71
+
72
+ ##
73
+ # Write a line.
74
+ #
75
+ # @param [String] str The line to write
76
+ # @param [Symbol...] styles Styles to apply to the entire line.
77
+ #
78
+ def puts(str = "", *styles)
79
+ if styled
80
+ str = color(str, *styles) unless styles.empty?
81
+ else
82
+ str = ::HighLine.uncolor(str)
83
+ end
84
+ case sink
85
+ when ::Logger
86
+ sink.log(log_level, str)
87
+ when ::IO
88
+ sink.puts(str)
89
+ end
90
+ self
91
+ end
92
+
93
+ ##
94
+ # Apply the given styles to a string
95
+ #
96
+ # @param [String] str The string
97
+ # @param [Symbol...] styles Styles to apply to the string.
98
+ # @return [String] The string with styles applied.
99
+ #
100
+ def color(str, *styles)
101
+ ::HighLine.color(str, *styles)
102
+ end
103
+ end
104
+ end
105
+ end
@@ -35,58 +35,106 @@ module Toys
35
35
  class WrappableString
36
36
  ##
37
37
  # Create a wrapped string.
38
- # @param [String] string The string.
38
+ # @param [String,Array<String>] string The string or array of string
39
+ # fragments
39
40
  #
40
41
  def initialize(string = "")
41
- @string = string
42
+ @fragments = string.is_a?(::Array) ? string.map(&:to_s) : string.to_s.split
42
43
  end
43
44
 
44
45
  ##
45
- # Returns the string.
46
+ # Returns the fragments.
47
+ # @return [Array<String>]
48
+ #
49
+ attr_reader :fragments
50
+
51
+ ##
52
+ # Concatenates this WrappableString with another WrappableString
53
+ # @param [WrappableString] other
54
+ #
55
+ def +(other)
56
+ other = WrappableString.new(other) unless other.is_a?(WrappableString)
57
+ WrappableString.new(fragments + other.fragments)
58
+ end
59
+
60
+ ##
61
+ # Returns true if the string is empty (i.e. has no fragments)
46
62
  # @return [String]
47
63
  #
48
- attr_reader :string
64
+ def empty?
65
+ @fragments.empty?
66
+ end
49
67
 
50
68
  ##
51
- # Returns the string.
69
+ # Returns the string without any wrapping
52
70
  # @return [String]
53
71
  #
54
72
  def to_s
55
- string
73
+ @fragments.join(" ")
56
74
  end
75
+ alias string to_s
57
76
 
58
77
  ## @private
59
78
  def ==(other)
60
- other.is_a?(WrappableString) ? other.string == string : false
79
+ return false unless other.is_a?(WrappableString)
80
+ other.fragments == fragments
61
81
  end
62
82
  alias eql? ==
63
83
 
64
84
  ## @private
65
85
  def hash
66
- string.hash
86
+ fragments.hash
67
87
  end
68
88
 
69
89
  ##
70
90
  # Wraps the string to the given width.
71
91
  #
72
- # @param [Integer] width Width in characters.
92
+ # @param [Integer,nil] width Width in characters, or `nil` for infinite.
93
+ # @param [Integer,nil] width2 Width in characters for the second and
94
+ # subsequent lines, or `nil` to use the same as width.
73
95
  # @return [Array<String>] Wrapped lines
74
96
  #
75
- def wrap(width)
97
+ def wrap(width, width2 = nil)
76
98
  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
99
+ line = ""
100
+ line_len = 0
101
+ fragments.each do |frag|
102
+ frag_len = ::HighLine.uncolor(frag).size
103
+ if line_len.zero?
104
+ line = frag
105
+ line_len = frag_len
106
+ elsif width && line_len + 1 + frag_len > width
107
+ lines << line
108
+ line = frag
109
+ line_len = frag_len
110
+ width = width2 if width2
111
+ else
112
+ line_len += frag_len + 1
113
+ line = "#{line} #{frag}"
84
114
  end
85
- lines << str[0, i]
86
- str = str[i..-1].sub(/^\s+/, "")
87
115
  end
116
+ lines << line if line_len > 0
88
117
  lines
89
118
  end
119
+
120
+ ##
121
+ # Wraps an array of lines to the given width.
122
+ #
123
+ # @param [Array<WrappableString>] strs Array of strings to wrap.
124
+ # @param [Integer,nil] width Width in characters, or `nil` for infinite.
125
+ # @param [Integer,nil] width2 Width in characters for the second and
126
+ # subsequent lines, or `nil` to use the same as width.
127
+ # @return [Array<String>] Wrapped lines
128
+ #
129
+ def self.wrap_lines(strs, width, width2 = nil)
130
+ result = Array(strs).map do |s|
131
+ lines = s.empty? ? [""] : s.wrap(width, width2)
132
+ width = width2 if width2
133
+ lines
134
+ end.flatten
135
+ result = [] if result.all?(&:empty?)
136
+ result
137
+ end
90
138
  end
91
139
  end
92
140
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: toys-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.3
4
+ version: 0.3.4
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-10 00:00:00.000000000 Z
11
+ date: 2018-05-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: highline
@@ -119,11 +119,11 @@ files:
119
119
  - lib/toys/helpers/spinner.rb
120
120
  - lib/toys/loader.rb
121
121
  - lib/toys/middleware.rb
122
- - lib/toys/middleware/add_verbosity_switches.rb
122
+ - lib/toys/middleware/add_verbosity_flags.rb
123
123
  - lib/toys/middleware/base.rb
124
124
  - lib/toys/middleware/handle_usage_errors.rb
125
125
  - lib/toys/middleware/set_default_descriptions.rb
126
- - lib/toys/middleware/show_usage.rb
126
+ - lib/toys/middleware/show_help.rb
127
127
  - lib/toys/middleware/show_version.rb
128
128
  - lib/toys/template.rb
129
129
  - lib/toys/templates.rb
@@ -133,8 +133,9 @@ files:
133
133
  - lib/toys/templates/rubocop.rb
134
134
  - lib/toys/templates/yardoc.rb
135
135
  - lib/toys/tool.rb
136
+ - lib/toys/utils/help_text.rb
137
+ - lib/toys/utils/line_output.rb
136
138
  - lib/toys/utils/module_lookup.rb
137
- - lib/toys/utils/usage.rb
138
139
  - lib/toys/utils/wrappable_string.rb
139
140
  homepage: https://github.com/dazuma/toys
140
141
  licenses:
@@ -1,99 +0,0 @@
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
- require "toys/middleware/base"
31
-
32
- module Toys
33
- module Middleware
34
- ##
35
- # A middleware that provides switches for editing the verbosity.
36
- #
37
- # This middleware adds `-v`, `--verbose`, `-q`, and `--quiet` switches, if
38
- # not already defined by the tool. These switches affect the setting of
39
- # {Toys::Context::VERBOSITY}, and, thus, the logger level.
40
- #
41
- class AddVerbositySwitches < Base
42
- ##
43
- # Default verbose switches
44
- # @return [Array<String>]
45
- #
46
- DEFAULT_VERBOSE_SWITCHES = ["-v", "--verbose"].freeze
47
-
48
- ##
49
- # Default quiet switches
50
- # @return [Array<String>]
51
- #
52
- DEFAULT_QUIET_SWITCHES = ["-q", "--quiet"].freeze
53
-
54
- ##
55
- # Create a AddVerbositySwitches middleware.
56
- #
57
- # @param [Boolean,Array<String>,Proc] verbose_switches Specify switches
58
- # to increase verbosity. The value may be any of the following:
59
- # * An array of switches that increase verbosity.
60
- # * The `true` value to use {DEFAULT_VERBOSE_SWITCHES}. (Default)
61
- # * The `false` value to disable verbose switches.
62
- # * A proc that takes a tool and returns any of the above.
63
- # @param [Boolean,Array<String>,Proc] quiet_switches Specify switches
64
- # to decrease verbosity. The value may be any of the following:
65
- # * An array of switches that decrease verbosity.
66
- # * The `true` value to use {DEFAULT_QUIET_SWITCHES}. (Default)
67
- # * The `false` value to disable quiet switches.
68
- # * A proc that takes a tool and returns any of the above.
69
- #
70
- def initialize(verbose_switches: true, quiet_switches: true)
71
- @verbose_switches = verbose_switches
72
- @quiet_switches = quiet_switches
73
- end
74
-
75
- ##
76
- # Configure the tool switches.
77
- #
78
- def config(tool)
79
- verbose_switches = Middleware.resolve_switches_spec(@verbose_switches, tool,
80
- DEFAULT_VERBOSE_SWITCHES)
81
- unless verbose_switches.empty?
82
- tool.add_switch(Context::VERBOSITY, *verbose_switches,
83
- docs: "Increase verbosity",
84
- handler: ->(_val, cur) { cur + 1 },
85
- only_unique: true)
86
- end
87
- quiet_switches = Middleware.resolve_switches_spec(@quiet_switches, tool,
88
- DEFAULT_QUIET_SWITCHES)
89
- unless quiet_switches.empty?
90
- tool.add_switch(Context::VERBOSITY, *quiet_switches,
91
- docs: "Decrease verbosity",
92
- handler: ->(_val, cur) { cur - 1 },
93
- only_unique: true)
94
- end
95
- yield
96
- end
97
- end
98
- end
99
- end
@@ -1,174 +0,0 @@
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
- require "highline"
31
-
32
- require "toys/middleware/base"
33
- require "toys/utils/usage"
34
-
35
- module Toys
36
- module Middleware
37
- ##
38
- # A middleware that shows usage documentation.
39
- #
40
- # This can be configured to display usage text when a switch (typically
41
- # `--help`) is provided. It can also be configured to display usage text
42
- # automatically for tools that do not have an executor.
43
- #
44
- # If a tool has no executor, this middleware can also add a
45
- # `--[no-]recursive` flag, which, when set to `true` (the default), shows
46
- # all subcommands recursively rather than only immediate subcommands.
47
- #
48
- class ShowUsage < Base
49
- ##
50
- # Default help switches
51
- # @return [Array<String>]
52
- #
53
- DEFAULT_HELP_SWITCHES = ["-?", "--help"].freeze
54
-
55
- ##
56
- # Default recursive switches
57
- # @return [Array<String>]
58
- #
59
- DEFAULT_RECURSIVE_SWITCHES = ["-r", "--[no-]recursive"].freeze
60
-
61
- ##
62
- # Default search switches
63
- # @return [Array<String>]
64
- #
65
- DEFAULT_SEARCH_SWITCHES = ["-s WORD", "--search=WORD"].freeze
66
-
67
- ##
68
- # Create a ShowUsage middleware.
69
- #
70
- # @param [Boolean,Array<String>,Proc] help_switches Specify switches to
71
- # activate help. The value may be any of the following:
72
- # * An array of switches.
73
- # * The `true` value to use {DEFAULT_HELP_SWITCHES}. (Default)
74
- # * The `false` value for no switches.
75
- # * A proc that takes a tool and returns any of the above.
76
- # @param [Boolean,Array<String>,Proc] recursive_switches Specify switches
77
- # to control recursive subcommand search. The value may be any of the
78
- # following:
79
- # * An array of switches.
80
- # * The `true` value to use {DEFAULT_RECURSIVE_SWITCHES}. (Default)
81
- # * The `false` value for no switches.
82
- # * A proc that takes a tool and returns any of the above.
83
- # @param [Boolean,Array<String>,Proc] search_switches Specify switches
84
- # to search subcommands for a search term. The value may be any of
85
- # the following:
86
- # * An array of switches.
87
- # * The `true` value to use {DEFAULT_SEARCH_SWITCHES}. (Default)
88
- # * The `false` value for no switches.
89
- # * A proc that takes a tool and returns any of the above.
90
- # @param [Boolean] default_recursive Whether to search recursively for
91
- # subcommands by default. Default is `true`.
92
- # @param [Boolean] fallback_execution Cause the tool to display its own
93
- # usage text if it does not otherwise have an executor. This is
94
- # mostly useful for groups, which have children but no executor.
95
- # Default is `true`.
96
- #
97
- def initialize(help_switches: true,
98
- recursive_switches: true,
99
- search_switches: true,
100
- default_recursive: true,
101
- fallback_execution: true)
102
- @help_switches = help_switches
103
- @recursive_switches = recursive_switches
104
- @search_switches = search_switches
105
- @default_recursive = default_recursive ? true : false
106
- @fallback_execution = fallback_execution
107
- end
108
-
109
- ##
110
- # Configure switches and default data.
111
- #
112
- def config(tool)
113
- help_switches = Middleware.resolve_switches_spec(@help_switches, tool,
114
- DEFAULT_HELP_SWITCHES)
115
- is_default = !tool.includes_executor? && @fallback_execution
116
- if !help_switches.empty?
117
- docs = "Show help message"
118
- docs << " (default for groups)" if is_default
119
- tool.add_switch(:_help, *help_switches,
120
- docs: docs,
121
- default: is_default,
122
- only_unique: true)
123
- elsif is_default
124
- tool.default_data[:_help] = true
125
- end
126
- if !tool.includes_executor? && (!help_switches.empty? || @fallback_execution)
127
- add_recursive_switches(tool)
128
- add_search_switches(tool)
129
- end
130
- yield
131
- end
132
-
133
- ##
134
- # Display usage text if requested.
135
- #
136
- def execute(context)
137
- if context[:_help]
138
- usage = Utils::Usage.from_context(context)
139
- width = ::HighLine.new.output_cols
140
- puts(usage.string(recursive: context[:_recursive_subcommands],
141
- search: context[:_search_subcommands],
142
- show_path: context.verbosity > 0,
143
- wrap_width: width))
144
- else
145
- yield
146
- end
147
- end
148
-
149
- private
150
-
151
- def add_recursive_switches(tool)
152
- recursive_switches = Middleware.resolve_switches_spec(@recursive_switches, tool,
153
- DEFAULT_RECURSIVE_SWITCHES)
154
- unless recursive_switches.empty?
155
- tool.add_switch(:_recursive_subcommands, *recursive_switches,
156
- default: @default_recursive,
157
- docs: "Show all subcommands recursively" \
158
- " (default is #{@default_recursive})",
159
- only_unique: true)
160
- end
161
- end
162
-
163
- def add_search_switches(tool)
164
- search_switches = Middleware.resolve_switches_spec(@search_switches, tool,
165
- DEFAULT_SEARCH_SWITCHES)
166
- unless search_switches.empty?
167
- tool.add_switch(:_search_subcommands, *search_switches,
168
- docs: "Search subcommands for the given term",
169
- only_unique: true)
170
- end
171
- end
172
- end
173
- end
174
- end