Module: Freyia::Automations

Included in:
Setup
Defined in:
lib/freyia/automations.rb,
lib/freyia/automations/directory.rb,
lib/freyia/automations/create_file.rb,
lib/freyia/automations/create_link.rb,
lib/freyia/automations/empty_directory.rb,
lib/freyia/automations/inject_into_file.rb,
lib/freyia/automations/file_manipulation.rb

Defined Under Namespace

Classes: CapturableERB, CreateFile, CreateLink, Directory, EmptyDirectory, InjectIntoFile

Constant Summary collapse

WARNINGS =
{
  unchanged_no_flag: "File unchanged! Either the supplied flag value not found or the " \
                     "content has already been inserted!",
}.freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.escape_globs(path) ⇒ String

Returns a string that has had any glob characters escaped. The glob characters are * ? { } [ ].

Examples:

Freya::Automations.escape_globs('[apps]')   # => '\[apps\]'

Parameters:

  • path (String)

Returns:

  • (String)


20
21
22
# File 'lib/freyia/automations.rb', line 20

def self.escape_globs(path)
  path.to_s.gsub(%r{[*?{}\[\]]}, '\\\\\\&')
end

Instance Method Details

#append_to_file(path, **config) ⇒ Object Also known as: append_file

Append text to a file.

Examples:

Prepend destination file with a string

append_to_file 'config/environments/test.rb', 'config.gem "rspec"'

Prepend destination file with results of block

append_to_file 'config/environments/test.rb' do
  'config.gem "rspec"'
end

Parameters:

  • path (String)

    path of the file to be changed

  • data (String)

    the data to append to the file, can be also given as a block.

  • config (Hash)

    give verbose: false to not log the status.



185
186
187
188
# File 'lib/freyia/automations/file_manipulation.rb', line 185

def append_to_file(path, *, **config, &)
  config[:before] = %r{\z}
  insert_into_file(path, *, **config, &)
end

#apply(path, verbose: true) ⇒ Object

Loads an external file and execute it in the instance binding.

Parameters

path

The path to the file to execute. Can be a web address or a relative path from the source root.

Examples

apply "http://gist.github.com/103208"

apply "recipes/jquery.rb"


135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/freyia/automations.rb', line 135

