kamaze-project 1.0.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.
Files changed (106) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +18 -0
  3. data/bin/plop +5 -0
  4. data/lib/kamaze-project.rb +39 -0
  5. data/lib/kamaze/project.rb +155 -0
  6. data/lib/kamaze/project/boot/core_ext.rb +16 -0
  7. data/lib/kamaze/project/boot/listen.rb +38 -0
  8. data/lib/kamaze/project/concern.rb +13 -0
  9. data/lib/kamaze/project/concern/cli.rb +53 -0
  10. data/lib/kamaze/project/concern/cli/with_exit_on_failure.rb +36 -0
  11. data/lib/kamaze/project/concern/env.rb +63 -0
  12. data/lib/kamaze/project/concern/helper.rb +20 -0
  13. data/lib/kamaze/project/concern/mode.rb +19 -0
  14. data/lib/kamaze/project/concern/observable.rb +132 -0
  15. data/lib/kamaze/project/concern/sh.rb +91 -0
  16. data/lib/kamaze/project/concern/tasks.rb +55 -0
  17. data/lib/kamaze/project/concern/tools.rb +27 -0
  18. data/lib/kamaze/project/config.rb +58 -0
  19. data/lib/kamaze/project/core_ext/object.rb +27 -0
  20. data/lib/kamaze/project/core_ext/pp.rb +28 -0
  21. data/lib/kamaze/project/debug.rb +136 -0
  22. data/lib/kamaze/project/dsl.rb +16 -0
  23. data/lib/kamaze/project/dsl/definition.rb +43 -0
  24. data/lib/kamaze/project/dsl/setup.rb +11 -0
  25. data/lib/kamaze/project/helper.rb +63 -0
  26. data/lib/kamaze/project/helper/inflector.rb +44 -0
  27. data/lib/kamaze/project/helper/project.rb +34 -0
  28. data/lib/kamaze/project/helper/project/config.rb +25 -0
  29. data/lib/kamaze/project/observable.rb +20 -0
  30. data/lib/kamaze/project/observer.rb +37 -0
  31. data/lib/kamaze/project/resources/Vagrantfile +128 -0
  32. data/lib/kamaze/project/resources/config/tools.yml +15 -0
  33. data/lib/kamaze/project/struct.rb +34 -0
  34. data/lib/kamaze/project/tasks/cs.rb +23 -0
  35. data/lib/kamaze/project/tasks/cs/control.rb +17 -0
  36. data/lib/kamaze/project/tasks/cs/correct.rb +17 -0
  37. data/lib/kamaze/project/tasks/cs/pre_commit.rb +66 -0
  38. data/lib/kamaze/project/tasks/doc.rb +21 -0
  39. data/lib/kamaze/project/tasks/doc/watch.rb +41 -0
  40. data/lib/kamaze/project/tasks/gem.rb +14 -0
  41. data/lib/kamaze/project/tasks/gem/build.rb +34 -0
  42. data/lib/kamaze/project/tasks/gem/compile.rb +30 -0
  43. data/lib/kamaze/project/tasks/gem/gemspec.rb +20 -0
  44. data/lib/kamaze/project/tasks/gem/install.rb +32 -0
  45. data/lib/kamaze/project/tasks/gem/push.rb +27 -0
  46. data/lib/kamaze/project/tasks/misc/gitignore.rb +29 -0
  47. data/lib/kamaze/project/tasks/shell.rb +17 -0
  48. data/lib/kamaze/project/tasks/sources.rb +6 -0
  49. data/lib/kamaze/project/tasks/sources/license.rb +35 -0
  50. data/lib/kamaze/project/tasks/test.rb +16 -0
  51. data/lib/kamaze/project/tasks/vagrant.rb +71 -0
  52. data/lib/kamaze/project/tasks/version/edit.rb +16 -0
  53. data/lib/kamaze/project/tools.rb +18 -0
  54. data/lib/kamaze/project/tools/base_tool.rb +55 -0
  55. data/lib/kamaze/project/tools/console.rb +48 -0
  56. data/lib/kamaze/project/tools/console/output.rb +120 -0
  57. data/lib/kamaze/project/tools/console/output/buffer.rb +48 -0
  58. data/lib/kamaze/project/tools/gemspec.rb +33 -0
  59. data/lib/kamaze/project/tools/gemspec/builder.rb +99 -0
  60. data/lib/kamaze/project/tools/gemspec/concern.rb +13 -0
  61. data/lib/kamaze/project/tools/gemspec/concern/reading.rb +49 -0
  62. data/lib/kamaze/project/tools/gemspec/packager.rb +67 -0
  63. data/lib/kamaze/project/tools/gemspec/packer.rb +89 -0
  64. data/lib/kamaze/project/tools/gemspec/packer/command.rb +143 -0
  65. data/lib/kamaze/project/tools/gemspec/reader.rb +70 -0
  66. data/lib/kamaze/project/tools/gemspec/reader/decorator.rb +78 -0
  67. data/lib/kamaze/project/tools/gemspec/writer.rb +127 -0
  68. data/lib/kamaze/project/tools/gemspec/writer/dep_gen.rb +61 -0
  69. data/lib/kamaze/project/tools/gemspec/writer/dependency.rb +104 -0
  70. data/lib/kamaze/project/tools/git.rb +95 -0
  71. data/lib/kamaze/project/tools/git/hooks.rb +66 -0
  72. data/lib/kamaze/project/tools/git/hooks/base_hook.rb +33 -0
  73. data/lib/kamaze/project/tools/git/hooks/pre_commit.rb +81 -0
  74. data/lib/kamaze/project/tools/git/status.rb +101 -0
  75. data/lib/kamaze/project/tools/git/status/decorator.rb +39 -0
  76. data/lib/kamaze/project/tools/git/status/file.rb +180 -0
  77. data/lib/kamaze/project/tools/git/status/files_array.rb +39 -0
  78. data/lib/kamaze/project/tools/git/status/index.rb +77 -0
  79. data/lib/kamaze/project/tools/git/status/worktree.rb +19 -0
  80. data/lib/kamaze/project/tools/git/util.rb +35 -0
  81. data/lib/kamaze/project/tools/licenser.rb +197 -0
  82. data/lib/kamaze/project/tools/packager.rb +86 -0
  83. data/lib/kamaze/project/tools/packager/filesystem.rb +178 -0
  84. data/lib/kamaze/project/tools/packager/filesystem/operator.rb +83 -0
  85. data/lib/kamaze/project/tools/packager/filesystem/operator/utils.rb +77 -0
  86. data/lib/kamaze/project/tools/process_locker.rb +106 -0
  87. data/lib/kamaze/project/tools/rspec.rb +115 -0
  88. data/lib/kamaze/project/tools/rubocop.rb +128 -0
  89. data/lib/kamaze/project/tools/rubocop/arguments.rb +19 -0
  90. data/lib/kamaze/project/tools/rubocop/config.rb +43 -0
  91. data/lib/kamaze/project/tools/shell.rb +85 -0
  92. data/lib/kamaze/project/tools/vagrant.rb +182 -0
  93. data/lib/kamaze/project/tools/vagrant/composer.rb +88 -0
  94. data/lib/kamaze/project/tools/vagrant/composer/file.rb +79 -0
  95. data/lib/kamaze/project/tools/vagrant/remote.rb +79 -0
  96. data/lib/kamaze/project/tools/vagrant/shell.rb +102 -0
  97. data/lib/kamaze/project/tools/vagrant/writer.rb +81 -0
  98. data/lib/kamaze/project/tools/yardoc.rb +75 -0
  99. data/lib/kamaze/project/tools/yardoc/file.rb +55 -0
  100. data/lib/kamaze/project/tools/yardoc/watchable.rb +70 -0
  101. data/lib/kamaze/project/tools/yardoc/watcher.rb +106 -0
  102. data/lib/kamaze/project/tools_provider.rb +161 -0
  103. data/lib/kamaze/project/tools_provider/resolver.rb +42 -0
  104. data/lib/kamaze/project/version.rb +104 -0
  105. data/lib/kamaze/project/version.yml +17 -0
  106. metadata +321 -0
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (C) 2017-2018 Dimitri Arrigoni <dimitri@arrigoni.me>
4
+ # License GPLv3+: GNU GPL version 3 or later
5
+ # <http://www.gnu.org/licenses/gpl.html>.
6
+ # This is free software: you are free to change and redistribute it.
7
+ # There is NO WARRANTY, to the extent permitted by law.
8
+
9
+ require_relative '../vagrant'
10
+ require 'pathname'
11
+ require 'yaml'
12
+
13
+ # rubocop:disable Style/Documentation
14
+
15
+ class Kamaze::Project::Tools::Vagrant
16
+ class Composer
17
+ end
18
+ require_relative 'composer/file'
19
+ end
20
+
21
+ # rubocop:enable Style/Documentation
22
+
23
+ # Compose ``boxes`` data structure from files
24
+ class Kamaze::Project::Tools::Vagrant::Composer
25
+ # Path to files describing boxes
26
+ #
27
+ # defaults to ``./vagrant``
28
+ #
29
+ # @return [Pathname]
30
+ attr_reader :path
31
+
32
+ # Initialize from given path
33
+ #
34
+ # @param [String] path
35
+ def initialize(path)
36
+ @path = ::Pathname.new(path)
37
+ end
38
+
39
+ # Dump (boxes) config
40
+ #
41
+ # @return [String]
42
+ def dump
43
+ ::YAML.dump(boxes)
44
+ end
45
+
46
+ # Denote existence of configured boxes
47
+ #
48
+ # @return [Boolean]
49
+ def boxes?
50
+ !boxes.empty?
51
+ end
52
+
53
+ # Get boxes
54
+ #
55
+ # @return [Hash]
56
+ def boxes
57
+ results = {}
58
+ files.each { |file| results[file.name] = file.load }
59
+
60
+ results
61
+ end
62
+
63
+ # Get files used to generate ``boxes``
64
+ #
65
+ # Files are indexed by ``name``.
66
+ # Overrides and non-loablde files are excluded during listing.
67
+ #
68
+ # @return [Array<File>]
69
+ def files
70
+ Dir.glob("#{path}/*.yml")
71
+ .delete_if { |file| /\.override.yml$/ =~ file }
72
+ .map { |file| File.new(file) }
73
+ .keep_if(&:loadable?)
74
+ .freeze
75
+ end
76
+
77
+ # Get files related to "box files"
78
+ #
79
+ # Almost all files stored in ``path`` are considered as ``source``
80
+ #
81
+ # @return [Array<Pathname>]
82
+ def sources
83
+ Dir.glob("#{path}/**/**")
84
+ .map { |path| ::Pathname.new(path) }
85
+ .keep_if(&:file?)
86
+ .sort_by(&:to_s).freeze
87
+ end
88
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (C) 2017-2018 Dimitri Arrigoni <dimitri@arrigoni.me>
4
+ # License GPLv3+: GNU GPL version 3 or later
5
+ # <http://www.gnu.org/licenses/gpl.html>.
6
+ # This is free software: you are free to change and redistribute it.
7
+ # There is NO WARRANTY, to the extent permitted by law.
8
+
9
+ require_relative '../composer'
10
+ require 'pathname'
11
+
12
+ class Kamaze::Project::Tools::Vagrant::Composer
13
+ class File < ::Pathname
14
+ end
15
+ end
16
+
17
+ # Describe a file
18
+ #
19
+ # File can be overriden (``.override.yml``). A file has a name.
20
+ # It is loadable.
21
+ class Kamaze::Project::Tools::Vagrant::Composer::File
22
+ # @return [String]
23
+ def name
24
+ self.basename('.yml').to_s
25
+ end
26
+
27
+ # Get path to potential override
28
+ def override
29
+ self.dirname.join("#{name}.override.yml")
30
+ end
31
+
32
+ # Denote file is empty
33
+ #
34
+ # Return ``true`` when content is empty
35
+ #
36
+ # @return [Boolean]
37
+ def empty?
38
+ self.read.empty?
39
+ end
40
+
41
+ # Denote file is loadable
42
+ #
43
+ # This test is based on filesystem and emptyness
44
+ #
45
+ # @return [Boolean]
46
+ def loadable?
47
+ self.file? and self.readable? and !self.empty?
48
+ end
49
+
50
+ # Denote file is overriden
51
+ #
52
+ # @return [Booolean]
53
+ def overriden?
54
+ override.file? and override.readable?
55
+ end
56
+
57
+ # Load file
58
+ #
59
+ # @return [Hash]
60
+ def load
61
+ return nil unless self.loadable?
62
+
63
+ data = yaml_read(self)
64
+ data.merge!(yaml_read(override)) if overriden?
65
+
66
+ data
67
+ end
68
+
69
+ protected
70
+
71
+ # Read a file (by given path)
72
+ #
73
+ # Use ``safe_load`` on file content
74
+ #
75
+ # @param [Pathname] path
76
+ def yaml_read(path)
77
+ YAML.safe_load(path.read, [Symbol])
78
+ end
79
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (C) 2017-2018 Dimitri Arrigoni <dimitri@arrigoni.me>
4
+ # License GPLv3+: GNU GPL version 3 or later
5
+ # <http://www.gnu.org/licenses/gpl.html>.
6
+ # This is free software: you are free to change and redistribute it.
7
+ # There is NO WARRANTY, to the extent permitted by law.
8
+
9
+ require_relative '../vagrant'
10
+ require 'pathname'
11
+
12
+ class Kamaze::Project::Tools::Vagrant
13
+ class Remote < Shell
14
+ end
15
+ end
16
+
17
+ # Provide remote command execution (ssh)
18
+ #
19
+ # Commands can use aliases through configuration: ``box_id.ssh.aliases``;
20
+ # where ``box_id`` is the box identifier.
21
+ class Kamaze::Project::Tools::Vagrant::Remote
22
+ # @return [Hash]
23
+ attr_reader :boxes
24
+
25
+ # Initialize remote shell with given boxes and options
26
+ #
27
+ # @param [Hash] boxes
28
+ # @param [Hash] options
29
+ def initialize(boxes, options = {})
30
+ @boxes = boxes
31
+
32
+ super(options)
33
+ end
34
+
35
+ # @return [Array]
36
+ def to_a
37
+ super.push('ssh')
38
+ end
39
+
40
+ # Run a command remotely on box identified by ``box_id``
41
+ #
42
+ # Sample of use:
43
+ #
44
+ # ```ruby
45
+ # remote.execute(:freebsd)
46
+ # remote.execute(:freebsd, 'rake clobber')
47
+ # ```
48
+ #
49
+ # At least, one argument is required.
50
+ def execute(*params, &block)
51
+ box_id = params.fetch(0)
52
+ command = apply_alias(box_id, params[1]) # remote command
53
+ params = command ? [box_id, '-c', command] : [box_id]
54
+
55
+ super(*params, &block)
56
+ end
57
+
58
+ protected
59
+
60
+ # Apply alias on command for given ``box_id``
61
+ #
62
+ # @param [String] box_id
63
+ # @param [String|nil] command
64
+ # @return [String|nil]
65
+ def apply_alias(box_id, command)
66
+ return unless command
67
+
68
+ conf = boxes.fetch(box_id.to_s) # fetch related config
69
+ args = Shellwords.split(command) # command split into words
70
+ exeb = conf['ssh']['aliases'][args[0]] # executable
71
+ if exeb
72
+ command = Shellwords.split(exeb)
73
+ .concat(args.drop(1))
74
+ .yield_self { |c| Shellwords.shelljoin(c) }
75
+ end
76
+
77
+ command
78
+ end
79
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (C) 2017-2018 Dimitri Arrigoni <dimitri@arrigoni.me>
4
+ # License GPLv3+: GNU GPL version 3 or later
5
+ # <http://www.gnu.org/licenses/gpl.html>.
6
+ # This is free software: you are free to change and redistribute it.
7
+ # There is NO WARRANTY, to the extent permitted by law.
8
+
9
+ require_relative '../vagrant'
10
+ require_relative '../../concern/cli/with_exit_on_failure'
11
+ require_relative '../../concern/sh'
12
+ require 'pathname'
13
+ require 'shellwords'
14
+
15
+ class Kamaze::Project::Tools::Vagrant
16
+ class Shell
17
+ end
18
+ end
19
+
20
+ # Execute commands using ``executable``
21
+ #
22
+ # Options are passed to ``Rake::FileUtilsExt.sh()``.
23
+ # Executable can be defined through ``options``.
24
+ class Kamaze::Project::Tools::Vagrant::Shell
25
+ include Kamaze::Project::Concern::Cli::WithExitOnFailure
26
+ include Kamaze::Project::Concern::Sh
27
+
28
+ # @return [Hash]
29
+ attr_reader :options
30
+
31
+ # Initialize a shell with given options
32
+ #
33
+ # @param [Hash] options
34
+ def initialize(options = {})
35
+ @options = options
36
+ # Executable used by command
37
+ @executable = options.delete(:executable) || :vagrant
38
+
39
+ # default ``sh`` options
40
+ @options[:verbose] = false unless options.key?(:verbose)
41
+ end
42
+
43
+ # @return [Boolean]
44
+ def executable?
45
+ executable
46
+ end
47
+
48
+ # Get (absolute) path to executable
49
+ #
50
+ # Return ``nil`` when executable CAN NOT be detected.
51
+ #
52
+ # @return [String|nil]
53
+ def executable
54
+ Cliver.detect(@executable)&.freeze
55
+ end
56
+
57
+ # @return [Array]
58
+ def to_a
59
+ [executable]
60
+ end
61
+
62
+ # @return [String]
63
+ def to_s
64
+ executable.to_s
65
+ end
66
+
67
+ # Run given arguments as system command using ``executable``.
68
+ def execute(*params, &block)
69
+ env = preserved_env
70
+
71
+ Bundler.with_clean_env do
72
+ with_exit_on_failure do
73
+ [env].concat(to_a.concat(params)).push(options).yield_self do |cmd|
74
+ sh(*cmd, &block)
75
+
76
+ self.retcode = self.shell_runner_last_status.exitstatus
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ protected
83
+
84
+ # Get preserved env (from given env)
85
+ #
86
+ # @param [ENV|Hash] from
87
+ # @return [Hash]
88
+ #
89
+ # @todo refactor
90
+ def preserved_env(from = ENV)
91
+ env = {}
92
+ from = from.to_h
93
+
94
+ ['SILENCE_DUPLICATE_DIRECTORY_ERRORS'].each do |key|
95
+ next unless from.key?(key)
96
+
97
+ env[key] = from.fetch(key)
98
+ end
99
+
100
+ env
101
+ end
102
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (C) 2017-2018 Dimitri Arrigoni <dimitri@arrigoni.me>
4
+ # License GPLv3+: GNU GPL version 3 or later
5
+ # <http://www.gnu.org/licenses/gpl.html>.
6
+ # This is free software: you are free to change and redistribute it.
7
+ # There is NO WARRANTY, to the extent permitted by law.
8
+
9
+ require_relative '../vagrant'
10
+ require 'pathname'
11
+ require 'yaml'
12
+
13
+ class Kamaze::Project::Tools::Vagrant
14
+ class Writer
15
+ end
16
+ end
17
+
18
+ # Responsible to write a ``Vagrantfile``
19
+ #
20
+ # ``Vagrantfile`` is written as a standalone, i. e. ``boxes`` variable
21
+ # is set as a base64 string. ``Vagrantfile`` defines the necessary methods
22
+ # to setup VMs from ``boxes``.
23
+ class Kamaze::Project::Tools::Vagrant::Writer
24
+ # Template used to generate ``Vagrantfile``
25
+ #
26
+ # @return [::Pathname]
27
+ attr_reader :template
28
+
29
+ # Path to Vagrantfile
30
+ #
31
+ # @return [Pathname]
32
+ attr_reader :output_file
33
+
34
+ # @param [String] template
35
+ # @param [String] output_file
36
+ def initialize(template, output_file = 'Vagrantfile')
37
+ template = ::Pathname.new(template)
38
+
39
+ @template = template
40
+ @template = template.join('Vagrantfile') if template.directory?
41
+ @output_file = output_file
42
+ end
43
+
44
+ # Write ``Vagrantfile`` based on given ``boxes``
45
+ #
46
+ # @param [Hash] boxes
47
+ def write(boxes)
48
+ ::YAML.dump(boxes)
49
+ .yield_self { |yaml| templatize(yaml) }
50
+ .yield_self { |content| output_file.write(content) }
51
+ end
52
+
53
+ protected
54
+
55
+ # Make content (used to write ``Vagrantfile``)
56
+ #
57
+ # @param [String] yaml
58
+ # @return [String]
59
+ def templatize(yaml)
60
+ boxes64 = Base64.strict_encode64(yaml).yield_self do |text|
61
+ word_wrap(text, 70).map { |s| "\s\s'#{s}'\\" }.join("\n").chomp('\\')
62
+ end
63
+
64
+ ['# frozen_string_literal: true',
65
+ '# vim: ai ts=2 sts=2 et sw=2 ft=ruby', nil,
66
+ '[:base64, :yaml, :pp].each { |req| require req.to_s }', nil,
67
+ "cnf64 = \\\n#{boxes64}", nil,
68
+ 'boxes = YAML.safe_load(Base64.strict_decode64(cnf64), [Symbol])', nil,
69
+ template.read.gsub(/^#.*\n/, '')]
70
+ .map(&:to_s).join("\n").gsub(/\n\n+/, "\n\n")
71
+ end
72
+
73
+ # Wrap text into small chunks
74
+ #
75
+ # @param [String] text
76
+ # @param [Fixnum] width
77
+ # @return [Array<String>]
78
+ def word_wrap(text, width = 80)
79
+ text.each_char.each_slice(width).to_a.map(&:join)
80
+ end
81
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (C) 2017-2018 Dimitri Arrigoni <dimitri@arrigoni.me>
4
+ # License GPLv3+: GNU GPL version 3 or later
5
+ # <http://www.gnu.org/licenses/gpl.html>.
6
+ # This is free software: you are free to change and redistribute it.
7
+ # There is NO WARRANTY, to the extent permitted by law.
8
+
9
+ require_relative '../tools'
10
+ require 'pathname'
11
+ require 'yard'
12
+
13
+ # rubocop:disable Style/Documentation
14
+
15
+ module Kamaze::Project::Tools
16
+ class Yardoc < BaseTool
17
+ end
18
+
19
+ require_relative 'yardoc/file'
20
+ require_relative 'yardoc/watchable'
21
+ end
22
+
23
+ # rubocop:enable Style/Documentation
24
+
25
+ # Tool to run ``CLI::Yardoc`` and generate documentation
26
+ #
27
+ # @see https://github.com/lsegal/yard/blob/49d885f29075cfef4cb954bb9247b6fbc8318cac/lib/yard/rake/yardoc_task.rb
28
+ class Kamaze::Project::Tools::Yardoc
29
+ include Watchable
30
+
31
+ # Options used by ``YARD::CLI::Yardoc``
32
+ #
33
+ # @type [Hash]
34
+ # @return [Hash]
35
+ attr_accessor :options
36
+
37
+ # @return [Fixnum]
38
+ def run
39
+ retcode = core.run
40
+ retcode = retcode ? 0 : 1 if [true, false].include?(retcode)
41
+
42
+ retcode
43
+ end
44
+
45
+ # Get output directory (default SHOULD be ``doc``)
46
+ #
47
+ # @return [Pathname]
48
+ def output_dir
49
+ core.options
50
+ .yield_self(&:serializer)
51
+ .yield_self(&:basepath).gsub(%r{^\./+}, '')
52
+ .yield_self { |path| ::Pathname.new(path) }
53
+ end
54
+
55
+ alias call run
56
+
57
+ def mutable_attributes
58
+ [:options]
59
+ end
60
+
61
+ protected
62
+
63
+ def setup
64
+ @options ||= {}
65
+ end
66
+
67
+ # @return [YARD::CLI::Yardoc]
68
+ def core
69
+ YARD::CLI::Yardoc.new.tap do |yard|
70
+ yard.parse_arguments([])
71
+
72
+ options.to_h.each { |k, v| yard.options[k.to_sym] = v }
73
+ end
74
+ end
75
+ end