toys-core 0.3.3 → 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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