ariadne_view_components 0.0.10-x86_64-darwin

Sign up to get free protection for your applications and to get access to all the features.
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-darwin/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