spud 0.2.0 → 0.2.5
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/lib/spud.rb +3 -2
- data/lib/spud/block_param_info.rb +80 -0
- data/lib/spud/cli/options.rb +35 -0
- data/lib/spud/cli/parser.rb +23 -19
- data/lib/spud/cli/results.rb +18 -11
- data/lib/spud/driver.rb +135 -0
- data/lib/spud/error.rb +1 -0
- data/lib/spud/help.rb +13 -13
- data/lib/spud/lister.rb +27 -22
- data/lib/spud/shell/command.rb +75 -0
- data/lib/spud/shell/result.rb +33 -0
- data/lib/spud/task_arg.rb +24 -12
- data/lib/spud/task_args.rb +20 -18
- data/lib/spud/task_runners/make/task.rb +67 -0
- data/lib/spud/task_runners/package.json/task.rb +71 -0
- data/lib/spud/task_runners/spud_task_runner/dependency.rb +49 -0
- data/lib/spud/task_runners/spud_task_runner/file_dsl.rb +57 -0
- data/lib/spud/task_runners/spud_task_runner/task.rb +148 -0
- data/lib/spud/task_runners/spud_task_runner/task_dsl.rb +90 -0
- data/lib/spud/task_runners/task.rb +40 -0
- data/lib/spud/task_runners/task_runners.rb +25 -0
- data/lib/spud/version.rb +2 -1
- data/lib/spud/watch.rb +73 -0
- metadata +46 -16
- data/lib/spud/build_tools/build_tools.rb +0 -13
- data/lib/spud/build_tools/make/task.rb +0 -27
- data/lib/spud/build_tools/package.json/task.rb +0 -47
- data/lib/spud/build_tools/spud/block_param_info.rb +0 -77
- data/lib/spud/build_tools/spud/dsl/file.rb +0 -23
- data/lib/spud/build_tools/spud/dsl/task.rb +0 -63
- data/lib/spud/build_tools/spud/shell/command.rb +0 -52
- data/lib/spud/build_tools/spud/shell/result.rb +0 -26
- data/lib/spud/build_tools/spud/task.rb +0 -125
- data/lib/spud/build_tools/task.rb +0 -33
- data/lib/spud/options.rb +0 -14
- data/lib/spud/runtime.rb +0 -104
@@ -0,0 +1,49 @@
|
|
1
|
+
# typed: true
|
2
|
+
|
3
|
+
module Spud
|
4
|
+
module TaskRunners
|
5
|
+
module SpudTaskRunner
|
6
|
+
class Dependency
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
sig {returns(T::Array[String])}
|
10
|
+
attr_reader :sources
|
11
|
+
|
12
|
+
sig {returns(T::Array[String])}
|
13
|
+
attr_reader :targets
|
14
|
+
|
15
|
+
sig {params(source: T.any(String, T::Array[String]), target: T.any(String, T::Array[String])).void}
|
16
|
+
def initialize(source, target)
|
17
|
+
@sources = [source].flatten
|
18
|
+
@targets = [target].flatten
|
19
|
+
end
|
20
|
+
|
21
|
+
sig {returns(T::Boolean)}
|
22
|
+
def need_to_update?
|
23
|
+
!up_to_date?
|
24
|
+
end
|
25
|
+
|
26
|
+
sig {returns(T::Boolean)}
|
27
|
+
def up_to_date?
|
28
|
+
source_filenames = Dir[*@sources]
|
29
|
+
return true if source_filenames.empty?
|
30
|
+
|
31
|
+
newest_source = source_filenames
|
32
|
+
.map(&File.method(:stat))
|
33
|
+
.map(&:mtime)
|
34
|
+
.max
|
35
|
+
|
36
|
+
target_filenames = Dir[*@targets]
|
37
|
+
return false if target_filenames.empty?
|
38
|
+
|
39
|
+
oldest_target = target_filenames
|
40
|
+
.map(&File.method(:stat))
|
41
|
+
.map(&:mtime)
|
42
|
+
.min
|
43
|
+
|
44
|
+
newest_source < oldest_target
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# typed: true
|
2
|
+
require 'sorbet-runtime'
|
3
|
+
require 'spud/driver'
|
4
|
+
require 'spud/task_runners/spud_task_runner/task'
|
5
|
+
|
6
|
+
module Spud
|
7
|
+
module TaskRunners
|
8
|
+
module SpudTaskRunner
|
9
|
+
class FileDSL
|
10
|
+
extend T::Sig
|
11
|
+
|
12
|
+
sig {params(driver: Driver, filename: String).returns(T::Array[SpudTaskRunner::Task])}
|
13
|
+
def self.run(driver, filename)
|
14
|
+
dsl = new(driver, filename)
|
15
|
+
dsl.instance_eval(::File.read(filename), filename)
|
16
|
+
|
17
|
+
dsl.instance_variable_get(:@__tasks)
|
18
|
+
end
|
19
|
+
|
20
|
+
sig {params(driver: Driver, filename: String).void}
|
21
|
+
def initialize(driver, filename)
|
22
|
+
@__driver = driver
|
23
|
+
@__filename = filename
|
24
|
+
|
25
|
+
@__tasks = T.let([], T::Array[Task])
|
26
|
+
end
|
27
|
+
|
28
|
+
sig {params(name: String).returns(T::Boolean)}
|
29
|
+
def require_relative(name)
|
30
|
+
require("./#{name}")
|
31
|
+
end
|
32
|
+
|
33
|
+
sig do
|
34
|
+
params(
|
35
|
+
name: T.any(String, Symbol),
|
36
|
+
dependencies: T::Hash[T.any(String, T::Array[String]), T.any(String, T::Array[String])],
|
37
|
+
block: Proc,
|
38
|
+
).void
|
39
|
+
end
|
40
|
+
def task(name, dependencies = {}, &block)
|
41
|
+
@__tasks << Task.new(
|
42
|
+
driver: @__driver,
|
43
|
+
name: Task.qualified_name(@__filename, name.to_s),
|
44
|
+
filename: @__filename,
|
45
|
+
dependencies: dependencies,
|
46
|
+
file_dsl: self,
|
47
|
+
&block
|
48
|
+
)
|
49
|
+
end
|
50
|
+
|
51
|
+
def method_missing(name, *args, &block)
|
52
|
+
T.unsafe(self).task(name, *args, &block)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
# typed: true
|
2
|
+
require 'sorbet-runtime'
|
3
|
+
require 'stringio'
|
4
|
+
require 'spud/driver'
|
5
|
+
require 'spud/error'
|
6
|
+
require 'spud/task_args'
|
7
|
+
require 'spud/task_runners/task'
|
8
|
+
require 'spud/task_runners/spud_task_runner/dependency'
|
9
|
+
require 'spud/task_runners/spud_task_runner/task'
|
10
|
+
require 'spud/task_runners/spud_task_runner/file_dsl'
|
11
|
+
require 'spud/task_runners/spud_task_runner/task_dsl'
|
12
|
+
|
13
|
+
module Spud
|
14
|
+
module TaskRunners
|
15
|
+
module SpudTaskRunner
|
16
|
+
class Task < TaskRunners::Task
|
17
|
+
extend T::Sig
|
18
|
+
|
19
|
+
sig {override.returns(String)}
|
20
|
+
attr_reader :filename
|
21
|
+
|
22
|
+
sig {override.returns(String)}
|
23
|
+
attr_reader :name
|
24
|
+
|
25
|
+
sig {returns(T::Array[Dependency])}
|
26
|
+
attr_reader :dependencies
|
27
|
+
|
28
|
+
sig {override.params(driver: Driver).returns(T::Array[TaskRunners::Task])}
|
29
|
+
def self.tasks(driver)
|
30
|
+
Dir['**/Spudfile', '**/*.spud'].flat_map { |filename| FileDSL.run(driver, filename) }
|
31
|
+
end
|
32
|
+
|
33
|
+
sig {params(filename: String, name: String).returns(String)}
|
34
|
+
def self.qualified_name(filename, name)
|
35
|
+
segments = File.dirname(filename)
|
36
|
+
.split('/')
|
37
|
+
.reject { |segment| segment == '.' }
|
38
|
+
|
39
|
+
basename = File.basename(filename, '.spud')
|
40
|
+
segments << basename unless basename == 'Spudfile'
|
41
|
+
|
42
|
+
segments << name
|
43
|
+
segments.join('.')
|
44
|
+
end
|
45
|
+
|
46
|
+
sig do
|
47
|
+
params(
|
48
|
+
driver: Driver,
|
49
|
+
name: String,
|
50
|
+
filename: String,
|
51
|
+
dependencies: T::Hash[T.any(String, T::Array[String]), T.any(String, T::Array[String])],
|
52
|
+
file_dsl: FileDSL,
|
53
|
+
block: Proc,
|
54
|
+
)
|
55
|
+
.void
|
56
|
+
end
|
57
|
+
def initialize(driver:, name:, filename:, dependencies:, file_dsl:, &block)
|
58
|
+
@driver = driver
|
59
|
+
@name = name
|
60
|
+
@filename = filename
|
61
|
+
@dependencies = dependencies.map { |to, from| Dependency.new(to, from) }
|
62
|
+
@file_dsl = file_dsl
|
63
|
+
@block = block
|
64
|
+
end
|
65
|
+
|
66
|
+
sig {override.params(ordered: T::Array[String], named: T::Hash[String, String]).returns(T.untyped)}
|
67
|
+
def invoke(ordered, named)
|
68
|
+
if up_to_date?
|
69
|
+
puts "'#{name}' up to date"
|
70
|
+
return nil
|
71
|
+
end
|
72
|
+
|
73
|
+
check_required_args!(ordered)
|
74
|
+
|
75
|
+
if args.any_named?
|
76
|
+
T.unsafe(task_dsl).instance_exec(*ordered, **symbolize_keys(named), &@block)
|
77
|
+
else
|
78
|
+
T.unsafe(task_dsl).instance_exec(*ordered, &@block)
|
79
|
+
end
|
80
|
+
rescue ArgumentError => error
|
81
|
+
raise Error, "invocation of '#{name}' with #{error.message}"
|
82
|
+
end
|
83
|
+
|
84
|
+
sig {override.returns(TaskArgs)}
|
85
|
+
def args
|
86
|
+
@args ||= TaskArgs.from_block(filename, &@block)
|
87
|
+
end
|
88
|
+
|
89
|
+
sig {override.returns(String)}
|
90
|
+
def details
|
91
|
+
filename, line_cursor = @block.source_location
|
92
|
+
line_cursor -= 1
|
93
|
+
|
94
|
+
lines = File.read(filename).split("\n")
|
95
|
+
builder = StringIO.new
|
96
|
+
|
97
|
+
# Move up for comments
|
98
|
+
while lines[line_cursor - 1]&.start_with?('#')
|
99
|
+
line_cursor -= 1
|
100
|
+
end
|
101
|
+
|
102
|
+
# Capture comments
|
103
|
+
while lines[line_cursor]&.start_with?('#')
|
104
|
+
builder.puts lines[line_cursor]
|
105
|
+
line_cursor += 1
|
106
|
+
end
|
107
|
+
|
108
|
+
# Capture block
|
109
|
+
until lines[line_cursor - 1]&.start_with?('end')
|
110
|
+
builder.puts lines[line_cursor]
|
111
|
+
line_cursor += 1
|
112
|
+
end
|
113
|
+
|
114
|
+
builder.string
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
sig {params(ordered: T::Array[String]).void}
|
120
|
+
def check_required_args!(ordered)
|
121
|
+
required_ordered = args.required_ordered
|
122
|
+
missing_ordered = required_ordered.length - ordered.length
|
123
|
+
if missing_ordered > 0
|
124
|
+
arguments = required_ordered.length - missing_ordered > 1 ? 'arguments' : 'argument'
|
125
|
+
raise Error, "invocation of '#{name}' missing required #{arguments} #{required_ordered.join(', ')}"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
sig {returns(T::Boolean)}
|
130
|
+
def up_to_date?
|
131
|
+
return false if @dependencies.empty?
|
132
|
+
|
133
|
+
@dependencies.all?(&:up_to_date?)
|
134
|
+
end
|
135
|
+
|
136
|
+
sig {returns(TaskDSL)}
|
137
|
+
def task_dsl
|
138
|
+
@task_dsl ||= TaskDSL.new(@driver, @filename, @file_dsl)
|
139
|
+
end
|
140
|
+
|
141
|
+
sig {params(hash: T::Hash[T.any(String, Symbol), T.untyped]).returns(T::Hash[Symbol, T.untyped])}
|
142
|
+
def symbolize_keys(hash)
|
143
|
+
hash.each_with_object({}) { |(key, value), new_hash| new_hash[key.to_sym] = value }
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# typed: true
|
2
|
+
require 'sorbet-runtime'
|
3
|
+
require 'spud/error'
|
4
|
+
require 'spud/driver'
|
5
|
+
require 'spud/shell/command'
|
6
|
+
require 'spud/shell/result'
|
7
|
+
require 'spud/task_runners/spud_task_runner/file_dsl'
|
8
|
+
|
9
|
+
module Spud
|
10
|
+
module TaskRunners
|
11
|
+
module SpudTaskRunner
|
12
|
+
class TaskDSL
|
13
|
+
extend T::Sig
|
14
|
+
|
15
|
+
sig {params(driver: Driver, filename: String, file_dsl: FileDSL).void}
|
16
|
+
def initialize(driver, filename, file_dsl)
|
17
|
+
@__filename = filename
|
18
|
+
@__driver = driver
|
19
|
+
|
20
|
+
@__commander = Shell::Command.commander(driver)
|
21
|
+
|
22
|
+
file_dsl.singleton_methods.each do |method|
|
23
|
+
define_singleton_method(method, &file_dsl.singleton_method(method))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
sig {params(command: String).returns(Shell::Result)}
|
28
|
+
def sh(command)
|
29
|
+
puts command
|
30
|
+
@__commander.(command)
|
31
|
+
end
|
32
|
+
|
33
|
+
sig {params(command: String).returns(Shell::Result)}
|
34
|
+
def shh(command)
|
35
|
+
@__commander.(command)
|
36
|
+
end
|
37
|
+
|
38
|
+
sig {params(command: String).returns(Shell::Result)}
|
39
|
+
def shhh(command)
|
40
|
+
@__commander.(command, silent: true)
|
41
|
+
end
|
42
|
+
|
43
|
+
sig {params(command: String).returns(Shell::Result)}
|
44
|
+
def sh!(command)
|
45
|
+
puts command
|
46
|
+
result = @__commander.(command)
|
47
|
+
raise Error, "sh failed for '#{command}'" unless result.success?
|
48
|
+
result
|
49
|
+
end
|
50
|
+
|
51
|
+
sig {params(command: String).returns(Shell::Result)}
|
52
|
+
def shh!(command)
|
53
|
+
result = @__commander.(command)
|
54
|
+
raise Error, "sh failed for '#{command}'" unless result.success?
|
55
|
+
result
|
56
|
+
end
|
57
|
+
|
58
|
+
sig {params(command: String).returns(Shell::Result)}
|
59
|
+
def shhh!(command)
|
60
|
+
result = @__commander.(command, silent: true)
|
61
|
+
raise Error, "sh failed for '#{command}'" unless result.success?
|
62
|
+
result
|
63
|
+
end
|
64
|
+
|
65
|
+
sig {params(task: String, ordered: String, named: String).returns(T.untyped)}
|
66
|
+
def invoke(task, *ordered, **named)
|
67
|
+
task = task.to_s
|
68
|
+
task = task.include?('.') ? task : Task.qualified_name(@__filename, task)
|
69
|
+
@__driver.invoke(task, ordered, named)
|
70
|
+
rescue Error => error
|
71
|
+
puts error.message
|
72
|
+
end
|
73
|
+
|
74
|
+
sig {params(task: String, ordered: String, named: String).returns(T.untyped)}
|
75
|
+
def invoke!(task, *ordered, **named)
|
76
|
+
task = task.to_s
|
77
|
+
task = task.include?('.') ? task : Task.qualified_name(@__filename, task)
|
78
|
+
@__driver.invoke(task, ordered, named)
|
79
|
+
end
|
80
|
+
|
81
|
+
sig {params(task: Symbol, ordered: String, named: String).returns(T.untyped)}
|
82
|
+
def method_missing(task, *ordered, **named)
|
83
|
+
task = task.to_s
|
84
|
+
task = task.include?('.') ? task : Task.qualified_name(@__filename, task)
|
85
|
+
@__driver.invoke(task, ordered, named)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# typed: strict
|
2
|
+
require 'sorbet-runtime'
|
3
|
+
require 'spud/driver'
|
4
|
+
require 'spud/task_args'
|
5
|
+
|
6
|
+
module Spud
|
7
|
+
module TaskRunners
|
8
|
+
class Task
|
9
|
+
extend T::Sig
|
10
|
+
extend T::Helpers
|
11
|
+
abstract!
|
12
|
+
|
13
|
+
sig {abstract.params(driver: Driver).returns(T::Array[TaskRunners::Task])}
|
14
|
+
def self.tasks(driver); end
|
15
|
+
|
16
|
+
sig {abstract.returns(String)}
|
17
|
+
def name; end
|
18
|
+
|
19
|
+
sig {abstract.returns(String)}
|
20
|
+
def filename; end
|
21
|
+
|
22
|
+
sig do
|
23
|
+
abstract
|
24
|
+
.params(ordered: T::Array[String], named: T::Hash[String, String])
|
25
|
+
.returns(T.untyped)
|
26
|
+
end
|
27
|
+
def invoke(ordered, named); end
|
28
|
+
|
29
|
+
sig {overridable.returns(TaskArgs)}
|
30
|
+
def args
|
31
|
+
TaskArgs.new([])
|
32
|
+
end
|
33
|
+
|
34
|
+
sig {overridable.returns(String)}
|
35
|
+
def details
|
36
|
+
name
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# typed: strict
|
2
|
+
require 'sorbet-runtime'
|
3
|
+
require 'spud/task_runners/task'
|
4
|
+
require 'spud/task_runners/spud_task_runner/task'
|
5
|
+
require 'spud/task_runners/make/task'
|
6
|
+
require 'spud/task_runners/package.json/task'
|
7
|
+
|
8
|
+
module Spud
|
9
|
+
module TaskRunners
|
10
|
+
extend T::Sig
|
11
|
+
|
12
|
+
sig {returns(T::Array[T.class_of(Task)])}
|
13
|
+
def self.get
|
14
|
+
# Ordered by priority
|
15
|
+
T.let(
|
16
|
+
[
|
17
|
+
T.let(SpudTaskRunner::Task, T.class_of(Task)),
|
18
|
+
T.let(Make::Task, T.class_of(Task)),
|
19
|
+
T.let(PackageJSON::Task, T.class_of(Task)),
|
20
|
+
],
|
21
|
+
T::Array[T.class_of(Task)]
|
22
|
+
)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/spud/version.rb
CHANGED
data/lib/spud/watch.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
# typed: true
|
2
|
+
require 'sorbet-runtime'
|
3
|
+
require 'spud/driver'
|
4
|
+
|
5
|
+
module Spud
|
6
|
+
class Watch
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
sig do
|
10
|
+
params(
|
11
|
+
driver: Driver,
|
12
|
+
task: String,
|
13
|
+
ordered: T::Array[String],
|
14
|
+
named: T::Hash[String, String],
|
15
|
+
watches: T::Array[String],
|
16
|
+
).void
|
17
|
+
end
|
18
|
+
def self.run!(driver:, task:, ordered:, named:, watches:)
|
19
|
+
new(driver: driver, task: task, ordered: ordered, named: named, watches: watches).run!
|
20
|
+
end
|
21
|
+
|
22
|
+
sig do
|
23
|
+
params(
|
24
|
+
driver: Driver,
|
25
|
+
task: String,
|
26
|
+
ordered: T::Array[String],
|
27
|
+
named: T::Hash[String, String],
|
28
|
+
watches: T::Array[String],
|
29
|
+
).void
|
30
|
+
end
|
31
|
+
def initialize(driver:, task:, ordered:, named:, watches:)
|
32
|
+
@driver = driver
|
33
|
+
@task = task
|
34
|
+
@ordered = ordered
|
35
|
+
@named = named
|
36
|
+
@watches = watches
|
37
|
+
|
38
|
+
@last_changed = Time.at(0)
|
39
|
+
end
|
40
|
+
|
41
|
+
sig {void}
|
42
|
+
def run!
|
43
|
+
thread = T.let(nil, T.nilable(Thread))
|
44
|
+
|
45
|
+
loop do
|
46
|
+
if watches_changed?
|
47
|
+
thread&.kill
|
48
|
+
Process.kill('SIGKILL', T.must(@driver.subprocess_pid)) if @driver.subprocess_pid
|
49
|
+
|
50
|
+
@last_changed = latest_watch_change
|
51
|
+
thread = Thread.new { @driver.invoke(@task, @ordered, @named) }
|
52
|
+
end
|
53
|
+
|
54
|
+
sleep(0.1)
|
55
|
+
end
|
56
|
+
rescue SystemExit, Interrupt => error
|
57
|
+
puts "handled interrupt #{error}" if @driver.debug?
|
58
|
+
end
|
59
|
+
|
60
|
+
sig {returns(T::Boolean)}
|
61
|
+
def watches_changed?
|
62
|
+
@last_changed < latest_watch_change
|
63
|
+
end
|
64
|
+
|
65
|
+
sig {returns(Time)}
|
66
|
+
def latest_watch_change
|
67
|
+
T.unsafe(Dir)[*@watches]
|
68
|
+
.map(&File.method(:stat))
|
69
|
+
.map(&:mtime)
|
70
|
+
.max
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|