spud 0.2.3 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) 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 +22 -19
  6. data/lib/spud/cli/results.rb +18 -11
  7. data/lib/spud/driver.rb +134 -0
  8. data/lib/spud/error.rb +1 -0
  9. data/lib/spud/help.rb +13 -14
  10. data/lib/spud/lister.rb +27 -22
  11. data/lib/spud/task_arg.rb +15 -12
  12. data/lib/spud/task_args.rb +20 -18
  13. data/lib/spud/task_runners/make/task.rb +66 -0
  14. data/lib/spud/task_runners/package.json/task.rb +70 -0
  15. data/lib/spud/{build_tools/spud → task_runners/spud_task_runner}/dependency.rb +3 -2
  16. data/lib/spud/task_runners/spud_task_runner/file_dsl.rb +56 -0
  17. data/lib/spud/task_runners/spud_task_runner/shell/command.rb +58 -0
  18. data/lib/spud/{build_tools/spud → task_runners/spud_task_runner}/shell/result.rb +9 -4
  19. data/lib/spud/task_runners/spud_task_runner/task.rb +143 -0
  20. data/lib/spud/task_runners/spud_task_runner/task_dsl.rb +82 -0
  21. data/lib/spud/task_runners/task.rb +43 -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 -17
  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/task.rb +0 -177
  34. data/lib/spud/build_tools/task.rb +0 -41
  35. data/lib/spud/options.rb +0 -14
  36. data/lib/spud/runtime.rb +0 -114
@@ -1,12 +1,17 @@
1
+ # typed: false
2
+ require 'sorbet-runtime'
3
+
1
4
  module Spud
2
- module BuildTools
3
- module Spud
5
+ module TaskRunners
6
+ module SpudTaskRunner
4
7
  module Shell
5
8
  class Result < String
9
+ extend T::Sig
10
+
11
+ sig {returns(Process::Status)}
6
12
  attr_reader :status
7
13
 
8
- # @param output [String]
9
- # @param status [Process::Status]
14
+ sig {params(output: String, status: Process::Status).void}
10
15
  def initialize(output, status)
11
16
  super(output)
12
17
  @status = status
