toys 0.2.2 → 0.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: f036d1f723d27ed317a4aaac039147badbc21c55
4
- data.tar.gz: 72974b402f863e4f07da29411393021c80728128
2
+ SHA256:
3
+ metadata.gz: e1303b77289f7e37c45866ec22513df6187556c2d7188b97d2706731b73377c5
4
+ data.tar.gz: 31c4013f235e7fb83575c9b031798dc410ab0bdee2395303f3f54e54fc870c05
5
5
  SHA512:
6
- metadata.gz: 4d1b8ab307213db7030cceee42c910bb21fa253cdc953654a7bcb611c2cd0ec71c2509c75b5e18bc833394d08b4d4c1bf8f3e8a707c486fe16de277cc6e7a0f1
7
- data.tar.gz: 82177adaf1bd37799aaa83a031690fc8e908a0292d3ad618cb51afbc192f269e148a6a192727ed91cc95dd3e042e1660e3d47f338731f4f6db6ebc57b960d44f
6
+ metadata.gz: 8389b529719c6135483a52a65ec2521e90964be02fac0da56fa7016672da8f4f9a8f7ad24c8a0493db8558040c3c80e47553656f3a567524f4a7e310669cd05a
7
+ data.tar.gz: 708270b18a969b0adb5aa16c9b517966d82a3531a4a88604b7ee3cae27ecb67c839ef40318378c89d6a3347658c6d3fe590a6482177d0a899d96eaf0847f6366
@@ -0,0 +1,5 @@
1
+ # Release History
2
+
3
+ ### 0.3.0 / 2018-04-30
4
+
5
+ * Initial generally usable release
data/README.md CHANGED
@@ -27,6 +27,14 @@ guidelines will be provided when the software stabilizes further.
27
27
  The source can be found on Github at
