freyia 0.5.3 → 0.6.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 +4 -4
- data/CHANGELOG.md +11 -0
- data/README.md +41 -1
- data/lib/freyia/automations/create_file.rb +12 -22
- data/lib/freyia/automations/create_link.rb +11 -21
- data/lib/freyia/automations/directory.rb +27 -34
- data/lib/freyia/automations/empty_directory.rb +16 -35
- data/lib/freyia/automations/file_manipulation.rb +136 -185
- data/lib/freyia/automations/inject_into_file.rb +26 -49
- data/lib/freyia/automations.rb +3 -5
- data/lib/freyia/line_editor.rb +1 -1
- data/lib/freyia/shell/basic.rb +14 -15
- data/lib/freyia/shell/color.rb +11 -0
- data/lib/freyia/shell/column_printer.rb +1 -1
- data/lib/freyia/shell/lcs_diff.rb +3 -3
- data/lib/freyia/shell/table_printer.rb +39 -15
- data/lib/freyia/shell.rb +2 -2
- data/lib/freyia/version.rb +1 -1
- data/lib/freyia.rb +9 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9ae683b60c6522f6af8b51b15e78a0cf656c6eb6030762a41e6c68c2490468c0
|
|
4
|
+
data.tar.gz: 66e517e9380662a71abaa0c89e1655a2b165802fb133dc93f29dcf09102e0738
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 16089e296d6588fc4d622fef87d272bf03e760e23042deac1868942ae0740eb9d82317172b6abf7b01943eca3156ac5202bb7c9dfd0daf859e8a6c61d74178f9
|
|
7
|
+
data.tar.gz: 8a1bead727f8f79ac8470935a76a4e21492928e4d6a92786b176aaa7dd51ead9d961dcf4b3d1ff2d44beec52352f14fdc6dc4be7a183d7942411b1f408196ac0
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
1
3
|
## [Unreleased]
|
|
2
4
|
|
|
5
|
+
.
|
|
6
|
+
|
|
7
|
+
## [0.6.0] - 2025-12-14
|
|
8
|
+
|
|
9
|
+
- Add support for Serbea-based templates (alternative to ERB)
|
|
10
|
+
- Refactor all `params = {}` to real keyword arguments
|
|
11
|
+
- Handle ANSI codes in columnar calculations (for `print_table`)
|
|
12
|
+
- Use yarddoc syntax for all doc comments
|
|
13
|
+
|
|
3
14
|
## [0.5.3] - 2025-11-21
|
|
4
15
|
|
|
5
16
|
- Fix syntax error
|
data/README.md
CHANGED
|
@@ -15,6 +15,10 @@ bundle add freyia
|
|
|
15
15
|
|
|
16
16
|
## Usage
|
|
17
17
|
|
|
18
|
+
Freyia is centered around the idea of running an "automation script". This is kicked off using a base instance, but that instance can incorporate other automation scripts using `apply` and those scripts don't need to include any of the typical Ruby class ceremony (everything in those scripts are run as if it were written to execute in the base instance via the magic of `instance_exec`).
|
|
19
|
+
|
|
20
|
+
An example using the standard Freya base:
|
|
21
|
+
|
|
18
22
|
```ruby
|
|
19
23
|
class TestAutomation < Freyia::Base
|
|
20
24
|
def call(external_file)
|
|
@@ -42,13 +46,49 @@ end
|
|
|
42
46
|
|
|
43
47
|
class MyLatestAutomation < MyAutomationSuperclass
|
|
44
48
|
def call
|
|
45
|
-
# ...automation
|
|
49
|
+
# ...automation script here
|
|
46
50
|
end
|
|
47
51
|
end
|
|
48
52
|
```
|
|
49
53
|
|
|
50
54
|
Note that the use of `call` here is simply a convention…you can write automations in any method or architecture you prefer.
|
|
51
55
|
|
|
56
|
+
> [!NOTE]
|
|
57
|
+
> Freyia file automations are generally in line with prior Thor actions, but one big difference: the template extension is now `.tmpl`, not `.tt`. In addition, we are phasing out "reversable actions". Automations are generally understood to be run on Git-backed repos which offer their own form of reverting changes.
|
|
58
|
+
|
|
59
|
+
Most of the Freyia documentation as at the [API level which you can read here](https://www.rubydoc.info/gems/freyia). Most important resources:
|
|
60
|
+
|
|
61
|
+
* [Freyia::Automations](https://www.rubydoc.info/gems/freyia/Freyia/Automations)
|
|
62
|
+
* [Freyia::Shell::Basic](https://www.rubydoc.info/gems/freyia/Freyia/Shell/Basic)
|
|
63
|
+
|
|
64
|
+
All of the automations listed there, such as `say_status`, `inject_into_file`, `template`, etc. are available to you within automation scripts.
|
|
65
|
+
|
|
66
|
+
## Template Types
|
|
67
|
+
|
|
68
|
+
The `template` automation lets you create files based on templates you define in either ERB or [Serbea](https://www.serbea.dev) syntax. The default is ERB, but you override the default for a Freyia base using the `template_type` class method:
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
class TestAutomation < Freyia::Base
|
|
72
|
+
template_type :serbea
|
|
73
|
+
end
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
You can also override the base default on a per-call basis using the `type` keyword argument:
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
template "configuration.yaml", type: :serbea
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
This would process a `configuration.yaml.tmpl` in your source folder to output a `configuration.yaml` file to the destination, like so:
|
|
83
|
+
|
|
84
|
+
```yaml
|
|
85
|
+
---
|
|
86
|
+
current_ruby_version: {{ RUBY_VERSION | to_json }}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
> [!NOTE]
|
|
90
|
+
> Freyia doesn't ship with a Serbea dependency, so you will also need to `bundle add serbea` or reference it in a `.gemspec` file if you want to use Serbea-based templates.
|
|
91
|
+
|
|
52
92
|
## Development
|
|
53
93
|
|
|
54
94
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
@@ -7,42 +7,36 @@ module Freyia
|
|
|
7
7
|
# Create a new file relative to the destination root with the given data,
|
|
8
8
|
# which is the return value of a block or a data string.
|
|
9
9
|
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
# config<Hash>:: give :verbose => false to not log the status.
|
|
14
|
-
#
|
|
15
|
-
# ==== Examples
|
|
10
|
+
# @param destination [String] the relative path to the destination root.
|
|
11
|
+
# @param data [String|NilClass] the data to append to the file.
|
|
12
|
+
# @param config [Hash] give `verbose: false` to not log the status.
|
|
16
13
|
#
|
|
14
|
+
# @example Creating a file using a block
|
|
17
15
|
# create_file "lib/fun_party.rb" do
|
|
18
16
|
# hostname = ask("What is the virtual hostname I should use?")
|
|
19
17
|
# "vhost.name = #{hostname}"
|
|
20
18
|
# end
|
|
21
19
|
#
|
|
20
|
+
# @example Creating a file using a string
|
|
22
21
|
# create_file "config/apache.conf", "your apache config"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
config = args.last.is_a?(Hash) ? args.pop : {}
|
|
26
|
-
data = args.first
|
|
27
|
-
CreateFile.new(self, destination, block || data.to_s, config).()
|
|
22
|
+
def create_file(destination, data = nil, **config, &block)
|
|
23
|
+
CreateFile.new(self, destination, block || data.to_s, **config).()
|
|
28
24
|
end
|
|
29
25
|
alias_method :add_file, :create_file
|
|
30
26
|
|
|
31
27
|
# CreateFile is a subset of Template, which instead of rendering a file with
|
|
32
28
|
# ERB, it gets the content from the user.
|
|
33
|
-
|
|
34
|
-
class CreateFile < EmptyDirectory #:nodoc:
|
|
29
|
+
class CreateFile < EmptyDirectory
|
|
35
30
|
attr_reader :data
|
|
36
31
|
|
|
37
|
-
def initialize(base, destination, data, config
|
|
32
|
+
def initialize(base, destination, data, **config)
|
|
38
33
|
@data = data
|
|
39
|
-
super(base, destination, config)
|
|
34
|
+
super(base, destination, **config)
|
|
40
35
|
end
|
|
41
36
|
|
|
42
37
|
# Checks if the content of the file at the destination is identical to the rendered result.
|
|
43
38
|
#
|
|
44
|
-
#
|
|
45
|
-
# Boolean:: true if it is identical, false otherwise.
|
|
39
|
+
# @return [Boolean] true if it is identical, false otherwise.
|
|
46
40
|
#
|
|
47
41
|
def identical?
|
|
48
42
|
# binread uses ASCII-8BIT, so to avoid false negatives, the string must use the same
|
|
@@ -52,11 +46,7 @@ module Freyia
|
|
|
52
46
|
# Holds the content to be added to the file.
|
|
53
47
|
#
|
|
54
48
|
def render
|
|
55
|
-
@render ||=
|
|
56
|
-
data.()
|
|
57
|
-
else
|
|
58
|
-
data
|
|
59
|
-
end
|
|
49
|
+
@render ||= data.is_a?(Proc) ? data.() : data
|
|
60
50
|
end
|
|
61
51
|
|
|
62
52
|
def call
|
|
@@ -4,36 +4,28 @@ require_relative "create_file"
|
|
|
4
4
|
|
|
5
5
|
module Freyia
|
|
6
6
|
module Automations
|
|
7
|
-
# Create a new
|
|
7
|
+
# Create a new symbolic link relative to the destination root from the given source.
|
|
8
8
|
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
# :: give :symbolic => false for hard link.
|
|
14
|
-
#
|
|
15
|
-
# ==== Examples
|
|
9
|
+
# @param destination [String] the relative path to the destination root.
|
|
10
|
+
# @param source [String|NilClass] the relative path to the source root.
|
|
11
|
+
# @param config [Hash] give `verbose: false` to not log the status.
|
|
12
|
+
# give `symbolic: false` for hard link.
|
|
16
13
|
#
|
|
14
|
+
# @example Creating a link
|
|
17
15
|
# create_link "config/apache.conf", "/etc/apache.conf"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
config = args.last.is_a?(Hash) ? args.pop : {}
|
|
21
|
-
source = args.first
|
|
22
|
-
CreateLink.new(self, destination, source, config).()
|
|
16
|
+
def create_link(destination, source, **config)
|
|
17
|
+
CreateLink.new(self, destination, source, **config).()
|
|
23
18
|
end
|
|
24
19
|
alias_method :add_link, :create_link
|
|
25
20
|
|
|
26
21
|
# CreateLink is a subset of CreateFile, which instead of taking a block of
|
|
27
22
|
# data, just takes a source string from the user.
|
|
28
|
-
|
|
29
|
-
class CreateLink < CreateFile #:nodoc:
|
|
23
|
+
class CreateLink < CreateFile
|
|
30
24
|
attr_reader :data
|
|
31
25
|
|
|
32
26
|
# Checks if the content of the file at the destination is identical to the rendered result.
|
|
33
27
|
#
|
|
34
|
-
#
|
|
35
|
-
# Boolean:: true if it is identical, false otherwise.
|
|
36
|
-
#
|
|
28
|
+
# @return [Boolean] true if it is identical, false otherwise.
|
|
37
29
|
def identical?
|
|
38
30
|
source = File.expand_path(render, File.dirname(destination))
|
|
39
31
|
exists? && File.identical?(source, destination)
|
|
@@ -55,9 +47,7 @@ module Freyia
|
|
|
55
47
|
given_destination
|
|
56
48
|
end
|
|
57
49
|
|
|
58
|
-
def exists?
|
|
59
|
-
super || File.symlink?(destination)
|
|
60
|
-
end
|
|
50
|
+
def exists? = super || File.symlink?(destination)
|
|
61
51
|
end
|
|
62
52
|
end
|
|
63
53
|
end
|
|
@@ -5,17 +5,17 @@ require_relative "empty_directory"
|
|
|
5
5
|
module Freyia
|
|
6
6
|
module Automations
|
|
7
7
|
# Copies recursively the files from source directory to root directory.
|
|
8
|
-
# If any of the files finishes with
|
|
9
|
-
# and is placed in the destination without the extension
|
|
8
|
+
# If any of the files finishes with `.tmpl`, it's considered to be a template
|
|
9
|
+
# and is placed in the destination without the extension `.tmpl`. If any
|
|
10
10
|
# empty directory is found, it's copied and all .empty_directory files are
|
|
11
|
-
# ignored. If any file name is wrapped within
|
|
12
|
-
# the
|
|
11
|
+
# ignored. If any file name is wrapped within `%` signs, the text within
|
|
12
|
+
# the `%` signs will be executed as a method and replaced with the returned
|
|
13
13
|
# value. Let's suppose a doc directory with the following files:
|
|
14
14
|
#
|
|
15
15
|
# doc/
|
|
16
16
|
# components/.empty_directory
|
|
17
17
|
# README
|
|
18
|
-
# rdoc.rb.
|
|
18
|
+
# rdoc.rb.tmpl
|
|
19
19
|
# %app_name%.rb
|
|
20
20
|
#
|
|
21
21
|
# When invoked as:
|
|
@@ -31,46 +31,39 @@ module Freyia
|
|
|
31
31
|
# rdoc.rb
|
|
32
32
|
# blog.rb
|
|
33
33
|
#
|
|
34
|
-
#
|
|
35
|
-
# expand
|
|
36
|
-
#
|
|
34
|
+
# **Encoded path note:** Since Freyia internals use Object#respond_to? to check if it can
|
|
35
|
+
# expand `%something%`, this `something` should be a public method in the class calling
|
|
36
|
+
# `#directory`.
|
|
37
37
|
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
# ==== Examples
|
|
38
|
+
# @param source [String] the relative path to the source root.
|
|
39
|
+
# @param destination [String] the relative path to the destination root.
|
|
40
|
+
# @param config [Hash]
|
|
41
|
+
# * give `verbose: false` to not log the status.
|
|
42
|
+
# * `recursive: false`, does not look for paths recursively.
|
|
43
|
+
# * `mode: :preserve`, preserve the file mode from the source.
|
|
44
|
+
# * `exclude_pattern: /regexp/`, prevents copying files that match that regexp.
|
|
47
45
|
#
|
|
46
|
+
# @example Copy a directory verbatim
|
|
48
47
|
# directory "doc"
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
def directory(source,
|
|
52
|
-
|
|
53
|
-
destination = args.first || source
|
|
54
|
-
Directory.new(self, source, destination || source, config, &).()
|
|
48
|
+
# @example Copy a directory using a new name and no subdirectories
|
|
49
|
+
# directory "doc", "docs", recursive: false
|
|
50
|
+
def directory(source, destination = nil, **config, &)
|
|
51
|
+
Directory.new(self, source, destination, **config, &).()
|
|
55
52
|
end
|
|
56
53
|
|
|
57
|
-
class Directory < EmptyDirectory
|
|
54
|
+
class Directory < EmptyDirectory
|
|
58
55
|
attr_reader :source
|
|
59
56
|
|
|
60
|
-
def initialize(base, source, destination =
|
|
57
|
+
def initialize(base, source, destination = source, **config, &block)
|
|
61
58
|
@source = File.expand_path(
|
|
62
59
|
Dir[Automations.escape_globs(base.find_in_source_paths(source.to_s))].first
|
|
63
60
|
)
|
|
64
61
|
@block = block
|
|
65
|
-
super(base, destination,
|
|
62
|
+
super(base, destination, recursive: true, **config)
|
|
66
63
|
end
|
|
67
64
|
|
|
68
65
|
def call
|
|
69
|
-
base.empty_directory given_destination, config
|
|
70
|
-
execute!
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def revoke!
|
|
66
|
+
base.empty_directory given_destination, **config
|
|
74
67
|
execute!
|
|
75
68
|
end
|
|
76
69
|
|
|
@@ -93,11 +86,11 @@ module Freyia
|
|
|
93
86
|
dirname = File.dirname(file_destination).gsub(%r{/\.$}, "")
|
|
94
87
|
next if dirname == given_destination
|
|
95
88
|
|
|
96
|
-
base.empty_directory(dirname, config)
|
|
89
|
+
base.empty_directory(dirname, **config)
|
|
97
90
|
when %r{#{TEMPLATE_EXTNAME}$}o
|
|
98
|
-
base.template(file_source, file_destination[0..-4], config, &@block)
|
|
91
|
+
base.template(file_source, file_destination[0..-4], **config, &@block)
|
|
99
92
|
else
|
|
100
|
-
base.copy_file(file_source, file_destination, config, &@block)
|
|
93
|
+
base.copy_file(file_source, file_destination, **config, &@block)
|
|
101
94
|
end
|
|
102
95
|
end
|
|
103
96
|
end
|
|
@@ -4,16 +4,13 @@ module Freyia
|
|
|
4
4
|
module Automations
|
|
5
5
|
# Creates an empty directory.
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# config<Hash>:: give :verbose => false to not log the status.
|
|
10
|
-
#
|
|
11
|
-
# ==== Examples
|
|
7
|
+
# @param destination [String] the relative path to the destination root.
|
|
8
|
+
# @param config [Hash] give `verbose: false` to not log the status.
|
|
12
9
|
#
|
|
10
|
+
# @example Create an empty directory
|
|
13
11
|
# empty_directory "doc"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
EmptyDirectory.new(self, destination, config).()
|
|
12
|
+
def empty_directory(destination, **config)
|
|
13
|
+
EmptyDirectory.new(self, destination, **config).()
|
|
17
14
|
end
|
|
18
15
|
|
|
19
16
|
# Class which holds create directory logic. This is the base class for
|
|
@@ -21,19 +18,18 @@ module Freyia
|
|
|
21
18
|
#
|
|
22
19
|
# This implementation is based in Templater automations, created by Jonas Nicklas
|
|
23
20
|
# and Michael S. Klishin under MIT LICENSE.
|
|
24
|
-
|
|
25
|
-
class EmptyDirectory #:nodoc:
|
|
21
|
+
class EmptyDirectory
|
|
26
22
|
attr_reader :base, :destination, :given_destination, :relative_destination, :config
|
|
27
23
|
|
|
28
24
|
# Initializes given the source and destination.
|
|
29
25
|
#
|
|
30
26
|
# ==== Parameters
|
|
31
|
-
# base<Freyia::
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
def initialize(base, destination, config
|
|
27
|
+
# @param base [Object<Freyia::Setup>]
|
|
28
|
+
# A {Freyia::Base} instance (or any object with the {Freyia::Setup} mixin)
|
|
29
|
+
# @param source [String] Relative path to the source of this file
|
|
30
|
+
# @param destination [String] Relative path to the destination of this file
|
|
31
|
+
# @param config [Hash] give `verbose: false` to not log the status.
|
|
32
|
+
def initialize(base, destination, **config)
|
|
37
33
|
@base = base
|
|
38
34
|
@config = { verbose: true }.merge(config)
|
|
39
35
|
self.destination = destination
|
|
@@ -41,9 +37,7 @@ module Freyia
|
|
|
41
37
|
|
|
42
38
|
# Checks if the destination file already exists.
|
|
43
39
|
#
|
|
44
|
-
#
|
|
45
|
-
# Boolean:: true if the file exists, false otherwise.
|
|
46
|
-
#
|
|
40
|
+
# @return [Boolean] true if the file exists, false otherwise.
|
|
47
41
|
def exists?
|
|
48
42
|
::File.exist?(destination)
|
|
49
43
|
end
|
|
@@ -55,17 +49,9 @@ module Freyia
|
|
|
55
49
|
end
|
|
56
50
|
end
|
|
57
51
|
|
|
58
|
-
def revoke!
|
|
59
|
-
say_status :remove, :red
|
|
60
|
-
require "fileutils"
|
|
61
|
-
::FileUtils.rm_rf(destination) if !pretend? && exists?
|
|
62
|
-
given_destination
|
|
63
|
-
end
|
|
64
|
-
|
|
65
52
|
protected
|
|
66
53
|
|
|
67
54
|
# Shortcut for pretend.
|
|
68
|
-
#
|
|
69
55
|
def pretend?
|
|
70
56
|
base.options[:pretend]
|
|
71
57
|
end
|
|
@@ -83,7 +69,6 @@ module Freyia
|
|
|
83
69
|
# destination #=> dest/bar/baz
|
|
84
70
|
# relative_destination #=> bar/baz
|
|
85
71
|
# given_destination #=> baz
|
|
86
|
-
#
|
|
87
72
|
def destination=(destination)
|
|
88
73
|
return unless destination
|
|
89
74
|
|
|
@@ -96,13 +81,12 @@ module Freyia
|
|
|
96
81
|
#
|
|
97
82
|
# %file_name%.rb
|
|
98
83
|
#
|
|
99
|
-
# It calls
|
|
100
|
-
# return value (should be String) of
|
|
84
|
+
# It calls `#file_name` from the base and replaces %-string with the
|
|
85
|
+
# return value (should be String) of `#file_name`:
|
|
101
86
|
#
|
|
102
87
|
# user.rb
|
|
103
88
|
#
|
|
104
|
-
# The method referenced
|
|
105
|
-
#
|
|
89
|
+
# The method referenced should be public.
|
|
106
90
|
def convert_encoded_instructions(filename)
|
|
107
91
|
filename.gsub(%r{%(.*?)%}) do |initial_string|
|
|
108
92
|
method = ::Regexp.last_match(1).strip
|
|
@@ -112,7 +96,6 @@ module Freyia
|
|
|
112
96
|
|
|
113
97
|
# Receives a hash of options and just execute the block if some
|
|
114
98
|
# conditions are met.
|
|
115
|
-
#
|
|
116
99
|
def invoke_with_conflict_check(&)
|
|
117
100
|
if exists?
|
|
118
101
|
on_conflict_behavior(&)
|
|
@@ -131,13 +114,11 @@ module Freyia
|
|
|
131
114
|
end
|
|
132
115
|
|
|
133
116
|
# What to do when the destination file already exists.
|
|
134
|
-
#
|
|
135
117
|
def on_conflict_behavior
|
|
136
118
|
say_status :exist, :blue
|
|
137
119
|
end
|
|
138
120
|
|
|
139
121
|
# Shortcut to say_status shell method.
|
|
140
|
-
#
|
|
141
122
|
def say_status(status, color)
|
|
142
123
|
base.shell.say_status status, relative_destination, color if config[:verbose]
|
|
143
124
|
end
|