toys 0.2.2 → 0.3.0

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