run_tasks 3.3.0 → 4.0.1

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
2
  SHA256:
3
- metadata.gz: db5998bcac4e11397fd0abb44484d48dc78ce538cd31a5768ddadf4f23a3a6ae
4
- data.tar.gz: 8fc911ccdf1f057f3fb8c310185fdd3021eb293710b05b5cc0ce4bf16bd98c6c
3
+ metadata.gz: 89b89a1b7418844bef966d37027b641a162cbcd4023a2e932a1848ccf3df69bc
4
+ data.tar.gz: d691b13bf7a3eab7ebc5bbbb9f5155c88686696667c8a305aa4ca9ce6cbd2c79
5
5
  SHA512:
6
- metadata.gz: 5387ed714265c8705665e5012d1186fc148c87e85d5a6441e66edabc02515eb58c62c03ba71d4451b647d2a5c02f0fbc7ac46a1d8c414734eb2803eb39222559
7
- data.tar.gz: 42ace3eb70928ae2d509cbfa8b26f3696ba55f6cf2a797280957c98e0b7704cafefce72d2e375cc570360b896cc9d1905947d0bd8e8ec8892f7f5297e4067d66
6
+ metadata.gz: 780a926350e17a09469794beb7e0fb8959df42ee893dd209436f1b85aefe2aca749a235060dc16764da502f0007b415421fadfa2294c8e66cc855b7eaf1f2a4d
7
+ data.tar.gz: 1ee1f1e74ae13f7962184f1e7134621fefb298068c89121af734bc7f74788b243c20f2554732491366635aea67e73d573f6f2c8ce96f572964d6d682a6aa1868
data/README.md ADDED
@@ -0,0 +1,484 @@
1
+ # Run
2
+
3
+ Manage your Ruby projects with a straightforward syntax.
4
+
5
+ ## Compatibility
6
+
7
+ Run is known to work with Ruby >=2.4.
8
+
9
+ ## Installation
10
+
11
+ ```sh
12
+ gem install "run_tasks"
13
+ ```
14
+
15
+ To enable ZSH completions:
16
+
17
+ ```sh
18
+ run install_completions
19
+ ```
20
+
21
+ ## Running Run
22
+
23
+ ```sh
24
+ run your_task
25
+ ```
26
+
27
+ If you want to display the available tasks:
28
+
29
+ ```sh
30
+ run help
31
+ ```
32
+
33
+ > The help provides Run's version, so `run version` will display the same screen.
34
+
35
+ > Note that the command `run` displays the help screen by default when no task is specified.
36
+
37
+ ## Runfile version
38
+
39
+ The first line of a Runfile declares the major version of Run it is written for:
40
+
41
+ ```rb
42
+ version 4
43
+ ```
44
+
45
+ When a Runfile declares another major version, Run hands over to the matching version if it is installed, so projects written for Run 3 and Run 4 can live side by side. A Runfile without this line is a version 2 one.
46
+
47
+ ## Writing tasks
48
+
49
+ ```rb
50
+ # Displays hello.
51
+ task :hello do
52
+ puts "hello!"
53
+ end
54
+ ```
55
+
56
+ The comment is used by the Run to display the help. It must be placed directly above the task and can be multiline.
57
+
58
+ > Note that help verbatims support Markdown syntax.
59
+
60
+ `task` also support aliases, so you can call a task with different names:
61
+
62
+ ```rb
63
+ task [:console, :c] do
64
+ run "bundle exec rails c"
65
+ end
66
+ ```
67
+
68
+ Running `run c` or `run console` will then start the Rails console.
69
+
70
+ ## Running shell commands
71
+
72
+ ```rb
73
+ # Accessing a non-existent file.
74
+ task :boom do
75
+ run "stat foo"
76
+ run "echo 'hello!'"
77
+ end
78
+ ```
79
+
80
+ It outputs:
81
+
82
+ ```sh
83
+ $ run boom
84
+
85
+ > stat foo
86
+
87
+ stat: cannot stat 'foo': No such file or directory
88
+ ```
89
+
90
+ > As you can see, `run` captures errors and stops when something bad happens.
91
+
92
+ ## Calling other tasks
93
+
94
+ You can run tasks arbitrarily from any other task by passing a symbol:
95
+
96
+ ```rb
97
+ task :eslint do
98
+ run "npx eslint"
99
+ end
100
+
101
+ task :flow do
102
+ run "npx flow"
103
+ end
104
+
105
+ task :lint_js do
106
+ run :eslint
107
+ run :flow
108
+ end
109
+ ```
110
+
111
+ By running `run lint_js` you will call `eslint` and `flow` tasks.
112
+
113
+ ## Passing arguments
114
+
115
+ You can pass arguments to tasks from the CLI:
116
+
117
+ ```rb
118
+ task :hello do |name, age|
119
+ puts "Hello #{name}, you are #{age}."
120
+ end
121
+ ```
122
+
123
+ ```sh
124
+ $ run hello 'John Doe' 30
125
+ Hello John Doe, you are 30.
126
+ ```
127
+
128
+ But also from the Runfile itself:
129
+
130
+ ```rb
131
+ task :test do
132
+ run :hello, "John Doe", 30
133
+ end
134
+ ```
135
+
136
+ Every argument passed from the CLI is auto-converted, as a whole:
137
+
138
+ | Argument | Result |
139
+ |-----------------------------------------------------------|-----------|
140
+ | an integer: `42`, `-3`, `0` | `Integer` |
141
+ | a decimal: `1.5`, `0.5`, `-0.5` | `Float` |
142
+ | `true` / `false` | boolean |
143
+ | anything else, including superfluous zeros: `007`, `00.5` | `String` |
144
+
145
+ > Arguments never become symbols: if your task needs one, call `.to_sym`.
146
+
147
+ ### Named parameters
148
+
149
+ Tasks also support named parameters, from the Runfile:
150
+
151
+ ```rb
152
+ task :hello do |name:, age:|
153
+ puts "Hello #{name}, you are #{age}."
154
+ end
155
+
156
+ task :test do
157
+ run :hello, name: "John Doe", age: 30
158
+ end
159
+ ```
160
+
161
+ And from the CLI, with `name=value`:
162
+
163
+ ```sh
164
+ $ run hello name='John Doe' age=30
165
+ Hello John Doe, you are 30.
166
+ ```
167
+
168
+ The value is auto-converted like any other argument. Separate values with commas to pass an array, each element being auto-converted:
169
+
170
+ | Command | Received by the task |
171
+ |----------------------------------------|-------------------------------------|
172
+ | `run review reviewer=clement` | `reviewer: "clement"` |
173
+ | `run review reviewer=clement,morisset` | `reviewer: ["clement", "morisset"]` |
174
+ | `run build retries=3` | `retries: 3` |
175
+ | `run build ids=1,2,x` | `ids: [1, 2, "x"]` |
176
+ | `run build dry=true` | `dry: true` |
177
+ | `run build tag=` | `tag: ""` |
178
+
179
+ > A single value gives a scalar, not an array: if your task expects a list, use `Array(value)`. Commas cannot be escaped.
180
+
181
+ ### Named booleans
182
+
183
+ We also support named booleans. For example, imagine you want to run your server in tunnel mode:
184
+
185
+ ```rb
186
+ task :server do |tunnel: false|
187
+ run "my_server #{tunnel ? "--tunnel" : ""}"
188
+ end
189
+ ```
190
+
191
+ When you call...
192
+
193
+ ```sh
194
+ run server +tunnel
195
+ ```
196
+
197
+ ...the parameter `tunnel: true` will be passed to your task. The same if you pass `-tunnel`, it will pass `false`.
198
+
199
+ ### Additional notes
200
+
201
+ - Hyphens in names are replaced by underscores: `+dry-run` and `dry-run=1` give `dry_run:`.
202
+ - A name cannot be given twice (`a=1 a=2`, `+a -a`, `+a a=2`).
203
+ - A task receiving named parameters or booleans must declare keywords (`reviewer:`, `**options`…). A task declaring keywords and receiving an unknown one fails too, as with any Ruby block.
204
+
205
+ ## Quiet mode
206
+
207
+ If you want to run a task (or a command) without it to being able to write on STDOUT/STDERR, pass the quiet flag to true :
208
+
209
+ ```rb
210
+ task :echo do
211
+ run "echo 'test'", quiet: true
212
+ end
213
+ ```
214
+
215
+ It's useful, for example, when you want to delete a file at the beginning of a task without it to being verbose when the file does not exist.
216
+
217
+ ## Colorization & styles
218
+
219
+ It is often needed to colorize/stylize the messages sent to the user with `puts`. For this matter, Run is natively shipped with basic styles.
220
+
221
+ For example:
222
+
223
+ ```rb
224
+ puts "Please wait...".yellow.bold
225
+ ```
226
+
227
+ The available styles are :
228
+
229
+ - bold
230
+ - dim
231
+ - italic
232
+ - underline
233
+ - inverse
234
+ - strikethrough
235
+
236
+ And the available colors are :
237
+
238
+ - black
239
+ - red
240
+ - green
241
+ - yellow
242
+ - blue
243
+ - magenta
244
+ - cyan
245
+ - white
246
+ - bright_black
247
+ - bright_red
248
+ - bright_green
249
+ - bright_yellow
250
+ - bright_blue
251
+ - bright_magenta
252
+ - bright_cyan
253
+ - bright_white
254
+
255
+ ## Helpers
256
+
257
+ Run comes with a few helpers to help writing tasks quicker.
258
+
259
+ ### are_you_sure
260
+
261
+ When called, it asks a question to the user which has the choice to enter `y`, `yes`, `n` or `no` (the default answer). When `no` is selected, the task will exit with a `9` code.
262
+
263
+ ```rb
264
+ task :dangerous_task do
265
+ are_you_sure "This task will delete your hard drive. Are you sure about that?"
266
+ # Some dangerous actions.
267
+ end
268
+ ```
269
+
270
+ ### bind
271
+
272
+ `bind` lets you group several tasks/processes so when one stops all the other will stop as well.
273
+
274
+ ```rb
275
+ task :dev do
276
+ bind :rails, :vite
277
+ end
278
+
279
+ task :rails do
280
+ run "bundle exec unicorn -c config/unicorn.rb -p 3000"
281
+ end
282
+
283
+ task :vite do
284
+ run "bin/vite dev"
285
+ end
286
+ ```
287
+
288
+ ### catch_interruption
289
+
290
+ Alternatively, if you have a command that stays open until you hit `CTRL+C` (like a Docker container) and you need to catch the binding, you probably want to use `catch_interruption` instead so it lets the command handle the interruption gracefully and executes some actions afterwards.
291
+
292
+ ```rb
293
+ # Run server in development mode.
294
+ task :server do
295
+ catch_interruption("docker run -d -p 8080:80 web_server") do
296
+ # Run some clean up actions here after the command has been interrupted.
297
+ end
298
+ end
299
+ ```
300
+
301
+ ### exists
302
+
303
+ Handy helper to verify if a program exists.
304
+
305
+ ```rb
306
+ task :man do
307
+ command = exists("bat") ? "bat" : "cat"
308
+ run "#{command} #{__dir__}/README.md"
309
+ end
310
+ ```
311
+
312
+ ### expand
313
+
314
+ This one takes a glob string and process it to return a list of files.
315
+
316
+ ```rb
317
+ task :scan do |glob|
318
+ expand(glob).each do |file_path|
319
+ # Do something.
320
+ end
321
+ end
322
+ ```
323
+
324
+ ### menu
325
+
326
+ It displays an advanced menu where you can choose an element that will be returned by the function.
327
+
328
+ ```rb
329
+ # Array syntax
330
+ task :deploy_aws do
331
+ region = menu "Location?", ["us‑east‑2", "us-west-1", "eu-west-1"]
332
+ end
333
+ ```
334
+
335
+ ```rb
336
+ # Hash syntax
337
+ task :deploy_aws do
338
+ region = menu "Location?", {
339
+ "US East (Ohio)" => "us‑east‑2",
340
+ "US West (N. California)" => "us-west-1",
341
+ "Europe (Ireland)" => "eu-west-1",
342
+ }
343
+ end
344
+ ```
345
+
346
+ ### pause
347
+
348
+ You can pause a task if needed, waiting for the user to press any key.
349
+
350
+ ```rb
351
+ task :deploy do
352
+ puts "Press enter to continue.".yellow
353
+ pause
354
+ run :deploy_production
355
+ end
356
+ ```
357
+
358
+ ### question
359
+
360
+ Displays a question and returns the answer.
361
+
362
+ ```rb
363
+ task :survey do
364
+ comment = question "Did your meal was good?"
365
+ end
366
+ ```
367
+
368
+ You can also pass a regex to validation the entered value:
369
+
370
+ ```rb
371
+ task :age do
372
+ age = question "What is your age?", /^\d+$/
373
+ end
374
+ ```
375
+
376
+ A default value can be set, it will be returned if the user enters nothing:
377
+
378
+ ```rb
379
+ task :age do
380
+ age = question "What is your age?", /^\d+$/, default: "30"
381
+ end
382
+ ```
383
+
384
+ ### yesno
385
+
386
+ Asks a yes/no question and returns a boolean. The user can answer `y`, `yes`, `n` or `no`.
387
+
388
+ ```rb
389
+ task :deploy do
390
+ run :migrate if yesno "Run migrations?"
391
+ end
392
+ ```
393
+
394
+ Without a default value, an answer is required. With one, it is returned if the user enters nothing:
395
+
396
+ ```rb
397
+ task :deploy do
398
+ run :migrate if yesno "Run migrations?", default: true
399
+ end
400
+ ```
401
+
402
+ ### wait_for_interruption
403
+
404
+ If you need to wait for the user to interrupt a task of, for example, several running servers, you can use the `wait_for_interruption` helper.
405
+
406
+ ```rb
407
+ # Run server in development mode.
408
+ task :server do
409
+ Thread.new do
410
+ # Run first server.
411
+ end
412
+ Thread.new do
413
+ # Run second server.
414
+ end
415
+ # Wait for CTRL+C.
416
+ wait_for_interruption do
417
+ # Some actions to handle before terminating the task, like killing the servers.
418
+ end
419
+ end
420
+ ```
421
+
422
+ ## Base tasks
423
+
424
+ Run also comes with some generic tasks for recurring tasks.
425
+
426
+ ### publish
427
+
428
+ You can publish your library with:
429
+
430
+ ```sh
431
+ run publish
432
+ ```
433
+
434
+ > At the moment, this is only compatible with gems.
435
+
436
+ ### rspec
437
+
438
+ You can call directly `run rspec` or abstract the task like this:
439
+
440
+ ```rb
441
+ # Run the tests.
442
+ task [:specs, :s, :tests, :t] do |path = "spec/src"|
443
+ run :rspec, path
444
+ end
445
+ ```
446
+
447
+ > The `rspec` task will look into `spec/src` by default, so the above task is just explanatory is you want overload default behaviour.
448
+
449
+ ## Advanced usage
450
+
451
+ ### Specifying Runfile path
452
+
453
+ If you want to load Run from another directory, you have two choices :
454
+
455
+ - change the working directory in your script,
456
+ - or pass the Runfile path with `RUNFILE=/path/to/Runfile.rb run my_task`
457
+
458
+ ### Exit codes
459
+
460
+ Run has specific exit codes so you can handle it better in some environments, like in CI or in deployments:
461
+
462
+ - 1: Run has exited abnormally.
463
+ - 2: The required task does not exist.
464
+ - 3: Unhandled Interrupt error coming from a failed shell command.
465
+ - 4: An error has been raised in the Runfile.
466
+ - 5: The Runfile contains a syntax error.
467
+ - 6: Some parameters are invalid.
468
+ - 7: Runfile not found.
469
+ - 8: Unable to load a remote file.
470
+ - 9: The user has answered "No" to an "Are you sure?" question.
471
+ - 10: The required task already exists.
472
+ - 11: The required command is not running.
473
+ - 12: The required task does not exist.
474
+
475
+ ## Development
476
+
477
+ For test purposes, it's often needed to install Run from a development branch. For this, you'll need to follow these steps:
478
+
479
+ ```sh
480
+ gem install specific_install
481
+ gem specific_install http://github.com/pyrsmk/run.git <branch>
482
+ ```
483
+
484
+ Do not forget to uninstall other `run_tasks` packages if needed, to avoid conflicts. And if you're under Rbenv, you'll probably need to do `rbenv rehash` too.
data/src/bootstrap.rb CHANGED
@@ -7,7 +7,6 @@ require "readline"
7
7
  require "rubygems"
