asgard 0.2.0 → 0.3.1
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.
- checksums.yaml +4 -4
- data/.loki +13 -26
- data/.rubocop.yml +157 -0
- data/CHANGELOG.md +89 -11
- data/CLAUDE.md +19 -9
- data/README.md +161 -54
- data/docs/api.md +91 -15
- data/docs/changelog.md +12 -0
- data/docs/dependencies.md +25 -25
- data/docs/environment.md +30 -14
- data/docs/examples.md +3 -3
- data/docs/getting-started.md +5 -6
- data/docs/helpers.md +117 -9
- data/docs/index.md +6 -6
- data/docs/options.md +42 -8
- data/docs/shell.md +11 -11
- data/docs/subcommands.md +9 -9
- data/docs/task-files.md +266 -113
- data/docs/tasks.md +17 -15
- data/docs/variables.md +267 -51
- data/examples/.env +4 -0
- data/examples/.loki +24 -2
- data/examples/concurrent.loki +5 -5
- data/examples/db_subcommands.loki +3 -3
- data/examples/env_usage.loki +27 -0
- data/examples/kitchen_sink.loki +48 -15
- data/examples/server_subcommands.loki +33 -24
- data/examples/subdir/.loki +12 -0
- data/examples/subdir/import_demo.loki +14 -0
- data/examples/subdir/import_up_demo.loki +18 -0
- data/gem_tasks.loki +35 -0
- data/lib/asgard/base.rb +158 -80
- data/lib/asgard/kernel_methods.rb +79 -0
- data/lib/asgard/shell.rb +8 -7
- data/lib/asgard/tasks.rb +12 -17
- data/lib/asgard/version.rb +1 -1
- data/lib/asgard.rb +6 -18
- data/quality.loki +75 -0
- metadata +15 -5
- data/Rakefile +0 -22
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
|
|
5
|
+
module Kernel
|
|
6
|
+
def debug? = $DEBUG
|
|
7
|
+
def verbose? = $VERBOSE
|
|
8
|
+
module_function :debug?, :verbose?
|
|
9
|
+
|
|
10
|
+
# Fetch an environment variable by symbol or string name.
|
|
11
|
+
# The name is converted to an uppercase string automatically.
|
|
12
|
+
# Raises KeyError when the variable is missing and no default is given.
|
|
13
|
+
def env(name, default = nil)
|
|
14
|
+
key = name.to_s.upcase
|
|
15
|
+
default.nil? ? ENV.fetch(key) : ENV.fetch(key, default)
|
|
16
|
+
end
|
|
17
|
+
module_function :env
|
|
18
|
+
|
|
19
|
+
def loki_up(name = ".loki")
|
|
20
|
+
dir = Pathname.new(Dir.pwd)
|
|
21
|
+
loop do
|
|
22
|
+
candidate = dir + name
|
|
23
|
+
return candidate if candidate.exist?
|
|
24
|
+
parent = dir.parent
|
|
25
|
+
break if parent == dir
|
|
26
|
+
dir = parent
|
|
27
|
+
end
|
|
28
|
+
nil
|
|
29
|
+
end
|
|
30
|
+
module_function :loki_up
|
|
31
|
+
|
|
32
|
+
def import(path)
|
|
33
|
+
path = path.to_s
|
|
34
|
+
raise ArgumentError, "import: path must end with .loki (got #{path.inspect})" unless path.end_with?(".loki")
|
|
35
|
+
unless File.absolute_path?(path)
|
|
36
|
+
caller_dir = File.dirname(caller_locations(1, 1).first.absolute_path)
|
|
37
|
+
path = File.expand_path(path, caller_dir)
|
|
38
|
+
end
|
|
39
|
+
paths = path =~ /[*?\[{]/ ? Dir.glob(path) : [path]
|
|
40
|
+
loaded = paths.map do |p|
|
|
41
|
+
if $LOADED_FEATURES.include?(p)
|
|
42
|
+
warn "import: skip #{p} (already loaded)" if debug?
|
|
43
|
+
next false
|
|
44
|
+
end
|
|
45
|
+
warn "import: #{p}" if verbose? || debug?
|
|
46
|
+
load p
|
|
47
|
+
$LOADED_FEATURES << p
|
|
48
|
+
true
|
|
49
|
+
end
|
|
50
|
+
loaded.any?
|
|
51
|
+
end
|
|
52
|
+
module_function :import
|
|
53
|
+
|
|
54
|
+
def import_up(name = ".loki")
|
|
55
|
+
if name =~ /[*?\[{]/
|
|
56
|
+
dir = Dir.pwd
|
|
57
|
+
loop do
|
|
58
|
+
matches = Dir.glob(File.join(dir, name))
|
|
59
|
+
unless matches.empty?
|
|
60
|
+
warn "import_up: #{name} → #{dir}" if verbose? || debug?
|
|
61
|
+
return matches.map { |p| import(p) }.any?
|
|
62
|
+
end
|
|
63
|
+
parent = File.dirname(dir)
|
|
64
|
+
break if parent == dir
|
|
65
|
+
dir = parent
|
|
66
|
+
end
|
|
67
|
+
warn "import_up: #{name} not found" if debug?
|
|
68
|
+
return false
|
|
69
|
+
end
|
|
70
|
+
path = loki_up(name)
|
|
71
|
+
unless path
|
|
72
|
+
warn "import_up: #{name} not found" if debug?
|
|
73
|
+
return false
|
|
74
|
+
end
|
|
75
|
+
warn "import_up: #{name} → #{path}" if verbose? || debug?
|
|
76
|
+
import path
|
|
77
|
+
end
|
|
78
|
+
module_function :import_up
|
|
79
|
+
end
|
data/lib/asgard/shell.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'English'
|
|
3
4
|
require "tempfile"
|
|
4
5
|
|
|
5
6
|
module Asgard
|
|
@@ -12,12 +13,12 @@ module Asgard
|
|
|
12
13
|
$stdout.puts script unless silent
|
|
13
14
|
|
|
14
15
|
success = if script.include?("\n")
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
system("bash", "-c", script)
|
|
17
|
+
else
|
|
18
|
+
system(script)
|
|
19
|
+
end
|
|
19
20
|
|
|
20
|
-
exit(
|
|
21
|
+
exit($CHILD_STATUS.exitstatus) unless success
|
|
21
22
|
end
|
|
22
23
|
|
|
23
24
|
# Write +script+ to a tempfile and execute it with +interpreter+.
|
|
@@ -34,11 +35,11 @@ module Asgard
|
|
|
34
35
|
|
|
35
36
|
$stdout.puts script unless silent
|
|
36
37
|
|
|
37
|
-
|
|
38
|
+
Tempfile.create(["asgard_", ext]) do |f|
|
|
38
39
|
f.write(script)
|
|
39
40
|
f.flush
|
|
40
41
|
system(interpreter.to_s, f.path)
|
|
41
|
-
exit(
|
|
42
|
+
exit($CHILD_STATUS.exitstatus) unless $CHILD_STATUS.success?
|
|
42
43
|
end
|
|
43
44
|
end
|
|
44
45
|
end
|
data/lib/asgard/tasks.rb
CHANGED
|
@@ -4,6 +4,13 @@
|
|
|
4
4
|
# It is pre-defined by the gem so .loki files never need to declare a class.
|
|
5
5
|
# Auxiliary *.loki files define modules which are imported into Tasks.
|
|
6
6
|
class Tasks < Asgard::Base
|
|
7
|
+
header "\nasgard v#{Asgard::VERSION} The Mighty Thor and Loki working for you"
|
|
8
|
+
|
|
9
|
+
footer <<~FOOT
|
|
10
|
+
\nDocumentation ... https://madbomber.github.io/asgard
|
|
11
|
+
Github Repo ..... https://github.com/MadBomber/asgard\n
|
|
12
|
+
FOOT
|
|
13
|
+
|
|
7
14
|
class_option :debug,
|
|
8
15
|
type: :boolean,
|
|
9
16
|
default: false,
|
|
@@ -14,21 +21,9 @@ class Tasks < Asgard::Base
|
|
|
14
21
|
default: false,
|
|
15
22
|
desc: "Enable verbose output ($VERBOSE = true)"
|
|
16
23
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
desc "--version", "Show asgard version"
|
|
24
|
-
map "--version" => :_version
|
|
25
|
-
def _version
|
|
26
|
-
puts Asgard::VERSION
|
|
27
|
-
exit
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
private
|
|
31
|
-
|
|
32
|
-
def debug? = $DEBUG
|
|
33
|
-
def verbose? = $VERBOSE
|
|
24
|
+
class_option :version,
|
|
25
|
+
type: :boolean,
|
|
26
|
+
default: false,
|
|
27
|
+
desc: "Show asgard version and exit"
|
|
28
|
+
no_negate :version
|
|
34
29
|
end
|
data/lib/asgard/version.rb
CHANGED
data/lib/asgard.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "asgard/version"
|
|
4
|
+
require_relative "asgard/kernel_methods"
|
|
4
5
|
require_relative "asgard/shell"
|
|
5
6
|
require_relative "asgard/base"
|
|
6
7
|
require_relative "asgard/tasks"
|
|
@@ -12,31 +13,18 @@ module Asgard
|
|
|
12
13
|
# Search the current directory and its ancestors for a .loki task file.
|
|
13
14
|
# Returns the path string, or nil if not found.
|
|
14
15
|
def self.find_task_file
|
|
15
|
-
|
|
16
|
-
loop do
|
|
17
|
-
candidate = File.join(dir, ".loki")
|
|
18
|
-
return candidate if File.exist?(candidate)
|
|
19
|
-
parent = File.dirname(dir)
|
|
20
|
-
break if parent == dir
|
|
21
|
-
dir = parent
|
|
22
|
-
end
|
|
23
|
-
nil
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
# Load all *.loki files from dir in alphabetical order.
|
|
27
|
-
# Each file typically reopens class Tasks to add tasks.
|
|
28
|
-
# The .loki entry point is excluded — it is loaded separately by run!.
|
|
29
|
-
def self.load_loki(dir)
|
|
30
|
-
Dir.glob(File.join(dir, "*.loki")).sort.each { |f| load f }
|
|
16
|
+
loki_up
|
|
31
17
|
end
|
|
32
18
|
|
|
33
19
|
# Main entry point invoked by the asgard executable.
|
|
34
20
|
def self.run!(argv)
|
|
35
|
-
auto_load = argv.delete("--auto-load")
|
|
36
21
|
abort "asgard: unknown command '#{argv.first}'" if argv.first&.start_with?("_")
|
|
22
|
+
if argv.include?("--version")
|
|
23
|
+
puts Asgard::VERSION
|
|
24
|
+
exit
|
|
25
|
+
end
|
|
37
26
|
task_file = find_task_file or abort "asgard: no .loki file found in #{Dir.pwd}"
|
|
38
27
|
before = Asgard::Base.subclasses.dup
|
|
39
|
-
load_loki(File.dirname(task_file)) if auto_load
|
|
40
28
|
load task_file
|
|
41
29
|
newly_defined = Asgard::Base.subclasses - before
|
|
42
30
|
(newly_defined + [Tasks]).uniq.each(&:validate_deps!)
|
data/quality.loki
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# Quality gate tasks — imported by .loki
|
|
3
|
+
|
|
4
|
+
class Tasks
|
|
5
|
+
desc "Run the test suite"
|
|
6
|
+
def test
|
|
7
|
+
output = `bundle exec ruby -Ilib:test test/test_asgard.rb 2>&1`
|
|
8
|
+
@test_result = $?.success? ? :pass : :fail
|
|
9
|
+
if @test_result == :fail
|
|
10
|
+
lines = output.lines
|
|
11
|
+
start = lines.index { |l| l =~ /^\s+1\) / } || 0
|
|
12
|
+
finish = lines.rindex { |l| l =~ /\d+ runs,/ } || -1
|
|
13
|
+
print lines[start..finish].join
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
desc "Run all quality gates (tests, RuboCop, Flog) in parallel"
|
|
18
|
+
depends_on [:test, :rubocop, :flog_check]
|
|
19
|
+
def quality
|
|
20
|
+
results = { tests: @test_result, rubocop: @rubocop_result, flog: @flog_result }
|
|
21
|
+
|
|
22
|
+
if @flog_failures&.any?
|
|
23
|
+
puts "\nFlog failures (must be refactored):"
|
|
24
|
+
@flog_failures.each { |v| puts " #{v}" }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
puts "\n#{"=" * 60}"
|
|
28
|
+
puts "Quality Summary"
|
|
29
|
+
puts "=" * 60
|
|
30
|
+
results.each { |gate, status| puts " [#{status == :pass ? "PASS" : "FAIL"}] #{gate}" }
|
|
31
|
+
puts "=" * 60
|
|
32
|
+
|
|
33
|
+
abort "\nQuality gate failed" if results.values.any?(:fail)
|
|
34
|
+
puts "\nAll quality gates passed."
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
desc "Check code style with RuboCop"
|
|
38
|
+
def rubocop
|
|
39
|
+
output = `RUBOCOP_CACHE_ROOT=tmp/rubocop_cache bundle exec rubocop 2>&1`
|
|
40
|
+
@rubocop_result = $?.success? ? :pass : :fail
|
|
41
|
+
if @rubocop_result == :fail
|
|
42
|
+
output.each_line { |l| print l if l =~ /:\d+:\d+: [CWEF]: / }
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
desc "Auto-correct RuboCop offenses"
|
|
47
|
+
def rubocop_fix
|
|
48
|
+
sh "RUBOCOP_CACHE_ROOT=tmp/rubocop_cache bundle exec rubocop -a"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
desc "Check code complexity with Flog (warn >=20, fail >=50)"
|
|
52
|
+
def flog_check
|
|
53
|
+
require "flog"
|
|
54
|
+
|
|
55
|
+
method_warn = 20.0
|
|
56
|
+
method_fail = 50.0
|
|
57
|
+
|
|
58
|
+
flogger = Flog.new(all: true)
|
|
59
|
+
flogger.flog(*Dir.glob("lib/**/*.rb"))
|
|
60
|
+
|
|
61
|
+
warnings = []
|
|
62
|
+
@flog_failures = []
|
|
63
|
+
|
|
64
|
+
flogger.each_by_score do |method_name, score|
|
|
65
|
+
next if method_name.end_with?("#none")
|
|
66
|
+
if score > method_fail
|
|
67
|
+
@flog_failures << "#{"%.1f" % score}: #{method_name}"
|
|
68
|
+
elsif score > method_warn
|
|
69
|
+
warnings << "#{"%.1f" % score}: #{method_name}"
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
@flog_result = @flog_failures.empty? ? :pass : :fail
|
|
74
|
+
end
|
|
75
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: asgard
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dewayne VanHoozer
|
|
@@ -51,9 +51,10 @@ dependencies:
|
|
|
51
51
|
- - "~>"
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
53
|
version: '3.0'
|
|
54
|
-
description:
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
description: |
|
|
55
|
+
A powerful Ruby-based task runner for any kind of project with task dependency tracking
|
|
56
|
+
and concurrent execution of designated tasks. Uses Thor for its rich CLI options, var
|
|
57
|
+
declarations, dotenv, sh/shebang helpers, and importable task files.
|
|
57
58
|
email:
|
|
58
59
|
- dewayne@vanhoozer.me
|
|
59
60
|
executables:
|
|
@@ -64,12 +65,12 @@ files:
|
|
|
64
65
|
- ".envrc"
|
|
65
66
|
- ".github/workflows/deploy-github-pages.yml"
|
|
66
67
|
- ".loki"
|
|
68
|
+
- ".rubocop.yml"
|
|
67
69
|
- CHANGELOG.md
|
|
68
70
|
- CLAUDE.md
|
|
69
71
|
- COMMITS.md
|
|
70
72
|
- LICENSE.txt
|
|
71
73
|
- README.md
|
|
72
|
-
- Rakefile
|
|
73
74
|
- bin/asgard
|
|
74
75
|
- bin/console
|
|
75
76
|
- bin/setup
|
|
@@ -89,17 +90,25 @@ files:
|
|
|
89
90
|
- docs/task-files.md
|
|
90
91
|
- docs/tasks.md
|
|
91
92
|
- docs/variables.md
|
|
93
|
+
- examples/.env
|
|
92
94
|
- examples/.loki
|
|
93
95
|
- examples/concurrent.loki
|
|
94
96
|
- examples/db_subcommands.loki
|
|
97
|
+
- examples/env_usage.loki
|
|
95
98
|
- examples/kitchen_sink.loki
|
|
96
99
|
- examples/server_subcommands.loki
|
|
100
|
+
- examples/subdir/.loki
|
|
101
|
+
- examples/subdir/import_demo.loki
|
|
102
|
+
- examples/subdir/import_up_demo.loki
|
|
103
|
+
- gem_tasks.loki
|
|
97
104
|
- lib/asgard.rb
|
|
98
105
|
- lib/asgard/base.rb
|
|
106
|
+
- lib/asgard/kernel_methods.rb
|
|
99
107
|
- lib/asgard/shell.rb
|
|
100
108
|
- lib/asgard/tasks.rb
|
|
101
109
|
- lib/asgard/version.rb
|
|
102
110
|
- mkdocs.yml
|
|
111
|
+
- quality.loki
|
|
103
112
|
- sig/asgard.rbs
|
|
104
113
|
homepage: https://github.com/madbomber/asgard
|
|
105
114
|
licenses:
|
|
@@ -108,6 +117,7 @@ metadata:
|
|
|
108
117
|
homepage_uri: https://github.com/madbomber/asgard
|
|
109
118
|
source_code_uri: https://github.com/madbomber/asgard
|
|
110
119
|
changelog_uri: https://github.com/madbomber/asgard/blob/master/CHANGELOG.md
|
|
120
|
+
rubygems_mfa_required: 'true'
|
|
111
121
|
rdoc_options: []
|
|
112
122
|
require_paths:
|
|
113
123
|
- lib
|
data/Rakefile
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "bundler/gem_tasks"
|
|
4
|
-
require "minitest/test_task"
|
|
5
|
-
|
|
6
|
-
SIMPLECOV_PRELUDE = <<~RUBY.freeze
|
|
7
|
-
require "simplecov"
|
|
8
|
-
SimpleCov.start do
|
|
9
|
-
add_filter "/test/"
|
|
10
|
-
minimum_coverage 95
|
|
11
|
-
end
|
|
12
|
-
RUBY
|
|
13
|
-
|
|
14
|
-
Minitest::TestTask.create do |t|
|
|
15
|
-
t.test_prelude = SIMPLECOV_PRELUDE
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
task quality: :test do
|
|
19
|
-
sh "flog lib/"
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
task default: :test
|