# frozen_string_literal: true
# rubocop:disable Naming/MethodParameterName
# :nocov:
require "yattho/yard/component_manifest"
require "yattho/yard/backend"
module Yattho
module YARD
# Backend that generates documentation for the legacy, Gatsby-powered PVC docsite.
class LegacyGatsbyBackend < Backend
class << self
def parse_example_tag(tag)
name = tag.name
description = nil
code = nil
if tag.text.include?("@description")
splitted = tag.text.split(/@description|@code/)
description = splitted.second.gsub(/^[ \t]{2}/, "").strip
code = splitted.last.gsub(/^[ \t]{2}/, "").strip
else
code = tag.text
end
[name, description, code]
end
end
attr_reader :registry
def initialize(registry)
@registry = registry
end
def generate
args_for_components = []
errors = []
each_component do |component|
docs = registry.find(component)
status_path = docs.status_module.nil? ? "" : "#{docs.status_module}/"
metadata = docs.metadata.merge(
source: source_url(component),
lookbook: lookbook_url(component),
path: "docs/content/components/#{status_path}#{docs.short_name.downcase}.md",
example_path: example_path(component),
require_js_path: require_js_path(component)
)
path = Pathname.new(metadata[:path])
path.dirname.mkpath unless path.dirname.exist?
File.open(path, "w") do |f|
f.puts("---")
f.puts("title: #{metadata[:title]}")
f.puts("componentId: #{metadata[:component_id]}")
f.puts("status: #{metadata[:status]}")
f.puts("source: #{metadata[:source]}")
f.puts("a11yReviewed: #{metadata[:a11y_reviewed]}")
f.puts("lookbook: #{metadata[:lookbook]}") if preview_exists?(component)
f.puts("---")
f.puts
f.puts("import Example from '#{metadata[:example_path]}'")
if docs.requires_js?
f.puts("import RequiresJSFlash from '#{metadata[:require_js_path]}'")
f.puts
f.puts("")
end
f.puts
f.puts("")
f.puts
f.puts(view_context.render(inline: docs.base_docstring))
if docs.tags(:deprecated).any?
f.puts
f.puts("## Deprecation")
docs.tags(:deprecated).each do |tag|
f.puts
f.puts view_context.render(inline: tag.text)
end
end
if docs.tags(:accessibility).any?
f.puts
f.puts("## Accessibility")
docs.tags(:accessibility).each do |tag|
f.puts
f.puts view_context.render(inline: tag.text)
end
end
errors << { component.name => { arguments: "No argument documentation found" } } unless docs.params.any?
f.puts
f.puts("## Arguments")
f.puts
f.puts("| Name | Type | Default | Description |")
f.puts("| :- | :- | :- | :- |")
documented_params = docs.params.map(&:name)
component_params = component.instance_method(:initialize).parameters.map { |p| p.last.to_s }
if (documented_params & component_params).size != component_params.size
err = { arguments: {} }
(component_params - documented_params).each do |arg|
err[:arguments][arg] = "Not documented"
end
errors << { component.name => err }
end
args = []
docs.params.each do |tag|
default_value = pretty_default_value(tag, component)
args << {
"name" => tag.name,
"type" => tag.types.join(", "),
"default" => default_value,
"description" => view_context.render(inline: tag.text.squish)
}
f.puts("| `#{tag.name}` | `#{tag.types.join(', ')}` | #{default_value} | #{view_context.render(inline: tag.text.squish)} |")
end
component_args = {
"component" => metadata[:title],
"status" => component.status.to_s,
"source" => metadata[:source],
"lookbook" => metadata[:lookbook],
"parameters" => args
}
args_for_components << component_args
if docs.slot_methods.any?
f.puts
f.puts("## Slots")
docs.slot_methods.each do |slot_docs|
emit_method(slot_docs, component, f)
end
end
example_tags = docs.constructor.tags(:example)
if example_tags.any?
f.puts
f.puts("## Examples")
example_tags.each do |tag|
name, description, code = parse_example_tag(tag)
f.puts
f.puts("### #{name}")
if description
f.puts
f.puts(view_context.render(inline: description.squish))
end
f.puts
html = view_context.render(inline: code)
f.puts("")
f.puts
f.puts("```erb")
f.puts(code.to_s)
f.puts("```")
end
elsif manifest.components_with_examples.include?(component)
errors << { component.name => { example: "No examples found" } }
end
end
end
# Build system arguments docs from BaseComponent
system_args_docs = registry.find(Yattho::BaseComponent)
File.open("docs/content/system-arguments.md", "w") do |f|
f.puts("---")
f.puts("title: System arguments")
f.puts("---")
f.puts
f.puts("")
f.puts
f.puts(system_args_docs.base_docstring)
f.puts
f.puts(view_context.render(inline: system_args_docs.constructor.base_docstring))
end
[args_for_components, errors]
end
private
def emit_method(method_docs, component, f)
f.puts
f.puts("### `#{method_docs.name}`")
if method_docs.base_docstring.to_s.present?
f.puts
f.puts(view_context.render(inline: method_docs.base_docstring))
end
param_tags = method_docs.tags(:param)
if param_tags.any?
f.puts
f.puts("| Name | Type | Default | Description |")
f.puts("| :- | :- | :- | :- |")
end
param_tags.each do |tag|
f.puts("| `#{tag.name}` | `#{tag.types.join(', ')}` | #{pretty_default_value(tag, component)} | #{view_context.render(inline: tag.text)} |")
end
end
def each_component(&block)
manifest.components_with_docs.sort_by(&:name).each(&block)
end
def manifest
Yattho::YARD::ComponentManifest
end
def source_url(component)
path = component.name.split("::").map(&:underscore).join("/")
"https://github.com/yattho/view_components/tree/main/app/components/#{path}.rb"
end
def lookbook_url(component)
path = component.name.underscore.gsub("_component", "")
"https://yattho.com/view-components/lookbook/inspect/#{path}/default/"
end
def example_path(component)
example_path = "../../src/@yattho/gatsby-theme-doctocat/components/example"
example_path = "../#{example_path}" if status_module?(component)
example_path
end
def require_js_path(component)
require_js_path = "../../src/@yattho/gatsby-theme-doctocat/components/requires-js-flash"
require_js_path = "../#{require_js_path}" if status_module?(component)
require_js_path
end
def preview_exists?(component)
path = component.name.underscore
File.exist?("previews/#{path}_preview.rb")
end
def status_module?(component)
(%w[Alpha Beta] & component.name.split("::")).any?
end
def parse_example_tag(tag)
self.class.parse_example_tag(tag)
end
end
end
end
# :nocov:
# rubocop:enable Naming/MethodParameterName