sleeping_king_studios-docs 0.2.1 → 0.3.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 (112) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +52 -0
  3. data/README.md +30 -14
  4. data/lib/sleeping_king_studios/docs/data/base.rb +24 -4
  5. data/lib/sleeping_king_studios/docs/data/constant_object.rb +24 -0
  6. data/lib/sleeping_king_studios/docs/data/metadata.rb +0 -2
  7. data/lib/sleeping_king_studios/docs/data/method_object.rb +32 -0
  8. data/lib/sleeping_king_studios/docs/data/module_object.rb +1 -1
  9. data/lib/sleeping_king_studios/docs/data/namespace_object.rb +26 -2
  10. data/lib/sleeping_king_studios/docs/data/see_tags/reference_tag.rb +1 -1
  11. data/lib/sleeping_king_studios/docs/data/types/type.rb +3 -6
  12. data/lib/sleeping_king_studios/docs/errors/outdated_documentation.rb +79 -0
  13. data/lib/sleeping_king_studios/docs/errors/registry_error.rb +13 -0
  14. data/lib/sleeping_king_studios/docs/errors.rb +4 -0
  15. data/lib/sleeping_king_studios/docs/jekyll/commands/backfill_versions.rb +177 -0
  16. data/lib/sleeping_king_studios/docs/jekyll/commands/check.rb +347 -0
  17. data/lib/sleeping_king_studios/docs/jekyll/commands/clobber.rb +99 -0
  18. data/lib/sleeping_king_studios/docs/jekyll/commands/document_version.rb +217 -0
  19. data/lib/sleeping_king_studios/docs/jekyll/commands/generate.rb +202 -0
  20. data/lib/sleeping_king_studios/docs/jekyll/commands/install.rb +271 -0
  21. data/lib/sleeping_king_studios/docs/jekyll/commands/install_templates.rb +125 -0
  22. data/lib/sleeping_king_studios/docs/jekyll/commands/install_workflow.rb +55 -0
  23. data/lib/sleeping_king_studios/docs/jekyll/commands/reference.rb +158 -0
  24. data/lib/sleeping_king_studios/docs/jekyll/commands/update.rb +32 -0
  25. data/lib/sleeping_king_studios/docs/jekyll/commands.rb +44 -0
  26. data/lib/sleeping_king_studios/docs/jekyll/generators/data_generator.rb +85 -0
  27. data/lib/sleeping_king_studios/docs/jekyll/generators/reference_generator.rb +40 -0
  28. data/lib/sleeping_king_studios/docs/jekyll/generators/version_generator.rb +66 -0
  29. data/lib/sleeping_king_studios/docs/jekyll/generators.rb +15 -0
  30. data/lib/sleeping_king_studios/docs/jekyll.rb +26 -1
  31. data/lib/sleeping_king_studios/docs/version.rb +2 -2
  32. data/lib/sleeping_king_studios/docs/yard/build.rb +42 -0
  33. data/lib/sleeping_king_studios/docs/{commands → yard}/parse.rb +18 -11
  34. data/lib/sleeping_king_studios/docs/yard/registry.rb +54 -0
  35. data/lib/sleeping_king_studios/docs/{registry_query.rb → yard/registry_query.rb} +9 -7
  36. data/lib/sleeping_king_studios/docs/yard.rb +13 -0
  37. data/lib/sleeping_king_studios/docs.rb +29 -17
  38. metadata +54 -86
  39. data/lib/sleeping_king_studios/docs/commands/generate.rb +0 -268
  40. data/lib/sleeping_king_studios/docs/commands/generators/base.rb +0 -97
  41. data/lib/sleeping_king_studios/docs/commands/generators/data_generator.rb +0 -68
  42. data/lib/sleeping_king_studios/docs/commands/generators/reference_generator.rb +0 -59
  43. data/lib/sleeping_king_studios/docs/commands/generators.rb +0 -15
  44. data/lib/sleeping_king_studios/docs/commands/installation/install_jekyll.rb +0 -273
  45. data/lib/sleeping_king_studios/docs/commands/installation/install_templates.rb +0 -125
  46. data/lib/sleeping_king_studios/docs/commands/installation/install_workflow.rb +0 -121
  47. data/lib/sleeping_king_studios/docs/commands/installation.rb +0 -15
  48. data/lib/sleeping_king_studios/docs/commands/write_file.rb +0 -89
  49. data/lib/sleeping_king_studios/docs/commands.rb +0 -14
  50. data/lib/sleeping_king_studios/docs/registry.rb +0 -22
  51. data/lib/sleeping_king_studios/docs/tasks/base.rb +0 -20
  52. data/lib/sleeping_king_studios/docs/tasks/generate.rb +0 -39
  53. data/lib/sleeping_king_studios/docs/tasks/installation/install_jekyll.rb +0 -67
  54. data/lib/sleeping_king_studios/docs/tasks/installation/install_templates.rb +0 -39
  55. data/lib/sleeping_king_studios/docs/tasks/installation/install_workflow.rb +0 -53
  56. data/lib/sleeping_king_studios/docs/tasks/installation.rb +0 -8
  57. data/lib/sleeping_king_studios/docs/tasks/update.rb +0 -35
  58. data/lib/sleeping_king_studios/docs/tasks.rb +0 -14
  59. data/lib/sleeping_king_studios/docs/templates/config.yml.erb +0 -24
  60. data/lib/sleeping_king_studios/docs/templates/deploy-pages.yml.erb +0 -67
  61. data/lib/sleeping_king_studios/docs/templates/includes/breadcrumbs.md +0 -7
  62. data/lib/sleeping_king_studios/docs/templates/includes/reference/attribute.md +0 -23
  63. data/lib/sleeping_king_studios/docs/templates/includes/reference/attributes/heading.md +0 -15
  64. data/lib/sleeping_king_studios/docs/templates/includes/reference/class.md +0 -27
  65. data/lib/sleeping_king_studios/docs/templates/includes/reference/constant.md +0 -8
  66. data/lib/sleeping_king_studios/docs/templates/includes/reference/constants/heading.md +0 -6
  67. data/lib/sleeping_king_studios/docs/templates/includes/reference/constants/inherited.md +0 -4
  68. data/lib/sleeping_king_studios/docs/templates/includes/reference/constants/overview.md +0 -60
  69. data/lib/sleeping_king_studios/docs/templates/includes/reference/definitions/breadcrumbs.md +0 -21
  70. data/lib/sleeping_king_studios/docs/templates/includes/reference/definitions/class_attributes.md +0 -9
  71. data/lib/sleeping_king_studios/docs/templates/includes/reference/definitions/class_methods.md +0 -9
  72. data/lib/sleeping_king_studios/docs/templates/includes/reference/definitions/constants.md +0 -9
  73. data/lib/sleeping_king_studios/docs/templates/includes/reference/definitions/constructor.md +0 -12
  74. data/lib/sleeping_king_studios/docs/templates/includes/reference/definitions/definitions.md +0 -23
  75. data/lib/sleeping_king_studios/docs/templates/includes/reference/definitions/details.md +0 -34
  76. data/lib/sleeping_king_studios/docs/templates/includes/reference/definitions/instance_attributes.md +0 -9
  77. data/lib/sleeping_king_studios/docs/templates/includes/reference/definitions/instance_methods.md +0 -17
  78. data/lib/sleeping_king_studios/docs/templates/includes/reference/definitions/overview.md +0 -74
  79. data/lib/sleeping_king_studios/docs/templates/includes/reference/definitions/subclasses.md +0 -10
  80. data/lib/sleeping_king_studios/docs/templates/includes/reference/definitions/table-of-contents/class_attributes.md +0 -10
  81. data/lib/sleeping_king_studios/docs/templates/includes/reference/definitions/table-of-contents/class_methods.md +0 -10
  82. data/lib/sleeping_king_studios/docs/templates/includes/reference/definitions/table-of-contents/constants.md +0 -10
  83. data/lib/sleeping_king_studios/docs/templates/includes/reference/definitions/table-of-contents/instance_attributes.md +0 -10
  84. data/lib/sleeping_king_studios/docs/templates/includes/reference/definitions/table-of-contents/instance_methods.md +0 -10
  85. data/lib/sleeping_king_studios/docs/templates/includes/reference/definitions/table-of-contents/overview.md +0 -41
  86. data/lib/sleeping_king_studios/docs/templates/includes/reference/definitions/table-of-contents.md +0 -27
  87. data/lib/sleeping_king_studios/docs/templates/includes/reference/method.md +0 -27
  88. data/lib/sleeping_king_studios/docs/templates/includes/reference/methods/aliases.md +0 -4
  89. data/lib/sleeping_king_studios/docs/templates/includes/reference/methods/heading.md +0 -14
  90. data/lib/sleeping_king_studios/docs/templates/includes/reference/methods/inherited.md +0 -4
  91. data/lib/sleeping_king_studios/docs/templates/includes/reference/methods/overload.md +0 -18
  92. data/lib/sleeping_king_studios/docs/templates/includes/reference/methods/overloads.md +0 -12
  93. data/lib/sleeping_king_studios/docs/templates/includes/reference/methods/overview.md +0 -41
  94. data/lib/sleeping_king_studios/docs/templates/includes/reference/methods/parameters.md +0 -31
  95. data/lib/sleeping_king_studios/docs/templates/includes/reference/methods/post_overview.md +0 -51
  96. data/lib/sleeping_king_studios/docs/templates/includes/reference/methods/raises.md +0 -14
  97. data/lib/sleeping_king_studios/docs/templates/includes/reference/methods/return_types.md +0 -9
  98. data/lib/sleeping_king_studios/docs/templates/includes/reference/methods/returns.md +0 -14
  99. data/lib/sleeping_king_studios/docs/templates/includes/reference/methods/yields.md +0 -42
  100. data/lib/sleeping_king_studios/docs/templates/includes/reference/module.md +0 -23
  101. data/lib/sleeping_king_studios/docs/templates/includes/reference/namespace.md +0 -33
  102. data/lib/sleeping_king_studios/docs/templates/includes/reference/parent_link.md +0 -6
  103. data/lib/sleeping_king_studios/docs/templates/includes/reference/reference_link.md +0 -7
  104. data/lib/sleeping_king_studios/docs/templates/includes/reference/see_link.md +0 -7
  105. data/lib/sleeping_king_studios/docs/templates/includes/reference/type.md +0 -14
  106. data/lib/sleeping_king_studios/docs/templates/includes/reference/type_list.md +0 -4
  107. data/lib/sleeping_king_studios/docs/templates/includes/reference/types/array_type.md +0 -1
  108. data/lib/sleeping_king_studios/docs/templates/includes/reference/types/hash_type.md +0 -1
  109. data/lib/sleeping_king_studios/docs/templates/includes/reference/types/ordered_type.md +0 -1
  110. data/lib/sleeping_king_studios/docs/templates/pages/index.md.erb +0 -22
  111. data/lib/sleeping_king_studios/docs/templates/pages/reference.md.erb +0 -14
  112. data/lib/sleeping_king_studios/docs/templates/pages/versions.md.erb +0 -13
