dss_tech_docs 0.1.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 (151) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +29 -0
  3. data/.gitignore +37 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +11 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +9 -0
  8. data/CHANGELOG.md +274 -0
  9. data/CONTRIBUTING.md +9 -0
  10. data/Gemfile +4 -0
  11. data/LICENCE +21 -0
  12. data/README.md +89 -0
  13. data/Rakefile +13 -0
  14. data/docs/configuration.md +205 -0
  15. data/docs/core-layout-without-sidebar.png +0 -0
  16. data/docs/core-layout.png +0 -0
  17. data/docs/expired-page.png +0 -0
  18. data/docs/frontmatter.md +145 -0
  19. data/docs/last-reviewed-only.png +0 -0
  20. data/docs/last-reviewed-only.svg +1 -0
  21. data/docs/layout-layout.png +0 -0
  22. data/docs/not-expired-page.png +0 -0
  23. data/docs/page-expiry.md +85 -0
  24. data/dss_tech_docs.gemspec +46 -0
  25. data/example/.ruby-version +1 -0
  26. data/example/Gemfile +3 -0
  27. data/example/config.rb +9 -0
  28. data/example/config/hide-expiry.yml +51 -0
  29. data/example/config/tech-docs.yml +50 -0
  30. data/example/source/api-path.html.md +7 -0
  31. data/example/source/api-reference.html.md +5 -0
  32. data/example/source/child-of-expired-page.html.md +8 -0
  33. data/example/source/core-layout-without-sidebar.html.md.erb +7 -0
  34. data/example/source/core-layout.html.md.erb +12 -0
  35. data/example/source/expired-page-with-owner.html.md +10 -0
  36. data/example/source/expired-page.html.md +9 -0
  37. data/example/source/headings.html.md +11 -0
  38. data/example/source/index.html.md.erb +19 -0
  39. data/example/source/javascripts/application.js +1 -0
  40. data/example/source/not-expired-page.html.md +9 -0
  41. data/example/source/pets.yml +109 -0
  42. data/example/source/stylesheets/print.css.scss +3 -0
  43. data/example/source/stylesheets/screen-old-ie.css.scss +4 -0
  44. data/example/source/stylesheets/screen.css.scss +1 -0
  45. data/example/source/templates/proxy_template.html.md +8 -0
  46. data/lib/assets/javascripts/_analytics.js +58 -0
  47. data/lib/assets/javascripts/_govuk/modules.js +57 -0
  48. data/lib/assets/javascripts/_modules/anchored-headings.js +18 -0
  49. data/lib/assets/javascripts/_modules/collapsible-navigation.js +95 -0
  50. data/lib/assets/javascripts/_modules/in-page-navigation.js +132 -0
  51. data/lib/assets/javascripts/_modules/navigation.js +34 -0
  52. data/lib/assets/javascripts/_modules/page-expiry.js +15 -0
  53. data/lib/assets/javascripts/_modules/search.js +367 -0
  54. data/lib/assets/javascripts/_modules/table-of-contents.js +111 -0
  55. data/lib/assets/javascripts/_start-modules.js +13 -0
  56. data/lib/assets/javascripts/_vendor/fixedsticky.js +194 -0
  57. data/lib/assets/javascripts/_vendor/jquery.js +5 -0
  58. data/lib/assets/javascripts/_vendor/jquery.mark.js +1081 -0
  59. data/lib/assets/javascripts/_vendor/lodash.js +613 -0
  60. data/lib/assets/javascripts/_vendor/modernizr.js +3 -0
  61. data/lib/assets/javascripts/govuk_tech_docs.js +10 -0
  62. data/lib/assets/stylesheets/_accessibility.scss +9 -0
  63. data/lib/assets/stylesheets/_core.scss +71 -0
  64. data/lib/assets/stylesheets/_fonts.scss +29 -0
  65. data/lib/assets/stylesheets/_govuk_tech_docs.scss +2 -0
  66. data/lib/assets/stylesheets/_syntax-highlighting.scss +196 -0
  67. data/lib/assets/stylesheets/_variables.scss +12 -0
  68. data/lib/assets/stylesheets/govuk_frontend_toolkit/_colours.scss +2 -0
  69. data/lib/assets/stylesheets/govuk_frontend_toolkit/_conditionals.scss +81 -0
  70. data/lib/assets/stylesheets/govuk_frontend_toolkit/_css3.scss +90 -0
  71. data/lib/assets/stylesheets/govuk_frontend_toolkit/_device-pixels.scss +10 -0
  72. data/lib/assets/stylesheets/govuk_frontend_toolkit/_font_stack.scss +19 -0
  73. data/lib/assets/stylesheets/govuk_frontend_toolkit/_grid_layout.scss +136 -0
  74. data/lib/assets/stylesheets/govuk_frontend_toolkit/_helpers.scss +16 -0
  75. data/lib/assets/stylesheets/govuk_frontend_toolkit/_measurements.scss +14 -0
  76. data/lib/assets/stylesheets/govuk_frontend_toolkit/_shims.scss +55 -0
  77. data/lib/assets/stylesheets/govuk_frontend_toolkit/_typography.scss +249 -0
  78. data/lib/assets/stylesheets/govuk_frontend_toolkit/_url-helpers.scss +16 -0
  79. data/lib/assets/stylesheets/govuk_frontend_toolkit/colours/_organisation.scss +103 -0
  80. data/lib/assets/stylesheets/govuk_frontend_toolkit/colours/_palette.scss +77 -0
  81. data/lib/assets/stylesheets/govuk_frontend_toolkit/design-patterns/_alpha-beta.scss +66 -0
  82. data/lib/assets/stylesheets/govuk_frontend_toolkit/design-patterns/_breadcrumbs.scss +53 -0
  83. data/lib/assets/stylesheets/govuk_frontend_toolkit/design-patterns/_buttons.scss +141 -0
  84. data/lib/assets/stylesheets/govuk_frontend_toolkit/design-patterns/_media-player.scss +242 -0
  85. data/lib/assets/stylesheets/modules/_anchored-heading.scss +54 -0
  86. data/lib/assets/stylesheets/modules/_app-pane.scss +64 -0
  87. data/lib/assets/stylesheets/modules/_collapsible.scss +52 -0
  88. data/lib/assets/stylesheets/modules/_contribution-banner.scss +22 -0
  89. data/lib/assets/stylesheets/modules/_footer.scss +130 -0
  90. data/lib/assets/stylesheets/modules/_govuk-logo.scss +47 -0
  91. data/lib/assets/stylesheets/modules/_header.scss +290 -0
  92. data/lib/assets/stylesheets/modules/_page-review.scss +35 -0
  93. data/lib/assets/stylesheets/modules/_phase-banner.scss +22 -0
  94. data/lib/assets/stylesheets/modules/_search.scss +137 -0
  95. data/lib/assets/stylesheets/modules/_skip-link.scss +31 -0
  96. data/lib/assets/stylesheets/modules/_technical-documentation.scss +241 -0
  97. data/lib/assets/stylesheets/modules/_toc.scss +216 -0
  98. data/lib/assets/stylesheets/modules/_warning-text.scss +73 -0
  99. data/lib/assets/stylesheets/palette/_syntax-highlighting.scss +23 -0
  100. data/lib/assets/stylesheets/utilities/_fonts.scss +29 -0
  101. data/lib/assets/stylesheets/utilities/_printable.scss +13 -0
  102. data/lib/assets/stylesheets/vendor/_fixedsticky.scss +22 -0
  103. data/lib/dss_tech_docs.rb +121 -0
  104. data/lib/govuk_tech_docs/api_reference/api_reference_extension.rb +101 -0
  105. data/lib/govuk_tech_docs/api_reference/api_reference_renderer.rb +279 -0
  106. data/lib/govuk_tech_docs/api_reference/templates/api_reference_full.html.erb +19 -0
  107. data/lib/govuk_tech_docs/api_reference/templates/operation.html.erb +11 -0
  108. data/lib/govuk_tech_docs/api_reference/templates/parameters.html.erb +28 -0
  109. data/lib/govuk_tech_docs/api_reference/templates/path.html.erb +4 -0
  110. data/lib/govuk_tech_docs/api_reference/templates/responses.html.erb +33 -0
  111. data/lib/govuk_tech_docs/api_reference/templates/schema.html.erb +29 -0
  112. data/lib/govuk_tech_docs/contribution_banner.rb +62 -0
  113. data/lib/govuk_tech_docs/meta_tags.rb +67 -0
  114. data/lib/govuk_tech_docs/page_review.rb +52 -0
  115. data/lib/govuk_tech_docs/pages.rb +32 -0
  116. data/lib/govuk_tech_docs/redirects.rb +39 -0
  117. data/lib/govuk_tech_docs/table_of_contents/heading.rb +30 -0
  118. data/lib/govuk_tech_docs/table_of_contents/heading_tree.rb +27 -0
  119. data/lib/govuk_tech_docs/table_of_contents/heading_tree_builder.rb +41 -0
  120. data/lib/govuk_tech_docs/table_of_contents/heading_tree_renderer.rb +46 -0
  121. data/lib/govuk_tech_docs/table_of_contents/headings_builder.rb +39 -0
  122. data/lib/govuk_tech_docs/table_of_contents/helpers.rb +79 -0
  123. data/lib/govuk_tech_docs/tech_docs_html_renderer.rb +34 -0
  124. data/lib/govuk_tech_docs/unique_identifier_extension.rb +13 -0
  125. data/lib/govuk_tech_docs/unique_identifier_generator.rb +72 -0
  126. data/lib/govuk_tech_docs/version.rb +3 -0
  127. data/lib/govuk_tech_docs/warning_text_extension.rb +23 -0
  128. data/lib/source/api/pages.json.erb +1 -0
  129. data/lib/source/favicon.ico +0 -0
  130. data/lib/source/images/anchored-heading-icon-2x.png +0 -0
  131. data/lib/source/images/anchored-heading-icon.png +0 -0
  132. data/lib/source/images/gov.uk_logotype_crown-2x.png +0 -0
  133. data/lib/source/images/gov.uk_logotype_crown.png +0 -0
  134. data/lib/source/images/gov.uk_logotype_crown_invert_trans.png +0 -0
  135. data/lib/source/images/govuk-crest-2x.png +0 -0
  136. data/lib/source/images/govuk-crest.png +0 -0
  137. data/lib/source/images/govuk-icn-close.png +0 -0
  138. data/lib/source/images/govuk-icn-close@2x.png +0 -0
  139. data/lib/source/images/govuk-icn-numbered-list.png +0 -0
  140. data/lib/source/images/govuk-icn-numbered-list@2x.png +0 -0
  141. data/lib/source/images/open-government-licence.png +0 -0
  142. data/lib/source/images/open-government-licence_2x.png +0 -0
  143. data/lib/source/images/search-result-caret.svg +13 -0
  144. data/lib/source/layouts/_analytics.erb +15 -0
  145. data/lib/source/layouts/_footer.erb +10 -0
  146. data/lib/source/layouts/_header.erb +44 -0
  147. data/lib/source/layouts/_page_review.erb +22 -0
  148. data/lib/source/layouts/_search.erb +16 -0
  149. data/lib/source/layouts/core.erb +82 -0
  150. data/lib/source/layouts/layout.erb +18 -0
  151. metadata +474 -0
