sod 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|