def apply(path, verbose: true)
  is_uri  = path =~ %r{^https?://}
  path    = find_in_source_paths(path) unless is_uri

  say_status :apply, path, verbose
  shell.padding += 1 if verbose

  contents = if is_uri
               require "open-uri"
               URI.open(path, "Accept" => "application/x-freyia-template", &:read) # rubocop:disable Security/Open
             else
               File.read(path)
             end

  instance_eval(contents, path)
  shell.padding -= 1 if verbose
end

#chmod(path, mode, **config) ⇒ Object

Changes the mode of the given file or directory.

Examples:

Update mode of the destination file script/server

chmod "script/server", 0755

Parameters:

  • mode (Integer)

    the file mode

  • path (String)

    the name of the file to change mode

  • config (Hash)

    give verbose: false to not log the status.



146
147
148
149
150
151
152
153
# File 'lib/freyia/automations/file_manipulation.rb', line 146

def chmod(path, mode, **config)
  path = File.expand_path(path, destination_root)
  say_status :chmod, relative_to_original_destination_root(path), config.fetch(:verbose, true)
  return if options[:pretend]

  require "fileutils"
  FileUtils.chmod_R(mode, path)
end

#comment_lines(path, flag) ⇒ Object

Comment all lines matching a given regex. It will leave the space which existed before the beginning of the line in tact and will insert a single space after the comment hash.

Examples:

Comment lines which match the pattern

comment_lines 'config/initializers/session_store.rb', /cookie_store/

Parameters:

  • path (String)

    path of the file to be changed

  • flag (Regexp|String)

    the regexp or string used to decide which lines to comment



293
294
295
296
297
# File 'lib/freyia/automations/file_manipulation.rb', line 293

def comment_lines(path, flag)
  flag = flag.source if flag.respond_to?(:source)

  gsub_file(path, %r{^(\s*)([^#\n]*#{flag})}, '\1# \2')
end

#copy_file(source, destination = source, **config, &block) ⇒ Object

Copies the file from the relative source to the relative destination. If the destination is not given it's assumed to be equal to the source.

Examples:

Copy a file to a new destination

copy_file "README", "doc/README"

Copy a file straight from source to destination

copy_file "doc/README"

Parameters:

  • source (String)

    the relative path to the source root.

  • destination (String) (defaults to: source)

    the relative path to the destination root.

  • config (Hash)

    give verbose: false to not log the status, and mode: :preserve, to preserve the file mode from the source.



19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/freyia/automations/file_manipulation.rb', line 19

def copy_file(source, destination = source, **config, &block)
  source = File.expand_path(find_in_source_paths(source.to_s))

  resulting_destination = create_file destination, nil, **config do
    content = File.binread(source)
    content = yield(content) if block
    content
  end
  return unless config[:mode] == :preserve

  mode = File.stat(source).mode
  chmod(resulting_destination, mode, **config)
end

#create_file(destination, data = nil, **config, &block) ⇒ Object Also known as: add_file

Create a new file relative to the destination root with the given data, which is the return value of a block or a data string.

Examples:

Creating a file using a block

create_file "lib/fun_party.rb" do
  hostname = ask("What is the virtual hostname I should use?")
  "vhost.name = #{hostname}"
end

Creating a file using a string

create_file "config/apache.conf", "your apache config"

Parameters:

  • destination (String)

    the relative path to the destination root.

  • data (String|NilClass) (defaults to: nil)

    the data to append to the file.

  • config (Hash)

    give verbose: false to not log the status.



22
23
24
# File 'lib/freyia/automations/create_file.rb', line 22

def create_file(destination, data = nil, **config, &block)
  CreateFile.new(self, destination, block || data.to_s, **config).()
end

Create a new symbolic link relative to the destination root from the given source.

Examples:

Creating a link

create_link "config/apache.conf", "/etc/apache.conf"

Parameters:

  • destination (String)

    the relative path to the destination root.

  • source (String|NilClass)

    the relative path to the source root.

  • config (Hash)

    give verbose: false to not log the status. give symbolic: false for hard link.



16
17
18
# File 'lib/freyia/automations/create_link.rb', line 16

def create_link(destination, source, **config)
  CreateLink.new(self, destination, source, **config).()
end

#destination_rootObject

Returns the root for this freyia class (also aliased as destination root).



26
27
28
# File 'lib/freyia/automations.rb', line 26

def destination_root
  @destination_stack.last
end

#destination_root=(root) ⇒ Object

Sets the root for this freyia class. Relatives path are added to the directory where the script was invoked and expanded.



33
34
35
36
# File 'lib/freyia/automations.rb', line 33

def destination_root=(root)
  @destination_stack ||= []
  @destination_stack[0] = File.expand_path(root || "")
end

#directory(source, destination = nil, **config) ⇒ Object

Copies recursively the files from source directory to root directory. If any of the files finishes with .tmpl, it's considered to be a template and is placed in the destination without the extension .tmpl. If any empty directory is found, it's copied and all .empty_directory files are ignored. If any file name is wrapped within % signs, the text within the % signs will be executed as a method and replaced with the returned value. Let's suppose a doc directory with the following files:

doc/
components/.empty_directory
README
rdoc.rb.tmpl
%app_name%.rb

When invoked as:

directory "doc"

It will create a doc directory in the destination with the following files (assuming that the app_name method returns the value "blog"):

doc/
components/
README
rdoc.rb
blog.rb

Encoded path note: Since Freyia internals use Object#respond_to? to check if it can expand %something%, this something should be a public method in the class calling #directory.

Examples:

Copy a directory verbatim

directory "doc"

Copy a directory using a new name and no subdirectories

directory "doc", "docs", recursive: false

Parameters:

  • source (String)

    the relative path to the source root.

  • destination (String) (defaults to: nil)

    the relative path to the destination root.

  • config (Hash)
    • give verbose: false to not log the status.
    • recursive: false, does not look for paths recursively.
    • mode: :preserve, preserve the file mode from the source.
    • exclude_pattern: /regexp/, prevents copying files that match that regexp.


50
51
52
# File 'lib/freyia/automations/directory.rb', line 50

def directory(source, destination = nil, **config, &)
  Directory.new(self, source, destination, **config, &).()
end

#empty_directory(destination, **config) ⇒ Object

Creates an empty directory.

Examples:

Create an empty directory

empty_directory "doc"

Parameters:

  • destination (String)

    the relative path to the destination root.

  • config (Hash)

    give verbose: false to not log the status.



12
13
14
# File 'lib/freyia/automations/empty_directory.rb', line 12

def empty_directory(destination, **config)
  EmptyDirectory.new(self, destination, **config).()
end

#find_in_source_paths(file) ⇒ Object

Receives a file or directory and search for it in the source paths.

Raises:



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/freyia/automations.rb', line 55

def find_in_source_paths(file)
  possible_files = [file, file + TEMPLATE_EXTNAME]
  relative_root = relative_to_original_destination_root(destination_root, remove_dot: false)

  source_paths.each do |source|
    possible_files.each do |f|
      source_file = File.expand_path(f, File.join(source, relative_root))
      return source_file if File.exist?(source_file)
    end
  end

  message = "Could not find #{file.inspect} in any of your source paths. "

  message << if source_paths.empty?
               "Currently you have no source paths."
             else
               "Your current source paths are: \n#{source_paths.join("\n")}"
             end

  raise Error, message
end

#get(source, destination = nil, **config, &block) ⇒ Object

Gets the content at the given address and places it at the given relative destination. If a block is given instead of destination, the content of the url is yielded and used as location.

get relies on open-uri, so passing application user input would provide a command injection attack vector.

Examples:

Create files from remote URLs

get "http://gist.github.com/103208", "doc/README"

get "http://gist.github.com/103208", "doc/README", :http_headers => {"Content-Type" => "application/json"}

Use a block to modify the remote content

get "http://gist.github.com/103208" do |content|
  content.split("\n").first
end

Parameters:

  • source (String)

    the address of the given content.

  • destination (String) (defaults to: nil)

    the relative path to the destination root.

  • config (Hash)

    give verbose: false to not log the status, and http_headers: <Hash> to add headers to an http request.



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/freyia/automations/file_manipulation.rb', line 69

def get(source, destination = nil, **config, &block)
  render = if %r{^https?://}.match?(source)
             require "open-uri"
             URI.send(:open, source, config.fetch(:http_headers, {})) do |input|
               input.binmode.read
             end
           else
             source = File.expand_path(find_in_source_paths(source.to_s))
             File.open(source) { |input| input.binmode.read }
           end

  destination ||= if block_given?
                    block.arity == 1 ? yield(render) : yield
                  else
                    File.basename(source)
                  end

  create_file destination, render, **config
end

#gsub_file(path, flag, *args, verbose: true) ⇒ Object

Run a regular expression replacement on a file.

Examples:

Modify destination file

gsub_file 'app/controllers/application_controller.rb',
          /#\s*(filter_parameter_logging :password)/, '\1'

Parameters:

  • path (String)

    path of the file to be changed

  • flag (Regexp|String)

    the regexp or string to be replaced

  • replacement (String)

    the replacement, can be also given as a block

  • config (Hash)

    give verbose: false to not log the status, and force: true, to force the replacement regardless of behavior.



263
264
265
266
267
268
# File 'lib/freyia/automations/file_manipulation.rb', line 263

def gsub_file(path, flag, *args, verbose: true, &)
  path = File.expand_path(path, destination_root)
  say_status :gsub, relative_to_original_destination_root(path), verbose

  actually_gsub_file(path, flag, args, false, &) unless options[:pretend]
end

#gsub_file!(path, flag, *args, verbose: true) ⇒ Object

Run a regular expression replacement on a file, raising an error if the contents of the file are not changed.

Examples:

Modify destination file

gsub_file! 'app/controllers/application_controller.rb',
           /#\s*(filter_parameter_logging :password)/, '\1'

Parameters:

  • path (String)

    path of the file to be changed

  • flag (Regexp|String)

    the regexp or string to be replaced

  • replacement (String)

    the replacement, can be also given as a block

  • config (Hash)

    give verbose: false to not log the status, and force: true, to force the replacement regardless of behavior.



245
246
247
248
249
250
# File 'lib/freyia/automations/file_manipulation.rb', line 245

def gsub_file!(path, flag, *args, verbose: true, &)
  path = File.expand_path(path, destination_root)
  say_status :gsub, relative_to_original_destination_root(path), verbose

  actually_gsub_file(path, flag, args, true, &) unless options[:pretend]
end

#in_rootObject

Goes to the root and execute the given block.



119
120
121
# File 'lib/freyia/automations.rb', line 119

def in_root(&)
  inside(@destination_stack.first, &)
end

#inject_into_class(path, klass, **config) ⇒ Object

Injects text right after the class definition.

Examples:

Inject class using provided string

inject_into_class "app/controllers/application_controller.rb",
                  "ApplicationController",
                  "  filter_parameter :password\n"

Inject class using result from block

inject_into_class "app/controllers/application_controller.rb", "ApplicationController" do
  "  filter_parameter :password\n"
end

Parameters:

  • path (String)

    path of the file to be changed

  • klass (String|Class)

    the class to be manipulated

  • data (String)

    the data to append to the class, can be also given as a block.

  • config (Hash)

    give verbose: false to not log the status.



207
208
209
210
# File 'lib/freyia/automations/file_manipulation.rb', line 207

def inject_into_class(path, klass, *, **config, &)
  config[:after] = %r{class #{klass}\n|class #{klass} .*\n}
  insert_into_file(path, *, **config, &)
end

#inject_into_module(path, module_name, **config) ⇒ Object

Injects text right after the module definition.

Examples:

Inject module using provided string

inject_into_module "app/helpers/application_helper.rb",
                   "ApplicationHelper",
                   "  def help; 'help'; end\n"

Inject module using result from block

inject_into_module "app/helpers/application_helper.rb", "ApplicationHelper" do
  "  def help; 'help'; end\n"
end

Parameters:

  • path (String)

    path of the file to be changed

  • module_name (String|Class)

    the module to be manipulated

  • data (String)

    the data to append to the class, can be also given as a block.

  • config (Hash)

    give :verbose => false to not log the status.



228
229
230
231
# File 'lib/freyia/automations/file_manipulation.rb', line 228

def inject_into_module(path, module_name, *, **config, &)
  config[:after] = %r{module #{module_name}\n|module #{module_name} .*\n}
  insert_into_file(path, *, **config, &)
end

#insert_into_file(destination, *args, **config, &block) ⇒ Object Also known as: inject_into_file

Injects the given content into a file.

Examples:

Inserting text after a match

insert_into_file "config/environment.rb", "config.gem :freyia",
                 after: "Rails::Initializer.run do |config|\n"

Inserting text based on input within a block

insert_into_file "config/environment.rb", after: "Rails::Initializer.run do |config|\n" do
  gems = ask "Which gems would you like to add?"
  gems.split(" ").map{ |gem| "  config.gem :#{gem}" }.join("\n")
end

Parameters:

  • destination (String)

    Relative path to the destination root

  • data (String)

    Data to add to the file. Can be given as a block.

  • config (Hash)

    give verbose: false to not log the status and the flag for injection (after: or before:) or force: true for inserting the same content multiple times.



56
57
58
59
60
61
62
# File 'lib/freyia/automations/inject_into_file.rb', line 56

def insert_into_file(destination, *args, **config, &block)
  data = block_given? ? block : args.shift

  config[:after] = %r{\z} unless config.key?(:before) || config.key?(:after)

  InjectIntoFile.new(self, destination, data, **config).()
end

#insert_into_file!(destination, *args, **config, &block) ⇒ Object Also known as: inject_into_file!

Injects the given content into a file, raising an error if the contents of the file are not changed.

Examples:

Inserting text after a match

insert_into_file! "config/environment.rb", "config.gem :freyia",
                  after: "Rails::Initializer.run do |config|\n"

Inserting text based on input within a block

insert_into_file! "config/environment.rb", after: "Rails::Initializer.run do |config|\n" do
  gems = ask "Which gems would you like to add?"
  gems.split(" ").map{ |gem| "  config.gem :#{gem}" }.join("\n")
end

Parameters:

  • destination (String)

    Relative path to the destination root

  • data (String)

    Data to add to the file. Can be given as a block.

  • config (Hash)

    give verbose: false to not log the status and the flag for injection (after: or before:) or force: true for inserting the same content multiple times.



30
31
32
33
34
35
36
# File 'lib/freyia/automations/inject_into_file.rb', line 30

def insert_into_file!(destination, *args, **config, &block)
  data = block_given? ? block : args.shift

  config[:after] = %r{\z} unless config.key?(:before) || config.key?(:after)

  InjectIntoFile.new(self, destination, data, error_on_no_change: true, **config).()
end

#inside(dir = "", verbose: false, &block) ⇒ Object

Do something in the root or on a provided subfolder. If a relative path is given it's referenced from the current root. The full path is yielded to the block you provide. The path is set back to the previous path when the method exits.

Returns the value yielded by the block.

Parameters

dir

the directory to move to.

config

give :verbose => true to log and use padding.



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/freyia/automations.rb', line 88

def inside(dir = "", verbose: false, &block) # rubocop:todo Metrics
  pretend = options[:pretend]

  say_status :inside, dir, verbose
  shell.padding += 1 if verbose
  @destination_stack.push File.expand_path(dir, destination_root)

  # If the directory doesn't exist and we're not pretending
  if !File.exist?(destination_root) && !pretend
    require "fileutils"
    FileUtils.mkdir_p(destination_root)
  end

  result = nil
  if pretend
    # In pretend mode, just yield down to the block
    result = block.arity == 1 ? yield(destination_root) : yield
  else
    require "fileutils"
    FileUtils.cd(destination_root) do
      result = block.arity == 1 ? yield(destination_root) : yield
    end
  end

  @destination_stack.pop
  shell.padding -= 1 if verbose
  result
end

Links the file from the relative source to the relative destination. If the destination is not given it's assumed to be equal to the source.

Examples:

Link a file to a new destination

link_file "README", "doc/README"

Link a file straight from source to destination

link_file "doc/README"

Parameters:

  • source (String)

    the relative path to the source root.

  • destination (String) (defaults to: source)

    the relative path to the destination root.

  • config (Hash)

    give :verbose => false to not log the status.



44
45
46
47
48
# File 'lib/freyia/automations/file_manipulation.rb', line 44

def link_file(source, destination = source, **config)
  source = File.expand_path(find_in_source_paths(source.to_s))

  create_link destination, source, **config
end

#prepend_to_file(path, **config) ⇒ Object Also known as: prepend_file

Prepend text to a file.

Examples:

Prepend destination file with a string

prepend_to_file 'config/environments/test.rb', 'config.gem "rspec"'

Prepend destination file with results of block

prepend_to_file 'config/environments/test.rb' do
  'config.gem "rspec"'
end

Parameters:

  • path (String)

    path of the file to be changed

  • data (String)

    the data to prepend to the file, can be also given as a block.

  • config (Hash)

    give verbose: false to not log the status.



167
168
169
170
# File 'lib/freyia/automations/file_manipulation.rb', line 167

def prepend_to_file(path, *, **config, &)
  config[:after] = %r{\A}
  insert_into_file(path, *, **config, &)
end

#relative_to_original_destination_root(path, remove_dot: true) ⇒ Object

Returns the given path relative to the absolute root (ie, root where the script started).



41
42
43
44
45
46
47
48
49
50
51
# File 'lib/freyia/automations.rb', line 41

def relative_to_original_destination_root(path, remove_dot: true)
  root = @destination_stack[0]
  if path.start_with?(root) && [File::SEPARATOR, File::ALT_SEPARATOR, nil,
                                "",].include?(path[root.size..root.size])
    path = path.dup
    path[0...root.size] = "."
    remove_dot ? (path[2..] || "") : path
  else
    path
  end
end

#remove_file(path, verbose: true) ⇒ Object Also known as: remove_dir

Removes a file at the given location.

Examples:

Removing files

remove_file 'README'
remove_file 'app/controllers/application_controller.rb'

Parameters:

  • path (String)

    path of the file to be changed

  • config (Hash)

    give verbose: false to not log the status.



307
308
309
310
311
312
313
314
315
# File 'lib/freyia/automations/file_manipulation.rb', line 307

def remove_file(path, verbose: true)
  path = File.expand_path(path, destination_root)

  say_status :remove, relative_to_original_destination_root(path), verbose
  return unless !options[:pretend] && (File.exist?(path) || File.symlink?(path))

  require "fileutils"
  ::FileUtils.rm_rf(path)
end

#run(command, **config) ⇒ Object

Executes a command returning the contents of the command.

Parameters

command

the command to be executed.

config

give :verbose => false to not log the status, :capture => true to hide to output. Specify :with to append an executable to command execution.

Example

inside('vendor') do
run('ln -s ~/edge rails')
end


166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/freyia/automations.rb', line 166

def run(command, **config) # rubocop:todo Metrics
  destination = relative_to_original_destination_root(destination_root, remove_dot: false)
  desc = "#{command} from #{destination.inspect}"

  if config[:with]
    desc = "#{File.basename(config[:with].to_s)} #{desc}"
    command = "#{config[:with]} #{command}"
  end

  say_status :run, desc, config.fetch(:verbose, true)

  return if options[:pretend]

  env_splat = [config[:env]] if config[:env]

  if config[:capture]
    require "open3"
    result, status = Open3.capture2e(*env_splat, command.to_s)
    success = status.success?
  else
    result = system(*env_splat, command.to_s)
    success = result
  end

  abort if !success &&
    config.fetch(:abort_on_failure,
                 self.class.respond_to?(:exit_on_failure?) && self.class.exit_on_failure?)

  result
end

#template(source, destination = nil, context: nil, type: nil, **config, &block) ⇒ Object

Gets an ERB template at the relative source, executes it and makes a copy at the relative destination. If the destination is not given it's assumed to be equal to the source removing .tmpl from the filename.

Examples:

Process README.tmpl and save to a new destination

template "README", "doc/README"

Process from source and save to destination

template "doc/README"

Parameters:

  • source (String)

    the relative path to the source root.

  • destination (String) (defaults to: nil)

    the relative path to the destination root.

  • context (Binding) (defaults to: nil)

    if you want access to local variables from the caller

  • type (Symbol) (defaults to: nil)

    to use a template type that differs from the default, specify here (:erb or :serbea)

  • config (Hash)

    give verbose: false to not log the status.



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/freyia/automations/file_manipulation.rb', line 104

def template(source, destination = nil, context: nil, type: nil, **config, &block) # rubocop:todo Metrics
  destination ||= source.sub(%r{#{TEMPLATE_EXTNAME}$}o, "")
  source = File.expand_path(find_in_source_paths(source.to_s))
  type ||= self.class.template_type

  create_file destination, nil, **config do
    if type == :serbea
      unless @_included_serbea
        require "serbea"
        singleton_class.include Serbea::Helpers
        @_included_serbea = true
      end

      variables = if context
                    context.local_variables.to_h { [_1, context.local_variable_get(_1)] }
                  else
                    {}
                  end
      tmpl = Tilt::SerbeaTemplate.new(source, strip_front_matter: false) { ::File.binread(source) }
      tmpl.render(self, variables)
    elsif type == :erb
      context ||= instance_eval("binding", __FILE__, __LINE__)
      capturable_erb = CapturableERB.new(
        ::File.binread(source), trim_mode: "-", eoutvar: "@output_buffer"
      )
      content = capturable_erb.tap do |erb|
        erb.filename = source
      end.result(context)
      content = yield(content) if block
      content
    end
  end
end

#uncomment_lines(path, flag) ⇒ Object

Uncomment all lines matching a given regex. Preserves indentation before the comment hash and removes the hash and any immediate following space.

Examples:

Uncomment the lines which match the pattern

uncomment_lines 'config/initializers/session_store.rb', /active_record/

Parameters:

  • path (String)

    path of the file to be changed

  • flag (Regexp|String)

    the regexp or string used to decide which lines to uncomment



278
279
280
281
282
# File 'lib/freyia/automations/file_manipulation.rb', line 278

def uncomment_lines(path, flag)
  flag = flag.source if flag.respond_to?(:source)

  gsub_file(path, %r{^(\s*)#[[:blank:]]?(.*#{flag})}, '\1\2')
end