ariadne_view_components 0.0.10-x86_64-linux

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 (130) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +49 -0
  3. data/README.md +73 -0
  4. data/app/assets/config/manifest.js +2 -0
  5. data/app/assets/javascripts/ariadne-form-with.d.ts +20 -0
  6. data/app/assets/javascripts/ariadne-form.d.ts +22 -0
  7. data/app/assets/javascripts/ariadne.d.ts +1 -0
  8. data/app/assets/javascripts/ariadne_view_components.js +8 -0
  9. data/app/assets/javascripts/ariadne_view_components.js.map +1 -0
  10. data/app/assets/javascripts/clipboard-copy-component.d.ts +4 -0
  11. data/app/assets/javascripts/comment-component.d.ts +13 -0
  12. data/app/assets/javascripts/rich-text-area-component.d.ts +4 -0
  13. data/app/assets/javascripts/slideover-component.d.ts +9 -0
  14. data/app/assets/javascripts/time-ago-component.d.ts +1 -0
  15. data/app/assets/javascripts/time_ago_component.d.ts +1 -0
  16. data/app/assets/javascripts/tooltip-component.d.ts +24 -0
  17. data/app/assets/stylesheets/ariadne_view_components.css +6 -0
  18. data/app/assets/stylesheets/prosemirror.css +323 -0
  19. data/app/assets/stylesheets/tooltip-component.css +37 -0
  20. data/app/components/ariadne/ariadne-form.ts +96 -0
  21. data/app/components/ariadne/ariadne.ts +20 -0
  22. data/app/components/ariadne/base_button.rb +61 -0
  23. data/app/components/ariadne/base_component.rb +37 -0
  24. data/app/components/ariadne/blankslate_component.html.erb +26 -0
  25. data/app/components/ariadne/blankslate_component.rb +146 -0
  26. data/app/components/ariadne/body_component.rb +30 -0
  27. data/app/components/ariadne/button_component.html.erb +4 -0
  28. data/app/components/ariadne/button_component.rb +157 -0
  29. data/app/components/ariadne/clipboard-copy-component.ts +19 -0
  30. data/app/components/ariadne/clipboard_copy_component.html.erb +9 -0
  31. data/app/components/ariadne/clipboard_copy_component.rb +90 -0
  32. data/app/components/ariadne/comment-component.ts +55 -0
  33. data/app/components/ariadne/comment_component.html.erb +22 -0
  34. data/app/components/ariadne/comment_component.rb +57 -0
  35. data/app/components/ariadne/component.rb +128 -0
  36. data/app/components/ariadne/container_component.html.erb +3 -0
  37. data/app/components/ariadne/container_component.rb +25 -0
  38. data/app/components/ariadne/content.rb +12 -0
  39. data/app/components/ariadne/counter_component.rb +100 -0
  40. data/app/components/ariadne/flash_component.html.erb +31 -0
  41. data/app/components/ariadne/flash_component.rb +125 -0
  42. data/app/components/ariadne/flex_component.rb +49 -0
  43. data/app/components/ariadne/footer_component.html.erb +7 -0
  44. data/app/components/ariadne/footer_component.rb +23 -0
  45. data/app/components/ariadne/grid_component.html.erb +26 -0
  46. data/app/components/ariadne/grid_component.rb +66 -0
  47. data/app/components/ariadne/header_component.html.erb +29 -0
  48. data/app/components/ariadne/header_component.rb +114 -0
  49. data/app/components/ariadne/heading_component.rb +49 -0
  50. data/app/components/ariadne/heroicon_component.html.erb +4 -0
  51. data/app/components/ariadne/heroicon_component.rb +129 -0
  52. data/app/components/ariadne/image_component.rb +53 -0
  53. data/app/components/ariadne/inline_flex_component.html.erb +5 -0
  54. data/app/components/ariadne/inline_flex_component.rb +65 -0
  55. data/app/components/ariadne/link_component.rb +65 -0
  56. data/app/components/ariadne/list_component.html.erb +15 -0
  57. data/app/components/ariadne/list_component.rb +68 -0
  58. data/app/components/ariadne/main_component.rb +32 -0
  59. data/app/components/ariadne/narrow_container_component.html.erb +3 -0
  60. data/app/components/ariadne/narrow_container_component.rb +30 -0
  61. data/app/components/ariadne/panel_bar_component.html.erb +20 -0
  62. data/app/components/ariadne/panel_bar_component.rb +79 -0
  63. data/app/components/ariadne/pill_component.html.erb +3 -0
  64. data/app/components/ariadne/pill_component.rb +30 -0
  65. data/app/components/ariadne/rich-text-area-component.ts +32 -0
  66. data/app/components/ariadne/rich_text_area_component.html.erb +6 -0
  67. data/app/components/ariadne/rich_text_area_component.rb +35 -0
  68. data/app/components/ariadne/slideover-component.ts +26 -0
  69. data/app/components/ariadne/slideover_component.html.erb +11 -0
  70. data/app/components/ariadne/slideover_component.rb +81 -0
  71. data/app/components/ariadne/tab_bar_component.html.erb +3 -0
  72. data/app/components/ariadne/tab_bar_component.rb +45 -0
  73. data/app/components/ariadne/tab_component.html.erb +7 -0
  74. data/app/components/ariadne/tab_component.rb +43 -0
  75. data/app/components/ariadne/text.rb +25 -0
  76. data/app/components/ariadne/time-ago-component.ts +1 -0
  77. data/app/components/ariadne/time_ago_component.rb +56 -0
  78. data/app/components/ariadne/timeline_component.html.erb +19 -0
  79. data/app/components/ariadne/timeline_component.rb +34 -0
  80. data/app/components/ariadne/tooltip-component.ts +57 -0
  81. data/app/components/ariadne/tooltip_component.html.erb +4 -0
  82. data/app/components/ariadne/tooltip_component.rb +108 -0
  83. data/app/lib/ariadne/action_view_extensions/form_helper.rb +26 -0
  84. data/app/lib/ariadne/audited/dsl.rb +32 -0
  85. data/app/lib/ariadne/class_name_helper.rb +22 -0
  86. data/app/lib/ariadne/fetch_or_fallback_helper.rb +102 -0
  87. data/app/lib/ariadne/form_builder.rb +71 -0
  88. data/app/lib/ariadne/icon_helper.rb +47 -0
  89. data/app/lib/ariadne/join_style_arguments_helper.rb +14 -0
  90. data/app/lib/ariadne/logger_helper.rb +23 -0
  91. data/app/lib/ariadne/status/dsl.rb +41 -0
  92. data/app/lib/ariadne/tab_nav_helper.rb +35 -0
  93. data/app/lib/ariadne/tabbed_component_helper.rb +39 -0
  94. data/app/lib/ariadne/test_selector_helper.rb +20 -0
  95. data/app/lib/ariadne/underline_nav_helper.rb +44 -0
  96. data/app/lib/ariadne/view_helper.rb +22 -0
  97. data/exe/tailwindcss +21 -0
  98. data/exe/x86_64-linux/tailwindcss +0 -0
  99. data/lib/ariadne/view_components/commands.rb +90 -0
  100. data/lib/ariadne/view_components/constants.rb +53 -0
  101. data/lib/ariadne/view_components/engine.rb +75 -0
  102. data/lib/ariadne/view_components/linters.rb +3 -0
  103. data/lib/ariadne/view_components/statuses.rb +14 -0
  104. data/lib/ariadne/view_components/upstream.rb +20 -0
  105. data/lib/ariadne/view_components/version.rb +7 -0
  106. data/lib/ariadne/view_components.rb +61 -0
  107. data/lib/rubocop/config/default.yml +8 -0
  108. data/lib/rubocop/cop/ariadne/base_cop.rb +26 -0
  109. data/lib/rubocop/cop/ariadne/no_tag_memoize.rb +44 -0
  110. data/lib/rubocop/cop/ariadne.rb +3 -0
  111. data/lib/tasks/ariadne_view_components.rake +48 -0
  112. data/lib/tasks/build.rake +30 -0
  113. data/lib/tasks/coverage.rake +19 -0
  114. data/lib/tasks/custom_utilities.yml +310 -0
  115. data/lib/tasks/docs.rake +524 -0
  116. data/lib/tasks/helpers/ast_processor.rb +44 -0
  117. data/lib/tasks/helpers/ast_traverser.rb +77 -0
  118. data/lib/tasks/static.rake +15 -0
  119. data/lib/yard/docs_helper.rb +83 -0
  120. data/lib/yard/renders_many_handler.rb +19 -0
  121. data/lib/yard/renders_one_handler.rb +19 -0
  122. data/static/arguments.yml +619 -0
  123. data/static/assets/view-components.svg +18 -0
  124. data/static/audited_at.json +38 -0
  125. data/static/classes.yml +291 -0
  126. data/static/constants.json +426 -0
  127. data/static/statuses.json +38 -0
  128. data/static/tailwindcss.yml +727 -0
  129. data/tailwind.config.js +65 -0
  130. metadata +264 -0
