hotdocs 0.1.0

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.
@@ -0,0 +1,177 @@
1
+ // Example usage:
2
+ // echo "# title" || deno --allow-read --allow-env --node-modules-dir=auto config/initializers/markdown.mjs
3
+
4
+ import remarkParse from "npm:remark-parse";
5
+ import remarkPrism from "npm:remark-prism";
6
+ import remarkRehype from "npm:remark-rehype";
7
+ import rehypeStringify from "npm:rehype-stringify";
8
+ import { unified } from "npm:unified";
9
+
10
+ process.stdin.on("data", async (data) => {
11
+ try {
12
+ const file = await unified()
13
+ .use(remarkParse)
14
+ .use(remarkDirective)
15
+ .use(remarkAdmonitions)
16
+ .use(remarkPrism)
17
+ // `allowDangerousHtml` to avoid sanitizing the raw Html from `data`
18
+ .use(remarkRehype, { allowDangerousHtml: true })
19
+ .use(rehypeStringify, { allowDangerousHtml: true })
20
+ .process(data);
21
+ process.stdout.write(String(file));
22
+ } catch (error) {
23
+ console.error(`Error: ${error.message}\nStack trace:\n${error.stack}`);
24
+ process.stdout.write(
25
+ `Error: ${error.message}<br />Stack trace:\n${error.stack}`
26
+ );
27
+ }
28
+ });
29
+
30
+ const ADMONITIONS = {
31
+ tip: {
32
+ svg: {
33
+ xmlns: "http://www.w3.org/2000/svg",
34
+ fill: "none",
35
+ viewBox: "0 0 24 24",
36
+ strokeWidth: "1.5",
37
+ stroke: "currentColor",
38
+ },
39
+ paths: [
40
+ {
41
+ strokeLinecap: "round",
42
+ strokeLinejoin: "round",
43
+ d: "M12 18v-5.25m0 0a6.01 6.01 0 0 0 1.5-.189m-1.5.189a6.01 6.01 0 0 1-1.5-.189m3.75 7.478a12.06 12.06 0 0 1-4.5 0m3.75 2.383a14.406 14.406 0 0 1-3 0M14.25 18v-.192c0-.983.658-1.823 1.508-2.316a7.5 7.5 0 1 0-7.517 0c.85.493 1.509 1.333 1.509 2.316V18",
44
+ },
45
+ ],
46
+ },
47
+ info: {
48
+ svg: {
49
+ xmlns: "http://www.w3.org/2000/svg",
50
+ fill: "none",
51
+ viewBox: "0 0 24 24",
52
+ strokeWidth: "1.5",
53
+ stroke: "currentColor",
54
+ },
55
+ paths: [
56
+ {
57
+ strokeLinecap: "round",
58
+ strokeLinejoin: "round",
59
+ d: "m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z",
60
+ },
61
+ ],
62
+ },
63
+ warning: {
64
+ svg: {
65
+ xmlns: "http://www.w3.org/2000/svg",
66
+ fill: "none",
67
+ viewBox: "0 0 24 24",
68
+ strokeWidth: "1.5",
69
+ stroke: "currentColor",
70
+ },
71
+ paths: [
72
+ {
73
+ strokeLinecap: "round",
74
+ strokeLinejoin: "round",
75
+ d: "M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z",
76
+ },
77
+ ],
78
+ },
79
+ danger: {
80
+ svg: {
81
+ xmlns: "http://www.w3.org/2000/svg",
82
+ fill: "none",
83
+ viewBox: "0 0 24 24",
84
+ strokeWidth: "1.5",
85
+ stroke: "currentColor",
86
+ },
87
+ paths: [
88
+ {
89
+ strokeLinecap: "round",
90
+ strokeLinejoin: "round",
91
+ d: "M15.362 5.214A8.252 8.252 0 0 1 12 21 8.25 8.25 0 0 1 6.038 7.047 8.287 8.287 0 0 0 9 9.601a8.983 8.983 0 0 1 3.361-6.867 8.21 8.21 0 0 0 3 2.48Z",
92
+ },
93
+ {
94
+ strokeLinecap: "round",
95
+ strokeLinejoin: "round",
96
+ d: "M12 18a3.75 3.75 0 0 0 .495-7.468 5.99 5.99 0 0 0-1.925 3.547 5.975 5.975 0 0 1-2.133-1.001A3.75 3.75 0 0 0 12 18Z",
97
+ },
98
+ ],
99
+ },
100
+ };
101
+
102
+ import { directiveFromMarkdown } from "npm:mdast-util-directive";
103
+ import { directive } from "npm:micromark-extension-directive";
104
+
105
+ // https://github.com/remarkjs/remark-directive/blob/23b8f416da165b6ddaa4c5e7e82addaf6dcb96a9/lib/index.js
106
+ function remarkDirective() {
107
+ const self = this;
108
+ const data = self.data();
109
+
110
+ const micromarkExtensions =
111
+ data.micromarkExtensions || (data.micromarkExtensions = []);
112
+ const fromMarkdownExtensions =
113
+ data.fromMarkdownExtensions || (data.fromMarkdownExtensions = []);
114
+
115
+ const directiveContainer = directive();
116
+ // From:
117
+ // {
118
+ // text: {[codes.colon]: directiveText},
119
+ // flow: {[codes.colon]: [directiveContainer, directiveLeaf]}
120
+ // }
121
+ // To:
122
+ // {
123
+ // text: {},
124
+ // flow: {[codes.colon]: [directiveContainer]}
125
+ // }
126
+ directiveContainer.text = {};
127
+ directiveContainer.flow[Object.keys(directiveContainer.flow)[0]].pop();
128
+
129
+ micromarkExtensions.push(directiveContainer);
130
+ fromMarkdownExtensions.push(directiveFromMarkdown());
131
+ }
132
+
133
+ import { h, s } from "npm:hastscript";
134
+ import { visit } from "npm:unist-util-visit";
135
+
136
+ function remarkAdmonitions() {
137
+ return (tree) => {
138
+ visit(tree, (node) => {
139
+ if (node.type === "containerDirective") {
140
+ const level = node.name;
141
+ const admonition = ADMONITIONS[level];
142
+ if (!admonition) return;
143
+
144
+ const svg = s(
145
+ "svg.admonition__icon",
146
+ admonition.svg,
147
+ admonition.paths.map((path) => s("path", path))
148
+ );
149
+
150
+ const label = h("span.admonition__label", level.toLocaleUpperCase());
151
+ const header = h("div", { class: "admonition__header" }, [svg, label]);
152
+ const content = h("div", { class: "admonition__content" }, [
153
+ ...node.children,
154
+ ]);
155
+
156
+ node.tagName = "div";
157
+ node.properties = h("div", {
158
+ class: `admonition admonition--${level}`,
159
+ }).properties;
160
+ node.children = [header, content];
161
+
162
+ const decorateHast = (node) => {
163
+ Object.assign(node.data ?? (node.data = {}), {
164
+ hName: node.tagName,
165
+ hProperties: node.properties,
166
+ });
167
+
168
+ if (node.children && Array.isArray(node.children)) {
169
+ node.children.forEach(decorateHast);
170
+ }
171
+ };
172
+
173
+ decorateHast(node);
174
+ }
175
+ });
176
+ };
177
+ }
@@ -0,0 +1,42 @@
1
+ require "open3"
2
+
3
+ class MarkdownHandler
4
+ def self.prepare(engine)
5
+ # Install npm packages
6
+ Open3.capture3("deno --allow-read --allow-env --node-modules-dir=auto #{engine.root.join("lib/hotdocs/markdown.mjs")}", stdin_data: "")
7
+ rescue
8
+ Rails.logger.info("deno not found: Could not install npm packages.")
9
+ end
10
+
11
+ def initialize(engine)
12
+ @engine = engine
13
+ end
14
+
15
+ def call(template, source)
16
+ compiled = ::HotdocsController.render(inline: source, handler: :erb)
17
+ # `capture3` raises if deno is not available
18
+ out, err, status = Open3.capture3("deno --allow-read --allow-env --node-modules-dir=auto #{@engine.root.join("lib/hotdocs/markdown.mjs")}", stdin_data: compiled)
19
+ Rails.logger.error("Failed to compile markdown: #{err}") unless status.success?
20
+
21
+ if !err.empty? && !err.include?("The following packages are deprecated")
22
+ # Render the compiled erb (without the md step).
23
+ # It won't look great, but better than nothing.
24
+ return <<~STRING
25
+ @output_buffer.safe_append='#{compiled}'.freeze;
26
+ @output_buffer
27
+ STRING
28
+ end
29
+
30
+ content_fors = ActionView::Template::Handlers::ERB
31
+ .new
32
+ .call(template, source)
33
+ .split(";")
34
+ .grep(/content_for.*\(.*:/)
35
+
36
+ <<~STRING
37
+ #{content_fors.join(";")}
38
+ @output_buffer.safe_append='#{out}'.freeze;
39
+ @output_buffer
40
+ STRING
41
+ end
42
+ end
@@ -0,0 +1,3 @@
1
+ module Hotdocs
2
+ VERSION = "0.1.0"
3
+ end
data/lib/hotdocs.rb ADDED
@@ -0,0 +1,5 @@
1
+ require "hotdocs/version"
2
+ require "hotdocs/engine"
3
+
4
+ module Hotdocs
5
+ end
@@ -0,0 +1,287 @@
1
+ def source_paths # used by copy() & template()
2
+ [ File.expand_path("../..", __dir__) ]
3
+ end
4
+
5
+ def gem?(name)
6
+ gemfile_path = Pathname(destination_root).join("Gemfile")
7
+
8
+ regex = /gem ["']#{name}["']/
9
+ if File.readlines(gemfile_path).grep(regex).any?
10
+ say "#{name} already installed"
11
+ false
12
+ else
13
+ run("bundle add #{name}") || abort("Failed to add #{name} to the bundle")
14
+ true
15
+ end
16
+ end
17
+
18
+ unless system("command -v deno > /dev/null 2>&1")
19
+ abort "Install deno before running this task. Read more: https://deno.com"
20
+ end
21
+
22
+ if File.exist?("app/assets/config/manifest.js")
23
+ abort "Migrate to Propshaft before running this task. Read more: https://github.com/rails/propshaft/blob/main/UPGRADING.md#3-migrate-from-sprockets-to-propshaft"
24
+ end
25
+
26
+ gem?("importmap-rails") && run("bin/rails importmap:install")
27
+ gem?("turbo-rails") && run("bin/rails turbo:install")
28
+ gem?("stimulus-rails") && run("bin/rails stimulus:install")
29
+
30
+ generate(:controller, "hotdocs", "index", "--skip-routes --no-helper --no-test-framework --no-view-specs")
31
+ remove_file(Pathname(destination_root).join("app/views/hotdocs/index.html.erb"))
32
+ inject_into_class(Pathname(destination_root).join("app/controllers/hotdocs_controller.rb"), "HotdocsController", <<-LINES)
33
+ helper Hotdocs::Engine.helpers
34
+ layout "hotdocs"
35
+
36
+ LINES
37
+
38
+ create_file(Pathname(destination_root).join("app/views/layouts/hotdocs.html.erb"), <<~LAYOUT)
39
+ <% content_for :head do %>
40
+ <%= content_for(:title, "HotDocs") unless content_for?(:title) %>
41
+ <meta name="viewport" content="width=device-width,initial-scale=1">
42
+ <%= stylesheet_link_tag :app %>
43
+ <%= javascript_importmap_tags %>
44
+ <% end %>
45
+
46
+ <%= render template: "layouts/hotdocs/application" %>
47
+ LAYOUT
48
+
49
+ create_file(Pathname(destination_root).join("app/views/hotdocs/index.html.mderb"), <<~VIEW)
50
+ <%= content_for(:title, "Welcome") %>
51
+
52
+ # Welcome to HotDocs
53
+
54
+ Find this markdown in `app/views/hotdocs/index.html.mderb`.
55
+
56
+ ## Todos
57
+
58
+ <input type="checkbox" id="first">
59
+ <label for="first"> Update <code>app/views/layouts/hotdocs.html.erb</code></label><br>
60
+ <input type="checkbox" id="second">
61
+ <label for="second"> Update <code>app/helpers/hotdocs_helper.rb</code></label><br>
62
+ <input type="checkbox" id="third">
63
+ <label for="third"> Maybe read the docs: <a href="https://hotdocsrails.com/" target="_blank">hotdocsrails.com</a></label>
64
+ VIEW
65
+
66
+ copy_file("app/assets/images/hotdocs/icon.svg", Pathname(destination_root).join("app/assets/images/hotdocs.svg"))
67
+
68
+ inject_into_module(Pathname(destination_root).join("app/helpers/application_helper.rb"), "ApplicationHelper", " include HotdocsHelper\n\n")
69
+ create_file(Pathname(destination_root).join("app/helpers/hotdocs_helper.rb"), <<~HELPER)
70
+ module HotdocsHelper
71
+ def logo
72
+ Struct.new(:src, :alt).new(asset_path("hotdocs.svg"), "A humanized and happy hot dog")
73
+ end
74
+
75
+ def nav_left_items(classes)
76
+ [
77
+ active_link_to("Docs", root_path, class: Array(classes))
78
+ ]
79
+ end
80
+
81
+ def nav_right_items(classes)
82
+ [
83
+ external_link_to("GitHub", "https://github.com/3v0k4/hotdocs", class: Array(classes))
84
+ ]
85
+ end
86
+
87
+ # { label: "", url: *_path, children: [], expanded: false/true }
88
+ def menu_items
89
+ [
90
+ { label: "Welcome", url: root_path },
91
+ ]
92
+ end
93
+
94
+ def repository_base_url
95
+ "https://github.com/3v0k4/hotdocs/blob/main"
96
+ end
97
+
98
+ def footer_items
99
+ [
100
+ {
101
+ heading: "Contribute",
102
+ items: [
103
+ external_link_to("Source Code", "https://github.com/3v0k4/hotdocs", class: "footer__link")
104
+ ]
105
+ },
106
+ {
107
+ heading: "Community",
108
+ items: [
109
+ external_link_to("GitHub Discussions", "https://github.com/3v0k4/hotdocs/discussions", class: "footer__link")
110
+ ]
111
+ },
112
+ {
113
+ heading: "(il)Legal",
114
+ items: [
115
+ "Nothing to see here."
116
+ ]
117
+ },
118
+ {
119
+ heading: "HotDocs",
120
+ items: [
121
+ "Write your docs with Ruby on Rails."
122
+ ]
123
+ }
124
+ ]
125
+ end
126
+ end
127
+ HELPER
128
+
129
+ create_file(Pathname(destination_root).join("app/assets/stylesheets/prism.css"), <<~CSS)
130
+ code[class*="language-"],
131
+ pre[class*="language-"] {
132
+ color: #a9b7c6;
133
+ font-family: Consolas, Monaco, 'Andale Mono', monospace;
134
+ direction: ltr;
135
+ text-align: left;
136
+ white-space: pre;
137
+ word-spacing: normal;
138
+ word-break: normal;
139
+ line-height: 1.5;
140
+
141
+ -moz-tab-size: 4;
142
+ -o-tab-size: 4;
143
+ tab-size: 4;
144
+
145
+ -webkit-hyphens: none;
146
+ -moz-hyphens: none;
147
+ -ms-hyphens: none;
148
+ hyphens: none;
149
+ }
150
+
151
+ pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
152
+ code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
153
+ color: inherit;
154
+ background: rgba(33, 66, 131, .85);
155
+ }
156
+
157
+ pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
158
+ code[class*="language-"]::selection, code[class*="language-"] ::selection {
159
+ color: inherit;
160
+ background: rgba(33, 66, 131, .85);
161
+ }
162
+
163
+ /* Code blocks */
164
+ pre[class*="language-"] {
165
+ padding: 1em;
166
+ margin: .5em 0;
167
+ overflow: auto;
168
+ }
169
+
170
+ :not(pre) > code[class*="language-"],
171
+ pre[class*="language-"] {
172
+ background: #2b2b2b;
173
+ }
174
+
175
+ /* Inline code */
176
+ :not(pre) > code[class*="language-"] {
177
+ padding: .1em;
178
+ border-radius: .3em;
179
+ }
180
+
181
+ .token.comment,
182
+ .token.prolog,
183
+ .token.cdata {
184
+ color: #808080;
185
+ }
186
+
187
+ .token.delimiter,
188
+ .token.boolean,
189
+ .token.keyword,
190
+ .token.selector,
191
+ .token.important,
192
+ .token.atrule {
193
+ color: #cc7832;
194
+ }
195
+
196
+ .token.operator,
197
+ .token.punctuation,
198
+ .token.attr-name {
199
+ color: #a9b7c6;
200
+ }
201
+
202
+ .token.tag,
203
+ .token.tag .punctuation,
204
+ .token.doctype,
205
+ .token.builtin {
206
+ color: #e8bf6a;
207
+ }
208
+
209
+ .token.entity,
210
+ .token.number,
211
+ .token.symbol {
212
+ color: #6897bb;
213
+ }
214
+
215
+ .token.property,
216
+ .token.constant,
217
+ .token.variable {
218
+ color: #9876aa;
219
+ }
220
+
221
+ .token.string,
222
+ .token.char {
223
+ color: #6a8759;
224
+ }
225
+
226
+ .token.attr-value,
227
+ .token.attr-value .punctuation {
228
+ color: #a5c261;
229
+ }
230
+
231
+ .token.attr-value .punctuation:first-child {
232
+ color: #a9b7c6;
233
+ }
234
+
235
+ .token.url {
236
+ color: #287bde;
237
+ text-decoration: underline;
238
+ }
239
+
240
+ .token.function {
241
+ color: #ffc66d;
242
+ }
243
+
244
+ .token.regex {
245
+ background: #364135;
246
+ }
247
+
248
+ .token.bold {
249
+ font-weight: bold;
250
+ }
251
+
252
+ .token.italic {
253
+ font-style: italic;
254
+ }
255
+
256
+ .token.inserted {
257
+ background: #294436;
258
+ }
259
+
260
+ .token.deleted {
261
+ background: #484a4a;
262
+ }
263
+
264
+ code.language-css .token.property,
265
+ code.language-css .token.property + .token.punctuation {
266
+ color: #a9b7c6;
267
+ }
268
+
269
+ code.language-css .token.id {
270
+ color: #ffc66d;
271
+ }
272
+
273
+ code.language-css .token.selector > .token.class,
274
+ code.language-css .token.selector > .token.attribute,
275
+ code.language-css .token.selector > .token.pseudo-class,
276
+ code.language-css .token.selector > .token.pseudo-element {
277
+ color: #ffc66d;
278
+ }
279
+ CSS
280
+
281
+ routes_path = Pathname(destination_root).join("config/routes.rb")
282
+ regex = /^\s*(?!#)root/
283
+ if File.readlines(routes_path).grep(regex).any?
284
+ route "get '/hotdocs', to: 'hotdocs#index'"
285
+ else
286
+ route "root to: 'hotdocs#index'"
287
+ end
@@ -0,0 +1,9 @@
1
+ namespace :hotdocs do
2
+ desc "Install HotDocs into the app"
3
+ task :install do
4
+ previous_location = ENV["LOCATION"]
5
+ ENV["LOCATION"] = File.expand_path("../install/install.rb", __dir__)
6
+ Rake::Task["app:template"].invoke
7
+ ENV["LOCATION"] = previous_location
8
+ end
9
+ end
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hotdocs
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - 3v0k4
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2025-03-20 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rails
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: 7.1.0
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: 7.1.0
26
+ description: HotDocs is a set of optimized Rails components & tools for writing docs.
27
+ email:
28
+ - riccardo.odone@gmail.com
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - LICENSE
34
+ - LICENSE-LGPL
35
+ - README.md
36
+ - Rakefile
37
+ - app/assets/images/hotdocs/icon.svg
38
+ - app/assets/javascript/controllers/accordion_controller.js
39
+ - app/assets/javascript/controllers/sidenav_controller.js
40
+ - app/assets/javascript/controllers/toc_controller.js
41
+ - app/assets/stylesheets/hotdocs/application.css
42
+ - app/assets/stylesheets/hotdocs/reset.css
43
+ - app/helpers/hotdocs/application_helper.rb
44
+ - app/views/hotdocs/_menu_row.html.erb
45
+ - app/views/layouts/hotdocs/application.html.erb
46
+ - config/importmap.rb
47
+ - lib/hotdocs.rb
48
+ - lib/hotdocs/engine.rb
49
+ - lib/hotdocs/markdown.mjs
50
+ - lib/hotdocs/markdown.rb
51
+ - lib/hotdocs/version.rb
52
+ - lib/install/install.rb
53
+ - lib/tasks/hotdocs_tasks.rake
54
+ homepage: https://hotdocsrails.com/
55
+ licenses:
56
+ - LGPL-3.0
57
+ - Commercial
58
+ metadata:
59
+ homepage_uri: https://hotdocsrails.com/
60
+ source_code_uri: https://github.com/3v0k4/hotdocs
61
+ changelog_uri: https://github.com/3v0k4/hotdocs/blob/main/CHANGELOG.md
62
+ rubygems_mfa_required: 'true'
63
+ rdoc_options: []
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: 3.1.0
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ requirements: []
77
+ rubygems_version: 3.6.2
78
+ specification_version: 4
79
+ summary: Write your docs with Ruby on Rails
80
+ test_files: []