sod 0.0.0
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 +7 -0
- checksums.yaml.gz.sig +2 -0
- data/LICENSE.adoc +134 -0
- data/README.adoc +952 -0
- data/lib/sod/action.rb +84 -0
- data/lib/sod/command.rb +74 -0
- data/lib/sod/container.rb +18 -0
- data/lib/sod/context.rb +33 -0
- data/lib/sod/error.rb +7 -0
- data/lib/sod/graph/loader.rb +40 -0
- data/lib/sod/graph/node.rb +107 -0
- data/lib/sod/graph/runner.rb +71 -0
- data/lib/sod/import.rb +7 -0
- data/lib/sod/models/action.rb +38 -0
- data/lib/sod/models/command.rb +11 -0
- data/lib/sod/prefabs/actions/config/create.rb +76 -0
- data/lib/sod/prefabs/actions/config/delete.rb +60 -0
- data/lib/sod/prefabs/actions/config/edit.rb +47 -0
- data/lib/sod/prefabs/actions/config/view.rb +47 -0
- data/lib/sod/prefabs/actions/help.rb +34 -0
- data/lib/sod/prefabs/actions/version.rb +27 -0
- data/lib/sod/prefabs/commands/config.rb +21 -0
- data/lib/sod/presenters/action.rb +54 -0
- data/lib/sod/presenters/node.rb +95 -0
- data/lib/sod/refines/option_parsers.rb +23 -0
- data/lib/sod/shell.rb +33 -0
- data/lib/sod/types/pathname.rb +6 -0
- data/lib/sod.rb +13 -0
- data/sod.gemspec +35 -0
- data.tar.gz.sig +4 -0
- metadata +190 -0
- metadata.gz.sig +0 -0
data/lib/sod/action.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "forwardable"
|
4
|
+
|
5
|
+
module Sod
|
6
|
+
# A generic action (and DSL) from which to inherit and build custom actions from.
|
7
|
+
# :reek:TooManyInstanceVariables
|
8
|
+
class Action
|
9
|
+
extend Forwardable
|
10
|
+
|
11
|
+
def self.inherited base
|
12
|
+
super
|
13
|
+
base.class_eval { @attributes = {} }
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.description text
|
17
|
+
@description ? fail(Error, "Description can only be defined once.") : @description = text
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.ancillary(*lines)
|
21
|
+
@ancillary ? fail(Error, "Ancillary can only be defined once.") : @ancillary = lines
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.on(aliases, **keywords)
|
25
|
+
fail Error, "On can only be defined once." if @attributes.any?
|
26
|
+
|
27
|
+
@attributes.merge! keywords, aliases: Array(aliases)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.default &block
|
31
|
+
@default ? fail(Error, "Default can only be defined once.") : @default = block
|
32
|
+
end
|
33
|
+
|
34
|
+
delegate [*Models::Action.members, :handle, :to_a, :to_h] => :record
|
35
|
+
|
36
|
+
attr_reader :record
|
37
|
+
|
38
|
+
def initialize context: Context::EMPTY, model: Models::Action
|
39
|
+
klass = self.class
|
40
|
+
|
41
|
+
@context = context
|
42
|
+
|
43
|
+
@record = model[
|
44
|
+
**klass.instance_variable_get(:@attributes),
|
45
|
+
description: klass.instance_variable_get(:@description),
|
46
|
+
ancillary: Array(klass.instance_variable_get(:@ancillary)).compact,
|
47
|
+
default: load_default
|
48
|
+
]
|
49
|
+
|
50
|
+
verify_argument
|
51
|
+
end
|
52
|
+
|
53
|
+
def call(*)
|
54
|
+
fail NotImplementedError,
|
55
|
+
"`#{self.class}##{__method__} #{method(__method__).parameters}` must be implemented."
|
56
|
+
end
|
57
|
+
|
58
|
+
def inspect
|
59
|
+
attributes = record.to_h.map { |key, value| "#{key}=#{value.inspect}" }
|
60
|
+
%(#<#{self.class} @context=#{context.inspect} #{attributes.join ", "}>)
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_proc = method(:call).to_proc
|
64
|
+
|
65
|
+
protected
|
66
|
+
|
67
|
+
attr_reader :context
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def verify_argument
|
72
|
+
return unless argument && !argument.start_with?("[") && default
|
73
|
+
|
74
|
+
fail Error, "Required argument can't be used with default."
|
75
|
+
end
|
76
|
+
|
77
|
+
def load_default
|
78
|
+
klass = self.class
|
79
|
+
fallback = klass.instance_variable_get(:@attributes)[:default].method :itself
|
80
|
+
|
81
|
+
(klass.instance_variable_get(:@default) || fallback).call
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
data/lib/sod/command.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "forwardable"
|
4
|
+
|
5
|
+
module Sod
|
6
|
+
# A generic command (and DSL) from which to inherit and build custom commands from.
|
7
|
+
# :reek:TooManyInstanceVariables
|
8
|
+
class Command
|
9
|
+
extend Forwardable
|
10
|
+
|
11
|
+
include Import[:logger]
|
12
|
+
|
13
|
+
def self.inherited base
|
14
|
+
super
|
15
|
+
base.class_eval { @actions = Set.new }
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.handle text
|
19
|
+
@handle ? fail(Error, "Handle can only be defined once.") : @handle = text
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.description text
|
23
|
+
@description ? fail(Error, "Description can only be defined once.") : @description = text
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.ancillary(*lines)
|
27
|
+
@ancillary ? fail(Error, "Ancillary can only be defined once.") : @ancillary = lines
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.on(action, *positionals, **keywords) = @actions.add [action, positionals, keywords]
|
31
|
+
|
32
|
+
delegate Models::Command.members => :record
|
33
|
+
|
34
|
+
attr_reader :record
|
35
|
+
|
36
|
+
def initialize(context: Context::EMPTY, model: Models::Command, **)
|
37
|
+
super(**)
|
38
|
+
klass = self.class
|
39
|
+
@context = context
|
40
|
+
|
41
|
+
@record = model[
|
42
|
+
handle: klass.instance_variable_get(:@handle),
|
43
|
+
description: klass.instance_variable_get(:@description),
|
44
|
+
ancillary: Array(klass.instance_variable_get(:@ancillary)).compact,
|
45
|
+
actions: Set[*build_actions],
|
46
|
+
operation: method(:call)
|
47
|
+
]
|
48
|
+
end
|
49
|
+
|
50
|
+
def call
|
51
|
+
logger.debug { "`#{self.class}##{__method__}}` called without implementation. Skipped." }
|
52
|
+
end
|
53
|
+
|
54
|
+
def inspect
|
55
|
+
attributes = record.to_h
|
56
|
+
.map { |key, value| "#{key}=#{value.inspect}" }
|
57
|
+
.join ", "
|
58
|
+
|
59
|
+
"#<#{self.class} @logger=#{logger.inspect} @context=#{context.inspect} #{attributes}>"
|
60
|
+
end
|
61
|
+
|
62
|
+
protected
|
63
|
+
|
64
|
+
attr_reader :context
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def build_actions
|
69
|
+
self.class.instance_variable_get(:@actions).map do |action, positionals, keywords|
|
70
|
+
action.new(*positionals, **keywords.merge!(context:))
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "cogger"
|
4
|
+
require "dry/container"
|
5
|
+
require "optparse"
|
6
|
+
require "tone"
|
7
|
+
|
8
|
+
module Sod
|
9
|
+
# The primary container.
|
10
|
+
module Container
|
11
|
+
extend Dry::Container::Mixin
|
12
|
+
|
13
|
+
register(:client) { OptionParser.new nil, 40, " " }
|
14
|
+
register(:color) { Tone.new }
|
15
|
+
register(:kernel) { Kernel }
|
16
|
+
register(:logger) { Cogger.new formatter: :emoji }
|
17
|
+
end
|
18
|
+
end
|
data/lib/sod/context.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sod
|
4
|
+
# Provides a sharable, read-only, context for commands and actions.
|
5
|
+
class Context
|
6
|
+
EMPTY = new.freeze
|
7
|
+
|
8
|
+
def self.[](...) = new(...)
|
9
|
+
|
10
|
+
def initialize **attributes
|
11
|
+
@attributes = attributes
|
12
|
+
end
|
13
|
+
|
14
|
+
# :reek:ControlParameter
|
15
|
+
def [] default, fallback
|
16
|
+
default || public_send(fallback)
|
17
|
+
rescue NoMethodError
|
18
|
+
raise Error, "Invalid context. Default or fallback (#{fallback.inspect}) values are missing."
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_h = attributes.dup
|
22
|
+
|
23
|
+
def method_missing(name, *) = respond_to_missing?(name) ? attributes[name] : super(name, *)
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
attr_reader :attributes
|
28
|
+
|
29
|
+
def respond_to_missing? name, include_private = false
|
30
|
+
(attributes && attributes.key?(name)) || super
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/sod/error.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sod
|
4
|
+
module Graph
|
5
|
+
# Loads and decorates option parsers within graph.
|
6
|
+
class Loader
|
7
|
+
include Import[:client]
|
8
|
+
|
9
|
+
using Refines::OptionParsers
|
10
|
+
|
11
|
+
def initialize(graph, **)
|
12
|
+
super(**)
|
13
|
+
@graph = graph
|
14
|
+
@registry = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def call
|
18
|
+
registry.clear
|
19
|
+
load graph
|
20
|
+
graph.children.each { |child| visit child, child.handle }
|
21
|
+
registry
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :graph, :registry
|
27
|
+
|
28
|
+
def visit command, key = ""
|
29
|
+
load command, key
|
30
|
+
command.children.each { |child| visit child, "#{key} #{child.handle}".strip }
|
31
|
+
end
|
32
|
+
|
33
|
+
def load node, key = ""
|
34
|
+
parser = client.replicate
|
35
|
+
node.actions.each { |action| parser.on(*action.to_a, action.to_proc) }
|
36
|
+
registry[key] = [parser, node]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "refinements/arrays"
|
4
|
+
|
5
|
+
module Sod
|
6
|
+
module Graph
|
7
|
+
# A generic graph node (and DSL) from which to build multiple lineages with.
|
8
|
+
Node = Struct.new :handle, :description, :ancillary, :actions, :operation, :children do
|
9
|
+
using Refinements::Arrays
|
10
|
+
|
11
|
+
def initialize(**)
|
12
|
+
super
|
13
|
+
self[:actions] = Set.new actions
|
14
|
+
self[:children] = Set.new children
|
15
|
+
self[:ancillary] = Array ancillary
|
16
|
+
@depth = 0
|
17
|
+
@lineage = []
|
18
|
+
end
|
19
|
+
|
20
|
+
def get_action *lineage
|
21
|
+
handle = lineage.pop
|
22
|
+
get_actions(*lineage).find { |action| action.handle.include? handle }
|
23
|
+
end
|
24
|
+
|
25
|
+
def get_actions *lineage, node: self
|
26
|
+
lineage.empty? ? node.actions : get(lineage, node, __method__)
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_child(*lineage, node: self) = lineage.empty? ? node : get(lineage, node, __method__)
|
30
|
+
|
31
|
+
def on(object, *, **, &block)
|
32
|
+
lineage.clear if depth.zero?
|
33
|
+
|
34
|
+
process(object, *, **)
|
35
|
+
|
36
|
+
increment
|
37
|
+
instance_eval(&block) if block
|
38
|
+
decrement
|
39
|
+
end
|
40
|
+
|
41
|
+
def call = (operation.call if operation)
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
attr_reader :lineage
|
46
|
+
|
47
|
+
attr_accessor :depth
|
48
|
+
|
49
|
+
# :reek:TooManyStatements
|
50
|
+
# rubocop:todo Metrics/AbcSize
|
51
|
+
def process(object, *, **)
|
52
|
+
ancestry = object.is_a?(Class) ? object.ancestors : []
|
53
|
+
|
54
|
+
if ancestry.include? Command
|
55
|
+
add_child(*lineage, self.class[**object.new(*, **).record.to_h])
|
56
|
+
elsif object.is_a? String
|
57
|
+
add_inline_command(object, *, **)
|
58
|
+
elsif ancestry.include? Action
|
59
|
+
add_action(*lineage, object.new(*, **))
|
60
|
+
else
|
61
|
+
fail Error, "Invalid command or action. Unable to add: #{object.inspect}."
|
62
|
+
end
|
63
|
+
end
|
64
|
+
# rubocop:enable Metrics/AbcSize
|
65
|
+
|
66
|
+
def add_inline_command handle, *positionals
|
67
|
+
description, *ancillary = positionals
|
68
|
+
|
69
|
+
fail Error, <<~CONTENT unless handle && description
|
70
|
+
Unable to add command. Invalid handle or description (both are required):
|
71
|
+
- Handle: #{handle.inspect}
|
72
|
+
- Description: #{description.inspect}
|
73
|
+
CONTENT
|
74
|
+
|
75
|
+
add_child(*lineage, self.class[handle:, description:, ancillary: ancillary.compact])
|
76
|
+
end
|
77
|
+
|
78
|
+
def add_child *lineage
|
79
|
+
node = lineage.pop
|
80
|
+
handle = node.handle
|
81
|
+
tracked_lineage = self.lineage
|
82
|
+
|
83
|
+
add lineage[...depth], node, :children
|
84
|
+
tracked_lineage.replace_at depth, handle
|
85
|
+
end
|
86
|
+
|
87
|
+
def add_action(*lineage) = add lineage, lineage.pop, :actions
|
88
|
+
|
89
|
+
def add lineage, node, message
|
90
|
+
get_child(*lineage).then { |child| child.public_send(message).add node }
|
91
|
+
end
|
92
|
+
|
93
|
+
def get lineage, node, message
|
94
|
+
handle = lineage.shift
|
95
|
+
node = node.children.find { |child| child.handle == handle }
|
96
|
+
|
97
|
+
fail Error, "Unable to find command or action: #{handle.inspect}." unless node
|
98
|
+
|
99
|
+
public_send(message, *lineage, node:)
|
100
|
+
end
|
101
|
+
|
102
|
+
def increment = self.depth += 1
|
103
|
+
|
104
|
+
def decrement = self.depth -= 1
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sod
|
4
|
+
module Graph
|
5
|
+
# Runs the appropriate parser for given command line arguments.
|
6
|
+
class Runner
|
7
|
+
include Import[:client, :logger]
|
8
|
+
|
9
|
+
using Refines::OptionParsers
|
10
|
+
|
11
|
+
HELP_PATTERN = /
|
12
|
+
\A # Start of string.
|
13
|
+
-h # Short alias.
|
14
|
+
| # Or.
|
15
|
+
--help # Long alias.
|
16
|
+
\Z # End of string.
|
17
|
+
/x
|
18
|
+
|
19
|
+
# rubocop:todo Metrics/ParameterLists
|
20
|
+
def initialize(graph, help_pattern: HELP_PATTERN, loader: Loader, **)
|
21
|
+
super(**)
|
22
|
+
@graph = graph
|
23
|
+
@registry = loader.new(graph).call
|
24
|
+
@help_pattern = help_pattern
|
25
|
+
@lineage = +""
|
26
|
+
end
|
27
|
+
# rubocop:enable Metrics/ParameterLists
|
28
|
+
|
29
|
+
# :reek:DuplicateMethodCall
|
30
|
+
# :reek:TooManyStatements
|
31
|
+
def call arguments = ARGV
|
32
|
+
lineage.clear
|
33
|
+
visit arguments.dup
|
34
|
+
rescue OptionParser::ParseError => error
|
35
|
+
log_error error.message
|
36
|
+
rescue Sod::Error => error
|
37
|
+
log_error error.message
|
38
|
+
help
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
attr_reader :graph, :registry, :help_pattern, :lineage
|
44
|
+
|
45
|
+
# :reek:TooManyStatements
|
46
|
+
def visit arguments
|
47
|
+
if arguments.empty? || arguments.any? { |argument| argument.match? help_pattern }
|
48
|
+
usage(*arguments)
|
49
|
+
else
|
50
|
+
parser, node = registry.fetch lineage, client
|
51
|
+
parser.order! arguments, command: node do |command|
|
52
|
+
lineage.concat(" ", command).tap(&:strip!)
|
53
|
+
visit arguments
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def usage(*arguments)
|
59
|
+
commands = arguments.grep_v help_pattern
|
60
|
+
commands = lineage.split if commands.empty?
|
61
|
+
help(*commands)
|
62
|
+
end
|
63
|
+
|
64
|
+
def help(*commands)
|
65
|
+
graph.get_action("help").then { |action| action.call(*commands) if action }
|
66
|
+
end
|
67
|
+
|
68
|
+
def log_error(message) = logger.error { message.capitalize }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/lib/sod/import.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "refinements/arrays"
|
4
|
+
|
5
|
+
module Sod
|
6
|
+
module Models
|
7
|
+
# Defines all attributes of an action.
|
8
|
+
Action = Data.define(
|
9
|
+
:aliases,
|
10
|
+
:argument,
|
11
|
+
:type,
|
12
|
+
:allow,
|
13
|
+
:default,
|
14
|
+
:description,
|
15
|
+
:ancillary
|
16
|
+
) do
|
17
|
+
using Refinements::Arrays
|
18
|
+
|
19
|
+
def initialize aliases: nil,
|
20
|
+
argument: nil,
|
21
|
+
type: nil,
|
22
|
+
allow: nil,
|
23
|
+
default: nil,
|
24
|
+
description: nil,
|
25
|
+
ancillary: nil
|
26
|
+
super
|
27
|
+
end
|
28
|
+
|
29
|
+
def handle = [Array(aliases).join(", "), argument].tap(&:compact!).join " "
|
30
|
+
|
31
|
+
def to_a = [*handles, type, allow, description, *ancillary].tap(&:compress!)
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def handles = Array(aliases).map { |item| [item, argument].tap(&:compact!).join " " }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "refinements/pathnames"
|
4
|
+
|
5
|
+
module Sod
|
6
|
+
module Prefabs
|
7
|
+
module Actions
|
8
|
+
module Config
|
9
|
+
# Creates project configuration.
|
10
|
+
class Create < Action
|
11
|
+
include Import[:kernel, :logger]
|
12
|
+
|
13
|
+
using Refinements::Pathnames
|
14
|
+
|
15
|
+
description "Create default configuration."
|
16
|
+
|
17
|
+
ancillary "Prompts for local or global path."
|
18
|
+
|
19
|
+
on %w[-c --create]
|
20
|
+
|
21
|
+
def initialize(xdg_config = nil, defaults_path: nil, **)
|
22
|
+
super(**)
|
23
|
+
@xdg_config = context[xdg_config, :xdg_config]
|
24
|
+
@defaults_path = Pathname context[defaults_path, :defaults_path]
|
25
|
+
end
|
26
|
+
|
27
|
+
def call(*)
|
28
|
+
ARGV.clear
|
29
|
+
check_defaults && choose
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
attr_reader :xdg_config, :defaults_path
|
35
|
+
|
36
|
+
def check_defaults
|
37
|
+
return true if defaults_path.exist?
|
38
|
+
|
39
|
+
logger.fatal { "Default configuration doesn't exist: #{defaults_path.to_s.inspect}." }
|
40
|
+
kernel.abort
|
41
|
+
false
|
42
|
+
end
|
43
|
+
|
44
|
+
def choose
|
45
|
+
kernel.print "Would you like to create (g)lobal, (l)ocal, or (n)o configuration? " \
|
46
|
+
"(g/l/n)? "
|
47
|
+
response = kernel.gets.chomp
|
48
|
+
|
49
|
+
case response
|
50
|
+
when "g" then create xdg_config.global
|
51
|
+
when "l" then create xdg_config.local
|
52
|
+
else quit
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# :reek:TooManyStatements
|
57
|
+
def create path
|
58
|
+
path_info = path.to_s.inspect
|
59
|
+
|
60
|
+
if path.exist?
|
61
|
+
logger.warn { "Skipped. Configuration exists: #{path_info}." }
|
62
|
+
else
|
63
|
+
defaults_path.copy path.make_ancestors
|
64
|
+
logger.info { "Created: #{path_info}." }
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def quit
|
69
|
+
logger.info { "Creation canceled." }
|
70
|
+
kernel.exit
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "refinements/pathnames"
|
4
|
+
require "refinements/strings"
|
5
|
+
|
6
|
+
module Sod
|
7
|
+
module Prefabs
|
8
|
+
module Actions
|
9
|
+
module Config
|
10
|
+
# Deletes project configuration.
|
11
|
+
class Delete < Action
|
12
|
+
include Import[:kernel, :logger]
|
13
|
+
|
14
|
+
using Refinements::Pathnames
|
15
|
+
using Refinements::Strings
|
16
|
+
|
17
|
+
description "Delete project configuration."
|
18
|
+
|
19
|
+
ancillary "Prompts for confirmation."
|
20
|
+
|
21
|
+
on %w[-d --delete]
|
22
|
+
|
23
|
+
# :reek:ControlParameter
|
24
|
+
def initialize(path = nil, **)
|
25
|
+
super(**)
|
26
|
+
@path = Pathname(path || context.xdg_config.active)
|
27
|
+
end
|
28
|
+
|
29
|
+
def call(*)
|
30
|
+
ARGV.clear
|
31
|
+
|
32
|
+
return confirm if path.exist?
|
33
|
+
|
34
|
+
logger.warn { "Skipped. Configuration doesn't exist: #{path_info}." }
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
attr_reader :path
|
40
|
+
|
41
|
+
def confirm
|
42
|
+
kernel.print "Are you sure you want to delete #{path_info} (y/n)? "
|
43
|
+
response = kernel.gets.chomp.to_bool
|
44
|
+
|
45
|
+
if response
|
46
|
+
path.delete
|
47
|
+
info "Deleted: #{path_info}."
|
48
|
+
else
|
49
|
+
info "Skipped: #{path_info}."
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def path_info = path.to_s.inspect
|
54
|
+
|
55
|
+
def info(message) = logger.info { message }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "refinements/pathnames"
|
4
|
+
|
5
|
+
module Sod
|
6
|
+
module Prefabs
|
7
|
+
module Actions
|
8
|
+
module Config
|
9
|
+
# Edits project configuration.
|
10
|
+
class Edit < Action
|
11
|
+
include Import[:kernel, :logger]
|
12
|
+
|
13
|
+
using Refinements::Pathnames
|
14
|
+
|
15
|
+
description "Edit project configuration."
|
16
|
+
|
17
|
+
on %w[-e --edit]
|
18
|
+
|
19
|
+
# :reek:ControlParameter
|
20
|
+
def initialize(path = nil, **)
|
21
|
+
super(**)
|
22
|
+
@path = Pathname(path || context.xdg_config.active)
|
23
|
+
end
|
24
|
+
|
25
|
+
def call(*)
|
26
|
+
return unless check
|
27
|
+
|
28
|
+
logger.info { "Editing: #{path.to_s.inspect}." }
|
29
|
+
kernel.system "$EDITOR #{path}"
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
attr_reader :path
|
35
|
+
|
36
|
+
def check
|
37
|
+
return true if path.exist?
|
38
|
+
|
39
|
+
logger.error { "Configuration doesn't exist: #{path.to_s.inspect}." }
|
40
|
+
kernel.abort
|
41
|
+
false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|