8
8
  require "securerandom"
9
9
  require "rb_monkey"
10
- require "rb_gemspec"
11
10
  require "rb_markdown"
12
11
 
13
12
  # Require all files.
@@ -77,11 +76,6 @@ task :publish do
77
76
  `rm #{gemspec.name}-#{gemspec.version}.gem`
78
77
  end
79
78
 
80
- # Display the manual.
81
- task :man do
82
- puts Markdown::Engine.new(File.read("#{__dir__}/../CHEATSHEET.md")).to_ansi
83
- end
84
-
85
79
  # Expose helpers.
86
80
  Dir.glob(File.join(__dir__, "run", "helper", "*.rb")) do |path|
87
81
  filename = File.basename(path, ".rb")
@@ -117,6 +111,9 @@ rescue Interrupt
117
111
  rescue SyntaxError => error
118
112
  format_error error
119
113
  exit 5
114
+ rescue Run::Error::DuplicateParameter, Run::Error::UnexpectedNamedParameters => error
115
+ puts error.message.red
116
+ exit 6
120
117
  rescue ArgumentError => error
121
118
  format_error error
122
119
  exit 6
data/src/run/core.rb CHANGED
@@ -4,6 +4,10 @@ module Run
4
4
  module Core
5
5
  RUNFILE_FILENAME = ENV.fetch('RUNFILE', 'Runfile.rb')
