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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d18d6f5e341842b0cd67728fdbec92ac6525b02878806f3b91b1ec60dbcf664b
4
- data.tar.gz: c5c1b224a6894a51a5a2b3ddd95fa4b6ac766273a5c57445243e659bf003a3c3
3
+ metadata.gz: 9ae683b60c6522f6af8b51b15e78a0cf656c6eb6030762a41e6c68c2490468c0
4
+ data.tar.gz: 66e517e9380662a71abaa0c89e1655a2b165802fb133dc93f29dcf09102e0738
5
5
  SHA512:
6
- metadata.gz: 042d8ddfa2963c03cd7b52df4b9a7ef1fc77cad9e5e7e671d3dd3a223f7bb5ff94f65af8b9197329cd3c31205b743745caf4d172659d485480459bd4b9873b4c
7
- data.tar.gz: 2a6974c2881e98b4580a175be710567a4a5b82468620f3758a910817d7d746a73e4783d2a1b5e436d300cf7c047d075c595dd8ccd04eeb05b99d6d31daf3dde0
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 statements here
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
- # ==== Parameters
11
- # destination<String>:: the relative path to the destination root.
12
- # data<String|NilClass>:: the data to append to the file.
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
- def create_file(destination, *args, &block)
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
- # ==== Returns
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 ||= if data.is_a?(Proc)
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 file relative to the destination root from the given source.
7
+ # Create a new symbolic link relative to the destination root from the given source.
8
8
  #
9
- # ==== Parameters
10
- # destination<String>:: the relative path to the destination root.
11
- # source<String|NilClass>:: the relative path to the source root.
12
- # config<Hash>:: give :verbose => false to not log the status.
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
- def create_link(destination, *args)
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
- # ==== Returns
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 .tt, it's considered to be a template
9
- # and is placed in the destination without the extension .tt. If any
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 % signs, the text within
12
- # the % signs will be executed as a method and replaced with the returned
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.tt
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
- # <b>Encoded path note:</b> 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. If a method is private, Freyia stack raises PrivateMethodEncodedError.
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
- # ==== Parameters
39
- # source<String>:: the relative path to the source root.
40
- # destination<String>:: the relative path to the destination root.
41
- # config<Hash>:: give :verbose => false to not log the status.
42
- # If :recursive => false, does not look for paths recursively.
43
- # If :mode => :preserve, preserve the file mode from the source.
44
- # If :exclude_pattern => /regexp/, prevents copying files that match that regexp.
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
- # directory "doc", "docs", :recursive => false
50
- #
51
- def directory(source, *args, &)
52
- config = args.last.is_a?(Hash) ? args.pop : {}
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 #:nodoc:
54
+ class Directory < EmptyDirectory
58
55
  attr_reader :source
59
56
 
60
- def initialize(base, source, destination = nil, config = {}, &block)
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, { recursive: true }.merge(config))
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
- # ==== Parameters
8
- # destination<String>:: the relative path to the destination root.
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
- def empty_directory(destination, config = {})
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::Base>:: A Freyia::Base instance
32
- # source<String>:: Relative path to the source of this file
33
- # destination<String>:: Relative path to the destination of this file
34
- # config<Hash>:: give :verbose => false to not log the status.
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
- # ==== Returns
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 #file_name from the base and replaces %-string with the
100
- # return value (should be String) of #file_name:
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 can be either public or private.
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