@@ -0,0 +1,347 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+
5
+ require 'sleeping_king_studios/docs/jekyll/commands'
6
+
7
+ module SleepingKingStudios::Docs::Jekyll::Commands
8
+ # Checks whether the current documentation is up to date.
9
+ class Check < SleepingKingStudios::Docs::Jekyll::Commands::Reference # rubocop:disable Metrics/ClassLength
10
+ full_name 'docs:jekyll:check'
11
+
12
+ description 'Checks whether the current documentation is up to date'
13
+
14
+ option :inspect_changes,
15
+ aliases: %i[inspect],
16
+ type: :boolean,
17
+ default: false
18
+
19
+ private
20
+
21
+ attr_reader :changed_files
22
+
23
+ attr_reader :extra_files
24
+
25
+ attr_reader :missing_files
26
+
27
+ attr_reader :registry
28
+
29
+ def build_command
30
+ @build_command ||= SleepingKingStudios::Docs::Yard::Build.new
31
+ end
32
+
33
+ def collection_path_for(data)
34
+ data_type = data_type_for(data)
35
+
36
+ data_collections[data_type]
37
+ end
38
+
39
+ def compare_files(actual:, expected:)
40
+ # Skip the full analysis if all files are matching.
41
+ return if actual == expected
42
+
43
+ missing = find_missing_files(actual:, expected:)
44
+ extra = find_extra_files(actual:, expected:)
45
+ matching = expected.except(*missing).reject { |_, value| value == true }
46
+ changed = find_changed_files(actual:, matching:)
47
+
48
+ @missing_files.concat(missing)
49
+ @extra_files.concat(extra)
50
+ @changed_files.concat(changed)
51
+ end
52
+
53
+ def data_collections
54
+ @data_collections ||= Hash.new do |hsh, data_type|
55
+ hsh[data_type] = tools.string_tools.pluralize(data_type)
56
+ end
57
+ end
58
+
59
+ def data_file_for(data)
60
+ "#{data_path_for(data)}/#{data.data_path}.yml"
61
+ end
62
+
63
+ def data_path_for(data)
64
+ collection_path = collection_path_for(data)
65
+
66
+ if version
67
+ File.join(docs_path, "_#{collection_path}", "version--#{version}")
68
+ else
69
+ File.join(docs_path, "_#{collection_path}")
70
+ end
71
+ end
72
+
73
+ def data_type_for(data)
74
+ data
75
+ .class
76
+ .name
77
+ .split('::')
78
+ .last
79
+ .sub(/Object\z/, '')
80
+ .then { |str| tools.string_tools.underscore(str) }
81
+ .then { |str| str == 'root' ? 'namespace' : str }
82
+ end
83
+
84
+ def file_not_found_error(**)
85
+ SleepingKingStudios::Docs::Errors::FileError.new(**)
86
+ end
87
+
88
+ def find_changed_files(actual:, matching:)
89
+ return [] if matching.empty?
90
+
91
+ matching
92
+ .reject { |key, value| value == actual[key] }
93
+ .keys
94
+ end
95
+
96
+ def find_data_files
97
+ [
98
+ *each_data_file(class_data_directory),
99
+ *each_data_file(constant_data_directory),
100
+ *each_data_file(method_data_directory),
101
+ *each_data_file(module_data_directory),
102
+ *each_data_file(namespace_data_directory)
103
+ ]
104
+ .to_h { |file_path| [file_path, step { read_checksum(file_path) }] }
105
+ end
106
+
107
+ def find_expected_files
108
+ expected_data = {}
109
+ expected_reference = {}
110
+
111
+ registry.each do |native|
112
+ data = step { build_command.call(native) }
113
+
114
+ next unless data.public?
115
+
116
+ expected_data[data_file_for(data)] = data.checksum
117
+
118
+ next unless reference?(data)
119
+
120
+ expected_reference[reference_file_for(data)] = true
121
+ end
122
+
123
+ [expected_data, expected_reference]
124
+ end
125
+
126
+ def find_extra_files(actual:, expected:)
127
+ actual.each_key.reject { |key| expected.key?(key) }
128
+ end
129
+
130
+ def find_missing_files(actual:, expected:)
131
+ expected.each_key.reject { |key| actual.key?(key) }
132
+ end
133
+
134
+ def find_native_object_for(file_path)
135
+ registry.each do |native|
136
+ data = step { build_command.call(native) }
137
+
138
+ next unless data.public?
139
+
140
+ next unless file_path == data_file_for(data)
141
+
142
+ return data
143
+ end
144
+
145
+ # :nocov:
146
+ nil
147
+ # :nocov:
148
+ end
149
+
150
+ def find_reference_files
151
+ each_reference_file.to_h { |file_path| [file_path, true] }
152
+ end
153
+
154
+ def generate_inspect_contents_for(object, mock_fs:)
155
+ SleepingKingStudios::Docs::Jekyll::Generators::DataGenerator
156
+ .new(object:, file_system: mock_fs, standard_io:, quiet: true)
157
+ .call
158
+ end
159
+
160
+ def indent(contents)
161
+ contents
162
+ .each_line
163
+ .map do |line|
164
+ next "\n" if line == "\n"
165
+
166
+ " #{line}"
167
+ end
168
+ .join
169
+ end
170
+
171
+ def inspect_changed_files # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
172
+ return unless inspect_changes? && !changed_files.empty?
173
+
174
+ say "\n"
175
+ say '-' * 80
176
+ say "\n"
177
+
178
+ mock_fs = Cuprum::Cli::Dependencies::FileSystem::Mock.new
179
+
180
+ changed_files.each do |file_path|
181
+ data = find_native_object_for(file_path)
182
+ result = generate_inspect_contents_for(data, mock_fs:)
183
+
184
+ next unless result.success?
185
+
186
+ say "Changed file #{file_path}:\n\n"
187
+
188
+ contents = mock_fs.read(file_path)
189
+
190
+ say indent(contents)
191
+ say "\n"
192
+ end
193
+ end
194
+
195
+ def outdated_documentation_error
196
+ SleepingKingStudios::Docs::Errors::OutdatedDocumentation.new(
197
+ docs_path:,
198
+ version:,
199
+ changed_files:,
200
+ extra_files:,
201
+ missing_files:
202
+ )
203
+ end
204
+
205
+ def parse_registry # rubocop:disable Metrics/MethodLength
206
+ @registry = step do
207
+ SleepingKingStudios::Docs::Yard::Parse.new.call
208
+ end
209
+
210
+ SleepingKingStudios::Docs::Yard::Registry
211
+ .provider
212
+ .set(:registry, registry)
213
+
214
+ registry
215
+ rescue Plumbum::Errors::ImmutableError => exception
216
+ message = "#{exception.class}: #{exception.message}"
217
+ error = SleepingKingStudios::Docs::Errors::RegistryError.new(message:)
218
+
219
+ failure(error)
220
+ end
221
+
222
+ def process # rubocop:disable Metrics/MethodLength
223
+ say "Checking documentation for #{version_string}..."
224
+ say "\n", verbose: true
225
+
226
+ @missing_files = []
227
+ @extra_files = []
228
+ @changed_files = []
229
+ data_files = step { find_data_files }
230
+ reference_files = step { find_reference_files }
231
+
232
+ step { parse_registry }
233
+
234
+ expected_data_files, expected_reference_files = step do
235
+ find_expected_files
236
+ end
237
+
238
+ compare_files(actual: data_files, expected: expected_data_files)
239
+ compare_files(actual: reference_files, expected: expected_reference_files)
240
+
241
+ report_results
242
+ end
243
+
244
+ def read_checksum(file_path) # rubocop:disable Metrics/MethodLength
245
+ raw = file_system.read(file_path)
246
+ yaml = YAML.safe_load(raw)
247
+
248
+ yaml.fetch('checksum') do
249
+ warn_missing_checksum(file_path)
250
+
251
+ nil
252
+ end
253
+ rescue Cuprum::Cli::Dependencies::FileSystem::FileError => exception
254
+ failure(file_not_found_error(message: exception.message, path: file_path))
255
+ rescue Psych::SyntaxError => exception
256
+ message =
257
+ "unable to parse file - error occurred on line #{exception.line}, " \
258
+ "column #{exception.column}"
259
+
260
+ failure(file_not_found_error(message:, path: file_path))
261
+ end
262
+
263
+ def reference?(data)
264
+ data.is_a?(SleepingKingStudios::Docs::Data::ModuleObject)
265
+ end
266
+
267
+ def reference_file_for(data)
268
+ "#{reference_path}/#{data.data_path}.md"
269
+ end
270
+
271
+ def reference_path
272
+ @reference_path ||=
273
+ if version
274
+ File.join(docs_path, 'versions', version, 'reference')
275
+ else
276
+ File.join(docs_path, 'reference')
277
+ end
278
+ end
279
+
280
+ def report_changed_files
281
+ return if changed_files.empty?
282
+
283
+ say 'Changed files:', verbose: true
284
+ say "\n", verbose: true
285
+
286
+ changed_files.each do |file_path|
287
+ say " - Changed file #{file_path}"
288
+ end
289
+
290
+ say "\n", verbose: true
291
+ end
292
+
293
+ def report_extra_files
294
+ return if extra_files.empty?
295
+
296
+ say 'Extra files:', verbose: true
297
+ say "\n", verbose: true
298
+
299
+ extra_files.each do |file_path|
300
+ say " - Extra file #{file_path}"
301
+ end
302
+
303
+ say "\n", verbose: true
304
+ end
305
+
306
+ def report_missing_files
307
+ return if missing_files.empty?
308
+
309
+ say 'Missing files:', verbose: true
310
+ say "\n", verbose: true
311
+
312
+ missing_files.each do |file_path|
313
+ say " - Missing file #{file_path}"
314
+ end
315
+
316
+ say "\n", verbose: true
317
+ end
318
+
319
+ def report_results
320
+ if changed_files.empty? && missing_files.empty? && extra_files.empty?
321
+ say 'Success!'
322
+
323
+ return []
324
+ end
325
+
326
+ report_missing_files
327
+ report_extra_files
328
+ report_changed_files
329
+
330
+ say 'Failure!'
331
+
332
+ inspect_changed_files
333
+
334
+ failure(outdated_documentation_error)
335
+ end
336
+
337
+ def version_string
338
+ version ? "version #{version}" : 'current version'
339
+ end
340
+
341
+ def warn_missing_checksum(file_path)
342
+ message = "Warning: file #{file_path} does not have a valid checksum\n"
343
+
344
+ standard_io.write_error(message)
345
+ end
346
+ end
347
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sleeping_king_studios/docs/jekyll/commands'
4
+
5
+ module SleepingKingStudios::Docs::Jekyll::Commands
6
+ # Clears out the existing reference documentation.
7
+ class Clobber < SleepingKingStudios::Docs::Jekyll::Commands::Reference
8
+ full_name 'docs:jekyll:clobber'
9
+
10
+ description \
11
+ 'Removes any existing reference documentation for the current version'
12
+
13
+ private
14
+
15
+ attr_reader :errors
16
+
17
+ def aggregate_errors
18
+ result = yield
19
+
20
+ errors << result.error if result.failure?
21
+
22
+ result
23
+ end
24
+
25
+ def process
26
+ @errors = []
27
+
28
+ remove_reference_files
29
+
30
+ remove_data_files(class_data_directory, as: 'class')
31
+ remove_data_files(constant_data_directory, as: 'constant')
32
+ remove_data_files(method_data_directory, as: 'method')
33
+ remove_data_files(module_data_directory, as: 'module')
34
+ remove_data_files(namespace_data_directory, as: 'namespace')
35
+
36
+ report_errors
37
+ end
38
+
39
+ def remove_data_files(directory, as:) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
40
+ data_count = each_data_file(directory).count
41
+
42
+ say "Removing #{data_count} #{as} objects from #{directory}..."
43
+ say("\n", verbose: true) unless data_count.zero?
44
+
45
+ each_data_file(directory) do |path|
46
+ aggregate_errors { delete_file(path) }
47
+ end
48
+
49
+ each_data_directory(directory) do |path|
50
+ aggregate_errors { delete_directory(path, recursive: true) }
51
+ end
52
+
53
+ if version && file_system.directory?(directory)
54
+ aggregate_errors { delete_directory(directory) }
55
+ end
56
+
57
+ say("\n", verbose: true)
58
+ end
59
+
60
+ def remove_reference_files
61
+ reference_count = each_reference_file.count
62
+
63
+ say "Removing #{reference_count} files from #{reference_directory}..."
64
+ say("\n", verbose: true) unless reference_count.zero?
65
+
66
+ each_reference_file do |path|
67
+ aggregate_errors { delete_file(path) }
68
+ end
69
+
70
+ each_reference_directory do |path|
71
+ aggregate_errors { delete_directory(path, recursive: true) }
72
+ end
73
+
74
+ say("\n", verbose: true)
75
+ end
76
+
77
+ def report_errors # rubocop:disable Metrics/MethodLength
78
+ if errors.empty?
79
+ say 'Success!'
80
+
81
+ return
82
+ end
83
+
84
+ warn 'Failures:'
85
+ warn "\n"
86
+
87
+ errors.each do |error|
88
+ warn " - Unable to remove #{error.path} - #{error.message}"
89
+ end
90
+
91
+ warn "\n"
92
+
93
+ message = 'unable to remove documentation files'
94
+ error = Cuprum::Errors::MultipleErrors.new(errors:, message:)
95
+
96
+ failure(error)
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,217 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sleeping_king_studios/docs/jekyll/commands'
4
+
5
+ module SleepingKingStudios::Docs::Jekyll::Commands
6
+ # Generates pinned documentation for the specified project version.
7
+ #
8
+ # - Adds the version to the _versions collection.
9
+ # - Generates reference documentation for the version.
10
+ # - Copies all static documentation files to the respective versions/*
11
+ # subdirectory.
12
+ class DocumentVersion < Cuprum::Cli::Command # rubocop:disable Metrics/ClassLength
13
+ dependency :file_system
14
+ dependency :standard_io
15
+
16
+ include Cuprum::Cli::Dependencies::StandardIo::Helpers
17
+ include Cuprum::Cli::Options::Quiet
18
+ include Cuprum::Cli::Options::Verbose
19
+
20
+ argument :version, type: :string, required: true
21
+
22
+ option :docs_path, default: 'docs'
23
+
24
+ option :dry_run, type: :boolean, default: false
25
+
26
+ full_name 'docs:jekyll:document_version'
27
+
28
+ description 'Generates pinned documentation for the specified version'
29
+
30
+ private
31
+
32
+ def copy_static_file(original_path)
33
+ relative_path = original_path.sub(%r{#{docs_path}/}, '')
34
+ updated_path = File.join(docs_path, 'versions', version, relative_path)
35
+
36
+ contents = file_system.read_file(original_path)
37
+ metadata, text = step { extract_front_matter(contents) }
38
+ metadata = update_metadata(metadata:, relative_path:)
39
+ contents = generate_contents(metadata:, text:)
40
+
41
+ files = step { write_static_file(contents:, updated_path:) }
42
+
43
+ @files.concat(files)
44
+ end
45
+
46
+ def copy_static_files
47
+ say "\nCopying static documentation..."
48
+ say("\n", verbose: true)
49
+
50
+ find_static_docs.each { |file_path| copy_static_file(file_path) }
51
+ end
52
+
53
+ def default_breadcrumbs_for(relative_path)
54
+ *dir_path, _ = relative_path.split(File::SEPARATOR)
55
+
56
+ return [] if dir_path.empty?
57
+
58
+ dir_path
59
+ .each
60
+ .map do |segment, index|
61
+ name = tools.string_tools.camelize(segment.tr('-_', ''))
62
+ path = "#{File::SEPARATOR}#{File.join(dir_path[0..index])}"
63
+
64
+ { 'name' => name, 'path' => path }
65
+ end
66
+ end
67
+
68
+ def exclude_static_file?(file_path)
69
+ return false if file_path == "#{docs_path}/reference/index.md"
70
+
71
+ file_path.start_with?("#{docs_path}/_includes/") ||
72
+ file_path.start_with?("#{docs_path}/reference") ||
73
+ file_path.start_with?("#{docs_path}/versions")
74
+ end
75
+
76
+ def extract_front_matter(contents)
77
+ return [{}, contents] unless contents.start_with?("---\n")
78
+
79
+ match = /^---\n$/.match(contents[4..])
80
+
81
+ return [{}, contents] unless match
82
+
83
+ text = match.post_match
84
+ yaml = YAML.safe_load(match.pre_match) || {}
85
+
86
+ [yaml, text]
87
+ end
88
+
89
+ def generate_contents(metadata:, text:)
90
+ "#{YAML.safe_dump(metadata)}---\n#{text}"
91
+ end
92
+
93
+ def generate_reference_files # rubocop:disable Metrics/MethodLength
94
+ say "\nGenerating data and reference files:"
95
+ say("\n", verbose: true)
96
+
97
+ files = step do
98
+ SleepingKingStudios::Docs::Jekyll::Commands::Generate
99
+ .new(file_system:, standard_io:)
100
+ .call(
101
+ docs_path:,
102
+ dry_run: dry_run?,
103
+ quiet: quiet?,
104
+ verbose: verbose?,
105
+ version:
106
+ )
107
+ end
108
+
109
+ @files.concat(files)
110
+ end
111
+
112
+ def generate_version # rubocop:disable Metrics/MethodLength
113
+ say "\nGenerating version file:"
114
+ say("\n", verbose: true)
115
+
116
+ files = step do
117
+ SleepingKingStudios::Docs::Jekyll::Generators::VersionGenerator
118
+ .new(
119
+ file_system:,
120
+ standard_io:,
121
+ docs_path:,
122
+ dry_run: dry_run?,
123
+ quiet: quiet?,
124
+ verbose: verbose?,
125
+ version:
126
+ )
127
+ .call
128
+ end
129
+
130
+ @files.concat(files)
131
+ end
132
+
133
+ def find_static_docs
134
+ file_system
135
+ .each_file("#{docs_path}/**/*.md")
136
+ .reject { |file_path| exclude_static_file?(file_path) }
137
+ .to_a
138
+ end
139
+
140
+ def process
141
+ say "Generating pinned documentation for version #{version}"
142
+
143
+ @files = []
144
+
145
+ step { generate_reference_files }
146
+
147
+ step { generate_version }
148
+
149
+ step { copy_static_files }
150
+
151
+ say "\nSuccess!"
152
+
153
+ @files
154
+ end
155
+
156
+ def top_level_breadcrumb?(obj)
157
+ obj.is_a?(Hash) && obj['path'] == File::SEPARATOR
158
+ end
159
+
160
+ def version_breadcrumbs # rubocop:disable Metrics/MethodLength
161
+ @version_breadcrumbs ||= [
162
+ {
163
+ 'name' => 'Documentation',
164
+ 'path' => File::SEPARATOR
165
+ },
166
+ {
167
+ 'name' => 'Versions',
168
+ 'path' => "#{File::SEPARATOR}versions"
169
+ },
170
+ {
171
+ 'name' => "Version #{version}",
172
+ 'path' => "#{File::SEPARATOR}#{File.join('versions', version)}"
173
+ }
174
+ ]
175
+ end
176
+
177
+ def update_breadcrumbs(breadcrumbs:, relative_path:)
178
+ prefix = "#{File::SEPARATOR}#{File.join('versions', version)}"
179
+ breadcrumbs = breadcrumbs[1..] if top_level_breadcrumb?(breadcrumbs.first)
180
+ breadcrumbs = breadcrumbs.map do |hsh|
181
+ hsh.merge('path' => File.join(prefix, hsh['path']))
182
+ end
183
+
184
+ if breadcrumbs.empty? && relative_path == 'index.md'
185
+ return version_breadcrumbs[...-1]
186
+ end
187
+
188
+ version_breadcrumbs + breadcrumbs
189
+ end
190
+
191
+ def update_metadata(metadata:, relative_path:)
192
+ breadcrumbs = update_breadcrumbs(
193
+ breadcrumbs: metadata.fetch(
194
+ 'breadcrumbs',
195
+ default_breadcrumbs_for(relative_path)
196
+ ),
197
+ relative_path:
198
+ )
199
+
200
+ metadata.merge('breadcrumbs' => breadcrumbs, 'version' => version)
201
+ end
202
+
203
+ def write_static_file(contents:, updated_path:) # rubocop:disable Metrics/MethodLength
204
+ Cuprum::Cli::Files::Generators::BasicGenerator
205
+ .new(
206
+ file_system:,
207
+ standard_io:,
208
+ contents:,
209
+ dry_run: dry_run?,
210
+ file_path: updated_path,
211
+ quiet: quiet?,
212
+ verbose: verbose?
213
+ )
214
+ .call
215
+ end
216
+ end
217
+ end