@@ -0,0 +1,143 @@
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 {override.params(driver: Driver).returns(T::Array[TaskRunners::Task])}
26
+ def self.tasks(driver)
27
+ Dir['**/Spudfile', '**/*.spud'].flat_map { |filename| FileDSL.run(driver, filename) }
28
+ end
29
+
30
+ sig {params(filename: String, name: String).returns(String)}
31
+ def self.qualified_name(filename, name)
32
+ segments = File.dirname(filename)
33
+ .split('/')
34
+ .reject { |segment| segment == '.' }
35
+
36
+ basename = File.basename(filename, '.spud')
37
+ segments << basename unless basename == 'Spudfile'
38
+
39
+ segments << name
40
+ segments.join('.')
41
+ end
42
+
43
+ sig do
44
+ params(
45
+ driver: Driver,
46
+ name: String,
47
+ filename: String,
48
+ dependencies: T::Hash[T.any(String, T::Array[String]), T.any(String, T::Array[String])],
49
+ block: Proc,
50
+ )
51
+ .void
52
+ end
53
+ def initialize(driver:, name:, filename:, dependencies:, &block)
54
+ @driver = driver
55
+ @name = name
56
+ @filename = filename
57
+ @dependencies = dependencies.map { |to, from| Dependency.new(to, from) }
58
+ @block = block
59
+ end
60
+
61
+ sig {override.params(ordered: T::Array[String], named: T::Hash[String, String]).returns(T.untyped)}
62
+ def invoke(ordered, named)
63
+ if up_to_date?
64
+ puts "'#{name}' up to date"
65
+ return nil
66
+ end
67
+
68
+ check_required_args!(ordered)
69
+
70
+ if args.any_named?
71
+ T.unsafe(task_dsl).instance_exec(*ordered, **symbolize_keys(named), &@block)
72
+ else
73
+ T.unsafe(task_dsl).instance_exec(*ordered, &@block)
74
+ end
75
+ rescue ArgumentError => error
76
+ raise Error, "invocation of '#{name}' with #{error.message}"
77
+ end
78
+
79
+ sig {override.returns(TaskArgs)}
80
+ def args
81
+ @args ||= TaskArgs.from_block(filename, &@block)
82
+ end
83
+
84
+ sig {override.returns(String)}
85
+ def details
86
+ filename, line_cursor = @block.source_location
87
+ line_cursor -= 1
88
+
89
+ lines = File.read(filename).split("\n")
90
+ builder = StringIO.new
91
+
92
+ # Move up for comments
93
+ while lines[line_cursor - 1]&.start_with?('#')
94
+ line_cursor -= 1
95
+ end
96
+
97
+ # Capture comments
98
+ while lines[line_cursor]&.start_with?('#')
99
+ builder.puts lines[line_cursor]
100
+ line_cursor += 1
101
+ end
102
+
103
+ # Capture block
104
+ until lines[line_cursor - 1]&.start_with?('end')
105
+ builder.puts lines[line_cursor]
106
+ line_cursor += 1
107
+ end
108
+
109
+ builder.string
110
+ end
111
+
112
+ private
113
+
114
+ sig {params(ordered: T::Array[String]).void}
115
+ def check_required_args!(ordered)
116
+ required_ordered = args.required_ordered
117
+ missing_ordered = required_ordered.length - ordered.length
118
+ if missing_ordered > 0
119
+ arguments = required_ordered.length - missing_ordered > 1 ? 'arguments' : 'argument'
120
+ raise Error, "invocation of '#{name}' missing required #{arguments} #{required_ordered.join(', ')}"
121
+ end
122
+ end
123
+
124
+ sig {returns(T::Boolean)}
125
+ def up_to_date?
126
+ return false if @dependencies.empty?
127
+
128
+ @dependencies.all?(&:up_to_date?)
129
+ end
130
+
131
+ sig {returns(SpudTaskRunner::TaskDSL)}
132
+ def task_dsl
133
+ @task_dsl ||= TaskDSL.new(@driver, @filename)
134
+ end
135
+
136
+ sig {params(hash: T::Hash[T.any(String, Symbol), T.untyped]).returns(T::Hash[Symbol, T.untyped])}
137
+ def symbolize_keys(hash)
138
+ hash.each_with_object({}) { |(key, value), new_hash| new_hash[key.to_sym] = value }
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,82 @@
1
+ # typed: false
2
+ require 'sorbet-runtime'
3
+ require 'spud/error'
4
+ require 'spud/driver'
5
+ require 'spud/task_runners/spud_task_runner/shell/command'
6
+ require 'spud/task_runners/spud_task_runner/shell/result'
7
+
8
+ module Spud
9
+ module TaskRunners
10
+ module SpudTaskRunner
11
+ class TaskDSL
12
+ extend T::Sig
13
+
14
+ sig {params(driver: Driver, filename: String).void}
15
+ def initialize(driver, filename)
16
+ @__filename = filename
17
+ @__driver = driver
18
+ end
19
+
20
+ sig {params(command: String).returns(Shell::Result)}
21
+ def sh(command)
22
+ puts command
23
+ Shell::Command.(@__driver, command)
24
+ end
25
+
26
+ sig {params(command: String).returns(Shell::Result)}
27
+ def shh(command)
28
+ Shell::Command.(@__driver, command)
29
+ end
30
+
31
+ sig {params(command: String).returns(Shell::Result)}
32
+ def shhh(command)
33
+ Shell::Command.(@__driver, command, silent: true)
34
+ end
35
+
36
+ sig {params(command: String).returns(Shell::Result)}
37
+ def sh!(command)
38
+ puts command
39
+ result = Shell::Command.(@__driver, command)
40
+ raise Error, "sh failed for '#{command}'" unless result.success?
41
+ result
42
+ end
43
+
44
+ sig {params(command: String).returns(Shell::Result)}
45
+ def shh!(command)
46
+ result = Shell::Command.(@__driver, 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 shhh!(command)
53
+ result = Shell::Command.(@__driver, command, silent: true)
54
+ raise Error, "sh failed for '#{command}'" unless result.success?
55
+ result
56
+ end
57
+
58
+ sig {params(task: String, ordered: String, named: String).returns(T.untyped)}
59
+ def invoke(task, *ordered, **named)
60
+ task = task.to_s
61
+ task = task.include?('.') ? task : Task.qualified_name(@__filename, task)
62
+ @__driver.invoke(task, ordered, named)
63
+ rescue Error => error
64
+ puts error.message
65
+ end
66
+
67
+ sig {params(task: String, ordered: String, named: String).returns(T.untyped)}
68
+ def invoke!(task, *ordered, **named)
69
+ task = task.to_s
70
+ task = task.include?('.') ? task : Task.qualified_name(@__filename, task)
71
+ @__driver.invoke(task, ordered, named)
72
+ end
73
+
74
+ def method_missing(symbol, *ordered, **named)
75
+ task = symbol.to_s
76
+ task = task.include?('.') ? task : Task.qualified_name(@__filename, task)
77
+ @__driver.invoke(task, ordered, named)
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,43 @@
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(
25
+ ordered: T::Array[String],
26
+ named: T::Hash[String, String],
27
+ )
28
+ .returns(T.untyped)
29
+ end
30
+ def invoke(ordered, named); end
31
+
32
+ sig {overridable.returns(TaskArgs)}
33
+ def args
34
+ TaskArgs.new([])
35
+ end
36
+
37
+ sig {overridable.returns(String)}
38
+ def details
39
+ name
40
+ end
41
+ end
42
+ end
43
+ 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.3'
3
+ VERSION = '0.2.4'
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
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spud
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Booth
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-11-18 00:00:00.000000000 Z
11
+ date: 2020-11-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -52,6 +52,34 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '11.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: sorbet
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.5.6100
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.5.6100
69
+ - !ruby/object:Gem::Dependency
70
+ name: sorbet-runtime
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.5.6100
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.5.6100
55
83
  description:
56
84
  email: andrew@andrewbooth.xyz
57
85
  executables:
@@ -61,27 +89,28 @@ extra_rdoc_files: []
61
89
  files:
62
90
  - bin/spud
63
91
  - lib/spud.rb
64
- - lib/spud/build_tools/build_tools.rb
65
- - lib/spud/build_tools/make/task.rb
66
- - lib/spud/build_tools/package.json/task.rb
67
- - lib/spud/build_tools/spud/block_param_info.rb
68
- - lib/spud/build_tools/spud/dependency.rb
69
- - lib/spud/build_tools/spud/dsl/file.rb
70
- - lib/spud/build_tools/spud/dsl/task.rb
71
- - lib/spud/build_tools/spud/shell/command.rb
72
- - lib/spud/build_tools/spud/shell/result.rb
73
- - lib/spud/build_tools/spud/task.rb
74
- - lib/spud/build_tools/task.rb
92
+ - lib/spud/block_param_info.rb
93
+ - lib/spud/cli/options.rb
75
94
  - lib/spud/cli/parser.rb
76
95
  - lib/spud/cli/results.rb
96
+ - lib/spud/driver.rb
77
97
  - lib/spud/error.rb
78
98
  - lib/spud/help.rb
79
99
  - lib/spud/lister.rb
80
- - lib/spud/options.rb
81
- - lib/spud/runtime.rb
82
100
  - lib/spud/task_arg.rb
83
101
  - lib/spud/task_args.rb
102
+ - lib/spud/task_runners/make/task.rb
103
+ - lib/spud/task_runners/package.json/task.rb
104
+ - lib/spud/task_runners/spud_task_runner/dependency.rb
105
+ - lib/spud/task_runners/spud_task_runner/file_dsl.rb
106
+ - lib/spud/task_runners/spud_task_runner/shell/command.rb
107
+ - lib/spud/task_runners/spud_task_runner/shell/result.rb
108
+ - lib/spud/task_runners/spud_task_runner/task.rb
109
+ - lib/spud/task_runners/spud_task_runner/task_dsl.rb
110
+ - lib/spud/task_runners/task.rb
111
+ - lib/spud/task_runners/task_runners.rb
84
112
  - lib/spud/version.rb
113
+ - lib/spud/watch.rb
85
114
  homepage: https://github.com/broothie/spud#readme
86
115
  licenses:
87
116
  - MIT
@@ -92,9 +121,9 @@ require_paths:
92
121
  - lib
93
122
  required_ruby_version: !ruby/object:Gem::Requirement
94
123
  requirements:
95
- - - ">="
124
+ - - '='
96
125
  - !ruby/object:Gem::Version
97
- version: '0'
126
+ version: 2.3.0
98
127
  required_rubygems_version: !ruby/object:Gem::Requirement
99
128
  requirements:
100
129
  - - ">="