6
6
  RESERVED_TASK_NAMES = ["help", "version"]
7
+ NAME_PATTERN = /[a-zA-Z_]\w*(?:-\w+)*/
8
+ OPTION_PATTERN = /\A([+-])(#{NAME_PATTERN})\z/
9
+ NAMED_PARAMETER_PATTERN = /\A(#{NAME_PATTERN})=(.*)\z/m
10
+ RUNFILE_VERSION_PATTERN = /^\s*version\s*\(?\s*(\d+)/
7
11
  @@tasks = []
8
12
  @@runfile_version = nil
9
13
 
@@ -15,6 +19,7 @@ module Run
15
19
 
16
20
  # @return [void]
17
21
  def self.run_run
22
+ delegate_to_runfile_version! if File.file?(RUNFILE_FILENAME)
18
23
  if ARGV.size == 1 && ARGV[0] == "--completions"
19
24
  require File.expand_path(RUNFILE_FILENAME) if File.exist?(RUNFILE_FILENAME)
20
25
  puts completions.join("\n")
@@ -22,7 +27,7 @@ module Run
22
27
  end
23
28
  raise Run::Error::NonExistingRunfile.new if !File.file?(RUNFILE_FILENAME)
24
29
  if ARGV.size == 1 && ARGV[0] == "version"
25
- puts version
30
+ puts VERSION
26
31
  elsif ARGV.size == 0 || (ARGV.size == 1 && ARGV[0] == "help")
27
32
  require File.expand_path(RUNFILE_FILENAME)
28
33
  check_runfile_version!
@@ -69,18 +74,36 @@ module Run
69
74
  }
70
75
  end
71
76
 
77
+ # @param contents [String]
78
+ # @return [Integer]
79
+ def self.declared_runfile_version(contents)
80
+ contents[RUNFILE_VERSION_PATTERN, 1]&.to_i || 2
81
+ end
82
+
83
+ # @param major [Integer]
84
+ # @return [Gem::Version, nil]
85
+ def self.installed_version_for(major)
86
+ Gem::Specification.find_all_by_name("run_tasks", "~> #{major}.0").map(&:version).max
87
+ end
88
+
72
89
  private
73
90
 
74
91
  # @return [void]
75
- def self.check_runfile_version!
76
- if (@@runfile_version || 2) != version.to_s.split(".").first.to_i
77
- raise Run::Error::RunfileVersionMismatch.new
78
- end
92
+ def self.delegate_to_runfile_version!
93
+ major = declared_runfile_version(File.read(RUNFILE_FILENAME))
94
+ return if major == VERSION.split(".").first.to_i
95
+
96
+ version = installed_version_for(major)
97
+ raise Run::Error::RunfileVersionMismatch.new(major) if !version
98
+
99
+ exec Gem.ruby, File.join(Gem.bindir, "run"), "_#{version}_", *ARGV
79
100
  end
80
101
 
81
102
  # @return [void]
82
- def self.display_version
83
- puts Gemspec::Metadata.new("run_tasks").read.version
103
+ def self.check_runfile_version!
104
+ if (@@runfile_version || 2) != VERSION.split(".").first.to_i
105
+ raise Run::Error::RunfileVersionMismatch.new
106
+ end
84
107
  end
85
108
 
86
109
  # @return [void]
@@ -91,19 +114,13 @@ module Run
91
114
  # @return [void]
92
115
  def self.display_help
93
116
  puts
94
- puts " Run v#{version}".bright_blue
117
+ puts " Run v#{VERSION}".bright_blue
95
118
  contents = Run::Core::Help.run(File.read(File.join(Dir.pwd, RUNFILE_FILENAME)))
96
119
  if contents.strip.size > 0
97
- puts
98
- puts " Project tasks:".magenta
99
120
  puts
100
121
  puts contents
101
122
  end
102
123
  puts
103
- puts " Global tasks:".magenta
104
- puts
105
- puts Run::Core::Help.run(File.read(File.join(__dir__, "..", "bootstrap.rb")))
106
- puts
107
124
  end
108
125
 
109
126
  # @return [void]
@@ -111,32 +128,60 @@ module Run
111
128
  name = ARGV[0].gsub('-', '_').to_sym # Auto-replace hyphens to underscores.
112
129
  raise Run::Error::UnknownTask.new(name) if !task_exist?(name)
113
130
 
131
+ args, options = parse_arguments(ARGV.drop(1))
132
+ run name, *args, **options
133
+ end
134
+
135
+ # @param words [Array<String>]
136
+ # @return [Array(Array, Hash)]
137
+ def self.parse_arguments(words)
114
138
  args = []
115
139
  options = {}
116
140
 
117
- ARGV.slice(1, ARGV.size - 1).each do |arg|
118
- if (match = arg.match(/^\+([a-zA-Z_]\w*)$/))
119
- options[match[1].to_sym] = true
120
- next
121
- elsif (match = arg.match(/^-([a-zA-Z_]\w*)$/))
122
- options[match[1].to_sym] = false
123
- next
124
- elsif arg.match?(/^-?(?:0|[1-9]\d*)$/)
125
- args << Integer(arg, 10)
126
- next
127
- elsif arg.match?(/^-?\d+\.\d+$/)
128
- args << Float(arg)
129
- next
141
+ words.each do |word|
142
+ if (match = word.match(OPTION_PATTERN))
143
+ sign, name = match.captures
144
+ add_option(options, name, sign == "+")
145
+ elsif (match = word.match(NAMED_PARAMETER_PATTERN))
146
+ name, value = match.captures
147
+ add_option(options, name, convert_named_value(value))
148
+ else
149
+ args << convert_argument(word)
130
150
  end
131
- raise
132
- rescue
133
- next args << true if arg == "true"
134
- next args << false if arg == "false"
135
- next args << arg.to_sym if arg.match?(/^\w+$/)
136
- args << arg
137
151
  end
138
152
 
139
- run name, *args, **options
153
+ [args, options]
154
+ end
155
+
156
+ # @param value [String]
157
+ # @return [Integer, Float, Boolean, String]
158
+ def self.convert_argument(value)
159
+ case value
160
+ when /\A-?(?:0|[1-9]\d*)\z/ then Integer(value, 10)
161
+ when /\A-?(?:0|[1-9]\d*)\.\d+\z/ then Float(value)
162
+ when "true" then true
163
+ when "false" then false
164
+ else value
165
+ end
166
+ end
167
+
168
+ # @param value [String]
169
+ # @return [Object, Array]
170
+ def self.convert_named_value(value)
171
+ return convert_argument(value) if !value.include?(",")
172
+
173
+ value.split(",", -1).map { |item| convert_argument(item) }
174
+ end
175
+
176
+ # @param options [Hash]
177
+ # @param name [String]
178
+ # @param value [Object]
179
+ # @return [void]
180
+ def self.add_option(options, name, value)
181
+ key = name.tr("-", "_").to_sym
182
+ raise Run::Error::DuplicateParameter.new(key) if options.key?(key)
183
+
184
+ options[key] = value
140
185
  end
141
186
 
142
187
  # @param name [Symbol]
@@ -152,6 +197,9 @@ module Run
152
197
  def self.run_block_task(name, *args, **options)
153
198
  task = @@tasks.find{ |item| item[:names].include? name }
154
199
  raise Error::NonExistingTask.new(name) if task.nil?
200
+ if options.any? && !task[:task].accepts_keywords?
201
+ raise Error::UnexpectedNamedParameters.new(name, options.keys)
202
+ end
155
203
 
156
204
  task[:task].run(*args, **options)
157
205
  end
@@ -162,11 +210,6 @@ module Run
162
210
  Run::Task::SystemTask.new(command).run
163
211
  end
164
212
 
165
- # @return [String]
166
- def self.version
167
- Gemspec::Metadata.new("run_tasks").read.version
168
- end
169
-
170
213
  # @return [Boolean]
171
214
  def self.quiet?
172
215
  !!Thread.current[:quiet]
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Run
4
+ module Error
5
+ class DuplicateParameter < StandardError
6
+ # @param name [Symbol]
7
+ def initialize(name)
8
+ super "Parameter '#{name}' is given more than once"
9
+ end
10
+ end
11
+ end
12
+ end
@@ -3,8 +3,13 @@
3
3
  module Run
4
4
  module Error
5
5
  class RunfileVersionMismatch < StandardError
6
- def initialize
7
- super "Runfile version mismatches Run version"
6
+ # @param major [Integer, nil]
7
+ def initialize(major = nil)
8
+ if major
9
+ super "Runfile declares version #{major}, which needs Run #{major}: gem install run_tasks -v '~> #{major}.0'"
10
+ else
11
+ super "Runfile version mismatches Run version"
12
+ end
8
13
  end
9
14
  end
10
15
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Run
4
+ module Error
5
+ class UnexpectedNamedParameters < StandardError
6
+ # @param task_name [Symbol]
7
+ # @param names [Array<Symbol>]
8
+ def initialize(task_name, names)
9
+ super "Task '#{task_name}' does not accept named parameters: #{names.join(", ")}"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -8,15 +8,16 @@ module Run
8
8
  @block = block
9
9
  end
10
10
 
11
+ # @return [Boolean]
12
+ def accepts_keywords?
13
+ @block.parameters.any?{ |type, _| [:key, :keyreq, :keyrest].include?(type) }
14
+ end
15
+
11
16
  # @param arguments [Array]
12
17
  # @param options [Hash]
13
18
  # @return [void]
14
19
  def run(*arguments, **options)
15
- if options.size == 0
16
- @block.call *arguments
17
- else
18
- @block.call *arguments, **options
19
- end
20
+ @block.call(*arguments, **options)
20
21
  end
21
22
  end
22
23
  end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Run
4
+ VERSION = "4.0.1"
5
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: run_tasks
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.3.0
4
+ version: 4.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aurélien Delogu
@@ -37,20 +37,6 @@ dependencies:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
39
  version: 0.1.0
40
- - !ruby/object:Gem::Dependency
41
- name: rb_gemspec
42
- requirement: !ruby/object:Gem::Requirement
43
- requirements:
44
- - - "~>"
45
- - !ruby/object:Gem::Version
46
- version: 0.2.1
47
- type: :runtime
48
- prerelease: false
49
- version_requirements: !ruby/object:Gem::Requirement
50
- requirements:
51
- - - "~>"
52
- - !ruby/object:Gem::Version
53
- version: 0.2.1
54
40
  - !ruby/object:Gem::Dependency
55
41
  name: rb_markdown
56
42
  requirement: !ruby/object:Gem::Requirement
@@ -71,18 +57,20 @@ executables:
71
57
  extensions: []
72
58
  extra_rdoc_files: []
73
59
  files:
74
- - CHEATSHEET.md
60
+ - README.md
75
61
  - bin/run
76
62
  - completions/_run
77
63
  - src/bootstrap.rb
78
64
  - src/run/core.rb
79
65
  - src/run/core/help.rb
80
66
  - src/run/error/aborted.rb
67
+ - src/run/error/duplicate_parameter.rb
81
68
  - src/run/error/existing_task.rb
82
69
  - src/run/error/non_existing_runfile.rb
83
70
  - src/run/error/non_existing_task.rb
84
71
  - src/run/error/reserved_task_name.rb
85
72
  - src/run/error/runfile_version_mismatch.rb
73
+ - src/run/error/unexpected_named_parameters.rb
86
74
  - src/run/error/unknown_task.rb
87
75
  - src/run/helper/are_you_sure_helper.rb
88
76
  - src/run/helper/bind_helper.rb
@@ -96,6 +84,7 @@ files:
96
84
  - src/run/helper/yesno_helper.rb
97
85
  - src/run/task/block_task.rb
98
86
  - src/run/task/system_task.rb
87
+ - src/run/version.rb
99
88
  homepage: https://github.com/pyrsmk/run
100
89
  licenses:
101
90
  - MIT
data/CHEATSHEET.md DELETED
@@ -1,238 +0,0 @@
1
- # Defining a task
2
-
3
- ```rb
4
- task :eslint do
5
- end
6
- ```
7
-
8
- # Defining a task with aliases
9
-
10
- ```rb
11
- task [:console, :c] do
12
- end
13
- ```
14
-
15
- # Defining a task with parameters
16
-
17
- ```rb
18
- task :hello do |name, age|
19
- puts "Hello #{name}, you are #{age}."
20
- end
21
- ```
22
-
23
- # Running commands
24
-
25
- ```rb
26
- task :clear_all do
27
- run "rm -rf /*"
28
- end
29
- ```
30
-
31
- # Running subtasks
32
-
33
- ```rb
34
- task :linting do
35
- run :eslint
36
- run :rubocop
37
- end
38
- ```
39
-
40
- # Running a task/command in quiet mode
41
-
42
- ```rb
43
- task :clear_cache do
44
- run "rm -rf cache/*", quiet: true
45
- end
46
- ```
47
-
48
- # Using named booleans
49
-
50
- ```sh
51
- run server +tunnel
52
- ```
53
-
54
- ```rb
55
- task :server do |tunnel: false|
56
- run "http_server #{tunnel ? "--tunnel" : ""}"
57
- end
58
- ```
59
-
60
- # Helpers
61
-
62
- ## are_you_sure
63
-
64
- ```rb
65
- task :dangerous_task do
66
- are_you_sure "This task will delete your hard drive. Are you sure about that?"
67
- # Some dangerous actions.
68
- end
69
- ```
70
-
71
- ## bind
72
-
73
- ```rb
74
- task :rails do
75
- run "bundle exec unicorn -c config/unicorn.rb -p 3000"
76
- end
77
-
78
- task :vite do
79
- run "bin/vite dev"
80
- end
81
-
82
- task :dev do
83
- # Bind `rails` and `vite` tasks together, if one closes, the other terminates too.
84
- bind :rails, :vite
85
- end
86
- ```
87
-
88
- ```rb
89
- task :dev do
90
- # Plug STDIN to the `rails` task.
91
- bind :rails, :vite, stdin: :rails
92
- end
93
- ```
94
-
95
- ## catch_interruption
96
-
97
- ```rb
98
- task :server do
99
- catch_interruption("docker run -d -p 8080:80 web_server") do
100
- run :clear_cache
101
- end
102
- end
103
- ```
104
-
105
- ## exists
106
-
107
- ```rb
108
- task :man do
109
- command = exists("bat") ? "bat" : "cat"
110
- run "#{command} #{__dir__}/README.md"
111
- end
112
- ```
113
-
114
- ## expand
115
-
116
- ```rb
117
- task :scan do |glob|
118
- expand(glob).each do |file_path|
119
- puts file_path
120
- end
121
- end
122
- ```
123
-
124
- ## menu
125
-
126
- ```rb
127
- task :deploy_aws do
128
- region = menu "Location?", ["us‑east‑2", "us-west-1", "eu-west-1"]
129
- end
130
- ```
131
-
132
- ```rb
133
- task :deploy_aws do
134
- region = menu "Location?", {
135
- "US East (Ohio)" => "us‑east‑2",
136
- "US West (N. California)" => "us-west-1",
137
- "Europe (Ireland)" => "eu-west-1",
138
- }
139
- end
140
- ```
141
-
142
- ## pause
143
-
144
- ```rb
145
- task :deploy do
146
- puts "Press enter to continue.".yellow
147
- pause
148
- run :deploy_production
149
- end
150
- ```
151
-
152
- ## question
153
-
154
- ```rb
155
- task :survey do
156
- comment = question "Did your meal was good?"
157
- end
158
- ```
159
-
160
- ```rb
161
- task :age do
162
- age = question "What is your age?", /^\d+$/
163
- end
164
- ```
165
-
166
- ```rb
167
- task :age do
168
- age = question "What is your age?", /^\d+$/, default: "30"
169
- end
170
- ```
171
-
172
- ## yesno
173
-
174
- ```rb
175
- task :deploy do
176
- run :migrate if yesno "Run migrations?"
177
- end
178
- ```
179
-
180
- ```rb
181
- task :deploy do
182
- run :migrate if yesno "Run migrations?", default: true
183
- end
184
- ```
185
-
186
- ## wait_for_interruption
187
-
188
- ```rb
189
- task :server do
190
- Thread.new do
191
- run :rails
192
- end
193
- Thread.new do
194
- run :vite
195
- end
196
- wait_for_interruption do
197
- run :kill_rails
198
- run :kill_vite
199
- end
200
- end
201
- ```
202
-
203
- # Colorization
204
-
205
- ```rb
206
- puts "hello".bold
207
- puts "hello".dim
208
- puts "hello".italic
209
- puts "hello".underline
210
- puts "hello".inverse
211
- puts "hello".strikethrough
212
- puts "hello".black
213
- puts "hello".red
214
- puts "hello".green
215
- puts "hello".yellow
216
- puts "hello".blue
217
- puts "hello".magenta
218
- puts "hello".cyan
219
- puts "hello".white
220
- puts "hello".bright_black
221
- puts "hello".bright_red
222
- puts "hello".bright_green
223
- puts "hello".bright_yellow
224
- puts "hello".bright_blue
225
- puts "hello".bright_magenta
226
- puts "hello".bright_cyan
227
- puts "hello".bright_white
228
- ```
229
-
230
- ```rb
231
- puts "hello".green.bold.italic
232
- ```
233
-
234
- # Advanced usage
235
-
236
- ```sh
237
- RUNFILE=/path/to/Runfile.rb run my_task
238
- ```