ansible_doc_generator 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 92c4b9b336f51128976073f25628e2f80418614fdd604228e06a8bb18e125646
4
+ data.tar.gz: 94e8389fc7029102b5e59c083b6412c74a022e54b103005d53a2932fd13396d6
5
+ SHA512:
6
+ metadata.gz: bcadf49014d4611a31971946f94245a39c133cb01daa486d931423a15502cbec776c1fe6a36da8900de4d95cfd55c20d6207857444b44bdcca27b847b368b35e
7
+ data.tar.gz: ff39c855e475d173ef69062ffc0483c7aff9976d14d600ae4f2f0f1876b5ccd0d65f437bd15ab4d126f9c698561882dd760922a4aac8da796ea5ab8c5450982e
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /*gem
11
+ .ruby-version
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,75 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at krzysztof.maicher@gmail.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
75
+
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in pg_export.gemspec
6
+ gemspec
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 Populate Tools SL
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
22
+
@@ -0,0 +1,107 @@
1
+ # ansible-doc-generator
2
+
3
+ CLI for documenting Ansible roles into Markdown files.
4
+
5
+ Documentation is generated from _magic_ comments declared in the tasks file of a role, where everycomment should be put before the declaration of the task.
6
+
7
+ These are the types of comments accepted:
8
+
9
+ - `@metatitle` defines a high level title (equivalent to h2). It's expected to be just one metatitle per role
10
+ - `@title` defines the title of the role or the title of the step (it's flexible)
11
+ - `@comment` completes the information provided by the title. This comment allows multiple lines and multiple instances
12
+ - `@input` defines the command or action executed by the task. Produces a block of code. This comment is also multiline
13
+ - `@output` defines the result of the execution. Produces a block of code
14
+
15
+ Comments allow variable interpolation with the syntax `#{var}`. If the variable is defined in a
16
+ sub-level it can be reached using the `>` operator, i.e. `#{apt>pkg}`
17
+
18
+ Here's an example of role that uses most of the comments and variable interpolation:
19
+
20
+ ```yaml
21
+ ---
22
+ # @metatitle Install Postgresql
23
+
24
+ # @input wget --quiet -O - #{apt_key>url} | sudo apt-key add -
25
+ - name: Add APT key
26
+ become: true
27
+ apt_key:
28
+ url: "https://www.postgresql.org/media/keys/ACCC4CF8.asc"
29
+ state: present
30
+
31
+ # @title Add repository
32
+ # @input sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt/ `lsb_release -cs`-pgdg main" >> /etc/apt/sources.list.d/pgdg.list'
33
+ - name: Add APT postgres repository
34
+ become: true
35
+ apt_repository:
36
+ repo: deb http://apt.postgresql.org/pub/repos/apt/ {{ansible_distribution_release}}-pgdg main
37
+ state: present
38
+ filename: 'pgdg.list'
39
+
40
+ # @title Install #{apt>pkg} packages
41
+ # @input apt install -y #{apt>pkg | join: ' '}
42
+ - name: Install Postgres
43
+ become: true
44
+ apt:
45
+ pkg: ['postgresql-12', 'postgresql-server-dev-12', 'postgresql-contrib-12']
46
+ state: present
47
+ update_cache: yes
48
+
49
+ # @title Install PostGis 3
50
+ # @input apt install -y #{apt>pkg | join: ' '}
51
+ - name: Install PostGis
52
+ become: true
53
+ apt:
54
+ pkg: postgresql-12-postgis-3
55
+ state: present
56
+ ```
57
+
58
+ ## Other features
59
+
60
+ ### Localization
61
+
62
+ `@metatitle`, `@title` and `@comment` support localization, by adding at the end of the label the locale code, i.e.:
63
+
64
+ ```
65
+ # @title_es Instalar Ruby
66
+ # @title_en Install Ruby
67
+ # @comment_es Este rol instala Ruby utilizando Rbenv.
68
+ # @comment_en This role installs Ruby using Rbenv
69
+ ```
70
+
71
+ When calling the generation, you'll need to add a second parameter with the locale you want to
72
+ generate:
73
+
74
+ ```
75
+ $ ansible-doc-generator ~/ansible/playbooks/ruby.yml es # to generate Spanish documentation
76
+ ```
77
+
78
+ ## Installation
79
+
80
+ ```
81
+ $ gem install ansible-doc-generator
82
+ ```
83
+
84
+ ## TODO
85
+
86
+ - [ ] Provide an example of documented repository
87
+ - [ ] better error reporting
88
+ - [ ] support includes
89
+ - [ ] deal with conditions
90
+
91
+ For a more complete list review the repository issues.
92
+
93
+ ## Development
94
+
95
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rspec` to run the tests.
96
+
97
+ ## Contributing
98
+
99
+ Bug reports and pull requests are welcome on GitHub at https://github.com/PopulateTools/ansible-doc-generator. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
100
+
101
+ ## License
102
+
103
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
104
+
105
+ ## Authors
106
+
107
+ This is a project developed by [Populate](https://populate.tools) team to document our Ansible roles and provide a great documentation to our customers.
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'ansible_doc_generator/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'ansible_doc_generator'
9
+ spec.version = AnsibleDocGenerator::VERSION
10
+ spec.authors = ['Víctor Martín', 'Fernando Blat']
11
+ spec.email = ['victor@populate.tools', 'fernando@populate.tools']
12
+
13
+ spec.summary = 'Document Ansible roles'
14
+ spec.description = "CLI for generating documentation of Ansible roles \
15
+ based on a markup language in their comments"
16
+ spec.homepage = 'https://github.com/PopulateTools/ansible-doc-generator'
17
+ spec.license = 'MIT'
18
+
19
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
20
+ spec.executables = ['ansible-doc-generator']
21
+ spec.require_paths = ['lib']
22
+ spec.required_ruby_version = '>= 2.7.0'
23
+
24
+ spec.add_development_dependency 'bundler', '~> 2.1'
25
+ spec.add_development_dependency 'pry'
26
+ spec.add_development_dependency 'rake', '~> 13.0'
27
+ spec.add_development_dependency 'rspec', '~> 3.4'
28
+ spec.add_development_dependency 'rubocop', '~> 0.59.2'
29
+ end
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'optparse'
5
+ require_relative '../lib/ansible_doc_generator'
6
+
7
+ options = {
8
+ lang: 'en'
9
+ }
10
+
11
+ option_parser = OptionParser.new do |opts|
12
+ opts.banner = 'Usage: ansible-doc-generator [options]'
13
+
14
+ opts.on("-p playbook_path", "--playbook=playbook_path", String, "Path to the playbook") do |playbook|
15
+ options[:playbook] = playbook
16
+ end
17
+
18
+ opts.on("-l es|en", "--lang=es|en", String, "Documentation language (default en)") do |lang|
19
+ options[:lang] = lang
20
+ end
21
+
22
+ opts.on('-h', '--help', 'Show this message') do
23
+ puts opts
24
+ exit
25
+ end
26
+ end
27
+
28
+ begin
29
+ option_parser.parse!
30
+ rescue OptionParser::ParseError => e
31
+ warn e.message.capitalize
32
+ warn 'Type "ansible_doc_generator -h" for available options'
33
+ exit
34
+ end
35
+
36
+ abort(option_parser.help) if options[:playbook].nil?
37
+
38
+ # Run generator
39
+ AnsibleDocGenerator::DocGenerator.new(options[:playbook], options[:lang]).call
40
+
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,8 @@
1
+ require "yaml"
2
+ require "pry"
3
+
4
+ require_relative "ansible_doc_generator/doc_generator"
5
+ require_relative "ansible_doc_generator/version"
6
+
7
+ module AnsibleDocGenerator
8
+ end
@@ -0,0 +1,63 @@
1
+ require_relative "playbook_helpers"
2
+ require_relative "doc_generator/role_doc_extractor"
3
+
4
+ module AnsibleDocGenerator
5
+ class DocGenerator
6
+ include PlaybookHelpers
7
+
8
+ attr_reader :playbook_path, :lang
9
+ attr_accessor :md_output
10
+
11
+ def initialize playbook_path, lang
12
+ @playbook_path = playbook_path
13
+ @lang = lang
14
+ @md_output = []
15
+ end
16
+
17
+ def call
18
+ parse_roles
19
+ write_readme
20
+ end
21
+
22
+ private
23
+
24
+ def parse_roles
25
+ paths.each do |path|
26
+ yml_path = tasks_yml_path_for_role(path)
27
+ maybe_generate_doc_for_role yml_path
28
+ end
29
+ end
30
+
31
+ def write_readme
32
+ doc_path = File.join(project_folder, 'docs', "installation_#{lang}.md")
33
+
34
+ delete_if_exists(doc_path)
35
+ FileUtils.mkdir_p(File.dirname(doc_path))
36
+ File.open(doc_path, 'w'){|file| file.write(clean_md_output) }
37
+
38
+ puts "> Installation guide saved in #{doc_path}"
39
+ end
40
+
41
+ def maybe_generate_doc_for_role yml_path
42
+ if File.exists?(yml_path)
43
+ puts "+ Generating doc for: #{yml_path}"
44
+ generate_doc_for_role yml_path
45
+ else
46
+ puts "- main.yml not found in path #{yml_path}"
47
+ end
48
+ end
49
+
50
+ def generate_doc_for_role yml_path
51
+ md_output << RoleDocExtractor.new(yml_path, lang).call
52
+ end
53
+
54
+ def clean_md_output
55
+ md_output.reject{|line| line.strip == '' || line.nil? }.join("\n\n")
56
+ end
57
+
58
+ def tasks_yml_path_for_role path
59
+ File.join(project_folder, path, 'tasks', 'main.yml')
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "interpolator/variable_extractor"
4
+ require_relative "interpolator/file_extractor"
5
+
6
+ module AnsibleDocGenerator
7
+ class DocGenerator
8
+ class Interpolator
9
+ attr_reader :input, :task, :role_path
10
+
11
+ def initialize input, task, role_path
12
+ @input = input
13
+ @task = task
14
+ @role_path = role_path
15
+ end
16
+
17
+ def call
18
+ variables_interpolated = Interpolator::VariableExtractor.new(input, task, role_path).call
19
+ Interpolator::FileExtractor.new(variables_interpolated, role_path).call
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnsibleDocGenerator
4
+ class DocGenerator
5
+ class Interpolator
6
+ class FileExtractor
7
+ attr_reader :input, :role_path
8
+
9
+ def initialize input, role_path
10
+ @input = input
11
+ @role_path = role_path
12
+ end
13
+
14
+ def call
15
+ output = input
16
+
17
+ input.scan(/f\{[^\}]+\}/).each do |interpolation|
18
+ file_content = get_file_content(interpolation)
19
+ output.gsub!(interpolation, file_content)
20
+ end
21
+
22
+ output
23
+ end
24
+
25
+ private
26
+
27
+ def get_file_content interpolation
28
+ file_name = interpolation.match(/f\{(\S*)\}/)[1]
29
+ file_path = get_file_path_from(file_name)
30
+
31
+ return interpolation if file_path.nil?
32
+ File.read(file_path)
33
+ end
34
+
35
+ def get_file_path_from file_name
36
+ %w(files templates)
37
+ .map{|folder_name| File.join(main_folder, folder_name, file_name) }
38
+ .find{|file_path| File.exists?(file_path) }
39
+ end
40
+
41
+ def main_folder
42
+ @main_folder ||= File.expand_path("..", File.dirname(role_path))
43
+ end
44
+
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnsibleDocGenerator
4
+ class DocGenerator
5
+ class Interpolator
6
+ class VariableExtractor
7
+
8
+ class MissingInterpolationError < StandardError; end
9
+
10
+ attr_reader :input, :task, :role_path
11
+
12
+ def initialize input, task, role_path
13
+ @input = input
14
+ @task = task
15
+ @role_path = role_path
16
+ @filters = {
17
+ join: ', '
18
+ }
19
+ end
20
+
21
+ def call
22
+ output = input
23
+
24
+ input.scan(/#\{[^\}]+\}/).each do |interpolation|
25
+ new_string = get_string(interpolation)
26
+ output.gsub!(interpolation, new_string)
27
+ end
28
+
29
+ output
30
+ end
31
+
32
+ private
33
+
34
+ def get_string interpolation
35
+ variable_match = interpolation.match(/#\{([^\}]+)\}/)[1]
36
+ yaml_path, filters = variable_match.split('|')
37
+ set_filters(filters)
38
+ yaml_value = dig(task, yaml_path.gsub(/\s/, '').split('>'))
39
+
40
+ case yaml_value
41
+ when String then yaml_value
42
+ when Array then yaml_value.join(@filters[:join])
43
+ else interpolation
44
+ end
45
+ rescue MissingInterpolationError => e
46
+ raise e, "Interpolation not found:\n - #{interpolation}\n - #{task}\n - #{role_path}"
47
+ end
48
+
49
+ def set_filters(filters)
50
+ return if filters.nil?
51
+ filter_name, filter_value = filters.split(':')
52
+ @filters[filter_name.gsub(/\s/, '').to_sym] = eval(filter_value)
53
+ end
54
+
55
+ def dig task, keys
56
+ head, *tail = keys
57
+ new_task = task[head]
58
+
59
+ raise MissingInterpolationError if new_task.nil?
60
+
61
+ if tail == []
62
+ return new_task
63
+ elsif new_task.is_a?(String) && tail != []
64
+ return scan_in_inline_syntax(new_task, tail.first)
65
+ else
66
+ dig(new_task, tail)
67
+ end
68
+ end
69
+
70
+ def scan_in_inline_syntax string, key
71
+ input = StringScanner.new(string)
72
+
73
+ # Scan until the key we are looking for
74
+ input.scan_until(/#{key}=/)
75
+ # Scan until the next key or until the end
76
+ output = input.scan_until(/\s{1}\S+=/) || input.scan(/.*/)
77
+ # Remove the next key part
78
+ output.gsub(/\s{1}\S+=/, '')
79
+ end
80
+
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "interpolator"
4
+
5
+ module AnsibleDocGenerator
6
+ class DocGenerator
7
+ class RoleDocExtractor
8
+ attr_reader :yml_path, :lang
9
+ attr_accessor :parsed_content, :md_output
10
+
11
+ META_KEYWORDS = %w(metatitle)
12
+
13
+ def initialize yml_path, lang
14
+ @yml_path = yml_path
15
+ @lang = lang
16
+ @parsed_content = {'meta' => []}
17
+ @md_output = []
18
+ end
19
+
20
+ def call
21
+ joined_output
22
+ end
23
+
24
+ private
25
+
26
+ def joined_output
27
+ @joined_output ||= begin
28
+ parse_file_comments
29
+ generate_meta_content
30
+ generate_tasks_content
31
+
32
+ join_elements(md_output, "\n\n")
33
+ end
34
+ end
35
+
36
+ # Extract relevant lines from the file and group them by task
37
+ def parse_file_comments
38
+ file = File.open(yml_path)
39
+ current_comments = []
40
+ file.each do |line|
41
+ if line.start_with?('#') && META_KEYWORDS.any?{|keyword| line.start_with?("# @#{keyword}") }
42
+ parsed_content['meta'] << line.gsub('# ', '').strip
43
+ elsif line.start_with?('#')
44
+ current_comments << line.gsub(/\A#\s{1}/, '')
45
+ elsif line.start_with?('- name:')
46
+ task_name = line.gsub('- name:','').strip
47
+ parsed_content[task_name] = current_comments
48
+ current_comments = []
49
+ end
50
+ end and nil
51
+ file.close
52
+ end
53
+
54
+ def generate_meta_content
55
+ return if parsed_content['meta'] == []
56
+ metatitle = extract_from(:metatitle, parsed_content['meta'])
57
+ return if metatitle.nil?
58
+
59
+ md_output << "## #{metatitle}"
60
+ end
61
+
62
+ # Parse previously extracted lines and separated them by keyword
63
+ def generate_tasks_content
64
+ parsed_content.each do |task_name, lines|
65
+ title = extract_from(:title, lines)
66
+
67
+ output = extract_multiline_from(:output, lines, separator: "\n")
68
+ input = extract_multiline_from(:input, lines, separator: "\n")
69
+ comments = extract_multiline_from(:comment, lines)
70
+
71
+ md_output << generate_md_with(task_name, title: title, comments: comments, output: output, input: input)
72
+ end
73
+ end
74
+
75
+ def generate_md_with task_name, params
76
+ temp_md = []
77
+ temp_md.push("### #{params[:title]}") if params[:title]
78
+ params.fetch(:comments, []).each{|comment| temp_md << comment }
79
+ temp_md << "```\n#{params.fetch(:input, []).join("\n")}\n```" if params[:input].length > 0
80
+ temp_md << "Expected output: \n\n```\n#{params[:output]}\n```" if params[:output].length > 0
81
+
82
+ non_interpolated_output = join_elements(temp_md, "\n\n")
83
+ task = tasks.find{|task| task['name'] == task_name}
84
+ Interpolator.new(non_interpolated_output, task, yml_path).call
85
+ end
86
+
87
+ def extract_from keyword, lines
88
+ line = lines.find{|line| selectable_line?(keyword, line) }
89
+ clean_line(keyword, line)
90
+ end
91
+
92
+ # - a comment, for instance, can be in several lines and only the first one will have the keyword.
93
+ # - if a new "@comment" appear, it's a new comment
94
+ # - this method always return an array, but some keywords like input or output will only use one item
95
+ def extract_multiline_from keyword, lines, separator: ' '
96
+ collection = [] # collection of @keyword with maybe several lines
97
+ current_item = [] # current @keyword that will change through the loop
98
+ current_keyword = nil
99
+
100
+ lines.each do |line|
101
+ # It's the beginning of a comment
102
+ if selectable_line?(keyword, line)
103
+ current_keyword = keyword
104
+ # we save the previous comment before parsing the new one
105
+ if current_item.any?
106
+ collection << join_elements(current_item, separator)
107
+ current_item = []
108
+ end
109
+ current_item << clean_line(current_keyword, line)
110
+ # If it's another keyword but not relevant, we store current comment. But we keep the loop in case there are others
111
+ elsif line.start_with?("@") && !selectable_line?(keyword, line) && current_item.any?
112
+ collection << join_elements(current_item, separator)
113
+ current_item = []
114
+ # It's the second/third... line of a comment
115
+ elsif current_item.any? && !line.start_with?('@')
116
+ current_item << clean_line(current_keyword, line)
117
+ end
118
+ end
119
+
120
+ collection << join_elements(current_item, separator) if current_item.any?
121
+
122
+ collection
123
+ end
124
+
125
+ def selectable_line?(keyword, line)
126
+ line.start_with?("@#{keyword}_#{lang}") || (line.start_with?("@#{keyword}") && !line.start_with?("@#{keyword}_"))
127
+ end
128
+
129
+ def clean_line(keyword, line)
130
+ return nil unless line
131
+
132
+ no_keyword_line = line.gsub(/@#{keyword}_#{lang}|@#{keyword}/, '')
133
+ if %w(input output).include?(keyword)
134
+ no_keyword_line
135
+ else
136
+ no_keyword_line.strip
137
+ end
138
+ end
139
+
140
+ def join_elements elements, separator
141
+ elements.reject{|line| line.strip == '' || line.nil? }.join(separator)
142
+ end
143
+
144
+ def tasks
145
+ @tasks = YAML.load_file(yml_path)
146
+ end
147
+
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,28 @@
1
+ require 'yaml'
2
+
3
+ module AnsibleDocGenerator
4
+ module PlaybookHelpers
5
+
6
+ private
7
+
8
+ def delete_if_exists path
9
+ FileUtils.remove_entry(path, true)
10
+ end
11
+
12
+ def paths
13
+ @paths ||= roles.map{|role| role['role'] }
14
+ end
15
+
16
+ def roles
17
+ playbook['roles']
18
+ end
19
+
20
+ def project_folder
21
+ @project_folder ||= File.dirname(playbook_path)
22
+ end
23
+
24
+ def playbook
25
+ @playbook ||= YAML.load_file(playbook_path).first
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnsibleDocGenerator
4
+ VERSION = '0.1.0'
5
+ end
metadata ADDED
@@ -0,0 +1,134 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ansible_doc_generator
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Víctor Martín
8
+ - Fernando Blat
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2021-01-12 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '2.1'
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '2.1'
28
+ - !ruby/object:Gem::Dependency
29
+ name: pry
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: rake
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '13.0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '13.0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rspec
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: '3.4'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '3.4'
70
+ - !ruby/object:Gem::Dependency
71
+ name: rubocop
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: 0.59.2
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: 0.59.2
84
+ description: CLI for generating documentation of Ansible roles based
85
+ on a markup language in their comments
86
+ email:
87
+ - victor@populate.tools
88
+ - fernando@populate.tools
89
+ executables:
90
+ - ansible-doc-generator
91
+ extensions: []
92
+ extra_rdoc_files: []
93
+ files:
94
+ - ".gitignore"
95
+ - ".rspec"
96
+ - CODE_OF_CONDUCT.md
97
+ - Gemfile
98
+ - LICENSE.txt
99
+ - README.md
100
+ - ansible_doc_generator.gemspec
101
+ - bin/ansible-doc-generator
102
+ - bin/setup
103
+ - lib/ansible_doc_generator.rb
104
+ - lib/ansible_doc_generator/doc_generator.rb
105
+ - lib/ansible_doc_generator/doc_generator/interpolator.rb
106
+ - lib/ansible_doc_generator/doc_generator/interpolator/file_extractor.rb
107
+ - lib/ansible_doc_generator/doc_generator/interpolator/variable_extractor.rb
108
+ - lib/ansible_doc_generator/doc_generator/role_doc_extractor.rb
109
+ - lib/ansible_doc_generator/playbook_helpers.rb
110
+ - lib/ansible_doc_generator/version.rb
111
+ homepage: https://github.com/PopulateTools/ansible-doc-generator
112
+ licenses:
113
+ - MIT
114
+ metadata: {}
115
+ post_install_message:
116
+ rdoc_options: []
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: 2.7.0
124
+ required_rubygems_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ requirements: []
130
+ rubygems_version: 3.2.3
131
+ signing_key:
132
+ specification_version: 4
133
+ summary: Document Ansible roles
134
+ test_files: []