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.
Files changed (37) hide show
  1. checksums.yaml +5 -5
  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 +136 -0
  8. data/lib/spud/error.rb +6 -1
  9. data/lib/spud/help.rb +17 -14
  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 +50 -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 +149 -0
  20. data/lib/spud/task_runners/spud_task_runner/task_dsl.rb +95 -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 +51 -20
  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,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