ariadne_view_components 0.0.1
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.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +68 -0
- data/app/assets/javascripts/ariadne_view_components.js +2 -0
- data/app/assets/javascripts/ariadne_view_components.js.map +1 -0
- data/app/assets/stylesheets/application.tailwind.css +3 -0
- data/app/components/ariadne/ariadne.ts +14 -0
- data/app/components/ariadne/base_button.rb +60 -0
- data/app/components/ariadne/base_component.rb +155 -0
- data/app/components/ariadne/button_component.html.erb +4 -0
- data/app/components/ariadne/button_component.rb +158 -0
- data/app/components/ariadne/clipboard_copy_component.html.erb +8 -0
- data/app/components/ariadne/clipboard_copy_component.rb +50 -0
- data/app/components/ariadne/clipboard_copy_component.ts +19 -0
- data/app/components/ariadne/component.rb +123 -0
- data/app/components/ariadne/content.rb +12 -0
- data/app/components/ariadne/counter_component.rb +100 -0
- data/app/components/ariadne/flash_component.html.erb +31 -0
- data/app/components/ariadne/flash_component.rb +125 -0
- data/app/components/ariadne/heading_component.rb +49 -0
- data/app/components/ariadne/heroicon_component.html.erb +7 -0
- data/app/components/ariadne/heroicon_component.rb +116 -0
- data/app/components/ariadne/image_component.rb +51 -0
- data/app/components/ariadne/text.rb +25 -0
- data/app/components/ariadne/tooltip_component.rb +105 -0
- data/app/lib/ariadne/audited/dsl.rb +32 -0
- data/app/lib/ariadne/class_name_helper.rb +22 -0
- data/app/lib/ariadne/fetch_or_fallback_helper.rb +100 -0
- data/app/lib/ariadne/icon_helper.rb +47 -0
- data/app/lib/ariadne/join_style_arguments_helper.rb +14 -0
- data/app/lib/ariadne/logger_helper.rb +23 -0
- data/app/lib/ariadne/status/dsl.rb +41 -0
- data/app/lib/ariadne/tab_nav_helper.rb +35 -0
- data/app/lib/ariadne/tabbed_component_helper.rb +39 -0
- data/app/lib/ariadne/test_selector_helper.rb +20 -0
- data/app/lib/ariadne/underline_nav_helper.rb +44 -0
- data/app/lib/ariadne/view_helper.rb +22 -0
- data/lib/ariadne/classify/utilities.rb +199 -0
- data/lib/ariadne/classify/utilities.yml +1817 -0
- data/lib/ariadne/classify/validation.rb +18 -0
- data/lib/ariadne/classify.rb +210 -0
- data/lib/ariadne/view_components/constants.rb +53 -0
- data/lib/ariadne/view_components/engine.rb +30 -0
- data/lib/ariadne/view_components/linters.rb +3 -0
- data/lib/ariadne/view_components/statuses.rb +14 -0
- data/lib/ariadne/view_components/version.rb +7 -0
- data/lib/ariadne/view_components.rb +59 -0
- data/lib/rubocop/config/default.yml +14 -0
- data/lib/rubocop/cop/ariadne/ariadne_heroicon.rb +252 -0
- data/lib/rubocop/cop/ariadne/base_cop.rb +26 -0
- data/lib/rubocop/cop/ariadne/component_name_migration.rb +35 -0
- data/lib/rubocop/cop/ariadne/no_tag_memoize.rb +43 -0
- data/lib/rubocop/cop/ariadne/system_argument_instead_of_class.rb +57 -0
- data/lib/rubocop/cop/ariadne.rb +3 -0
- data/lib/tasks/ariadne_view_components.rake +47 -0
- data/lib/tasks/coverage.rake +19 -0
- data/lib/tasks/custom_utilities.yml +310 -0
- data/lib/tasks/docs.rake +525 -0
- data/lib/tasks/helpers/ast_processor.rb +44 -0
- data/lib/tasks/helpers/ast_traverser.rb +77 -0
- data/lib/tasks/static.rake +15 -0
- data/lib/tasks/tailwind.rake +31 -0
- data/lib/tasks/utilities.rake +121 -0
- data/lib/yard/docs_helper.rb +83 -0
- data/lib/yard/renders_many_handler.rb +19 -0
- data/lib/yard/renders_one_handler.rb +19 -0
- data/static/arguments.yml +251 -0
- data/static/assets/view-components.svg +18 -0
- data/static/audited_at.json +14 -0
- data/static/classes.yml +89 -0
- data/static/constants.json +243 -0
- data/static/statuses.json +14 -0
- data/static/tailwindcss.yml +727 -0
- metadata +193 -0
data/lib/tasks/docs.rake
ADDED
@@ -0,0 +1,525 @@
|
|
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
|
+
desc "Generate the documentation."
|
36
|
+
task :build do
|
37
|
+
registry = generate_yard_registry
|
38
|
+
|
39
|
+
puts "Converting YARD documentation to Markdown files."
|
40
|
+
|
41
|
+
# Rails controller for rendering arbitrary ERB
|
42
|
+
view_context = ApplicationController.new.tap { |c| c.request = ActionDispatch::TestRequest.create }.view_context
|
43
|
+
components = [
|
44
|
+
# FIXME: these need to be filled in
|
45
|
+
Ariadne::BaseButton,
|
46
|
+
Ariadne::ButtonComponent,
|
47
|
+
# Ariadne::Alpha::Layout,
|
48
|
+
# Ariadne::HellipButton,
|
49
|
+
# Ariadne::BorderBox::Header,
|
50
|
+
# Ariadne::IconButton,
|
51
|
+
# Ariadne::Beta::AutoComplete,
|
52
|
+
# Ariadne::Beta::AutoComplete::Item,
|
53
|
+
# Ariadne::Beta::Avatar,
|
54
|
+
# Ariadne::Beta::AvatarStack,
|
55
|
+
# Ariadne::Beta::Blankslate,
|
56
|
+
# Ariadne::BorderBoxComponent,
|
57
|
+
# Ariadne::BoxComponent,
|
58
|
+
# Ariadne::Beta::Breadcrumbs,
|
59
|
+
# Ariadne::ButtonGroup,
|
60
|
+
# Ariadne::Alpha::ButtonMarketing,
|
61
|
+
Ariadne::ClipboardCopyComponent,
|
62
|
+
# Ariadne::CloseButton,
|
63
|
+
Ariadne::CounterComponent,
|
64
|
+
# Ariadne::DetailsComponent,
|
65
|
+
# Ariadne::Dropdown,
|
66
|
+
# Ariadne::DropdownMenuComponent,
|
67
|
+
Ariadne::FlashComponent,
|
68
|
+
# Ariadne::FlexComponent,
|
69
|
+
# Ariadne::FlexItemComponent,
|
70
|
+
Ariadne::HeadingComponent,
|
71
|
+
# Ariadne::HiddenTextExpander,
|
72
|
+
# Ariadne::LabelComponent,
|
73
|
+
# Ariadne::LayoutComponent,
|
74
|
+
# Ariadne::LinkComponent,
|
75
|
+
# Ariadne::Markdown,
|
76
|
+
# Ariadne::MenuComponent,
|
77
|
+
# Ariadne::Navigation::TabComponent,
|
78
|
+
Ariadne::HeroiconComponent,
|
79
|
+
# Ariadne::LocalTime,
|
80
|
+
Ariadne::ImageComponent,
|
81
|
+
# Ariadne::ImageCrop,
|
82
|
+
# Ariadne::PopoverComponent,
|
83
|
+
# Ariadne::ProgressBarComponent,
|
84
|
+
# Ariadne::StateComponent,
|
85
|
+
# Ariadne::SpinnerComponent,
|
86
|
+
# Ariadne::SubheadComponent,
|
87
|
+
# Ariadne::TabContainerComponent,
|
88
|
+
Ariadne::Text,
|
89
|
+
# Ariadne::TimeAgoComponent,
|
90
|
+
# Ariadne::TimelineItemComponent,
|
91
|
+
# Ariadne::TooltipComponent,
|
92
|
+
# Ariadne::Truncate,
|
93
|
+
# Ariadne::Beta::Truncate,
|
94
|
+
# Ariadne::Alpha::UnderlineNav,
|
95
|
+
# Ariadne::Alpha::UnderlinePanels,
|
96
|
+
# Ariadne::Alpha::TabNav,
|
97
|
+
# Ariadne::Alpha::TabPanels,
|
98
|
+
Ariadne::TooltipComponent,
|
99
|
+
]
|
100
|
+
|
101
|
+
js_components = [
|
102
|
+
# Ariadne::Dropdown,
|
103
|
+
# Ariadne::LocalTime,
|
104
|
+
# Ariadne::ImageCrop,
|
105
|
+
# Ariadne::Beta::AutoComplete,
|
106
|
+
Ariadne::ClipboardCopyComponent,
|
107
|
+
# Ariadne::TabContainerComponent,
|
108
|
+
# Ariadne::TimeAgoComponent,
|
109
|
+
# Ariadne::Alpha::UnderlinePanels,
|
110
|
+
# Ariadne::Alpha::TabPanels,
|
111
|
+
# Ariadne::TooltipComponent,
|
112
|
+
# Ariadne::ButtonComponent,
|
113
|
+
# Ariadne::LinkComponent,
|
114
|
+
]
|
115
|
+
|
116
|
+
all_components = Ariadne::Component.descendants - [Ariadne::BaseComponent, Ariadne::Content] # TODO: why is `Ariadne::Content` not picked up?
|
117
|
+
components_needing_docs = all_components - components
|
118
|
+
|
119
|
+
args_for_components = []
|
120
|
+
classes_found_in_examples = []
|
121
|
+
|
122
|
+
errors = []
|
123
|
+
|
124
|
+
# Deletes docs before regenerating them, guaranteeing that we don't keep stale docs.
|
125
|
+
components_content_glob = File.join("docs", "content", "components", "**", "*.md")
|
126
|
+
FileUtils.rm_rf(components_content_glob)
|
127
|
+
|
128
|
+
components.sort_by(&:name).each do |component|
|
129
|
+
documentation = registry.get(component.name)
|
130
|
+
|
131
|
+
data = docs_metadata(component)
|
132
|
+
|
133
|
+
path = Pathname.new(data[:path])
|
134
|
+
path.dirname.mkpath unless path.dirname.exist?
|
135
|
+
File.open(path, "w") do |f|
|
136
|
+
f.puts("---")
|
137
|
+
f.puts("title: #{data[:title]}")
|
138
|
+
f.puts("componentId: #{data[:component_id]}")
|
139
|
+
f.puts("status: #{data[:status]}")
|
140
|
+
f.puts("source: #{data[:source]}")
|
141
|
+
f.puts("lookbook: #{data[:lookbook]}")
|
142
|
+
f.puts("---")
|
143
|
+
f.puts
|
144
|
+
f.puts("import Example from '#{data[:example_path]}'")
|
145
|
+
|
146
|
+
initialize_method = documentation.meths.find(&:constructor?)
|
147
|
+
|
148
|
+
if js_components.include?(component)
|
149
|
+
f.puts("import RequiresJSFlash from '#{data[:require_js_path]}'")
|
150
|
+
f.puts
|
151
|
+
f.puts("<RequiresJSFlash />")
|
152
|
+
end
|
153
|
+
|
154
|
+
f.puts
|
155
|
+
f.puts("<!-- Warning: AUTO-GENERATED file, do not edit. Add code comments to your Ruby instead <3 -->")
|
156
|
+
f.puts
|
157
|
+
f.puts(view_context.render(inline: documentation.base_docstring))
|
158
|
+
|
159
|
+
if documentation.tags(:deprecated).any?
|
160
|
+
f.puts
|
161
|
+
f.puts("## Deprecation")
|
162
|
+
documentation.tags(:deprecated).each do |tag|
|
163
|
+
f.puts
|
164
|
+
f.puts view_context.render(inline: tag.text)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
if documentation.tags(:accessibility).any?
|
169
|
+
f.puts
|
170
|
+
f.puts("## Accessibility")
|
171
|
+
documentation.tags(:accessibility).each do |tag|
|
172
|
+
f.puts
|
173
|
+
f.puts view_context.render(inline: tag.text)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
params = initialize_method.tags(:param)
|
178
|
+
|
179
|
+
errors << { component.name => { arguments: "No argument documentation found" } } unless params.any?
|
180
|
+
|
181
|
+
f.puts
|
182
|
+
f.puts("## Arguments")
|
183
|
+
f.puts
|
184
|
+
f.puts("| Name | Type | Default | Description |")
|
185
|
+
f.puts("| :- | :- | :- | :- |")
|
186
|
+
|
187
|
+
documented_params = params.map(&:name)
|
188
|
+
component_params = component.instance_method(:initialize).parameters.map { |p| p.last.to_s }
|
189
|
+
|
190
|
+
if (documented_params & component_params).size != component_params.size
|
191
|
+
err = { arguments: {} }
|
192
|
+
(component_params - documented_params).each do |arg|
|
193
|
+
err[:arguments][arg] = "Not documented"
|
194
|
+
end
|
195
|
+
|
196
|
+
errors << { component.name => err }
|
197
|
+
end
|
198
|
+
|
199
|
+
args = []
|
200
|
+
params.each do |tag|
|
201
|
+
default_value = pretty_default_value(tag, component)
|
202
|
+
|
203
|
+
args << {
|
204
|
+
"name" => tag.name,
|
205
|
+
"type" => tag.types.join(", "),
|
206
|
+
"default" => default_value,
|
207
|
+
"description" => view_context.render(inline: tag.text.squish),
|
208
|
+
}
|
209
|
+
|
210
|
+
f.puts("| `#{tag.name}` | `#{tag.types.join(", ")}` | #{default_value} | #{view_context.render(inline: tag.text.squish)} |")
|
211
|
+
end
|
212
|
+
|
213
|
+
component_args = {
|
214
|
+
"component" => data[:title],
|
215
|
+
"source" => data[:source],
|
216
|
+
"parameters" => args,
|
217
|
+
}
|
218
|
+
|
219
|
+
args_for_components << component_args
|
220
|
+
|
221
|
+
# Slots V2 docs
|
222
|
+
slot_v2_methods = documentation.meths.select { |x| x[:renders_one] || x[:renders_many] }
|
223
|
+
|
224
|
+
if slot_v2_methods.any?
|
225
|
+
f.puts
|
226
|
+
f.puts("## Slots")
|
227
|
+
|
228
|
+
slot_v2_methods.each do |slot_documentation|
|
229
|
+
f.puts
|
230
|
+
f.puts("### `#{slot_documentation.name.to_s.capitalize}`")
|
231
|
+
|
232
|
+
if slot_documentation.base_docstring.to_s.present?
|
233
|
+
f.puts
|
234
|
+
f.puts(view_context.render(inline: slot_documentation.base_docstring))
|
235
|
+
end
|
236
|
+
|
237
|
+
param_tags = slot_documentation.tags(:param)
|
238
|
+
if param_tags.any?
|
239
|
+
f.puts
|
240
|
+
f.puts("| Name | Type | Default | Description |")
|
241
|
+
f.puts("| :- | :- | :- | :- |")
|
242
|
+
end
|
243
|
+
|
244
|
+
param_tags.each do |tag|
|
245
|
+
f.puts("| `#{tag.name}` | `#{tag.types.join(", ")}` | #{pretty_default_value(tag, component)} | #{view_context.render(inline: tag.text)} |")
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
errors << { component.name => { example: "No examples found" } } unless initialize_method.tags(:example).any?
|
251
|
+
|
252
|
+
f.puts
|
253
|
+
f.puts("## Examples")
|
254
|
+
|
255
|
+
initialize_method.tags(:example).each do |tag|
|
256
|
+
name, description, code = parse_example_tag(tag)
|
257
|
+
f.puts
|
258
|
+
f.puts("### #{name}")
|
259
|
+
if description
|
260
|
+
f.puts
|
261
|
+
f.puts(view_context.render(inline: description.squish))
|
262
|
+
end
|
263
|
+
f.puts
|
264
|
+
html = view_context.render(inline: code)
|
265
|
+
html.scan(/class="([^"]*)"/) do |classnames|
|
266
|
+
classes_found_in_examples.concat(classnames[0].split.reject { |c| c.starts_with?("heroicon", "js", "my-") }.map { ".#{_1}" })
|
267
|
+
end
|
268
|
+
f.puts("<Example src=\"#{html.tr('"', "\'").delete("\n")}\" />")
|
269
|
+
f.puts
|
270
|
+
f.puts("```erb")
|
271
|
+
f.puts(code.to_s)
|
272
|
+
f.puts("```")
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
unless errors.empty?
|
278
|
+
puts "==============================================="
|
279
|
+
puts "===================== ERRORS =================="
|
280
|
+
puts "===============================================\n\n"
|
281
|
+
puts JSON.pretty_generate(errors)
|
282
|
+
puts "\n\n==============================================="
|
283
|
+
puts "==============================================="
|
284
|
+
puts "==============================================="
|
285
|
+
|
286
|
+
raise
|
287
|
+
end
|
288
|
+
|
289
|
+
File.open("static/classes.yml", "w") do |f|
|
290
|
+
f.puts YAML.dump(classes_found_in_examples.sort.uniq)
|
291
|
+
end
|
292
|
+
|
293
|
+
File.open("static/arguments.yml", "w") do |f|
|
294
|
+
f.puts YAML.dump(args_for_components)
|
295
|
+
end
|
296
|
+
|
297
|
+
# Build system arguments docs from BaseComponent
|
298
|
+
documentation = registry.get(Ariadne::BaseComponent.name)
|
299
|
+
File.open("docs/content/system-arguments.md", "w") do |f|
|
300
|
+
f.puts("---")
|
301
|
+
f.puts("title: System arguments")
|
302
|
+
f.puts("---")
|
303
|
+
f.puts
|
304
|
+
f.puts("<!-- Warning: AUTO-GENERATED file, do not edit. Add code comments to your Ruby instead <3 -->")
|
305
|
+
f.puts
|
306
|
+
f.puts(documentation.base_docstring)
|
307
|
+
f.puts
|
308
|
+
|
309
|
+
initialize_method = documentation.meths.find(&:constructor?)
|
310
|
+
|
311
|
+
f.puts(view_context.render(inline: initialize_method.base_docstring))
|
312
|
+
end
|
313
|
+
|
314
|
+
# Copy over ADR docs and insert them into the nav
|
315
|
+
# puts "Copying ADRs..."
|
316
|
+
# Rake::Task["docs:build_adrs"].invoke
|
317
|
+
|
318
|
+
puts "Markdown compiled."
|
319
|
+
|
320
|
+
if components_needing_docs.any?
|
321
|
+
puts
|
322
|
+
puts "The following components needs docs. Care to contribute them? #{components_needing_docs.map(&:name).join(", ")}"
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
# task :build_adrs do
|
327
|
+
# adr_content_dir = File.join("docs", "content", "adr")
|
328
|
+
|
329
|
+
# FileUtils.rm_rf(File.join(adr_content_dir))
|
330
|
+
# FileUtils.mkdir(adr_content_dir)
|
331
|
+
|
332
|
+
# nav_entries = Dir[File.join("adr", "*.md")].sort.map do |orig_path|
|
333
|
+
# orig_file_name = File.basename(orig_path)
|
334
|
+
# url_name = orig_file_name.chomp(".md")
|
335
|
+
|
336
|
+
# file_contents = File.read(orig_path)
|
337
|
+
# file_contents = <<~CONTENTS.sub(/\n+\z/, "\n")
|
338
|
+
# <!-- Warning: AUTO-GENERATED file, do not edit. Make changes to the files in the adr/ directory instead. -->
|
339
|
+
# #{file_contents}
|
340
|
+
# CONTENTS
|
341
|
+
|
342
|
+
# title_match = /^# (.+)/.match(file_contents)
|
343
|
+
# title = title_match[1]
|
344
|
+
|
345
|
+
# # Don't include initial ADR for recording ADRs
|
346
|
+
# next nil if title == "Record architecture decisions"
|
347
|
+
|
348
|
+
# File.write(File.join(adr_content_dir, orig_file_name), file_contents)
|
349
|
+
# puts "Copied #{orig_path}"
|
350
|
+
|
351
|
+
# { "title" => title, "url" => "/adr/#{url_name}" }
|
352
|
+
# end
|
353
|
+
|
354
|
+
# nav_yaml_file = File.join("docs", "src", "@primer", "gatsby-theme-doctocat", "nav.yml")
|
355
|
+
# nav_yaml = YAML.load_file(nav_yaml_file)
|
356
|
+
# adr_entry = {
|
357
|
+
# "title" => "Architecture decisions",
|
358
|
+
# "children" => nav_entries.compact,
|
359
|
+
# }
|
360
|
+
|
361
|
+
# existing_index = nav_yaml.index { |entry| entry["title"] == "Architecture decisions" }
|
362
|
+
# if existing_index
|
363
|
+
# nav_yaml[existing_index] = adr_entry
|
364
|
+
# else
|
365
|
+
# nav_yaml << adr_entry
|
366
|
+
# end
|
367
|
+
|
368
|
+
# File.write(nav_yaml_file, YAML.dump(nav_yaml))
|
369
|
+
# end
|
370
|
+
|
371
|
+
desc "Generate previews from documentation examples"
|
372
|
+
task :preview do
|
373
|
+
registry = generate_yard_registry
|
374
|
+
|
375
|
+
FileUtils.rm_rf("lookbook/test/components/previews/ariadne/docs/")
|
376
|
+
|
377
|
+
components = Ariadne::Component.descendants
|
378
|
+
|
379
|
+
components.each do |component|
|
380
|
+
documentation = registry.get(component.name)
|
381
|
+
short_name = component.name.gsub(/Ariadne|::/, "")
|
382
|
+
initialize_method = documentation.meths.find(&:constructor?)
|
383
|
+
|
384
|
+
next unless initialize_method&.tags(:example)&.any?
|
385
|
+
|
386
|
+
yard_example_tags = initialize_method.tags(:example)
|
387
|
+
|
388
|
+
path = Pathname.new("lookbook/test/components/previews/ariadne/docs/#{short_name.underscore}_preview.rb")
|
389
|
+
FileUtils.mkdir_p("lookbook/test/components/previews/ariadne/docs") unless path.dirname.exist?
|
390
|
+
|
391
|
+
File.open(path, "w") do |f|
|
392
|
+
f.puts("module Ariadne")
|
393
|
+
f.puts(" module Docs")
|
394
|
+
f.puts(" class #{short_name}Preview < ViewComponent::Preview")
|
395
|
+
|
396
|
+
yard_example_tags.each_with_index do |tag, index|
|
397
|
+
name, _, code = parse_example_tag(tag)
|
398
|
+
method_name = name.split("|").first.downcase.parameterize.underscore
|
399
|
+
f.puts(" def #{method_name}; end")
|
400
|
+
f.puts unless index == yard_example_tags.size - 1
|
401
|
+
path = Pathname.new("lookbook/test/components/previews/ariadne/docs/#{short_name.underscore}_preview/#{method_name}.html.erb")
|
402
|
+
FileUtils.mkdir_p("lookbook/test/components/previews/ariadne/docs/#{short_name.underscore}_preview") unless path.dirname.exist?
|
403
|
+
File.open(path, "w") do |view_file|
|
404
|
+
view_file.puts(code.to_s)
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
f.puts(" end")
|
409
|
+
f.puts(" end")
|
410
|
+
f.puts("end")
|
411
|
+
end
|
412
|
+
end
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
def generate_yard_registry
|
417
|
+
require "action_dispatch"
|
418
|
+
require_relative "../../app/lib/ariadne/view_helper"
|
419
|
+
require File.expand_path("./../../lookbook/config/environment.rb", __dir__)
|
420
|
+
|
421
|
+
YARD::Registry.yardoc_file = ".yardoc"
|
422
|
+
|
423
|
+
require "./app/components/ariadne/component.rb"
|
424
|
+
require "ariadne/view_components"
|
425
|
+
require "yard/docs_helper"
|
426
|
+
require "view_component/base"
|
427
|
+
require "view_component/test_helpers"
|
428
|
+
include(ViewComponent::TestHelpers)
|
429
|
+
include(Ariadne::ViewHelper)
|
430
|
+
include(YARD::DocsHelper)
|
431
|
+
|
432
|
+
Dir["./app/components/ariadne/**/*.rb"].sort.each do |file|
|
433
|
+
puts file
|
434
|
+
require file
|
435
|
+
end
|
436
|
+
|
437
|
+
YARD::Rake::YardocTask.new
|
438
|
+
|
439
|
+
# Custom tags for yard
|
440
|
+
YARD::Tags::Library.define_tag("Accessibility", :accessibility)
|
441
|
+
YARD::Tags::Library.define_tag("Deprecation", :deprecation)
|
442
|
+
YARD::Tags::Library.define_tag("Parameter", :param, :with_types_name_and_default)
|
443
|
+
|
444
|
+
puts "Building YARD documentation."
|
445
|
+
Rake::Task["yard"].execute
|
446
|
+
|
447
|
+
registry = YARD::RegistryStore.new
|
448
|
+
registry.load!(".yardoc")
|
449
|
+
registry
|
450
|
+
end
|
451
|
+
|
452
|
+
def parse_example_tag(tag)
|
453
|
+
name = tag.name
|
454
|
+
description = nil
|
455
|
+
code = nil
|
456
|
+
|
457
|
+
if tag.text.include?("@description")
|
458
|
+
splitted = tag.text.split(/@description|@code/)
|
459
|
+
description = splitted.second.gsub(/^[ \t]{2}/, "").strip
|
460
|
+
code = splitted.last.gsub(/^[ \t]{2}/, "").strip
|
461
|
+
else
|
462
|
+
code = tag.text
|
463
|
+
end
|
464
|
+
|
465
|
+
[name, description, code]
|
466
|
+
end
|
467
|
+
|
468
|
+
def pretty_default_value(tag, component)
|
469
|
+
params = tag.object.parameters.find { |param| [tag.name.to_s, "#{tag.name}:"].include?(param[0]) }
|
470
|
+
default = tag.defaults&.first || params&.second
|
471
|
+
|
472
|
+
return "N/A" unless default
|
473
|
+
|
474
|
+
constant_name = "#{component.name}::#{default}"
|
475
|
+
constant_value = default.safe_constantize || constant_name.safe_constantize
|
476
|
+
|
477
|
+
return pretty_value(default) if constant_value.nil?
|
478
|
+
|
479
|
+
pretty_value(constant_value)
|
480
|
+
end
|
481
|
+
|
482
|
+
def docs_metadata(component)
|
483
|
+
(status_module, short_name) = status_module_and_short_name(component)
|
484
|
+
status_path = status_module.nil? ? "" : "/"
|
485
|
+
status = component.status.to_s
|
486
|
+
|
487
|
+
{
|
488
|
+
title: short_name,
|
489
|
+
component_id: short_name.underscore,
|
490
|
+
status: status.capitalize,
|
491
|
+
source: source_url(component),
|
492
|
+
lookbook: lookbook_url(component),
|
493
|
+
path: "docs/content/components/#{status_path}#{short_name.downcase}.md",
|
494
|
+
example_path: example_path(component),
|
495
|
+
require_js_path: require_js_path(component),
|
496
|
+
}
|
497
|
+
end
|
498
|
+
|
499
|
+
def source_url(component)
|
500
|
+
path = component.name.split("::").map(&:underscore).join("/")
|
501
|
+
|
502
|
+
"https://github.com/yettoapp/ariadne/ruby/view_components/tree/main/app/components/#{path}.rb"
|
503
|
+
end
|
504
|
+
|
505
|
+
def lookbook_url(component)
|
506
|
+
path = component.name.split("::").map { |n| n.underscore.dasherize }.join("-")
|
507
|
+
|
508
|
+
"https://ariadne.style/view-components/lookbook/?path=/component/#{path}"
|
509
|
+
end
|
510
|
+
|
511
|
+
def example_path(component)
|
512
|
+
example_path = "../../src/@primer/gatsby-theme-doctocat/components/example"
|
513
|
+
example_path = "../#{example_path}" if status_module?(component)
|
514
|
+
example_path
|
515
|
+
end
|
516
|
+
|
517
|
+
def require_js_path(component)
|
518
|
+
require_js_path = "../../src/@primer/gatsby-theme-doctocat/components/requires-js-flash"
|
519
|
+
require_js_path = "../#{require_js_path}" if status_module?(component)
|
520
|
+
require_js_path
|
521
|
+
end
|
522
|
+
|
523
|
+
def status_module?(component)
|
524
|
+
(["Alpha", "Beta"] & component.name.split("::")).any?
|
525
|
+
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
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# "Hacks, but they work" ™
|
4
|
+
if !defined?(Rails) || ENV.fetch("DOCS_BUILD", false).blank?
|
5
|
+
|
6
|
+
# Spoof the Rails environment
|
7
|
+
module Rails
|
8
|
+
def self.root
|
9
|
+
Pathname.new(File.join(File.dirname(__FILE__), "..", ".."))
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
rule "" do |t|
|
14
|
+
task_name = t.name
|
15
|
+
if task_name == "assets:precompile"
|
16
|
+
puts "Stubbing non-existent #{task_name}"
|
17
|
+
else
|
18
|
+
raise "No task named #{task_name}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
spec = Gem::Specification.find_by_name("tailwindcss-rails")
|
24
|
+
load "#{spec.gem_dir}/lib/tasks/build.rake"
|
25
|
+
|
26
|
+
Rake::Task["tailwindcss:build"].enhance do
|
27
|
+
if File.exist?("app/assets/builds/tailwind.css")
|
28
|
+
puts "Renaming tailwind.css to ariadne_view_components.css..."
|
29
|
+
File.rename("app/assets/builds/tailwind.css", "app/assets/builds/ariadne_view_components.css")
|
30
|
+
end
|
31
|
+
end
|