spud 0.2.1 → 0.2.6
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 +5 -5
- 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 +136 -0
- data/lib/spud/error.rb +6 -1
- data/lib/spud/help.rb +17 -14
- 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 +50 -0
- data/lib/spud/task_runners/spud_task_runner/file_dsl.rb +57 -0
- data/lib/spud/task_runners/spud_task_runner/task.rb +149 -0
- data/lib/spud/task_runners/spud_task_runner/task_dsl.rb +95 -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 +51 -20
- 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,71 @@
|
|
1
|
+
# typed: strict
|
2
|
+
require 'sorbet-runtime'
|
3
|
+
require 'json'
|
4
|
+
require 'spud/driver'
|
5
|
+
require 'spud/task_args'
|
6
|
+
require 'spud/shell/command'
|
7
|
+
require 'spud/task_runners/task'
|
8
|
+
|
9
|
+
module Spud
|
10
|
+
module TaskRunners
|
11
|
+
module PackageJSON
|
12
|
+
class Task < TaskRunners::Task
|
13
|
+
extend T::Sig
|
14
|
+
|
15
|
+
sig {override.returns(String)}
|
16
|
+
attr_reader :name
|
17
|
+
|
18
|
+
sig {override.params(driver: Driver).returns(T::Array[TaskRunners::Task])}
|
19
|
+
def self.tasks(driver)
|
20
|
+
if File.exist?('package.lock')
|
21
|
+
if `command -v npm`.empty?
|
22
|
+
puts 'package.json detected, but no installation of `npm` exists. Skipping npm...'
|
23
|
+
return []
|
24
|
+
else
|
25
|
+
command = 'npm'
|
26
|
+
end
|
27
|
+
elsif File.exist?('yarn.lock')
|
28
|
+
if `command -v yarn`.empty?
|
29
|
+
puts 'package.json detected, but no installation of `yarn` exists. Skipping yarn...'
|
30
|
+
return []
|
31
|
+
else
|
32
|
+
command = 'yarn'
|
33
|
+
end
|
34
|
+
else
|
35
|
+
return []
|
36
|
+
end
|
37
|
+
|
38
|
+
source = File.read('package.json')
|
39
|
+
json = JSON.parse(source)
|
40
|
+
scripts = json['scripts']
|
41
|
+
return [] unless scripts
|
42
|
+
|
43
|
+
scripts.keys.map { |name| new(driver, name, command, scripts) }
|
44
|
+
end
|
45
|
+
|
46
|
+
sig {params(driver: Driver, name: String, command: String, scripts: T::Hash[String, String]).void}
|
47
|
+
def initialize(driver, name, command, scripts)
|
48
|
+
@driver = driver
|
49
|
+
@name = name
|
50
|
+
@command = command
|
51
|
+
@scripts = scripts
|
52
|
+
end
|
53
|
+
|
54
|
+
sig {override.params(ordered: T::Array[String], named: T::Hash[String, String]).returns(T.untyped)}
|
55
|
+
def invoke(ordered, named)
|
56
|
+
Shell::Command.("#{@command} run #{name}", driver: @driver)
|
57
|
+
end
|
58
|
+
|
59
|
+
sig {override.returns(String)}
|
60
|
+
def filename
|
61
|
+
'package.json'
|
62
|
+
end
|
63
|
+
|
64
|
+
sig {override.returns(String)}
|
65
|
+
def details
|
66
|
+
%({ "#{name}": "#{@scripts[name]}" })
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# typed: true
|
2
|
+
require 'sorbet-runtime'
|
3
|
+
|
4
|
+
module Spud
|
5
|
+
module TaskRunners
|
6
|
+
module SpudTaskRunner
|
7
|
+
class Dependency
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
sig {returns(T::Array[String])}
|
11
|
+
attr_reader :sources
|
12
|
+
|
13
|
+
sig {returns(T::Array[String])}
|
14
|
+
attr_reader :targets
|
15
|
+
|
16
|
+
sig {params(source: T.any(String, T::Array[String]), target: T.any(String, T::Array[String])).void}
|
17
|
+
def initialize(source, target)
|
18
|
+
@sources = [source].flatten
|
19
|
+
@targets = [target].flatten
|
20
|
+
end
|
21
|
+
|
22
|
+
sig {returns(T::Boolean)}
|
23
|
+
def need_to_update?
|
24
|
+
!up_to_date?
|
25
|
+
end
|
26
|
+
|
27
|
+
sig {returns(T::Boolean)}
|
28
|
+
def up_to_date?
|
29
|
+
source_filenames = Dir[*@sources]
|
30
|
+
return true if source_filenames.empty?
|
31
|
+
|
32
|
+
newest_source = source_filenames
|
33
|
+
.map(&File.method(:stat))
|
34
|
+
.map(&:mtime)
|
35
|
+
.max
|
36
|
+
|
37
|
+
target_filenames = Dir[*@targets]
|
38
|
+
return false if target_filenames.empty?
|
39
|
+
|
40
|
+
oldest_target = target_filenames
|
41
|
+
.map(&File.method(:stat))
|
42
|
+
.map(&:mtime)
|
43
|
+
.min
|
44
|
+
|
45
|
+
newest_source < oldest_target
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
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,149 @@
|
|
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
|
+
raise Error, "'#{name}' up to date"
|
70
|
+
end
|
71
|
+
|
72
|
+
check_required_args!(ordered)
|
73
|
+
|
74
|
+
catch :halt do
|
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
|
+
end
|
81
|
+
rescue ArgumentError => error
|
82
|
+
raise Error, "invocation of '#{name}' with #{error.message}"
|
83
|
+
end
|
84
|
+
|
85
|
+
sig {override.returns(TaskArgs)}
|
86
|
+
def args
|
87
|
+
@args ||= TaskArgs.from_block(filename, &@block)
|
88
|
+
end
|
89
|
+
|
90
|
+
sig {override.returns(String)}
|
91
|
+
def details
|
92
|
+
filename, line_cursor = @block.source_location
|
93
|
+
line_cursor -= 1
|
94
|
+
|
95
|
+
lines = File.read(filename).split("\n")
|
96
|
+
builder = StringIO.new
|
97
|
+
|
98
|
+
# Move up for comments
|
99
|
+
while lines[line_cursor - 1]&.start_with?('#')
|
100
|
+
line_cursor -= 1
|
101
|
+
end
|
102
|
+
|
103
|
+
# Capture comments
|
104
|
+
while lines[line_cursor]&.start_with?('#')
|
105
|
+
builder.puts lines[line_cursor]
|
106
|
+
line_cursor += 1
|
107
|
+
end
|
108
|
+
|
109
|
+
# Capture block
|
110
|
+
until lines[line_cursor - 1]&.start_with?('end')
|
111
|
+
builder.puts lines[line_cursor]
|
112
|
+
line_cursor += 1
|
113
|
+
end
|
114
|
+
|
115
|
+
builder.string
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
sig {params(ordered: T::Array[String]).void}
|
121
|
+
def check_required_args!(ordered)
|
122
|
+
required_ordered = args.required_ordered
|
123
|
+
missing_ordered = required_ordered.length - ordered.length
|
124
|
+
if missing_ordered > 0
|
125
|
+
arguments = required_ordered.length - missing_ordered > 1 ? 'arguments' : 'argument'
|
126
|
+
raise Error, "invocation of '#{name}' missing required #{arguments} #{required_ordered.join(', ')}"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
sig {returns(T::Boolean)}
|
131
|
+
def up_to_date?
|
132
|
+
return false if @dependencies.empty?
|
133
|
+
|
134
|
+
@dependencies.all?(&:up_to_date?)
|
135
|
+
end
|
136
|
+
|
137
|
+
sig {returns(TaskDSL)}
|
138
|
+
def task_dsl
|
139
|
+
@task_dsl ||= TaskDSL.new(@driver, @filename, @file_dsl)
|
140
|
+
end
|
141
|
+
|
142
|
+
sig {params(hash: T::Hash[T.any(String, Symbol), T.untyped]).returns(T::Hash[Symbol, T.untyped])}
|
143
|
+
def symbolize_keys(hash)
|
144
|
+
hash.each_with_object({}) { |(key, value), new_hash| new_hash[key.to_sym] = value }
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,95 @@
|
|
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(value: T.untyped).void}
|
28
|
+
def halt(value = nil)
|
29
|
+
value ? throw(:halt, value) : throw(:halt)
|
30
|
+
end
|
31
|
+
|
32
|
+
sig {params(command: String).returns(Shell::Result)}
|
33
|
+
def sh(command)
|
34
|
+
puts command
|
35
|
+
@__commander.(command)
|
36
|
+
end
|
37
|
+
|
38
|
+
sig {params(command: String).returns(Shell::Result)}
|
39
|
+
def shh(command)
|
40
|
+
@__commander.(command)
|
41
|
+
end
|
42
|
+
|
43
|
+
sig {params(command: String).returns(Shell::Result)}
|
44
|
+
def shhh(command)
|
45
|
+
@__commander.(command, silent: true)
|
46
|
+
end
|
47
|
+
|
48
|
+
sig {params(command: String).returns(Shell::Result)}
|
49
|
+
def sh!(command)
|
50
|
+
puts command
|
51
|
+
result = @__commander.(command)
|
52
|
+
raise Error, "sh failed for '#{command}'" unless result.success?
|
53
|
+
result
|
54
|
+
end
|
55
|
+
|
56
|
+
sig {params(command: String).returns(Shell::Result)}
|
57
|
+
def shh!(command)
|
58
|
+
result = @__commander.(command)
|
59
|
+
raise Error, "sh failed for '#{command}'" unless result.success?
|
60
|
+
result
|
61
|
+
end
|
62
|
+
|
63
|
+
sig {params(command: String).returns(Shell::Result)}
|
64
|
+
def shhh!(command)
|
65
|
+
result = @__commander.(command, silent: true)
|
66
|
+
raise Error, "sh failed for '#{command}'" unless result.success?
|
67
|
+
result
|
68
|
+
end
|
69
|
+
|
70
|
+
sig {params(task: String, ordered: String, named: String).returns(T.untyped)}
|
71
|
+
def invoke(task, *ordered, **named)
|
72
|
+
task = task.to_s
|
73
|
+
task = task.include?('.') ? task : Task.qualified_name(@__filename, task)
|
74
|
+
@__driver.invoke(task, ordered, named)
|
75
|
+
rescue Error => error
|
76
|
+
puts error.message
|
77
|
+
end
|
78
|
+
|
79
|
+
sig {params(task: String, ordered: String, named: String).returns(T.untyped)}
|
80
|
+
def invoke!(task, *ordered, **named)
|
81
|
+
task = task.to_s
|
82
|
+
task = task.include?('.') ? task : Task.qualified_name(@__filename, task)
|
83
|
+
@__driver.invoke(task, ordered, named)
|
84
|
+
end
|
85
|
+
|
86
|
+
sig {params(task: Symbol, ordered: String, named: String).returns(T.untyped)}
|
87
|
+
def method_missing(task, *ordered, **named)
|
88
|
+
task = task.to_s
|
89
|
+
task = task.include?('.') ? task : Task.qualified_name(@__filename, task)
|
90
|
+
@__driver.invoke(task, ordered, named)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
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
|