@@ -0,0 +1,524 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/inflector"
4
+ require "fileutils"
5
+
6
+ namespace :docs do
7
+ desc "Rebuilds docs on change; run via the Procfile"
8
+ task :livereload do
9
+ require "listen"
10
+
11
+ Rake::Task["docs:build"].execute
12
+
13
+ puts "Listening for changes to documentation..."
14
+
15
+ listener = Listen.to("app") do |modified, added, removed|
16
+ puts "modified absolute path: #{modified}"
17
+ puts "added absolute path: #{added}"
18
+ puts "removed absolute path: #{removed}"
19
+
20
+ unless modified.length.zero?
21
+ changed = modified.dup.uniq
22
+ while (path = changed.shift)
23
+ puts "Reloading #{path}"
24
+ # reload constants (in case they changed)
25
+ load(path)
26
+ end
27
+ end
28
+
29
+ Rake::Task["docs:build"].execute
30
+ end
31
+ listener.start # not blocking
32
+ sleep
33
+ end
34
+
35
+ # for classes in hashes
36
+ CLASS_MAPPINGS = '(?:\\S+|:\\"[\\S-]+\\"): \\"([\\S -]+)\\"'
37
+ # for classes in constants
38
+ CLASS_CONSTANT = '[A-Z_]+ = \\"([\\S -]+)\\"'
39
+ CLASS_REGEX = Regexp.new("(?:#{CLASS_MAPPINGS}|#{CLASS_CONSTANT})")
40
+
41
+ desc "Generate the documentation."
42
+ task :build do
43
+ registry = generate_yard_registry
44
+
45
+ puts "Converting YARD documentation to Markdown files."
46
+
47
+ # Rails controller for rendering arbitrary ERB
48
+ view_context = ApplicationController.new.tap { |c| c.request = ActionDispatch::TestRequest.create }.view_context
49
+ components = [
50
+ Ariadne::RichTextAreaComponent,
51
+ Ariadne::CommentComponent,
52
+ Ariadne::BodyComponent,
53
+ Ariadne::BlankslateComponent,
54
+ Ariadne::BaseButton,
55
+ Ariadne::ButtonComponent,
56
+ Ariadne::ContainerComponent,
57
+ Ariadne::ClipboardCopyComponent,
58
+ Ariadne::CounterComponent,
59
+ Ariadne::GridComponent,
60
+ Ariadne::FlashComponent,
61
+ Ariadne::FlexComponent,
62
+ Ariadne::FooterComponent,
63
+ Ariadne::HeaderComponent,
64
+ Ariadne::HeadingComponent,
65
+ Ariadne::HeroiconComponent,
66
+ Ariadne::ImageComponent,
67
+ Ariadne::InlineFlexComponent,
68
+ Ariadne::LinkComponent,
69
+ Ariadne::ListComponent,
70
+ Ariadne::MainComponent,
71
+ Ariadne::NarrowContainerComponent,
72
+ Ariadne::PanelBarComponent,
73
+ Ariadne::PillComponent,
74
+ Ariadne::SlideoverComponent,
75
+ Ariadne::TabComponent,
76
+ Ariadne::TabBarComponent,
77
+ Ariadne::Text,
78
+ Ariadne::TimeAgoComponent,
79
+ Ariadne::TimelineComponent,
80
+ Ariadne::TooltipComponent,
81
+ ]
82
+
83
+ js_components = [
84
+ Ariadne::ClipboardCopyComponent,
85
+ Ariadne::TimeAgoComponent,
86
+ ]
87
+
88
+ all_components = Ariadne::Component.descendants - [Ariadne::BaseComponent, Ariadne::Content] # TODO: why is `Ariadne::Content` not picked up?
89
+ components_needing_docs = all_components - components
90
+
91
+ args_for_components = []
92
+ classes_found_in_examples = []
93
+
94
+ errors = []
95
+
96
+ # Deletes docs before regenerating them, guaranteeing that we don't keep stale docs.
97
+ components_content_glob = File.join("docs", "content", "components", "**", "*.md")
98
+ FileUtils.rm_rf(components_content_glob)
99
+
100
+ components.sort_by(&:name).each do |component|
101
+ documentation = registry.get(component.name)
102
+
103
+ data = docs_metadata(component)
104
+
105
+ path = Pathname.new(data[:path])
106
+ path.dirname.mkpath unless path.dirname.exist?
107
+ File.open(path, "w") do |f|
108
+ f.puts("---")
109
+ f.puts("title: #{data[:title]}")
110
+ f.puts("componentId: #{data[:component_id]}")
111
+ f.puts("status: #{data[:status]}")
112
+ f.puts("source: #{data[:source]}")
113
+ f.puts("lookbook: #{data[:lookbook]}")
114
+ f.puts("---")
115
+ f.puts
116
+ f.puts("import Example from '#{data[:example_path]}'")
117
+
118
+ initialize_method = documentation.meths.find(&:constructor?)
119
+
120
+ if js_components.include?(component)
121
+ f.puts("import RequiresJSFlash from '#{data[:require_js_path]}'")
122
+ f.puts
123
+ f.puts("<RequiresJSFlash />")
124
+ end
125
+
126
+ f.puts
127
+ f.puts("<!-- Warning: AUTO-GENERATED file, do not edit. Add code comments to your Ruby instead <3 -->")
128
+ f.puts
129
+ f.puts(view_context.render(inline: documentation.base_docstring))
130
+
131
+ if documentation.tags(:deprecated).any?
132
+ f.puts
133
+ f.puts("## Deprecation")
134
+ documentation.tags(:deprecated).each do |tag|
135
+ f.puts
136
+ f.puts view_context.render(inline: tag.text)
137
+ end
138
+ end
139
+
140
+ if documentation.tags(:accessibility).any?
141
+ f.puts
142
+ f.puts("## Accessibility")
143
+ documentation.tags(:accessibility).each do |tag|
144
+ f.puts
145
+ f.puts view_context.render(inline: tag.text)
146
+ end
147
+ end
148
+
149
+ params = initialize_method.tags(:param)
150
+
151
+ errors << { component.name => { arguments: "No argument documentation found" } } unless params.any?
152
+
153
+ f.puts
154
+ f.puts("## Arguments")
155
+ f.puts
156
+ f.puts("| Name | Type | Default | Description |")
157
+ f.puts("| :- | :- | :- | :- |")
158
+
159
+ documented_params = params.map(&:name)
160
+ component_params = component.instance_method(:initialize).parameters.map { |p| p.last.to_s }
161
+
162
+ if (documented_params & component_params).size != component_params.size
163
+ err = { arguments: {} }
164
+ (component_params - documented_params).each do |arg|
165
+ err[:arguments][arg] = "Not documented"
166
+ end
167
+
168
+ errors << { component.name => err }
169
+ end
170
+
171
+ args = []
172
+ params.each do |tag|
173
+ default_value = pretty_default_value(tag, component)
174
+
175
+ args << {
176
+ "name" => tag.name,
177
+ "type" => tag.types.join(", "),
178
+ "default" => default_value,
179
+ "description" => view_context.render(inline: tag.text.squish),
180
+ }
181
+
182
+ f.puts("| `#{tag.name}` | `#{tag.types.join(", ")}` | #{default_value} | #{view_context.render(inline: tag.text.squish)} |")
183
+ end
184
+
185
+ component_args = {
186
+ "component" => data[:title],
187
+ "source" => data[:source],
188
+ "parameters" => args,
189
+ }
190
+
191
+ args_for_components << component_args
192
+
193
+ # Slots V2 docs
194
+ slot_v2_methods = documentation.meths.select { |x| x[:renders_one] || x[:renders_many] }
195
+
196
+ if slot_v2_methods.any?
197
+ f.puts
198
+ f.puts("## Slots")
199
+
200
+ slot_v2_methods.each do |slot_documentation|
201
+ f.puts
202
+ f.puts("### `#{slot_documentation.name.to_s.capitalize}`")
203
+
204
+ if slot_documentation.base_docstring.to_s.present?
205
+ f.puts
206
+ f.puts(view_context.render(inline: slot_documentation.base_docstring))
207
+ end
208
+
209
+ param_tags = slot_documentation.tags(:param)
210
+ if param_tags.any?
211
+ f.puts
212
+ f.puts("| Name | Type | Default | Description |")
213
+ f.puts("| :- | :- | :- | :- |")
214
+ end
215
+
216
+ param_tags.each do |tag|
217
+ f.puts("| `#{tag.name}` | `#{tag.types.join(", ")}` | #{pretty_default_value(tag, component)} | #{view_context.render(inline: tag.text)} |")
218
+ end
219
+ end
220
+ end
221
+
222
+ errors << { component.name => { example: "No examples found" } } unless initialize_method.tags(:example).any?
223
+
224
+ f.puts
225
+ f.puts("## Examples")
226
+
227
+ initialize_method.tags(:example).each do |tag|
228
+ name, description, code = parse_example_tag(tag)
229
+ f.puts
230
+ f.puts("### #{name}")
231
+ if description
232
+ f.puts
233
+ f.puts(view_context.render(inline: description.squish))
234
+ end
235
+ f.puts
236
+ html = view_context.render(inline: code)
237
+ html.scan(/class="([^"]*)"/) do |classnames|
238
+ classes_found_in_examples.concat(classnames[0].split.reject { |c| c.starts_with?("heroicon", "js") }.map { ".#{_1}" })
239
+ end
240
+ f.puts("<Example src=\"#{html.tr('"', "\'").delete("\n")}\" />")
241
+ f.puts
242
+ f.puts("```erb")
243
+ f.puts(code.to_s)
244
+ f.puts("```")
245
+ end
246
+ end
247
+ end
248
+
249
+ unless errors.empty?
250
+ puts "==============================================="
251
+ puts "===================== ERRORS =================="
252
+ puts "===============================================\n\n"
253
+ puts JSON.pretty_generate(errors)
254
+ puts "\n\n==============================================="
255
+ puts "==============================================="
256
+ puts "==============================================="
257
+
258
+ raise
259
+ end
260
+
261
+ additional_classes = []
262
+
263
+ tailwind_utility_prefixes = ["bg-", "border-", "fill-", "stroke-", "text-"]
264
+
265
+ additional_color_classes = [
266
+ "billy-purple",
267
+ "state-closed",
268
+ "state-open",
269
+ ]
270
+
271
+ additional_color_classes.each do |color_class|
272
+ tailwind_utility_prefixes.each do |prefix|
273
+ additional_classes.concat([".#{prefix}#{color_class}"])
274
+ end
275
+ end
276
+
277
+ Dir.glob("./app/components/**/*.rb") do |rb_filename|
278
+ content = File.read(rb_filename)
279
+ matches = content.scan(CLASS_REGEX)
280
+
281
+ if matches.present?
282
+ result = matches.flatten.compact.map { |c| c.split(" ") }.flatten.select { |r| r =~ /\A[0-9a-z:\-]+\z/ }.uniq
283
+ additional_classes.concat(result.map { |r| ".#{r}" }) if result.present?
284
+ end
285
+ end
286
+
287
+ final_list_of_classes = classes_found_in_examples.concat(additional_classes).uniq.sort
288
+ File.open("static/classes.yml", "w") do |f|
289
+ f.puts YAML.dump(final_list_of_classes)
290
+ end
291
+
292
+ File.open("static/arguments.yml", "w") do |f|
293
+ f.puts YAML.dump(args_for_components)
294
+ end
295
+
296
+ # Build system arguments docs from BaseComponent
297
+ documentation = registry.get(Ariadne::BaseComponent.name)
298
+ File.open("docs/content/system-arguments.md", "w") do |f|
299
+ f.puts("---")
300
+ f.puts("title: System arguments")
301
+ f.puts("---")
302
+ f.puts
303
+ f.puts("<!-- Warning: AUTO-GENERATED file, do not edit. Add code comments to your Ruby instead <3 -->")
304
+ f.puts
305
+ f.puts(documentation.base_docstring)
306
+ f.puts
307
+
308
+ initialize_method = documentation.meths.find(&:constructor?)
309
+
310
+ f.puts(view_context.render(inline: initialize_method.base_docstring))
311
+ end
312
+
313
+ # Copy over ADR docs and insert them into the nav
314
+ # puts "Copying ADRs..."
315
+ # Rake::Task["docs:build_adrs"].invoke
316
+
317
+ puts "Markdown compiled."
318
+
319
+ if components_needing_docs.any?
320
+ puts
321
+ puts "The following components needs docs. Care to contribute them? #{components_needing_docs.map(&:name).join(", ")}"
322
+ end
323
+ end
324
+
325
+ # task :build_adrs do
326
+ # adr_content_dir = File.join("docs", "content", "adr")
327
+
328
+ # FileUtils.rm_rf(File.join(adr_content_dir))
329
+ # FileUtils.mkdir(adr_content_dir)
330
+
331
+ # nav_entries = Dir[File.join("adr", "*.md")].sort.map do |orig_path|
332
+ # orig_file_name = File.basename(orig_path)
333
+ # url_name = orig_file_name.chomp(".md")
334
+
335
+ # file_contents = File.read(orig_path)
336
+ # file_contents = <<~CONTENTS.sub(/\n+\z/, "\n")
337
+ # <!-- Warning: AUTO-GENERATED file, do not edit. Make changes to the files in the adr/ directory instead. -->
338
+ # #{file_contents}
339
+ # CONTENTS
340
+
341
+ # title_match = /^# (.+)/.match(file_contents)
342
+ # title = title_match[1]
343
+
344
+ # # Don't include initial ADR for recording ADRs
345
+ # next nil if title == "Record architecture decisions"
346
+
347
+ # File.write(File.join(adr_content_dir, orig_file_name), file_contents)
348
+ # puts "Copied #{orig_path}"
349
+
350
+ # { "title" => title, "url" => "/adr/#{url_name}" }
351
+ # end
352
+
353
+ # nav_yaml_file = File.join("docs", "src", "@primer", "gatsby-theme-doctocat", "nav.yml")
354
+ # nav_yaml = YAML.load_file(nav_yaml_file)
355
+ # adr_entry = {
356
+ # "title" => "Architecture decisions",
357
+ # "children" => nav_entries.compact,
358
+ # }
359
+
360
+ # existing_index = nav_yaml.index { |entry| entry["title"] == "Architecture decisions" }
361
+ # if existing_index
362
+ # nav_yaml[existing_index] = adr_entry
363
+ # else
364
+ # nav_yaml << adr_entry
365
+ # end
366
+
367
+ # File.write(nav_yaml_file, YAML.dump(nav_yaml))
368
+ # end
369
+
370
+ desc "Generate previews from documentation examples"
371
+ task :preview do
372
+ registry = generate_yard_registry
373
+
374
+ FileUtils.rm_rf("lookbook/test/components/previews/ariadne/docs/")
375
+
376
+ components = Ariadne::Component.descendants
377
+
378
+ components.each do |component|
379
+ documentation = registry.get(component.name)
380
+ short_name = component.name.gsub(/Ariadne|::/, "")
381
+ initialize_method = documentation.meths.find(&:constructor?)
382
+
383
+ next unless initialize_method&.tags(:example)&.any?
384
+
385
+ yard_example_tags = initialize_method.tags(:example)
386
+
387
+ path = Pathname.new("lookbook/test/components/previews/ariadne/docs/#{short_name.underscore}_preview.rb")
388
+ FileUtils.mkdir_p("lookbook/test/components/previews/ariadne/docs") unless path.dirname.exist?
389
+
390
+ File.open(path, "w") do |f|
391
+ f.puts("module Ariadne")
392
+ f.puts(" module Docs")
393
+ f.puts(" class #{short_name}Preview < ViewComponent::Preview")
394
+
395
+ yard_example_tags.each_with_index do |tag, index|
396
+ name, _, code = parse_example_tag(tag)
397
+ method_name = name.split("|").first.downcase.parameterize.underscore
398
+ f.puts(" def #{method_name}; end")
399
+ f.puts unless index == yard_example_tags.size - 1
400
+ path = Pathname.new("lookbook/test/components/previews/ariadne/docs/#{short_name.underscore}_preview/#{method_name}.html.erb")
401
+ FileUtils.mkdir_p("lookbook/test/components/previews/ariadne/docs/#{short_name.underscore}_preview") unless path.dirname.exist?
402
+ File.open(path, "w") do |view_file|
403
+ view_file.puts(code.to_s)
404
+ end
405
+ end
406
+
407
+ f.puts(" end")
408
+ f.puts(" end")
409
+ f.puts("end")
410
+ end
411
+ end
412
+ end
413
+ end
414
+
415
+ def generate_yard_registry
416
+ require "action_dispatch"
417
+ require_relative "../../app/lib/ariadne/view_helper"
418
+ require File.expand_path("./../../lookbook/config/environment.rb", __dir__)
419
+
420
+ YARD::Registry.yardoc_file = ".yardoc"
421
+
422
+ require "./app/components/ariadne/component.rb"
423
+ require "ariadne/view_components"
424
+ require "yard/docs_helper"
425
+ require "view_component/base"
426
+ require "view_component/test_helpers"
427
+ include(ViewComponent::TestHelpers)
428
+ include(Ariadne::ViewHelper)
429
+ include(YARD::DocsHelper)
430
+
431
+ Dir["./app/components/ariadne/**/*.rb"].sort.each do |file|
432
+ puts file
433
+ require file
434
+ end
435
+
436
+ YARD::Rake::YardocTask.new
437
+
438
+ # Custom tags for yard
439
+ YARD::Tags::Library.define_tag("Accessibility", :accessibility)
440
+ YARD::Tags::Library.define_tag("Deprecation", :deprecation)
441
+ YARD::Tags::Library.define_tag("Parameter", :param, :with_types_name_and_default)
442
+
443
+ puts "Building YARD documentation."
444
+ Rake::Task["yard"].execute
445
+
446
+ registry = YARD::RegistryStore.new
447
+ registry.load!(".yardoc")
448
+ registry
449
+ end
450
+
451
+ def parse_example_tag(tag)
452
+ name = tag.name
453
+ description = nil
454
+ code = nil
455
+
456
+ if tag.text.include?("@description")
457
+ splitted = tag.text.split(/@description|@code/)
458
+ description = splitted.second.gsub(/^[ \t]{2}/, "").strip
459
+ code = splitted.last.gsub(/^[ \t]{2}/, "").strip
460
+ else
461
+ code = tag.text
462
+ end
463
+
464
+ [name, description, code]
465
+ end
466
+
467
+ def pretty_default_value(tag, component)
468
+ params = tag.object.parameters.find { |param| [tag.name.to_s, "#{tag.name}:"].include?(param[0]) }
469
+ default = tag.defaults&.first || params&.second
470
+
471
+ return "N/A" unless default
472
+
473
+ constant_name = "#{component.name}::#{default}"
474
+ constant_value = default.safe_constantize || constant_name.safe_constantize
475
+
476
+ return pretty_value(default) if constant_value.nil?
477
+
478
+ pretty_value(constant_value)
479
+ end
480
+
481
+ def docs_metadata(component)
482
+ (status_module, short_name) = status_module_and_short_name(component)
483
+ status_path = status_module.nil? ? "" : "/"
484
+ status = component.status.to_s
485
+
486
+ {
487
+ title: short_name,
488
+ component_id: short_name.underscore,
489
+ status: status.capitalize,
490
+ source: source_url(component),
491
+ lookbook: lookbook_url(component),
492
+ path: "docs/content/components/#{status_path}#{short_name.downcase}.md",
493
+ example_path: example_path(component),
494
+ require_js_path: require_js_path(component),
495
+ }
496
+ end
497
+
498
+ def source_url(component)
499
+ path = component.name.split("::").map(&:underscore).join("/")
500
+
501
+ "https://github.com/yettoapp/ariadne/ruby/view_components/tree/main/app/components/#{path}.rb"
502
+ end
503
+
504
+ def lookbook_url(component)
505
+ path = component.name.split("::").map { |n| n.underscore.dasherize }.join("-")
506
+
507
+ "https://ariadne.style/view-components/lookbook/?path=/component/#{path}"
508
+ end
509
+
510
+ def example_path(component)
511
+ example_path = "../../src/@primer/gatsby-theme-doctocat/components/example"
512
+ example_path = "../#{example_path}" if status_module?(component)
513
+ example_path
514
+ end
515
+
516
+ def require_js_path(component)
517
+ require_js_path = "../../src/@primer/gatsby-theme-doctocat/components/requires-js-flash"
518
+ require_js_path = "../#{require_js_path}" if status_module?(component)
519
+ require_js_path
520
+ end
521
+
522
+ def status_module?(component)
523
+ (["Alpha", "Beta"] & component.name.split("::")).any?
524
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "ast_traverser"
4
+
5
+ # :nodoc:
6
+ class AstProcessor
7
+ class << self
8
+ def increment(stats, component, arg_name, value)
9
+ stats[component][:arguments][arg_name][value] = 0 unless stats[component][:arguments][arg_name][value]
10
+ stats[component][:arguments][arg_name][value] += 1
11
+ end
12
+
13
+ def process_ast(ast, stats)
14
+ traverser = AstTraverser.new
15
+ traverser.walk(ast)
16
+
17
+ return if traverser.stats.empty?
18
+
19
+ traverser.stats.each do |component, component_info|
20
+ stats[component] ||= {
21
+ paths: [],
22
+ }
23
+
24
+ stats[component][:paths] << component_info[:path]
25
+ stats[component][:paths].uniq!
26
+ stats[component][:arguments] ||= {}
27
+
28
+ component_info[:arguments]&.each do |arg, value|
29
+ arg_name = arg.to_s
30
+ stats[component][:arguments][arg_name] ||= {}
31
+
32
+ # we want to count each class separately
33
+ if arg_name == "classes"
34
+ value.split.each do |val|
35
+ increment(stats, component, arg_name, val)
36
+ end
37
+ else
38
+ increment(stats, component, arg_name, value)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ariadne/view_components/statuses"
4
+ require_relative "../../../app/lib/ariadne/view_helper"
5
+
6
+ # :nodoc:
7
+ class AstTraverser
8
+ include RuboCop::AST::Traversal
9
+
10
+ attr_reader :stats
11
+
12
+ def initialize
13
+ @stats = {}
14
+ end
15
+
16
+ def on_send(node)
17
+ return super(node) unless component_node?(node)
18
+
19
+ name = component_name(node)
20
+ args = extract_arguments(node, name)
21
+
22
+ @stats[name] = { path: node.loc.expression.source_buffer.name }
23
+ @stats[name][:arguments] = args unless args.empty?
24
+
25
+ super(node) # recursively iterate over children
26
+ end
27
+
28
+ def view_helpers
29
+ @view_helpers ||= ::Ariadne::ViewHelper::HELPERS.keys.map { |key| "ariadne_#{key}".to_sym }
30
+ end
31
+
32
+ def component_node?(node)
33
+ view_helpers.include?(node.method_name) || (node.method_name == :new && !node.receiver.nil? && ::Ariadne::ViewComponents::STATUSES.key?(node.receiver.const_name))
34
+ end
35
+
36
+ def component_name(node)
37
+ return node.receiver.const_name if node.method_name == :new
38
+
39
+ helper_key = node.method_name.to_s.gsub("ariadne_", "").to_sym
40
+ Ariadne::ViewHelper::HELPERS[helper_key]
41
+ end
42
+
43
+ def extract_arguments(node, name)
44
+ args = node.arguments
45
+ res = {}
46
+
47
+ return res if args.empty?
48
+
49
+ kwargs = args.last
50
+ if kwargs.respond_to?(:pairs)
51
+ res = kwargs.pairs.each_with_object({}) do |pair, h|
52
+ h.merge!(extract_values(pair))
53
+ end
54
+ end
55
+
56
+ # Heroicon is the only component that accepts positional arguments.
57
+ res[:icon] = args.first.source if name == "Ariadne::HeroiconComponent" && args.size > 1
58
+
59
+ res
60
+ end
61
+
62
+ def extract_values(pair)
63
+ return { pair.key.value => pair.value.source } unless pair.value.type == :hash
64
+
65
+ flatten_pairs(pair, prefix: "#{pair.key.value}-")
66
+ end
67
+
68
+ def flatten_pairs(pair, prefix: "")
69
+ pair.value.pairs.each_with_object({}) do |value_pair, h|
70
+ if value_pair.value.type == :hash
71
+ h.merge!(flatten_pairs(value_pair, prefix: "#{prefix}#{value_pair.key.value}-"))
72
+ else
73
+ h["#{prefix}#{value_pair.key.value}"] = value_pair.value.source
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :static do
4
+ desc "Generate static JSON mappings of components for easier loading"
5
+ task :dump do
6
+ require File.expand_path("./../../lookbook/config/environment.rb", __dir__)
7
+ require "ariadne/view_components"
8
+ # Loads all components for `.descendants` to work properly
9
+ Dir["./app/components/ariadne/**/*.rb"].sort.each { |file| require file }
10
+
11
+ Ariadne::ViewComponents.dump(:statuses)
12
+ Ariadne::ViewComponents.dump(:constants)
13
+ Ariadne::ViewComponents.dump(:audited_at)
14
+ end
15
+ end