spud 0.2.0 → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
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