@@ -0,0 +1,19 @@
1
+ <h1 id="<%= info.title.parameterize %>"><%= info.title %> v<%= info.version %></h1>
2
+ <%= markdown(info.description) %>
3
+
4
+ <% unless servers.empty? %>
5
+ <h2 id="servers">Servers</h2>
6
+ <div id="server-list">
7
+ <% servers.each do |server| %>
8
+ <% if server.description %>
9
+ <p><strong><%= server.description %></strong></p>
10
+ <% end %>
11
+ <a href="<%= server.url %>"><%= server.url %></a>
12
+ <% end %>
13
+ </div>
14
+ <% end %>
15
+
16
+ <%= paths %>
17
+
18
+ <h2 id="schemas">Schemas</h2>
19
+ <%= schemas %>
@@ -0,0 +1,11 @@
1
+ <h3 id="<%= id %>"><%= key %></h3>
2
+ <% if operation.summary %>
3
+ <p><em><%= operation.summary %></em></p>
4
+ <% end %>
5
+ <% if operation.description %>
6
+ <p><%= markdown(operation.description) %></p>
7
+ <% end %>
8
+
9
+ <%= parameters %>
10
+
11
+ <%= responses %>
@@ -0,0 +1,28 @@
1
+ <% if parameters.any? %>
2
+ <h4 id="<%= id %>">Parameters</h4>
3
+ <table>
4
+ <thead>
5
+ <tr><th>Parameter</th><th>In</th><th>Type</th><th>Required</th><th>Description</th></tr>
6
+ </thead>
7
+ <tbody>
8
+ <% parameters.each do |parameter| %>
9
+ <tr>
10
+ <td><%= parameter.name %></td>
11
+ <td><%= parameter.in %></td>
12
+ <td><%= parameter.schema.type %></td>
13
+ <td><%= parameter.required? %></td>
14
+ <td><%= markdown(parameter.description) %>
15
+ <% if parameter.schema.enum %>
16
+ <p>Available items:</p>
17
+ <ul>
18
+ <% parameter.schema.enum.each do |item| %>
19
+ <li><%= item %></li>
20
+ <% end %>
21
+ </ul>
22
+ <% end %>
23
+ </td>
24
+ </tr>
25
+ <% end %>
26
+ </tbody>
27
+ </table>
28
+ <% end %>
@@ -0,0 +1,4 @@
1
+ <% if text %>
2
+ <h2 id="<%= id %>"><%= text %></h2>
3
+ <% end %>
4
+ <%= operations %>
@@ -0,0 +1,33 @@
1
+ <% if responses.any? %>
2
+ <h4 id="<%= id %>">Responses</h4>
3
+ <table>
4
+ <thead>
5
+ <tr><th>Status</th><th>Description</th><th>Schema</th></tr>
6
+ </thead>
7
+ <tbody>
8
+ <% responses.each do |key,response| %>
9
+ <tr>
10
+ <td><%= key %></td>
11
+ <td>
12
+ <%= markdown(response.description) %>
13
+ <% if response.content['application/json']
14
+ if response.content['application/json']["example"]
15
+ request_body = json_prettyprint(response.content['application/json']["example"])
16
+ else
17
+ request_body = json_output(response.content['application/json'].schema)
18
+ end
19
+ end %>
20
+ <% if !request_body.blank? %>
21
+ <pre><code><%= request_body %></code></pre>
22
+ <% end %>
23
+ </td>
24
+ <td>
25
+ <%= if response.content['application/json']
26
+ get_schema_link(response.content['application/json'].schema)
27
+ end %>
28
+ </td>
29
+ </tr>
30
+ <% end %>
31
+ </tbody>
32
+ </table>
33
+ <% end %>
@@ -0,0 +1,29 @@
1
+ <h3 id="<%= id = 'schema-' + title; id.parameterize %>"><%= title %></h3>
2
+ <%= markdown(schema.description) %>
3
+ <% if properties.any? %>
4
+ <table>
5
+ <thead>
6
+ <tr><th>Name</th><th>Type</th><th>Required</th><th>Description</th><th>Schema</th></tr>
7
+ </thead>
8
+ <tbody>
9
+ <% properties.each do |property| %>
10
+ <tr>
11
+ <td><%= property[0] %></td>
12
+ <td><%= property[1].type %></td>
13
+ <td><%= property[1].required.present? %></td>
14
+ <td><%= markdown(property[1].description) %></td>
15
+ <td>
16
+ <%=
17
+ schema = property[1]
18
+ # If property is an array, check the items property for a reference.
19
+ if property[1].type == 'array'
20
+ schema = property[1]['items']
21
+ end
22
+ # Only print a link if it's a referenced object.
23
+ get_schema_link(schema) if schema.node_context.referenced_by.to_s.include? '#/components/schemas' and !schema.node_context.source_location.to_s.include? '/properties/' %>
24
+ </td>
25
+ </tr>
26
+ <% end %>
27
+ </tbody>
28
+ </table>
29
+ <% end %>
@@ -0,0 +1,62 @@
1
+ module GovukTechDocs
2
+ # Helper included
3
+ module ContributionBanner
4
+ def source_urls
5
+ SourceUrls.new(current_page, config)
6
+ end
7
+ end
8
+
9
+ class SourceUrls
10
+ attr_reader :current_page, :config
11
+
12
+ def initialize(current_page, config)
13
+ @current_page = current_page
14
+ @config = config
15
+ end
16
+
17
+ def view_source_url
18
+ override_from_page || source_from_yaml_file || source_from_file
19
+ end
20
+
21
+ def report_issue_url
22
+ url = config[:source_urls]&.[](:report_issue_url)
23
+
24
+ if url.nil?
25
+ "#{repo_url}/issues/new?labels=bug&title=Re: '#{current_page.data.title}'&body=Problem with '#{current_page.data.title}' (#{config[:tech_docs][:host]}#{current_page.url})"
26
+ else
27
+ "#{url}?subject=Re: '#{current_page.data.title}'&body=Problem with '#{current_page.data.title}' (#{config[:tech_docs][:host]}#{current_page.url})"
28
+ end
29
+ end
30
+
31
+ def repo_url
32
+ "https://github.com/#{config[:tech_docs][:github_repo]}"
33
+ end
34
+
35
+ def repo_branch
36
+ config[:tech_docs][:github_branch] || 'master'
37
+ end
38
+
39
+ private
40
+
41
+ # If a `page` local exists, see if it has a `source_url`. This is used by the
42
+ # pages that are created by the proxy system because they can't use frontmatter
43
+ def override_from_page
44
+ locals.key?(:page) ? locals[:page].try(:source_url) : false
45
+ end
46
+
47
+ # In the frontmatter we can specify a `source_url`. Use this if the actual
48
+ # source of the page is in another GitHub repo.
49
+ def source_from_yaml_file
50
+ current_page.data.source_url
51
+ end
52
+
53
+ # As the last fallback link to the source file in this repository.
54
+ def source_from_file
55
+ "#{repo_url}/blob/#{repo_branch}/source/#{current_page.file_descriptor[:relative_path]}"
56
+ end
57
+
58
+ def locals
59
+ current_page.metadata[:locals]
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,67 @@
1
+ module GovukTechDocs
2
+ class MetaTags
3
+ def initialize(config, current_page)
4
+ @config = config
5
+ @current_page = current_page
6
+ end
7
+
8
+ def tags
9
+ all_tags = {
10
+ 'description' => page_description,
11
+ 'og:description' => page_description,
12
+ 'og:image' => page_image,
13
+ 'og:site_name' => site_name,
14
+ 'og:title' => page_title,
15
+ 'og:type' => 'object',
16
+ 'og:url' => canonical_url,
17
+ 'twitter:card' => 'summary',
18
+ 'twitter:domain' => URI.parse(host).host,
19
+ 'twitter:image' => page_image,
20
+ 'twitter:title' => browser_title,
21
+ 'twitter:url' => canonical_url,
22
+ }
23
+
24
+ Hash[all_tags.select { |_k, v| v }]
25
+ end
26
+
27
+ def browser_title
28
+ [page_title, site_name].select(&:present?).uniq.join(' | ')
29
+ end
30
+
31
+ def canonical_url
32
+ "#{host}#{current_page.url}"
33
+ end
34
+
35
+ private
36
+
37
+ attr_reader :config, :current_page
38
+
39
+ def page_image
40
+ "#{host}/images/govuk-large.png"
41
+ end
42
+
43
+ def site_name
44
+ config[:tech_docs][:full_service_name] || config[:tech_docs][:service_name]
45
+ end
46
+
47
+ def page_description
48
+ locals[:description] || frontmatter.description
49
+ end
50
+
51
+ def page_title
52
+ locals[:title] || frontmatter.title
53
+ end
54
+
55
+ def host
56
+ config[:tech_docs][:host].to_s
57
+ end
58
+
59
+ def locals
60
+ current_page.metadata[:locals]
61
+ end
62
+
63
+ def frontmatter
64
+ current_page.data
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,52 @@
1
+ module GovukTechDocs
2
+ class PageReview
3
+ attr_reader :page
4
+
5
+ def initialize(page, config = {})
6
+ @page = page
7
+ @config = config
8
+ end
9
+
10
+ def review_by
11
+ return unless last_reviewed_on
12
+
13
+ @review_by ||= Chronic.parse(
14
+ "in #{page.data.review_in}",
15
+ now: last_reviewed_on.to_time
16
+ ).to_date
17
+ end
18
+
19
+ def under_review?
20
+ page.data.review_in.present?
21
+ end
22
+
23
+ def last_reviewed_on
24
+ page.data.last_reviewed_on
25
+ end
26
+
27
+ def owner_slack
28
+ page.data.owner_slack || default_owner_slack
29
+ end
30
+
31
+ def owner_slack_url
32
+ return "" unless owner_slack_workspace
33
+ # Slack URLs don't have the # (channels) or @ (usernames)
34
+ slack_identifier = owner_slack.to_s.delete('#').delete('@')
35
+ "https://#{owner_slack_workspace}.slack.com/messages/#{slack_identifier}"
36
+ end
37
+
38
+ def show_expiry?
39
+ @config[:tech_docs].fetch(:show_expiry, true)
40
+ end
41
+
42
+ private
43
+
44
+ def default_owner_slack
45
+ @config[:tech_docs][:default_owner_slack]
46
+ end
47
+
48
+ def owner_slack_workspace
49
+ @config[:tech_docs][:owner_slack_workspace]
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,32 @@
1
+ module GovukTechDocs
2
+ class Pages
3
+ attr_reader :sitemap
4
+
5
+ def initialize(sitemap, config)
6
+ @sitemap = sitemap
7
+ @config = config
8
+ end
9
+
10
+ def to_json
11
+ as_json.to_json
12
+ end
13
+
14
+ private
15
+
16
+ def as_json
17
+ pages.map do |page|
18
+ review = PageReview.new(page, @config)
19
+ {
20
+ title: page.data.title,
21
+ url: "#{@config[:tech_docs][:host]}#{page.url}",
22
+ review_by: review.review_by,
23
+ owner_slack: review.owner_slack,
24
+ }
25
+ end
26
+ end
27
+
28
+ def pages
29
+ sitemap.resources.select { |page| page.data.title }
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,39 @@
1
+ module GovukTechDocs
2
+ class Redirects
3
+ LEADING_SLASH = %r[\A\/]
4
+
5
+ def initialize(context)
6
+ @context = context
7
+ end
8
+
9
+ def redirects
10
+ all_redirects = redirects_from_config + redirects_from_frontmatter
11
+
12
+ all_redirects.map do |from, to|
13
+ # Middleman needs paths without leading slashes
14
+ [from.sub(LEADING_SLASH, ''), to: to.sub(LEADING_SLASH, '')]
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ attr_reader :context
21
+
22
+ def redirects_from_config
23
+ context.config[:tech_docs][:redirects].to_a
24
+ end
25
+
26
+ def redirects_from_frontmatter
27
+ reds = []
28
+ context.sitemap.resources.each do |page|
29
+ next unless page.data.old_paths
30
+
31
+ page.data.old_paths.each do |old_path|
32
+ reds << [old_path, page.path]
33
+ end
34
+ end
35
+
36
+ reds
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,30 @@
1
+ module GovukTechDocs
2
+ module TableOfContents
3
+ class Heading
4
+ def initialize(element_name:, text:, attributes:, page_url: '')
5
+ @element_name = element_name
6
+ @text = text
7
+ @attributes = attributes
8
+ @page_url = page_url
9
+ end
10
+
11
+ def size
12
+ @element_name.scan(/h(\d)/) && $1 && Integer($1)
13
+ end
14
+
15
+ def href
16
+ @page_url + '#' + @attributes['id']
17
+ end
18
+
19
+ def title
20
+ @text
21
+ end
22
+
23
+ def ==(other)
24
+ @element_name == other.instance_variable_get("@element_name") &&
25
+ @text == other.instance_variable_get("@text") &&
26
+ @attributes == other.instance_variable_get("@attributes")
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,27 @@
1
+ module GovukTechDocs
2
+ module TableOfContents
3
+ class HeadingTree
4
+ attr_accessor :parent, :heading, :children
5
+
6
+ def initialize(parent: nil, heading: nil, children: [])
7
+ @parent = parent
8
+ @heading = heading
9
+ @children = children
10
+ end
11
+
12
+ def depth
13
+ if parent
14
+ parent.depth + 1
15
+ else
16
+ 1
17
+ end
18
+ end
19
+
20
+ def ==(other)
21
+ heading == other.heading &&
22
+ children.length == other.children.length &&
23
+ children.map.with_index { |child, index| child == other.children[index] }.all?
24
+ end
25
+ end
26
+ end
27
+ end