run_tasks 1.7.6 → 2.0.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 +4 -4
- data/bin/run +1 -1
- data/src/bootstrap.rb +88 -0
- data/src/gemspec/metadata.rb +23 -0
- data/src/{run/markdown → markdown}/abstract_tag.rb +8 -5
- data/src/{run/markdown → markdown}/bold_tag.rb +6 -4
- data/src/{run/markdown → markdown}/code_tag.rb +6 -4
- data/src/{run/markdown → markdown}/engine.rb +2 -7
- data/src/{run/markdown → markdown}/italic_tag.rb +6 -4
- data/src/{run → monkey}/string.rb +0 -1
- data/src/run/core/help.rb +125 -0
- data/src/run/core/reload_run.rb +11 -0
- data/src/run/core/update_run.rb +22 -0
- data/src/run/core.rb +98 -0
- data/src/run/error/aborted.rb +6 -0
- data/src/run/error/existing_task.rb +10 -0
- data/src/run/error/non_existing_runfile.rb +6 -0
- data/src/run/error/reserved_task_name.rb +10 -0
- data/src/run/error/unknown_task.rb +10 -0
- data/src/run/helper/are_you_sure_helper.rb +24 -0
- data/src/run/helper/catch_interruption_helper.rb +20 -0
- data/src/run/helper/expand_helper.rb +22 -0
- data/src/run/helper/menu_helper.rb +43 -0
- data/src/run/helper/pause_helper.rb +10 -0
- data/src/run/helper/wait_for_interruption_helper.rb +16 -0
- data/src/run/task/block_task.rb +21 -0
- data/src/run/task/system_task.rb +31 -0
- data/src/version/local_gemspec_version.rb +13 -0
- data/src/version/remote_gemspec_version.rb +21 -0
- data/src/version/semver.rb +62 -0
- data/src/version/unreachable_error.rb +4 -0
- metadata +33 -11
- data/src/run.rb +0 -253
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d60ff20317e3b85c858eb5f2c55a2fa747cd4532
|
4
|
+
data.tar.gz: f797f79c91bfc9a0a0d9c5a307d0434a730fad13
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 61eaabe8d4d2655149051bd0d41f2b9d0bb0c5858316a310e90005af531c43f549e47eb434c2e40fc7ad068f3bf57801ba30eff31e39a1c8a9c0b1dda24b243c
|
7
|
+
data.tar.gz: d24126b435e53bfec9bf9447c78330fc763875f3cea4a29d4d2c457a60b24b190dae52a428bf7eeed1754ec4504c8dffe7fd771b11d16a279fecfa2397952ddb
|
data/bin/run
CHANGED
data/src/bootstrap.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
require "digest"
|
2
|
+
require "fileutils"
|
3
|
+
require "io/console"
|
4
|
+
require "readline"
|
5
|
+
require "rubygems"
|
6
|
+
require "securerandom"
|
7
|
+
|
8
|
+
# Require all files.
|
9
|
+
Dir.glob(
|
10
|
+
File.join(__dir__, "{monkey,gemspec,markdown,version,run}", "**", "*.rb"),
|
11
|
+
&method(:require)
|
12
|
+
)
|
13
|
+
|
14
|
+
# Check for new versions.
|
15
|
+
Run::Core::UpdateRun.run
|
16
|
+
|
17
|
+
# Expose global methods.
|
18
|
+
[:task, :run].each do |name|
|
19
|
+
define_method name do |*args, **options, &block|
|
20
|
+
if options.size == 0
|
21
|
+
Run::Core.send(name, *args, &block)
|
22
|
+
else
|
23
|
+
Run::Core.send(name, *args, **options, &block)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Define global tasks.
|
29
|
+
task :rspec do |path|
|
30
|
+
command = "bundle exec rspec"
|
31
|
+
|
32
|
+
if path.include?(":")
|
33
|
+
run "#{command} #{path}"
|
34
|
+
else
|
35
|
+
run "#{command} #{expand(File.directory?(path) ? "#{path}/**/*" : path)}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Expose helpers.
|
40
|
+
Dir.glob(File.join(__dir__, "run", "helper", "*.rb")) do |path|
|
41
|
+
filename = File.basename(path, ".rb")
|
42
|
+
classname = filename.split('_').map(&:capitalize).join
|
43
|
+
helper = Object.const_get("Run::Helper::#{classname}")
|
44
|
+
name = filename.slice(0, filename.size - 7)
|
45
|
+
define_method(name) do |*args, **options, &block|
|
46
|
+
if options.size == 0
|
47
|
+
helper.new(*args).run(&block)
|
48
|
+
else
|
49
|
+
helper.new(*args, **options).run(&block)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# @param error [StandardError]
|
55
|
+
# @return [void]
|
56
|
+
def format_error(error)
|
57
|
+
puts "· #{error.message}".red
|
58
|
+
puts "· #{error.backtrace[0]}".red
|
59
|
+
puts "· #{error.backtrace[1]}".red
|
60
|
+
puts "· #{error.backtrace[2]}".red
|
61
|
+
end
|
62
|
+
|
63
|
+
# Run Run.
|
64
|
+
begin
|
65
|
+
Run::Core.run_run
|
66
|
+
rescue Run::Error::UnknownTask => error
|
67
|
+
puts error.message.red
|
68
|
+
exit 2
|
69
|
+
rescue Interrupt
|
70
|
+
exit 3
|
71
|
+
rescue SyntaxError => error
|
72
|
+
format_error error
|
73
|
+
exit 5
|
74
|
+
rescue ArgumentError => error
|
75
|
+
format_error error
|
76
|
+
exit 6
|
77
|
+
rescue Run::Error::NonExistingRunfile
|
78
|
+
puts "Runfile.rb does not exist in '#{Dir.pwd}'".red
|
79
|
+
exit 7
|
80
|
+
rescue Run::Error::Aborted => error
|
81
|
+
exit 9
|
82
|
+
rescue Run::Error::ExistingTask => error
|
83
|
+
puts error.message.red
|
84
|
+
exit 10
|
85
|
+
rescue => error
|
86
|
+
format_error error
|
87
|
+
exit 4
|
88
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Gemspec
|
2
|
+
class Metadata
|
3
|
+
# @param lib_name [String]
|
4
|
+
def initialize(lib_name)
|
5
|
+
@lib_name = lib_name
|
6
|
+
end
|
7
|
+
|
8
|
+
# @return [Hash]
|
9
|
+
def read
|
10
|
+
@_read ||= (
|
11
|
+
path = "#{__dir__}/../../#{@lib_name}.gemspec"
|
12
|
+
|
13
|
+
# Development.
|
14
|
+
if File.exist?(path)
|
15
|
+
Gem::Specification::load(path)
|
16
|
+
# Production.
|
17
|
+
else
|
18
|
+
Gem::Specification::find_by_name(@lib_name) rescue nil
|
19
|
+
end
|
20
|
+
)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -7,7 +7,7 @@ module Markdown
|
|
7
7
|
when AbstractTag
|
8
8
|
@tag = value
|
9
9
|
else
|
10
|
-
raise "Invalid value of '#{value.class.name}' class"
|
10
|
+
raise ArgumentError.new("Invalid value of '#{value.class.name}' class")
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
@@ -23,7 +23,7 @@ module Markdown
|
|
23
23
|
|
24
24
|
loop do
|
25
25
|
string = string.sub(pattern) do
|
26
|
-
Regexp.last_match[1] +
|
26
|
+
Regexp.last_match[1] + convert(Regexp.last_match[2]) + Regexp.last_match[3]
|
27
27
|
end
|
28
28
|
break if !Regexp.last_match
|
29
29
|
end
|
@@ -33,12 +33,15 @@ module Markdown
|
|
33
33
|
|
34
34
|
protected
|
35
35
|
|
36
|
+
# @return [Array<String>]
|
36
37
|
def tokens
|
37
|
-
raise
|
38
|
+
raise NotImplementedError.new
|
38
39
|
end
|
39
40
|
|
40
|
-
|
41
|
-
|
41
|
+
# @param string [String]
|
42
|
+
# @return [String]
|
43
|
+
def convert(string)
|
44
|
+
raise NotImplementedError.new
|
42
45
|
end
|
43
46
|
end
|
44
47
|
end
|
@@ -1,17 +1,19 @@
|
|
1
1
|
require_relative "./abstract_tag"
|
2
|
-
require_relative "../string"
|
3
2
|
|
4
3
|
module Markdown
|
5
|
-
class
|
4
|
+
class BoldTag
|
6
5
|
include AbstractTag
|
7
6
|
|
8
7
|
protected
|
9
8
|
|
9
|
+
# @return [Array<String>]
|
10
10
|
def tokens
|
11
|
-
[
|
11
|
+
["**", "__"]
|
12
12
|
end
|
13
13
|
|
14
|
-
|
14
|
+
# @param string [String]
|
15
|
+
# @return [String]
|
16
|
+
def convert(string)
|
15
17
|
string.bold
|
16
18
|
end
|
17
19
|
end
|
@@ -1,17 +1,19 @@
|
|
1
1
|
require_relative "./abstract_tag"
|
2
|
-
require_relative "../string"
|
3
2
|
|
4
3
|
module Markdown
|
5
|
-
class
|
4
|
+
class CodeTag
|
6
5
|
include AbstractTag
|
7
6
|
|
8
7
|
protected
|
9
8
|
|
9
|
+
# @return [Array<String>]
|
10
10
|
def tokens
|
11
|
-
[
|
11
|
+
["`"]
|
12
12
|
end
|
13
13
|
|
14
|
-
|
14
|
+
# @param string [String]
|
15
|
+
# @return [String]
|
16
|
+
def convert(string)
|
15
17
|
string.cyan
|
16
18
|
end
|
17
19
|
end
|
@@ -10,13 +10,8 @@ module Markdown
|
|
10
10
|
|
11
11
|
def to_ansi
|
12
12
|
# The tags are ordered by priority.
|
13
|
-
#
|
14
|
-
|
15
|
-
Italic.new(
|
16
|
-
Bold.new(@string)
|
17
|
-
)
|
18
|
-
)
|
19
|
-
tag.to_ansi
|
13
|
+
# For example: `Bold` should run before `Italic`.
|
14
|
+
CodeTag.new(ItalicTag.new(BoldTag.new(@string))).to_ansi
|
20
15
|
end
|
21
16
|
end
|
22
17
|
end
|
@@ -1,17 +1,19 @@
|
|
1
1
|
require_relative "./abstract_tag"
|
2
|
-
require_relative "../string"
|
3
2
|
|
4
3
|
module Markdown
|
5
|
-
class
|
4
|
+
class ItalicTag
|
6
5
|
include AbstractTag
|
7
6
|
|
8
7
|
protected
|
9
8
|
|
9
|
+
# @return [Array<String>]
|
10
10
|
def tokens
|
11
|
-
[
|
11
|
+
["*", "_"]
|
12
12
|
end
|
13
13
|
|
14
|
-
|
14
|
+
# @param string [String]
|
15
|
+
# @return [String]
|
16
|
+
def convert(string)
|
15
17
|
string.italic
|
16
18
|
end
|
17
19
|
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require "ripper"
|
2
|
+
|
3
|
+
module Run
|
4
|
+
module Core
|
5
|
+
module Help
|
6
|
+
# @return [String]
|
7
|
+
def self.run(contents)
|
8
|
+
tasks = extract_tasks(contents)
|
9
|
+
help_verbatims = extract_help_verbatims(contents, tasks)
|
10
|
+
tasks_column_length = compute_tasks_column_length(tasks)
|
11
|
+
|
12
|
+
output = help_verbatims.reduce("") do |output, help_verbatim|
|
13
|
+
name_verbatim = help_verbatim[:names].sort.join(", ")
|
14
|
+
name_verbatim = " #{name_verbatim}".yellow +
|
15
|
+
" " * (tasks_column_length - name_verbatim.size - 1)
|
16
|
+
|
17
|
+
help_verbatim[:comments].each_with_index do |comment, comment_line|
|
18
|
+
split_comment = split_verbatim(
|
19
|
+
comment,
|
20
|
+
STDOUT.winsize[1] - tasks_column_length - 2
|
21
|
+
)
|
22
|
+
if split_comment.size > 0
|
23
|
+
split_comment.map.with_index do |chunk, chunk_line|
|
24
|
+
text = comment_line == 0 && chunk_line == 0 ?
|
25
|
+
name_verbatim :
|
26
|
+
" " * tasks_column_length
|
27
|
+
text += chunk
|
28
|
+
output += "#{Markdown::Engine.new(text).to_ansi}\n"
|
29
|
+
end
|
30
|
+
else
|
31
|
+
output += "#{Markdown::Engine.new(name_verbatim).to_ansi}\n"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
output
|
36
|
+
end
|
37
|
+
|
38
|
+
puts output
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
# @param contents [String]
|
44
|
+
# @param tasks [Array<Hash>]
|
45
|
+
# @return [Array<Hash>]
|
46
|
+
def self.extract_help_verbatims(contents, tasks)
|
47
|
+
lines = contents.lines.map(&:chomp)
|
48
|
+
|
49
|
+
tasks.map do |names:, line:|
|
50
|
+
lines_to_scan = (0..(line - 2 < 0 ? 0 : line - 2)).to_a.reverse
|
51
|
+
{
|
52
|
+
names: names,
|
53
|
+
comments: lines_to_scan.each_with_object([]) do |current_line, comments|
|
54
|
+
match = /^\s*#\s*(?<comment>.*?)\s*$/.match(lines[current_line])
|
55
|
+
break comments if !match
|
56
|
+
comments << match[:comment]
|
57
|
+
end.reverse
|
58
|
+
}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# @param contents [String]
|
63
|
+
# @return [Array<Hash>]
|
64
|
+
def self.extract_tasks(contents)
|
65
|
+
Ripper.sexp(contents)[1].each_with_object([]) do |(id, sexp), tasks|
|
66
|
+
next if id != :method_add_block || sexp.fetch(1, nil)&.fetch(1, nil) != "task"
|
67
|
+
|
68
|
+
names = case sexp[2][1][0][0]
|
69
|
+
when :symbol_literal
|
70
|
+
[sexp[2][1][0][1][1][1].to_sym]
|
71
|
+
when :array
|
72
|
+
sexp[2][1][0][1].map{ |tokens| tokens[1][1][1].to_sym }.sort
|
73
|
+
else
|
74
|
+
raise "Unsupported task name type"
|
75
|
+
end
|
76
|
+
|
77
|
+
line = case sexp[2][1][0][0]
|
78
|
+
when :symbol_literal
|
79
|
+
sexp[2][1][0][1][1][2][0]
|
80
|
+
when :array
|
81
|
+
sexp[2][1][0][1][0][1][1][2][0]
|
82
|
+
else
|
83
|
+
raise "Unsupported task name type"
|
84
|
+
end
|
85
|
+
|
86
|
+
tasks << {
|
87
|
+
names: names,
|
88
|
+
line: line,
|
89
|
+
}
|
90
|
+
end.sort do |a, b|
|
91
|
+
a[:names].first <=> b[:names].first
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# @param tasks [Array<Hash>]
|
96
|
+
# @return [Integer]
|
97
|
+
def self.compute_tasks_column_length(tasks)
|
98
|
+
tasks
|
99
|
+
.map do |item|
|
100
|
+
item[:names].join(", ")
|
101
|
+
end
|
102
|
+
.reduce(0) do |max, name|
|
103
|
+
next max if name.size <= max
|
104
|
+
name.size
|
105
|
+
end + 2
|
106
|
+
end
|
107
|
+
|
108
|
+
# @param verbatim [String]
|
109
|
+
# @param available_length [Integer]
|
110
|
+
# @return [Array<String>]
|
111
|
+
def self.split_verbatim(verbatim, available_length)
|
112
|
+
verbatim.split(/\s+/).each_with_object([]) do |word, lines|
|
113
|
+
line = lines.pop || ""
|
114
|
+
if (line + word).size > available_length
|
115
|
+
lines << line
|
116
|
+
line = word
|
117
|
+
else
|
118
|
+
line += line.size == 0 ? word : " #{word}"
|
119
|
+
end
|
120
|
+
lines << line
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Run
|
2
|
+
module Core
|
3
|
+
module UpdateRun
|
4
|
+
REMOTE_GEMSPEC_URL = "https://raw.githubusercontent.com/pyrsmk/run/master/run_tasks.gemspec"
|
5
|
+
|
6
|
+
# @return [void]
|
7
|
+
def self.run
|
8
|
+
local_version = Version::Semver.new(
|
9
|
+
Version::LocalGemspecVersion.new(Gemspec::Metadata.new("run_tasks")).extract
|
10
|
+
)
|
11
|
+
remote_version = Version::Semver.new(
|
12
|
+
Version::RemoteGemspecVersion.new(REMOTE_GEMSPEC_URL).extract
|
13
|
+
)
|
14
|
+
if local_version.major == remote_version.major && local_version < remote_version
|
15
|
+
Run::Task::SystemTask.new("gem update run-tasks").run
|
16
|
+
Run::Core::ReloadRun.run
|
17
|
+
end
|
18
|
+
rescue Version::UnreachableError
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/src/run/core.rb
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
module Run
|
2
|
+
module Core
|
3
|
+
RUNFILE_FILENAME = "Runfile.rb"
|
4
|
+
RESERVED_TASK_NAMES = ["help", "version"]
|
5
|
+
@@tasks = []
|
6
|
+
|
7
|
+
# @return [void]
|
8
|
+
def self.run_run
|
9
|
+
raise Run::Error::NonExistingRunfile.new if !File.exists?(RUNFILE_FILENAME)
|
10
|
+
require "./#{RUNFILE_FILENAME}"
|
11
|
+
run_requested_task if !display_help_if_needed
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param task_name_or_command [Symbol, String]
|
15
|
+
# @param arguments [Array] Optional arguments sent to the task.
|
16
|
+
# @param options [Hash] Optional options sent to the task.
|
17
|
+
# @return [void]
|
18
|
+
def self.run(task_name_or_command, *arguments, **options)
|
19
|
+
if task_name_or_command.is_a?(Symbol)
|
20
|
+
run_block_task(task_name_or_command, *arguments, **options)
|
21
|
+
else
|
22
|
+
run_system_task(task_name_or_command)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# @param names [Array<Symbol> | Symbol]
|
27
|
+
# @param block [Proc]
|
28
|
+
# @return [void]
|
29
|
+
def self.task(names, &block)
|
30
|
+
names = !names.is_a?(Array) ? [names] : names
|
31
|
+
|
32
|
+
names.each do |name|
|
33
|
+
if !name.is_a?(Symbol)
|
34
|
+
raise ArgumentError.new("'name' must be a Symbol or an Array of Symbol")
|
35
|
+
end
|
36
|
+
raise Run::Error::ExistingTask.new(name) if task_exist?(name)
|
37
|
+
raise Run::Error::ReservedTaskName.new(name) if RESERVED_TASK_NAMES.include?(name)
|
38
|
+
end
|
39
|
+
|
40
|
+
@@tasks << {
|
41
|
+
names: names,
|
42
|
+
task: Run::Task::BlockTask.new(&block),
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
# @return [Boolean]
|
49
|
+
def self.display_help_if_needed
|
50
|
+
if ARGV.size == 0 || (ARGV.size == 1 && ["help", "version"].include?(ARGV[0]))
|
51
|
+
puts
|
52
|
+
puts " Run v#{Gemspec::Metadata.new("run_tasks").read.version}".bright_blue
|
53
|
+
puts
|
54
|
+
Run::Core::Help.run(File.read("./#{RUNFILE_FILENAME}"))
|
55
|
+
return true
|
56
|
+
end
|
57
|
+
false
|
58
|
+
end
|
59
|
+
|
60
|
+
# @return [void]
|
61
|
+
def self.run_requested_task
|
62
|
+
name = ARGV[0].gsub('-', '_').to_sym # Auto-replace hyphens to underscores.
|
63
|
+
raise Run::Error::UnknownTask.new(name) if !task_exist?(name)
|
64
|
+
|
65
|
+
# Cast value to the right type.
|
66
|
+
args = ARGV.slice(1, ARGV.size - 1).map do |arg|
|
67
|
+
value = Float(arg) rescue nil
|
68
|
+
next value if !value.nil?
|
69
|
+
next true if arg == "true"
|
70
|
+
next false if arg == "false"
|
71
|
+
next arg.to_sym if arg.match?(/^\w+$/)
|
72
|
+
arg
|
73
|
+
end
|
74
|
+
|
75
|
+
run name, *args
|
76
|
+
end
|
77
|
+
|
78
|
+
# @param name [Symbol]
|
79
|
+
# @return [Boolean]
|
80
|
+
def self.task_exist?(name)
|
81
|
+
!!@@tasks.find_index{ |item| item[:names].include? name }
|
82
|
+
end
|
83
|
+
|
84
|
+
# @param name [Symbol]
|
85
|
+
# @param args [Array]
|
86
|
+
# @param options [Hash]
|
87
|
+
# @return [void]
|
88
|
+
def self.run_block_task(name, *args, **options)
|
89
|
+
@@tasks.find{ |item| item[:names].include? name }[:task].run(*args, **options)
|
90
|
+
end
|
91
|
+
|
92
|
+
# @param name [Symbol]
|
93
|
+
# @return [void]
|
94
|
+
def self.run_system_task(name)
|
95
|
+
Run::Task::SystemTask.new(name).run
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Run
|
2
|
+
module Helper
|
3
|
+
class AreYouSureHelper
|
4
|
+
# @param message [String]
|
5
|
+
def initialize(message = "Are you sure?")
|
6
|
+
raise ArgumentError.new("'message' must be a String") if !message.is_a?(String)
|
7
|
+
@message = message
|
8
|
+
end
|
9
|
+
|
10
|
+
# @return [void]
|
11
|
+
def run
|
12
|
+
puts "#{@message.yellow.bold} [yN]"
|
13
|
+
raise Run::Error::Aborted.new unless answer == "y"
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
# @return [String, Nil]
|
19
|
+
def answer
|
20
|
+
STDIN.gets.chomp.downcase.chars.first
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Run
|
2
|
+
module Helper
|
3
|
+
class CatchInterruptionHelper
|
4
|
+
# @param command [String]
|
5
|
+
# @param &block [Proc]
|
6
|
+
def initialize(command, &block)
|
7
|
+
@command = command
|
8
|
+
@block = block
|
9
|
+
end
|
10
|
+
|
11
|
+
# @return [void]
|
12
|
+
def run
|
13
|
+
Run::Task::SystemTask.new(@command).run
|
14
|
+
rescue Interrupt
|
15
|
+
ensure
|
16
|
+
@block&.call
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "shellwords"
|
2
|
+
|
3
|
+
module Run
|
4
|
+
module Helper
|
5
|
+
class ExpandHelper
|
6
|
+
# @param glob [String]
|
7
|
+
def initialize(glob)
|
8
|
+
@glob = glob
|
9
|
+
end
|
10
|
+
|
11
|
+
# @return [String] space-separated list of found paths
|
12
|
+
def run
|
13
|
+
Dir.glob(@glob)
|
14
|
+
.each_with_object([]) do |path, paths|
|
15
|
+
next if File.directory?(path)
|
16
|
+
paths << File.realpath(path).shellescape
|
17
|
+
end
|
18
|
+
.join(" ")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require "tty-prompt"
|
2
|
+
|
3
|
+
module Run
|
4
|
+
module Helper
|
5
|
+
class MenuHelper
|
6
|
+
# @param text [String]
|
7
|
+
# @param choices [Array | Hash]
|
8
|
+
def initialize(text, choices)
|
9
|
+
raise ArgumentError.new("'text' must be a String") if !text.is_a?(String)
|
10
|
+
|
11
|
+
@text = text
|
12
|
+
@choices = format_choices(choices)
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [any]
|
16
|
+
def run
|
17
|
+
TTY::Prompt.new.select(@text.bright_yellow, show_help: "never") do |menu|
|
18
|
+
@choices.each do |(label, value)|
|
19
|
+
menu.choice(name: label, value: value)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
# @param choices [Array, Hash]
|
27
|
+
# @return [Hash]
|
28
|
+
def format_choices(choices)
|
29
|
+
if !choices.is_a?(Array) && !choices.is_a?(Hash)
|
30
|
+
raise ArgumentError.new("'choices' must be an Array or an Hash")
|
31
|
+
end
|
32
|
+
|
33
|
+
if choices.is_a?(Array)
|
34
|
+
return choices.each_with_object({}) do |value, hash|
|
35
|
+
hash[value] = value
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
choices
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Run
|
2
|
+
module Task
|
3
|
+
class BlockTask
|
4
|
+
# @param &block [Proc]
|
5
|
+
def initialize(&block)
|
6
|
+
@block = block
|
7
|
+
end
|
8
|
+
|
9
|
+
# @param arguments [Array]
|
10
|
+
# @param options [Hash]
|
11
|
+
# @return [void]
|
12
|
+
def run(*arguments, **options)
|
13
|
+
if options.size == 0
|
14
|
+
@block.call *arguments
|
15
|
+
else
|
16
|
+
@block.call *arguments, **options
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Run
|
2
|
+
module Task
|
3
|
+
class SystemTask
|
4
|
+
# @param command [String]
|
5
|
+
def initialize(command)
|
6
|
+
@command = command
|
7
|
+
end
|
8
|
+
|
9
|
+
# @param arguments [Array] (unused)
|
10
|
+
# @param options [Hash] (unused)
|
11
|
+
# @return [void]
|
12
|
+
def run(*arguments, **options)
|
13
|
+
puts ">".bright_blue + " #{@command}".bright_white
|
14
|
+
puts
|
15
|
+
|
16
|
+
case system(@command)
|
17
|
+
when false
|
18
|
+
puts "The command has exited with return code: #{$?.exitstatus}.".magenta
|
19
|
+
puts
|
20
|
+
raise Interrupt.new
|
21
|
+
when nil
|
22
|
+
puts "The command has failed.".magenta
|
23
|
+
puts
|
24
|
+
raise Interrupt.new
|
25
|
+
end
|
26
|
+
|
27
|
+
puts
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "open-uri"
|
2
|
+
require "socket"
|
3
|
+
|
4
|
+
module Version
|
5
|
+
class RemoteGemspecVersion
|
6
|
+
# @param url [String]
|
7
|
+
def initialize(url)
|
8
|
+
@url = url
|
9
|
+
end
|
10
|
+
|
11
|
+
# @return [String]
|
12
|
+
def extract
|
13
|
+
contents = URI.parse(@url).open.read
|
14
|
+
matches = /^\s*s.version\s*=\s*"(.+?)"\s*$/.match(contents)
|
15
|
+
raise UnreachableError.new if matches.nil?
|
16
|
+
matches[1]
|
17
|
+
rescue SocketError
|
18
|
+
raise UnreachableError.new
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Version
|
2
|
+
class Semver
|
3
|
+
# @return [String]
|
4
|
+
def initialize(version)
|
5
|
+
match = /^(\d+)\.(\d+)\.(\d+)$/.match(version)
|
6
|
+
raise ArgumentError.new("Invalid SEMVER number") if match.nil?
|
7
|
+
|
8
|
+
@major = match[1].to_i
|
9
|
+
@minor = match[2].to_i
|
10
|
+
@patch = match[3].to_i
|
11
|
+
end
|
12
|
+
|
13
|
+
# @return [Integer]
|
14
|
+
def major
|
15
|
+
@major
|
16
|
+
end
|
17
|
+
|
18
|
+
# @return [Integer]
|
19
|
+
def minor
|
20
|
+
@minor
|
21
|
+
end
|
22
|
+
|
23
|
+
# @return [Integer]
|
24
|
+
def patch
|
25
|
+
@patch
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [Boolean]
|
29
|
+
def <(semver)
|
30
|
+
is_a_semver?(semver)
|
31
|
+
|
32
|
+
@major < semver.major ||
|
33
|
+
(@major == semver.major && @minor < semver.minor) ||
|
34
|
+
(@major == semver.major && @minor == semver.minor && @patch < semver.patch)
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [Boolean]
|
38
|
+
def >(semver)
|
39
|
+
is_a_semver?(semver)
|
40
|
+
|
41
|
+
@major > semver.major ||
|
42
|
+
(@major == semver.major && @minor > semver.minor) ||
|
43
|
+
(@major == semver.major && @minor == semver.minor && @patch > semver.patch)
|
44
|
+
end
|
45
|
+
|
46
|
+
# @return [Boolean]
|
47
|
+
def ==(semver)
|
48
|
+
is_a_semver?(semver)
|
49
|
+
|
50
|
+
@major == semver.major && @minor == semver.minor && @patch == semver.patch
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
# @return [Boolean]
|
56
|
+
def is_a_semver?(semver)
|
57
|
+
if !semver.respond_to?(:major) || !semver.respond_to?(:minor) || !semver.respond_to?(:patch)
|
58
|
+
raise ArgumentError.new("SemVer compatible object expected")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: run_tasks
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aurélien Delogu
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-02-27 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email: aurelien.delogu@gmail.com
|
@@ -18,13 +18,35 @@ extensions: []
|
|
18
18
|
extra_rdoc_files: []
|
19
19
|
files:
|
20
20
|
- bin/run
|
21
|
-
- src/
|
22
|
-
- src/
|
23
|
-
- src/
|
24
|
-
- src/
|
25
|
-
- src/
|
26
|
-
- src/
|
27
|
-
- src/
|
21
|
+
- src/bootstrap.rb
|
22
|
+
- src/gemspec/metadata.rb
|
23
|
+
- src/markdown/abstract_tag.rb
|
24
|
+
- src/markdown/bold_tag.rb
|
25
|
+
- src/markdown/code_tag.rb
|
26
|
+
- src/markdown/engine.rb
|
27
|
+
- src/markdown/italic_tag.rb
|
28
|
+
- src/monkey/string.rb
|
29
|
+
- src/run/core.rb
|
30
|
+
- src/run/core/help.rb
|
31
|
+
- src/run/core/reload_run.rb
|
32
|
+
- src/run/core/update_run.rb
|
33
|
+
- src/run/error/aborted.rb
|
34
|
+
- src/run/error/existing_task.rb
|
35
|
+
- src/run/error/non_existing_runfile.rb
|
36
|
+
- src/run/error/reserved_task_name.rb
|
37
|
+
- src/run/error/unknown_task.rb
|
38
|
+
- src/run/helper/are_you_sure_helper.rb
|
39
|
+
- src/run/helper/catch_interruption_helper.rb
|
40
|
+
- src/run/helper/expand_helper.rb
|
41
|
+
- src/run/helper/menu_helper.rb
|
42
|
+
- src/run/helper/pause_helper.rb
|
43
|
+
- src/run/helper/wait_for_interruption_helper.rb
|
44
|
+
- src/run/task/block_task.rb
|
45
|
+
- src/run/task/system_task.rb
|
46
|
+
- src/version/local_gemspec_version.rb
|
47
|
+
- src/version/remote_gemspec_version.rb
|
48
|
+
- src/version/semver.rb
|
49
|
+
- src/version/unreachable_error.rb
|
28
50
|
homepage: https://github.com/pyrsmk/run
|
29
51
|
licenses:
|
30
52
|
- MIT
|
@@ -45,8 +67,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
45
67
|
version: '0'
|
46
68
|
requirements: []
|
47
69
|
rubyforge_project:
|
48
|
-
rubygems_version: 2.
|
70
|
+
rubygems_version: 2.6.14.4
|
49
71
|
signing_key:
|
50
72
|
specification_version: 4
|
51
|
-
summary:
|
73
|
+
summary: Easy project management for the masses
|
52
74
|
test_files: []
|
data/src/run.rb
DELETED
@@ -1,253 +0,0 @@
|
|
1
|
-
#!/usr/bin/ruby
|
2
|
-
|
3
|
-
require "digest"
|
4
|
-
require "fileutils"
|
5
|
-
require "open-uri"
|
6
|
-
require "readline"
|
7
|
-
require "rubygems"
|
8
|
-
require "securerandom"
|
9
|
-
|
10
|
-
##########################################################################################
|
11
|
-
|
12
|
-
require_relative "#{__dir__}/run/string"
|
13
|
-
require_relative "#{__dir__}/run/markdown/engine"
|
14
|
-
|
15
|
-
##########################################################################################
|
16
|
-
|
17
|
-
GEMSPEC_PATH = "#{__dir__}/../run_tasks.gemspec"
|
18
|
-
GEM = if File.exist?(GEMSPEC_PATH)
|
19
|
-
Gem::Specification::load(GEMSPEC_PATH) # Development.
|
20
|
-
else
|
21
|
-
Gem::Specification::find_by_name("run_tasks") rescue nil # Production.
|
22
|
-
end
|
23
|
-
VERSION = GEM&.version
|
24
|
-
HOMEPAGE = GEM&.homepage
|
25
|
-
|
26
|
-
##########################################################################################
|
27
|
-
|
28
|
-
@tasks = {}
|
29
|
-
|
30
|
-
# @param name [Symbol]
|
31
|
-
# @param help [String]
|
32
|
-
# @yield [*Array, **Hash]
|
33
|
-
def task(name, help = nil, &block)
|
34
|
-
if !name.is_a?(Symbol)
|
35
|
-
puts
|
36
|
-
puts "'name' parameter must be a symbol".red
|
37
|
-
exit 6
|
38
|
-
end
|
39
|
-
if !help.nil?
|
40
|
-
if !help.is_a?(String)
|
41
|
-
puts
|
42
|
-
puts "'help' parameter must be a string".red
|
43
|
-
exit 6
|
44
|
-
end
|
45
|
-
else
|
46
|
-
# Load comments directly above the task as help verbatim.
|
47
|
-
caller = caller_locations[0]
|
48
|
-
lines = File.readlines(caller.absolute_path)
|
49
|
-
help = (0..(caller.lineno - 2)).to_a.reverse.reduce([]) do |comments, lineno|
|
50
|
-
match = /^\s*#\s*(?<comment>.+?)\s*$/.match(lines[lineno])
|
51
|
-
break comments if match.nil?
|
52
|
-
comments << match[:comment]
|
53
|
-
comments
|
54
|
-
end.reverse
|
55
|
-
end
|
56
|
-
@tasks.store(
|
57
|
-
name,
|
58
|
-
{
|
59
|
-
:help => help.is_a?(String) ? [help] : help,
|
60
|
-
:block => block
|
61
|
-
}
|
62
|
-
)
|
63
|
-
end
|
64
|
-
|
65
|
-
# @param task_name_or_command [Symbol, String]
|
66
|
-
# @param arguments [Array] Optional arguments sent to the task.
|
67
|
-
# @param options [Hash] Optional options sent to the task.
|
68
|
-
def run(task_name_or_command, *arguments, **options)
|
69
|
-
if task_name_or_command.is_a?(Symbol)
|
70
|
-
if options.empty?
|
71
|
-
@tasks[task_name_or_command][:block].call *arguments
|
72
|
-
else
|
73
|
-
@tasks[task_name_or_command][:block].call *arguments, **options
|
74
|
-
end
|
75
|
-
return
|
76
|
-
end
|
77
|
-
|
78
|
-
puts ">".bright_blue + " #{task_name_or_command}".bright_white
|
79
|
-
puts
|
80
|
-
case system(task_name_or_command)
|
81
|
-
when false
|
82
|
-
puts
|
83
|
-
puts "The command has exited with return code: #{$?.exitstatus}.".magenta
|
84
|
-
puts
|
85
|
-
raise Interrupt.new
|
86
|
-
when nil
|
87
|
-
puts
|
88
|
-
puts "The command has failed.".magenta
|
89
|
-
puts
|
90
|
-
raise Interrupt.new
|
91
|
-
end
|
92
|
-
puts
|
93
|
-
end
|
94
|
-
|
95
|
-
def are_you_sure?(text = "Are you sure?")
|
96
|
-
puts "#{text.yellow.bold} [yN]"
|
97
|
-
answer = STDIN.gets.chomp.downcase.chars.first
|
98
|
-
exit 9 unless answer == "y"
|
99
|
-
end
|
100
|
-
|
101
|
-
def menu(text, choices)
|
102
|
-
labels = nil
|
103
|
-
values = nil
|
104
|
-
choice = nil
|
105
|
-
|
106
|
-
if choices.is_a?(Array)
|
107
|
-
labels = choices
|
108
|
-
values = choices
|
109
|
-
elsif choices.is_a?(Hash)
|
110
|
-
labels = choices.keys
|
111
|
-
values = choices.values
|
112
|
-
else
|
113
|
-
puts "menu() 'choices' parameter must be an Array or an Hash".red
|
114
|
-
exit 10
|
115
|
-
end
|
116
|
-
|
117
|
-
loop do
|
118
|
-
labels.each_with_index do |label, index|
|
119
|
-
puts "#{index + 1}. #{label}"
|
120
|
-
end
|
121
|
-
puts text
|
122
|
-
choice = STDIN.gets.chomp.to_i
|
123
|
-
break if !values[choice - 1].nil?
|
124
|
-
end
|
125
|
-
puts
|
126
|
-
|
127
|
-
values[choice - 1]
|
128
|
-
end
|
129
|
-
|
130
|
-
# @param uri [String]
|
131
|
-
def require_remote(uri)
|
132
|
-
cache_path = "/tmp/run_cache_#{Digest::MD5.hexdigest(uri)}"
|
133
|
-
if !File.exist? cache_path
|
134
|
-
File.write(cache_path, URI.parse(uri).open.read)
|
135
|
-
end
|
136
|
-
eval File.read(cache_path)
|
137
|
-
rescue => error
|
138
|
-
puts
|
139
|
-
puts "Unable to load #{uri}:".red
|
140
|
-
puts "#{error.class}: #{error.message}".red
|
141
|
-
exit 8
|
142
|
-
end
|
143
|
-
|
144
|
-
# @param name [String]
|
145
|
-
def require_extension(name)
|
146
|
-
require_remote "https://pyrsmk.fra1.cdn.digitaloceanspaces.com" \
|
147
|
-
"/run_extensions/#{name}.rb"
|
148
|
-
end
|
149
|
-
|
150
|
-
##########################################################################################
|
151
|
-
|
152
|
-
RUNFILE = "Runfile.rb"
|
153
|
-
|
154
|
-
if !File.exist?(RUNFILE)
|
155
|
-
puts
|
156
|
-
puts "#{RUNFILE} does not exist".red
|
157
|
-
exit 7
|
158
|
-
end
|
159
|
-
|
160
|
-
begin
|
161
|
-
require "./#{RUNFILE}"
|
162
|
-
rescue SyntaxError => error
|
163
|
-
puts
|
164
|
-
puts "The Runfile contains a syntax error:".red
|
165
|
-
puts error.message.red
|
166
|
-
exit 5
|
167
|
-
end
|
168
|
-
|
169
|
-
##########################################################################################
|
170
|
-
|
171
|
-
# Show the help screen if there is no provided task, or if it's explicitly requested.
|
172
|
-
if ARGV.size == 0 || (ARGV.size == 1 && ARGV[0] == "help")
|
173
|
-
puts
|
174
|
-
if VERSION
|
175
|
-
puts " Run v#{VERSION}".bright_blue
|
176
|
-
else
|
177
|
-
puts " Run".bright_blue
|
178
|
-
end
|
179
|
-
puts
|
180
|
-
# Compute the max task names size.
|
181
|
-
max_size = @tasks.keys.reduce(0) do |max, name|
|
182
|
-
next max if name.size <= max
|
183
|
-
name.size
|
184
|
-
end
|
185
|
-
# Display each task and their help.
|
186
|
-
@tasks.sort.to_h.each do |name, task|
|
187
|
-
if task[:help].size == 0
|
188
|
-
puts " #{name}".yellow
|
189
|
-
next
|
190
|
-
end
|
191
|
-
task[:help].each_with_index do |help, index|
|
192
|
-
help = Markdown::Engine.new(help).to_ansi
|
193
|
-
if index == 0
|
194
|
-
puts " #{name}".yellow + (" " * (max_size - name.size + 4)) + help
|
195
|
-
next
|
196
|
-
end
|
197
|
-
puts (" " * (max_size + 5)) + help
|
198
|
-
end
|
199
|
-
end
|
200
|
-
exit
|
201
|
-
end
|
202
|
-
|
203
|
-
# Verify the latest release version.
|
204
|
-
if VERSION && HOMEPAGE
|
205
|
-
Thread.new do
|
206
|
-
begin
|
207
|
-
contents = URI.parse("#{HOMEPAGE}/master/run.gemspec")
|
208
|
-
.open
|
209
|
-
.read
|
210
|
-
version = /^\s*s.version\s*=\s*"(.+?)"\s*$/.match(contents)
|
211
|
-
if !version.nil?
|
212
|
-
next if File.exist?("/tmp/run_dismiss_#{version}")
|
213
|
-
current = VERSION.split "."
|
214
|
-
latest = version[1].split "."
|
215
|
-
if current[0].to_i < latest[0].to_i ||
|
216
|
-
current[1].to_i < latest[1].to_i ||
|
217
|
-
current[2].to_i < latest[2].to_i
|
218
|
-
puts "New ".cyan + version[1].yellow + " version released!".cyan
|
219
|
-
puts
|
220
|
-
puts "You can upgrade with:".cyan + "gem update run_tasks".yellow
|
221
|
-
puts
|
222
|
-
File.write "/tmp/run_dismiss_#{version}", ""
|
223
|
-
end
|
224
|
-
end
|
225
|
-
rescue
|
226
|
-
end
|
227
|
-
end
|
228
|
-
end
|
229
|
-
|
230
|
-
# Run the requested task.
|
231
|
-
name = ARGV[0].gsub('-', '_').to_sym # Auto-fix hyphens to underscores.
|
232
|
-
if !@tasks.include?(name)
|
233
|
-
puts
|
234
|
-
puts "Unknown '#{name}' task".red
|
235
|
-
exit 2
|
236
|
-
end
|
237
|
-
begin
|
238
|
-
run(name, *ARGV.slice(Range.new(1, ARGV.size - 1)))
|
239
|
-
rescue Interrupt
|
240
|
-
exit 3
|
241
|
-
rescue => error
|
242
|
-
puts
|
243
|
-
message = if error.message.size > 300
|
244
|
-
"#{error.message[0, 300]}..."
|
245
|
-
else
|
246
|
-
error.message
|
247
|
-
end
|
248
|
-
puts "· #{message}".red
|
249
|
-
error.backtrace.each do |trace|
|
250
|
-
puts "· #{trace}".red
|
251
|
-
end
|
252
|
-
exit 4
|
253
|
-
end
|