freyia 0.5.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.
@@ -0,0 +1,191 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "automations/create_file"
4
+ require_relative "automations/create_link"
5
+ require_relative "automations/directory"
6
+ require_relative "automations/empty_directory"
7
+ require_relative "automations/file_manipulation"
8
+ require_relative "automations/inject_into_file"
9
+
10
+ module Freyia
11
+ module Automations
12
+ # Wraps an action object and call it accordingly to the freyia class behavior.
13
+ #
14
+ def action(instance) #:nodoc:
15
+ instance.invoke!
16
+ end
17
+
18
+ # Returns the root for this freyia class (also aliased as destination root).
19
+ #
20
+ def destination_root
21
+ @destination_stack.last
22
+ end
23
+
24
+ # Sets the root for this freyia class. Relatives path are added to the
25
+ # directory where the script was invoked and expanded.
26
+ #
27
+ def destination_root=(root)
28
+ @destination_stack ||= []
29
+ @destination_stack[0] = File.expand_path(root || "")
30
+ end
31
+
32
+ # Returns the given path relative to the absolute root (ie, root where
33
+ # the script started).
34
+ #
35
+ def relative_to_original_destination_root(path, remove_dot: true)
36
+ root = @destination_stack[0]
37
+ if path.start_with?(root) && [File::SEPARATOR, File::ALT_SEPARATOR, nil,
38
+ "",].include?(path[root.size..root.size])
39
+ path = path.dup
40
+ path[0...root.size] = "."
41
+ remove_dot ? (path[2..] || "") : path
42
+ else
43
+ path
44
+ end
45
+ end
46
+
47
+ # Receives a file or directory and search for it in the source paths.
48
+ #
49
+ def find_in_source_paths(file)
50
+ possible_files = [file, file + TEMPLATE_EXTNAME]
51
+ relative_root = relative_to_original_destination_root(destination_root, remove_dot: false)
52
+
53
+ source_paths.each do |source|
54
+ possible_files.each do |f|
55
+ source_file = File.expand_path(f, File.join(source, relative_root))
56
+ return source_file if File.exist?(source_file)
57
+ end
58
+ end
59
+
60
+ message = "Could not find #{file.inspect} in any of your source paths. "
61
+
62
+ message << if source_paths.empty?
63
+ "Currently you have no source paths."
64
+ else
65
+ "Your current source paths are: \n#{source_paths.join("\n")}"
66
+ end
67
+
68
+ raise Error, message
69
+ end
70
+
71
+ # Do something in the root or on a provided subfolder. If a relative path
72
+ # is given it's referenced from the current root. The full path is yielded
73
+ # to the block you provide. The path is set back to the previous path when
74
+ # the method exits.
75
+ #
76
+ # Returns the value yielded by the block.
77
+ #
78
+ # ==== Parameters
79
+ # dir<String>:: the directory to move to.
80
+ # config<Hash>:: give :verbose => true to log and use padding.
81
+ #
82
+ def inside(dir = "", config = {}, &block) # rubocop:todo Metrics
83
+ verbose = config.fetch(:verbose, false)
84
+ pretend = options[:pretend]
85
+
86
+ say_status :inside, dir, verbose
87
+ shell.padding += 1 if verbose
88
+ @destination_stack.push File.expand_path(dir, destination_root)
89
+
90
+ # If the directory doesn't exist and we're not pretending
91
+ if !File.exist?(destination_root) && !pretend
92
+ require "fileutils"
93
+ FileUtils.mkdir_p(destination_root)
94
+ end
95
+
96
+ result = nil
97
+ if pretend
98
+ # In pretend mode, just yield down to the block
99
+ result = block.arity == 1 ? yield(destination_root) : yield
100
+ else
101
+ require "fileutils"
102
+ FileUtils.cd(destination_root) do
103
+ result = block.arity == 1 ? yield(destination_root) : yield
104
+ end
105
+ end
106
+
107
+ @destination_stack.pop
108
+ shell.padding -= 1 if verbose
109
+ result
110
+ end
111
+
112
+ # Goes to the root and execute the given block.
113
+ #
114
+ def in_root(&)
115
+ inside(@destination_stack.first, &)
116
+ end
117
+
118
+ # Loads an external file and execute it in the instance binding.
119
+ #
120
+ # ==== Parameters
121
+ # path<String>:: The path to the file to execute. Can be a web address or
122
+ # a relative path from the source root.
123
+ #
124
+ # ==== Examples
125
+ #
126
+ # apply "http://gist.github.com/103208"
127
+ #
128
+ # apply "recipes/jquery.rb"
129
+ #
130
+ def apply(path, config = {})
131
+ verbose = config.fetch(:verbose, true)
132
+ is_uri = path =~ %r{^https?://}
133
+ path = find_in_source_paths(path) unless is_uri
134
+
135
+ say_status :apply, path, verbose
136
+ shell.padding += 1 if verbose
137
+
138
+ contents = if is_uri
139
+ require "open-uri"
140
+ URI.open(path, "Accept" => "application/x-freyia-template", &:read) # rubocop:disable Security/Open
141
+ else
142
+ File.read(path)
143
+ end
144
+
145
+ instance_eval(contents, path)
146
+ shell.padding -= 1 if verbose
147
+ end
148
+
149
+ # Executes a command returning the contents of the command.
150
+ #
151
+ # ==== Parameters
152
+ # command<String>:: the command to be executed.
153
+ # config<Hash>:: give :verbose => false to not log the status, :capture => true to hide to
154
+ # output. Specify :with to append an executable to command execution.
155
+ #
156
+ # ==== Example
157
+ #
158
+ # inside('vendor') do
159
+ # run('ln -s ~/edge rails')
160
+ # end
161
+ #
162
+ def run(command, config = {}) # rubocop:todo Metrics
163
+ destination = relative_to_original_destination_root(destination_root, remove_dot: false)
164
+ desc = "#{command} from #{destination.inspect}"
165
+
166
+ if config[:with]
167
+ desc = "#{File.basename(config[:with].to_s)} #{desc}"
168
+ command = "#{config[:with]} #{command}"
169
+ end
170
+
171
+ say_status :run, desc, config.fetch(:verbose, true)
172
+
173
+ return if options[:pretend]
174
+
175
+ env_splat = [config[:env]] if config[:env]
176
+
177
+ if config[:capture]
178
+ require "open3"
179
+ result, status = Open3.capture2e(*env_splat, command.to_s)
180
+ success = status.success?
181
+ else
182
+ result = system(*env_splat, command.to_s)
183
+ success = result
184
+ end
185
+
186
+ abort if !success && config.fetch(:abort_on_failure, self.class.exit_on_failure?)
187
+
188
+ result
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Freyia
4
+ module LineEditor
5
+ class Basic
6
+ attr_reader :prompt, :options
7
+
8
+ def self.available?
9
+ true
10
+ end
11
+
12
+ def initialize(prompt, options)
13
+ @prompt = prompt
14
+ @options = options
15
+ end
16
+
17
+ def readline
18
+ $stdout.print(prompt)
19
+ get_input
20
+ end
21
+
22
+ private
23
+
24
+ def get_input # rubocop:disable Naming/AccessorMethodName
25
+ if echo?
26
+ $stdin.gets
27
+ else
28
+ # Lazy-load io/console since it is gem-ified as of 2.3
29
+ require "io/console"
30
+ $stdin.noecho(&:gets)
31
+ end
32
+ end
33
+
34
+ def echo?
35
+ options.fetch(:echo, true)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Freyia
4
+ module LineEditor
5
+ class Readline < Basic
6
+ def self.available?
7
+ begin
8
+ require "readline"
9
+ rescue LoadError # rubocop:disable Lint/SuppressedException
10
+ end
11
+
12
+ Object.const_defined?(:Readline)
13
+ end
14
+
15
+ def readline
16
+ if echo?
17
+ ::Readline.completion_append_character = nil
18
+ # rb-readline does not allow Readline.completion_proc= to receive nil.
19
+ if (complete = completion_proc)
20
+ ::Readline.completion_proc = complete
21
+ end
22
+ ::Readline.readline(prompt, add_to_history?)
23
+ else
24
+ super
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def add_to_history?
31
+ options.fetch(:add_to_history, true)
32
+ end
33
+
34
+ def completion_proc
35
+ if use_path_completion?
36
+ proc { |text| PathCompletion.new(text).matches }
37
+ elsif completion_options.any?
38
+ proc do |text|
39
+ completion_options.select { |option| option.start_with?(text) }
40
+ end
41
+ end
42
+ end
43
+
44
+ def completion_options
45
+ options.fetch(:limited_to, [])
46
+ end
47
+
48
+ def use_path_completion?
49
+ options.fetch(:path, false)
50
+ end
51
+
52
+ class PathCompletion
53
+ attr_reader :text
54
+ private :text
55
+
56
+ def initialize(text)
57
+ @text = text
58
+ end
59
+
60
+ def matches
61
+ relative_matches
62
+ end
63
+
64
+ private
65
+
66
+ def relative_matches
67
+ absolute_matches.map { |path| path.sub(base_path, "") }
68
+ end
69
+
70
+ def absolute_matches
71
+ Dir[glob_pattern].map do |path|
72
+ if File.directory?(path)
73
+ "#{path}/"
74
+ else
75
+ path
76
+ end
77
+ end
78
+ end
79
+
80
+ def glob_pattern
81
+ "#{base_path}#{text}*"
82
+ end
83
+
84
+ def base_path
85
+ "#{Dir.pwd}/"
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "line_editor/basic"
4
+ require_relative "line_editor/readline"
5
+
6
+ module Freyia
7
+ module LineEditor
8
+ def self.readline(prompt, options = {})
9
+ best_available.new(prompt, options).readline
10
+ end
11
+
12
+ def self.best_available
13
+ [
14
+ Freyia::LineEditor::Readline,
15
+ Freyia::LineEditor::Basic,
16
+ ].detect(&:available?)
17
+ end
18
+ end
19
+ end