spud 0.2.1 → 0.2.6

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 +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