run_tasks 1.7.6 → 2.0.3

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
  SHA1:
3
- metadata.gz: 42142bd1919ce9f61fb6df67f5f7dcf7213145ee
4
- data.tar.gz: 31771d47371d92754667f988c76101d0e8f3d27e
3
+ metadata.gz: 651b53b567a43d2b6b660b7df04eb1f29402cc01
4
+ data.tar.gz: 48e8c2c044195c180818179dc654a06070180350
5
5
  SHA512:
6
- metadata.gz: 383f68e5b67d0a0117be4aca5a82b97019d62a8de2ba8919feb98a08f1c9b470bf26eec353940c6a669834f375cf3df9849c6a9328dfff22d085ac76c45948eb
7
- data.tar.gz: 3132a335135485e784fc10d6ffa513bcc27e53aa9d0d964c19f78579b0a7b27f815e34c7d165a8c5168d71f2f279be34737c09615754d50ff0db45e05795de9d
6
+ metadata.gz: b44b338980d23b2beb8fd2aff9ceb53116ff33c7b6496c2d40a864ac2cb103999a28d4dd3d7f89d976624599ed7fdec46d6d0cd367e73b92486c4593d1735c65
7
+ data.tar.gz: 16afb42816e127b41360fd3df15798de29234c4032e0a3354603437f1082d16eb5514325724a8abab6e00b7633a91121c416648ee8751951e4bab6e3f0175df9
data/bin/run CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require_relative '../src/run'
3
+ require_relative '../src/bootstrap'
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::VerifyRunVersion.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] + replace_by(Regexp.last_match[2]) + Regexp.last_match[3]
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 "Not implemented"
38
+ raise NotImplementedError.new
38
39
  end
39
40
 
40
- def replace_by(string)
41
- raise "Not implemented"
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 Bold
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
- def replace_by(string)
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 Code
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
- def replace_by(string)
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
- # E.g. `Bold` should run before `Italic`.
14
- tag = Code.new(
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 Italic
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
- def replace_by(string)
14
+ # @param string [String]
15
+ # @return [String]
16
+ def convert(string)
15
17
  string.italic
16
18
  end
17
19
  end
@@ -1,5 +1,4 @@
1
1
  # https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797
2
-
3
2
  class String
4
3
  @@styles = {
5
4
  :bold => "1",
@@ -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,24 @@
1
+ module Run
2
+ module Core
3
+ module VerifyRunVersion
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
+ puts
16
+ puts " A new version of Run is available: #{remote_version}".yellow.bright
17
+ puts " Please update with: `gem update run_tasks`".yellow.bright
18
+ puts
19
+ end
20
+ rescue Version::UnreachableError
21
+ end
22
+ end
23
+ end
24
+ 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,6 @@
1
+ module Run
2
+ module Error
3
+ class Aborted < StandardError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,10 @@
1
+ module Run
2
+ module Error
3
+ class ExistingTask < StandardError
4
+ # @param name [String]
5
+ def initialize(name)
6
+ super "'#{name}' task already exists"
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,6 @@
1
+ module Run
2
+ module Error
3
+ class NonExistingRunfile < StandardError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,10 @@
1
+ module Run
2
+ module Error
3
+ class ReservedTaskName < StandardError
4
+ # @param name [String]
5
+ def initialize(name)
6
+ super "'#{name}' task name is reserved"
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module Run
2
+ module Error
3
+ class UnknownTask < StandardError
4
+ # @param name [String]
5
+ def initialize(name)
6
+ super "Unknown '#{name}' task"
7
+ end
8
+ end
9
+ end
10
+ 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,10 @@
1
+ module Run
2
+ module Helper
3
+ class PauseHelper
4
+ # @return [void]
5
+ def run
6
+ STDIN.gets("\n")
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,16 @@
1
+ module Run
2
+ module Helper
3
+ class WaitForInterruptionHelper
4
+ # @param &block [Proc]
5
+ def initialize(&block)
6
+ @block = block
7
+ end
8
+
9
+ # @return [void]
10
+ def run
11
+ STDIN.gets while true
12
+ @block.call
13
+ end
14
+ end
15
+ end
16
+ 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,13 @@
1
+ module Version
2
+ class LocalGemspecVersion
3
+ # @param gemspec [Gemspec::Metadata]
4
+ def initialize(gemspec)
5
+ @gemspec = gemspec
6
+ end
7
+
8
+ # @return [String]
9
+ def extract
10
+ @gemspec.read.version.to_s
11
+ end
12
+ end
13
+ 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
@@ -0,0 +1,4 @@
1
+ module Version
2
+ class UnreachableError < StandardError
3
+ end
4
+ end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: run_tasks
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.6
4
+ version: 2.0.3
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: 2023-09-13 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2025-02-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: tty-prompt
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.23.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.23.1
13
27
  description:
14
28
  email: aurelien.delogu@gmail.com
15
29
  executables:
@@ -18,13 +32,34 @@ extensions: []
18
32
  extra_rdoc_files: []
19
33
  files:
20
34
  - bin/run
21
- - src/run.rb
22
- - src/run/markdown/abstract_tag.rb
23
- - src/run/markdown/bold_tag.rb
24
- - src/run/markdown/code_tag.rb
25
- - src/run/markdown/engine.rb
26
- - src/run/markdown/italic_tag.rb
27
- - src/run/string.rb
35
+ - src/bootstrap.rb
36
+ - src/gemspec/metadata.rb
37
+ - src/markdown/abstract_tag.rb
38
+ - src/markdown/bold_tag.rb
39
+ - src/markdown/code_tag.rb
40
+ - src/markdown/engine.rb
41
+ - src/markdown/italic_tag.rb
42
+ - src/monkey/string.rb
43
+ - src/run/core.rb
44
+ - src/run/core/help.rb
45
+ - src/run/core/verify_run_version.rb
46
+ - src/run/error/aborted.rb
47
+ - src/run/error/existing_task.rb
48
+ - src/run/error/non_existing_runfile.rb
49
+ - src/run/error/reserved_task_name.rb
50
+ - src/run/error/unknown_task.rb
51
+ - src/run/helper/are_you_sure_helper.rb
52
+ - src/run/helper/catch_interruption_helper.rb
53
+ - src/run/helper/expand_helper.rb
54
+ - src/run/helper/menu_helper.rb
55
+ - src/run/helper/pause_helper.rb
56
+ - src/run/helper/wait_for_interruption_helper.rb
57
+ - src/run/task/block_task.rb
58
+ - src/run/task/system_task.rb
59
+ - src/version/local_gemspec_version.rb
60
+ - src/version/remote_gemspec_version.rb
61
+ - src/version/semver.rb
62
+ - src/version/unreachable_error.rb
28
63
  homepage: https://github.com/pyrsmk/run
29
64
  licenses:
30
65
  - MIT
@@ -45,8 +80,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
45
80
  version: '0'
46
81
  requirements: []
47
82
  rubyforge_project:
48
- rubygems_version: 2.5.2
83
+ rubygems_version: 2.6.14.4
49
84
  signing_key:
50
85
  specification_version: 4
51
- summary: Run project tasks with ease
86
+ summary: Easy project management for the masses
52
87
  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