28
28
  [https://github.com/dazuma/toys](https://github.com/dazuma/toys)
29
29
 
30
+ ### TODO items
31
+
32
+ * Document methods
33
+ * Write overall documentation
34
+ * Decide about highline integration
35
+ * Output formats middleware
36
+ * System paths tool
37
+
30
38
  ## License
31
39
 
32
40
  Copyright 2018 Daniel Azuma
@@ -35,21 +35,19 @@
35
35
  #
36
36
  module Toys
37
37
  ##
38
- # Namespace for common helper modules
38
+ # Namespace for common utility classes
39
39
  #
40
- module Helpers; end
41
-
42
- ##
43
- # Namespace for common templates
44
- #
45
- module Templates; end
40
+ module Utils; end
46
41
  end
47
42
 
48
43
  require "toys/builder"
49
44
  require "toys/cli"
50
45
  require "toys/context"
51
46
  require "toys/errors"
52
- require "toys/lookup"
47
+ require "toys/helpers"
48
+ require "toys/loader"
49
+ require "toys/middleware"
53
50
  require "toys/template"
51
+ require "toys/templates"
54
52
  require "toys/tool"
55
53
  require "toys/version"
@@ -32,17 +32,18 @@ module Toys
32
32
  # The object context in effect in a toys configuration file
33
33
  #
34
34
  class Builder
35
- def initialize(path, tool, remaining_words, priority, lookup)
35
+ def initialize(path, tool, remaining_words, priority, loader, type)
36
36
  @path = path
37
37
  @tool = tool
38
38
  @remaining_words = remaining_words
39
39
  @priority = priority
40
- @lookup = lookup
40
+ @loader = loader
41
+ @type = type
41
42
  end
42
43
 
43
- def name(word, alias_of: nil, &block)
44
+ def tool(word, alias_of: nil, &block)
44
45
  word = word.to_s
45
- subtool = @lookup.get_tool(@tool.full_name + [word], @priority)
46
+ subtool = @loader.get_tool(@tool.full_name + [word], @priority, assume_parent: true)
46
47
  return self if subtool.nil?
47
48
  if alias_of
48
49
  if block
@@ -51,8 +52,26 @@ module Toys
51
52
  subtool.make_alias_of_word(alias_of.to_s)
52
53
  return self
53
54
  end
54
- next_remaining = Lookup.next_remaining_words(@remaining_words, word)
55
- Builder.build(@path, subtool, next_remaining, @priority, @lookup, block)
55
+ next_remaining = Loader.next_remaining_words(@remaining_words, word)
56
+ Builder.build(@path, subtool, next_remaining, @priority, @loader, block, :tool)
57
+ self
58
+ end
59
+ alias name tool
60
+
61
+ def append(word, &block)
62
+ word = word.to_s
63
+ subtool = @loader.get_tool(@tool.full_name + [word], nil, assume_parent: true)
64
+ next_remaining = Loader.next_remaining_words(@remaining_words, word)
65
+ Builder.build(@path, subtool, next_remaining, @priority, @loader, block, :append)
66
+ self
67
+ end
68
+
69
+ def group(word, &block)
70
+ word = word.to_s
71
+ subtool = @loader.get_tool(@tool.full_name + [word], @priority, assume_parent: true)
72
+ return self if subtool.nil?
73
+ next_remaining = Loader.next_remaining_words(@remaining_words, word)
74
+ Builder.build(@path, subtool, next_remaining, @priority, @loader, block, :group)
56
75
  self
57
76
  end
58
77
 
@@ -60,34 +79,40 @@ module Toys
60
79
  if @tool.root?
61
80
  raise ToolDefinitionError, "Cannot make an alias of the root tool"
62
81
  end
82
+ if @type == :group || @type == :append
83
+ raise ToolDefinitionError, "Cannot make an alias of a group"
84
+ end
63
85
  alias_name = @tool.full_name.slice(0..-2) + [word.to_s]
64
- alias_tool = @lookup.get_tool(alias_name, @priority)
86
+ alias_tool = @loader.get_tool(alias_name, @priority)
65
87
  alias_tool.make_alias_of(@tool.simple_name) if alias_tool
66
88
  self
67
89
  end
68
90
 
69
91
  def alias_of(word)
92
+ if @tool.root?
93
+ raise ToolDefinitionError, "Cannot make the root tool an alias"
94
+ end
95
+ if @type == :group || @type == :append
96
+ raise ToolDefinitionError, "Cannot make a group an alias"
97
+ end
70
98
  @tool.make_alias_of(word.to_s)
71
99
  self
72
100
  end
73
101
 
74
102
  def include(path)
75
103
  @tool.yield_definition do
76
- @lookup.include_path(path, @tool.full_name, @remaining_words, @priority)
104
+ @loader.include_path(path, @tool.full_name, @remaining_words, @priority)
77
105
  end
78
106
  self
79
107
  end
80
108
 
81
109
  def expand(template_class, *args)
82
110
  unless template_class.is_a?(::Class)
83
- template_class = template_class.to_s
84
- file_name =
85
- template_class
86
- .gsub(/([a-zA-Z])([A-Z])/) { |_m| "#{$1}_#{$2.downcase}" }
87
- .downcase
88
- require "toys/templates/#{file_name}"
89
- const_name = template_class.gsub(/(^|_)([a-zA-Z0-9])/) { |_m| $2.upcase }
90
- template_class = Templates.const_get(const_name)
111
+ name = template_class.to_s
112
+ template_class = Templates.lookup(name)
113
+ if template_class.nil?
114
+ raise ToolDefinitionError, "Template not found: #{name.inspect}"
115
+ end
91
116
  end
92
117
  template = template_class.new(*args)
93
118
  yield template if block_given?
@@ -96,46 +121,77 @@ module Toys
96
121
  end
97
122
 
98
123
  def long_desc(desc)
124
+ if @type == :append
125
+ raise ToolDefinitionError, "Cannot set the description when appending"
126
+ end
99
127
  @tool.long_desc = desc
100
128
  self
101
129
  end
102
130
 
103
- def short_desc(desc)
104
- @tool.short_desc = desc
131
+ def desc(desc)
132
+ if @type == :append
133
+ raise ToolDefinitionError, "Cannot set the description when appending"
134
+ end
135
+ @tool.desc = desc
105
136
  self
106
137
  end
138
+ alias short_desc desc
107
139
 
108
- def switch(key, *switches, accept: nil, default: nil, doc: nil)
109
- @tool.add_switch(key, *switches, accept: accept, default: default, doc: doc)
140
+ def switch(key, *switches,
141
+ accept: nil, default: nil, doc: nil, only_unique: false, handler: nil)
142
+ if @type == :append
143
+ raise ToolDefinitionError, "Cannot add a switch when appending"
144
+ end
145
+ @tool.add_switch(key, *switches,
146
+ accept: accept, default: default, doc: doc,
147
+ only_unique: only_unique, handler: handler)
110
148
  self
111
149
  end
112
150
 
113
151
  def required_arg(key, accept: nil, doc: nil)
152
+ if @type == :append
153
+ raise ToolDefinitionError, "Cannot add an argument when appending"
154
+ end
114
155
  @tool.add_required_arg(key, accept: accept, doc: doc)
115
156
  self
116
157
  end
117
158
 
118
159
  def optional_arg(key, accept: nil, default: nil, doc: nil)
160
+ if @type == :append
161
+ raise ToolDefinitionError, "Cannot add an argument when appending"
162
+ end
119
163
  @tool.add_optional_arg(key, accept: accept, default: default, doc: doc)
120
164
  self
121
165
  end
122
166
 
123
167
  def remaining_args(key, accept: nil, default: [], doc: nil)
168
+ if @type == :append
169
+ raise ToolDefinitionError, "Cannot add an argument when appending"
170
+ end
124
171
  @tool.set_remaining_args(key, accept: accept, default: default, doc: doc)
125
172
  self
126
173
  end
127
174
 
128
175
  def execute(&block)
176
+ if @type == :group || @type == :append
177
+ raise ToolDefinitionError, "Cannot set the executor of a group"
178
+ end
129
179
  @tool.executor = block
130
180
  self
131
181
  end
132
182
 
133
183
  def helper(name, &block)
184
+ if @type == :group || @type == :append
185
+ raise ToolDefinitionError, "Cannot define a helper method to a group"
186
+ end
134
187
  @tool.add_helper(name, &block)
135
188
  self
136
189
  end
137
190
 
138
191
  def use(mod)
192
+ if @type == :group || @type == :append
193
+ raise ToolDefinitionError, "Cannot use a helper module in a group"
194
+ end
139
195
  @tool.use_module(mod)
140
196
  self
141
197
  end
@@ -144,19 +200,28 @@ module Toys
144
200
  binding
145
201
  end
146
202
 
147
- def self.build(path, tool, remaining_words, priority, lookup, source)
148
- builder = new(path, tool, remaining_words, priority, lookup)
149
- tool.defining_from(path) do
150
- case source
151
- when String
152
- # rubocop:disable Security/Eval
153
- eval(source, builder._binding, path, 1)
154
- # rubocop:enable Security/Eval
155
- when ::Proc
156
- builder.instance_eval(&source)
203
+ def self.build(path, tool, remaining_words, priority, loader, source, type)
204
+ builder = new(path, tool, remaining_words, priority, loader, type)
205
+ if type == :append
206
+ eval_source(builder, path, source)
207
+ else
208
+ tool.defining_from(path) do
209
+ eval_source(builder, path, source)
210
+ tool.finish_definition
157
211
  end
158
212
  end
159
213
  tool
160
214
  end
215
+
216
+ def self.eval_source(builder, path, source)
217
+ case source
218
+ when String
219
+ # rubocop:disable Security/Eval
220
+ eval(source, builder._binding, path, 1)
221
+ # rubocop:enable Security/Eval
222
+ when ::Proc
223
+ builder.instance_eval(&source)
224
+ end
225
+ end
161
226
  end
162
227
  end
@@ -0,0 +1,44 @@
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
+ desc "Run multiple tools in order"
31
+
32
+ switch(:delim, "-d", "--delim=VALUE", default: ",", doc: "Set the delimiter")
33
+
34
+ remaining_args(:args)
35
+
36
+ execute do
37
+ delim = self[:delim]
38
+ self[:args]
39
+ .chunk { |arg| arg == delim ? :_separator : true }
40
+ .each do |_, action|
41
+ code = run(action)
42
+ exit(code) unless code.zero?
43
+ end
44
+ end
@@ -27,18 +27,18 @@
27
27
  # POSSIBILITY OF SUCH DAMAGE.
28
28
  ;
29
29
 
30
- short_desc "A collection of system commands for toys"
30
+ desc "A group of system commands for toys"
31
31
 
32
- name "version" do
33
- short_desc "Print current toys version"
32
+ tool "version" do
33
+ desc "Print current toys version."
34
34
 
35
35
  execute do
36
36
  puts ::Toys::VERSION
37
37
  end
38
38
  end
39
39
 
40
- name "update" do
41
- short_desc "Update toys if a newer version is available"
40
+ tool "update" do
41
+ desc "Update toys if a newer version is available."
42
42
 
43
43
  use :exec
44
44
 
@@ -64,34 +64,50 @@ module Toys
64
64
  #
65
65
  DEFAULT_BINARY_NAME = "toys".freeze
66
66
 
67
+ ##
68
+ # Default help text for the root tool
69
+ # @return [String]
70
+ #
71
+ DEFAULT_ROOT_DESC =
72
+ "Toys is your personal command line tool. You can add to the list of" \
73
+ " commands below by writing scripts in Ruby using a simple DSL, and" \
74
+ " toys will organize and document them, and make them available" \
75
+ " globally or scoped to specific directories that you choose." \
76
+ " For detailed information, see https://www.rubydoc.info/gems/toys".freeze
77
+
67
78
  def initialize(
68
79
  binary_name: nil,
69
80
  logger: nil,
70
81
  config_dir_name: nil,
71
82
  config_file_name: nil,
72
83
  index_file_name: nil,
73
- preload_file_name: nil
84
+ preload_file_name: nil,
85
+ middleware: [],
86
+ root_desc: nil
74
87
  )
75
- @lookup = Lookup.new(
88
+ logger ||= self.class.default_logger
89
+ @loader = Loader.new(
76
90
  config_dir_name: config_dir_name,
77
91
  config_file_name: config_file_name,
78
92
  index_file_name: index_file_name,
79
- preload_file_name: preload_file_name
93
+ preload_file_name: preload_file_name,
94
+ middleware: middleware,
95
+ root_desc: root_desc
80
96
  )
81
- @context_base = Context::Base.new(@lookup, binary_name, logger || self.class.default_logger)
97
+ @context_base = Context::Base.new(@loader, binary_name, logger)
82
98
  end
83
99
 
84
- def add_paths(paths)
85
- @lookup.add_paths(paths)
100
+ def add_config_paths(paths)
101
+ @loader.add_config_paths(paths)
86
102
  self
87
103
  end
88
104
 
89
- def add_config_paths(paths)
90
- @lookup.add_config_paths(paths)
105
+ def add_paths(paths)
106
+ @loader.add_paths(paths)
91
107
  self
92
108
  end
93
109
 
94
- def add_config_path_hierarchy(path = nil, base = "/")
110
+ def add_path_hierarchy(path = nil, base = "/")
95
111
  path ||= ::Dir.pwd
96
112
  paths = []
97
113
  loop do
@@ -101,21 +117,22 @@ module Toys
101
117
  break if next_path == path
102
118
  path = next_path
103
119
  end
104
- @lookup.add_config_paths(paths)
120
+ @loader.add_paths(paths)
105
121
  self
106
122
  end
107
123
 
108
- def add_standard_config_paths
124
+ def add_standard_paths
109
125
  toys_path = ::ENV["TOYS_PATH"].to_s.split(::File::PATH_SEPARATOR)
110
126
  if toys_path.empty?
111
127
  toys_path << ::ENV["HOME"] if ::ENV["HOME"]
112
- toys_path << "/etc" if File.directory?("/etc") && ::File.readable?("/etc")
128
+ toys_path << "/etc" if ::File.directory?("/etc") && ::File.readable?("/etc")
113
129
  end
114
- @lookup.add_config_paths(toys_path)
130
+ @loader.add_paths(toys_path)
131
+ self
115
132
  end
116
133
 
117
134
  def run(*args)
118
- @context_base.run(*args)
135
+ exit(@context_base.run(args.flatten, verbosity: 0))
119
136
  end
120
137
 
121
138
  class << self
@@ -125,14 +142,25 @@ module Toys
125
142
  config_dir_name: DEFAULT_DIR_NAME,
126
143
  config_file_name: DEFAULT_FILE_NAME,
127
144
  index_file_name: DEFAULT_FILE_NAME,
128
- preload_file_name: DEFAULT_PRELOAD_NAME
145
+ preload_file_name: DEFAULT_PRELOAD_NAME,
146
+ middleware: default_middleware_stack,
147
+ root_desc: DEFAULT_ROOT_DESC
129
148
  )
130
- cli.add_config_path_hierarchy
131
- cli.add_standard_config_paths
132
- cli.add_paths(BUILTINS_PATH)
149
+ cli.add_path_hierarchy
150
+ cli.add_standard_paths
151
+ cli.add_config_paths(BUILTINS_PATH)
133
152
  cli
134
153
  end
135
154
 
155
+ def default_middleware_stack
156
+ [
157
+ Middleware.lookup(:show_usage_errors).new,
158
+ Middleware.lookup(:group_default).new,
159
+ Middleware.lookup(:show_tool_help).new,
160
+ Middleware.lookup(:set_verbosity).new
161
+ ]
162
+ end
163
+
136
164
  def default_logger
137
165
  logger = ::Logger.new(::STDERR)
138
166
  logger.formatter = proc do |severity, time, _progname, msg|