|
1 | 1 | # frozen_string_literal: true |
2 | 2 |
|
3 | | -# Rake task to generate /public/llms.txt from doc views and routes. |
| 3 | +require_relative "../llms_txt_generator" |
| 4 | + |
| 5 | +# Rake task to generate /public/llms.txt from doc views. |
4 | 6 | # |
5 | 7 | # Usage: |
6 | 8 | # rake llms:generate |
7 | 9 | # rake llms:generate[https://rubyui.com] # custom base URL |
8 | | -# |
9 | | -# Run this after adding/removing components to keep llms.txt in sync. |
10 | 10 |
|
11 | 11 | namespace :llms do |
12 | 12 | desc "Generate public/llms.txt from doc view files" |
13 | 13 | task :generate, [:base_url] do |_t, args| |
14 | | - base_url = (args[:base_url] || "https://rubyui.com").chomp("/") |
15 | 14 | docs_dir = File.expand_path("../../app/views/docs", __dir__) |
| 15 | + output_path = File.expand_path("../../public/llms.txt", __dir__) |
16 | 16 |
|
17 | | - # Category mapping: filename => [category, subcategory] |
18 | | - categories = { |
19 | | - # Form & Input |
20 | | - "button" => "Form & Input", |
21 | | - "input" => "Form & Input", |
22 | | - "masked_input" => "Form & Input", |
23 | | - "textarea" => "Form & Input", |
24 | | - "checkbox" => "Form & Input", |
25 | | - "checkbox_group" => "Form & Input", |
26 | | - "radio_button" => "Form & Input", |
27 | | - "select" => "Form & Input", |
28 | | - "switch" => "Form & Input", |
29 | | - "calendar" => "Form & Input", |
30 | | - "date_picker" => "Form & Input", |
31 | | - "combobox" => "Form & Input", |
32 | | - "form" => "Form & Input", |
33 | | - # Layout & Navigation |
34 | | - "accordion" => "Layout & Navigation", |
35 | | - "breadcrumb" => "Layout & Navigation", |
36 | | - "tabs" => "Layout & Navigation", |
37 | | - "sidebar" => "Layout & Navigation", |
38 | | - "separator" => "Layout & Navigation", |
39 | | - "pagination" => "Layout & Navigation", |
40 | | - "link" => "Layout & Navigation", |
41 | | - "collapsible" => "Layout & Navigation", |
42 | | - # Overlays & Dialogs |
43 | | - "dialog" => "Overlays & Dialogs", |
44 | | - "alert_dialog" => "Overlays & Dialogs", |
45 | | - "sheet" => "Overlays & Dialogs", |
46 | | - "popover" => "Overlays & Dialogs", |
47 | | - "tooltip" => "Overlays & Dialogs", |
48 | | - "hover_card" => "Overlays & Dialogs", |
49 | | - "context_menu" => "Overlays & Dialogs", |
50 | | - "dropdown_menu" => "Overlays & Dialogs", |
51 | | - "command" => "Overlays & Dialogs", |
52 | | - # Feedback & Status |
53 | | - "alert" => "Feedback & Status", |
54 | | - "progress" => "Feedback & Status", |
55 | | - "skeleton" => "Feedback & Status", |
56 | | - "badge" => "Feedback & Status", |
57 | | - # Display & Media |
58 | | - "avatar" => "Display & Media", |
59 | | - "card" => "Display & Media", |
60 | | - "table" => "Display & Media", |
61 | | - "chart" => "Display & Media", |
62 | | - "carousel" => "Display & Media", |
63 | | - "aspect_ratio" => "Display & Media", |
64 | | - "typography" => "Display & Media", |
65 | | - "codeblock" => "Display & Media", |
66 | | - "clipboard" => "Display & Media", |
67 | | - "shortcut_key" => "Display & Media", |
68 | | - "theme_toggle" => "Display & Media" |
69 | | - } |
70 | | - |
71 | | - # Parse title and description from a doc view .rb file |
72 | | - def extract_header(file_path) |
73 | | - content = File.read(file_path) |
74 | | - if content =~ /Docs::Header\.new\(title:\s*(?:"([^"]+)"|'([^']+)'|(\w+))\s*,\s*description:\s*"([^"]+)"/ |
75 | | - title = $1 || $2 || $3 |
76 | | - description = $4 |
77 | | - # If title is a variable reference (like `component`), look for the assignment |
78 | | - if title =~ /\A[a-z_]+\z/ |
79 | | - content.match(/#{title}\s*=\s*"([^"]+)"/) { |m| title = m[1] } |
80 | | - end |
81 | | - [title, description] |
82 | | - end |
83 | | - end |
84 | | - |
85 | | - # Collect components |
86 | | - components_by_category = Hash.new { |h, k| h[k] = [] } |
87 | | - Dir.glob(File.join(docs_dir, "*.rb")).each do |file| |
88 | | - next if File.basename(file) == "base.rb" |
89 | | - |
90 | | - slug = File.basename(file, ".rb") |
91 | | - result = extract_header(file) |
92 | | - next unless result |
93 | | - |
94 | | - title, description = result |
95 | | - category = categories[slug] || "Miscellaneous" |
96 | | - components_by_category[category] << { slug: slug, title: title, description: description } |
97 | | - end |
98 | | - |
99 | | - # Build llms.txt |
100 | | - lines = [] |
101 | | - lines << "# RubyUI" |
102 | | - lines << "" |
103 | | - lines << "> RubyUI is a UI component library for Ruby developers, built on top of Phlex, TailwindCSS, and Stimulus JS. Components are inspired by shadcn/ui and use compatible theming with CSS variables. Install via the ruby_ui gem into any Rails application. Components are written in pure Ruby using Phlex (up to 12x faster than ERB) and use custom Stimulus.js controllers for interactivity." |
104 | | - lines << "" |
105 | | - |
106 | | - # Getting Started section |
107 | | - lines << "## Getting Started" |
108 | | - lines << "" |
109 | | - lines << "- [Introduction](#{base_url}/docs/introduction): Overview of RubyUI, core ingredients (Phlex, TailwindCSS, Stimulus JS), and design philosophy." |
110 | | - lines << "- [Installation](#{base_url}/docs/installation): How to install RubyUI in your Rails application." |
111 | | - lines << "- [Installation with Rails Bundler](#{base_url}/docs/installation/rails_bundler): Setup using Rails with bundler and JS bundling." |
112 | | - lines << "- [Installation with Rails Importmaps](#{base_url}/docs/installation/rails_importmaps): Setup using Rails with importmaps." |
113 | | - lines << "- [Theming](#{base_url}/docs/theming): Guide to customizing colors and design tokens using CSS variables." |
114 | | - lines << "- [Dark Mode](#{base_url}/docs/dark_mode): How to implement dark mode support." |
115 | | - lines << "- [Customizing Components](#{base_url}/docs/customizing_components): How to customize and extend RubyUI components." |
116 | | - lines << "" |
117 | | - |
118 | | - # Components section |
119 | | - lines << "## Components" |
120 | | - lines << "" |
121 | | - |
122 | | - category_order = ["Form & Input", "Layout & Navigation", "Overlays & Dialogs", "Feedback & Status", "Display & Media", "Miscellaneous"] |
123 | | - category_order.each do |category| |
124 | | - next unless components_by_category.key?(category) |
125 | | - |
126 | | - items = components_by_category[category].sort_by { |c| c[:title] } |
127 | | - lines << "### #{category}" |
128 | | - lines << "" |
129 | | - items.each do |comp| |
130 | | - lines << "- [#{comp[:title]}](#{base_url}/docs/#{comp[:slug]}): #{comp[:description]}" |
131 | | - end |
132 | | - lines << "" |
133 | | - end |
| 17 | + generator = LlmsTxtGenerator.new( |
| 18 | + docs_dir: docs_dir, |
| 19 | + base_url: args[:base_url] || "https://rubyui.com" |
| 20 | + ) |
134 | 21 |
|
135 | | - output_path = File.expand_path("../../public/llms.txt", __dir__) |
136 | | - File.write(output_path, lines.join("\n")) |
137 | | - puts "Generated #{output_path} (#{components_by_category.values.sum(&:length)} components)" |
| 22 | + count = generator.write(output_path) |
| 23 | + puts "Generated #{output_path} (#{count} components)" |
138 | 24 | end |
139 | 25 | end |
0 commit comments