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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/lib/spud.rb +3 -2
  3. data/lib/spud/block_param_info.rb +80 -0
  4. data/lib/spud/cli/options.rb +35 -0
  5. data/lib/spud/cli/parser.rb +23 -19
  6. data/lib/spud/cli/results.rb +18 -11
  7. data/lib/spud/driver.rb +135 -0
  8. data/lib/spud/error.rb +1 -0
  9. data/lib/spud/help.rb +13 -13
  10. data/lib/spud/lister.rb +27 -22
  11. data/lib/spud/shell/command.rb +75 -0
  12. data/lib/spud/shell/result.rb +33 -0
  13. data/lib/spud/task_arg.rb +24 -12
  14. data/lib/spud/task_args.rb +20 -18
  15. data/lib/spud/task_runners/make/task.rb +67 -0
  16. data/lib/spud/task_runners/package.json/task.rb +71 -0
  17. data/lib/spud/task_runners/spud_task_runner/dependency.rb +49 -0
  18. data/lib/spud/task_runners/spud_task_runner/file_dsl.rb +57 -0
  19. data/lib/spud/task_runners/spud_task_runner/task.rb +148 -0
  20. data/lib/spud/task_runners/spud_task_runner/task_dsl.rb +90 -0
  21. data/lib/spud/task_runners/task.rb +40 -0
  22. data/lib/spud/task_runners/task_runners.rb +25 -0
  23. data/lib/spud/version.rb +2 -1
  24. data/lib/spud/watch.rb +73 -0
  25. metadata +46 -16
  26. data/lib/spud/build_tools/build_tools.rb +0 -13
  27. data/lib/spud/build_tools/make/task.rb +0 -27
  28. data/lib/spud/build_tools/package.json/task.rb +0 -47
  29. data/lib/spud/build_tools/spud/block_param_info.rb +0 -77
  30. data/lib/spud/build_tools/spud/dsl/file.rb +0 -23
  31. data/lib/spud/build_tools/spud/dsl/task.rb +0 -63
  32. data/lib/spud/build_tools/spud/shell/command.rb +0 -52
  33. data/lib/spud/build_tools/spud/shell/result.rb +0 -26
  34. data/lib/spud/build_tools/spud/task.rb +0 -125
  35. data/lib/spud/build_tools/task.rb +0 -33
  36. data/lib/spud/options.rb +0 -14
  37. 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
@@ -1,3 +1,4 @@
1
+ # typed: strict
1
2
  module Spud
2
- VERSION = '0.2.0'
3
+ VERSION = '0.2.5'
3
4
  end
@@ -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