docyard 0.5.0 → 0.7.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.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/CHANGELOG.md +34 -1
  4. data/lib/docyard/build/static_generator.rb +3 -44
  5. data/lib/docyard/builder.rb +14 -4
  6. data/lib/docyard/cli.rb +6 -3
  7. data/lib/docyard/components/aliases.rb +29 -0
  8. data/lib/docyard/components/base_processor.rb +6 -0
  9. data/lib/docyard/components/processors/callout_processor.rb +124 -0
  10. data/lib/docyard/components/processors/code_block_diff_preprocessor.rb +106 -0
  11. data/lib/docyard/components/processors/code_block_focus_preprocessor.rb +79 -0
  12. data/lib/docyard/components/processors/code_block_options_preprocessor.rb +78 -0
  13. data/lib/docyard/components/processors/code_block_processor.rb +175 -0
  14. data/lib/docyard/components/processors/code_snippet_import_preprocessor.rb +127 -0
  15. data/lib/docyard/components/processors/heading_anchor_processor.rb +39 -0
  16. data/lib/docyard/components/processors/icon_processor.rb +53 -0
  17. data/lib/docyard/components/processors/table_of_contents_processor.rb +68 -0
  18. data/lib/docyard/components/processors/table_wrapper_processor.rb +22 -0
  19. data/lib/docyard/components/processors/tabs_processor.rb +48 -0
  20. data/lib/docyard/components/registry.rb +4 -4
  21. data/lib/docyard/components/support/code_block/feature_extractor.rb +117 -0
  22. data/lib/docyard/components/support/code_block/icon_detector.rb +44 -0
  23. data/lib/docyard/components/support/code_block/line_parser.rb +84 -0
  24. data/lib/docyard/components/support/code_block/line_wrapper.rb +50 -0
  25. data/lib/docyard/components/support/code_block/patterns.rb +55 -0
  26. data/lib/docyard/components/support/code_detector.rb +61 -0
  27. data/lib/docyard/components/support/tabs/icon_detector.rb +62 -0
  28. data/lib/docyard/components/support/tabs/parser.rb +195 -0
  29. data/lib/docyard/components/support/tabs/range_finder.rb +46 -0
  30. data/lib/docyard/config/branding_resolver.rb +74 -0
  31. data/lib/docyard/{constants.rb → config/constants.rb} +1 -0
  32. data/lib/docyard/config/validator.rb +8 -0
  33. data/lib/docyard/config.rb +17 -1
  34. data/lib/docyard/{prev_next_builder.rb → navigation/prev_next_builder.rb} +2 -2
  35. data/lib/docyard/{sidebar → navigation/sidebar}/renderer.rb +3 -14
  36. data/lib/docyard/{sidebar → navigation/sidebar}/tree_builder.rb +9 -2
  37. data/lib/docyard/{sidebar_builder.rb → navigation/sidebar_builder.rb} +3 -15
  38. data/lib/docyard/{icons → rendering/icons}/file_types.rb +0 -13
  39. data/lib/docyard/{icons → rendering/icons}/phosphor.rb +4 -1
  40. data/lib/docyard/{markdown.rb → rendering/markdown.rb} +23 -14
  41. data/lib/docyard/{renderer.rb → rendering/renderer.rb} +24 -20
  42. data/lib/docyard/search/build_indexer.rb +74 -0
  43. data/lib/docyard/search/dev_indexer.rb +110 -0
  44. data/lib/docyard/search/pagefind_support.rb +31 -0
  45. data/lib/docyard/{asset_handler.rb → server/asset_handler.rb} +1 -1
  46. data/lib/docyard/{server.rb → server/dev_server.rb} +32 -9
  47. data/lib/docyard/{preview_server.rb → server/preview_server.rb} +1 -1
  48. data/lib/docyard/{rack_application.rb → server/rack_application.rb} +53 -50
  49. data/lib/docyard/server/resolution_result.rb +29 -0
  50. data/lib/docyard/{router.rb → server/router.rb} +4 -4
  51. data/lib/docyard/templates/assets/css/code.css +12 -4
  52. data/lib/docyard/templates/assets/css/components/code-block.css +427 -24
  53. data/lib/docyard/templates/assets/css/components/navigation.css +12 -9
  54. data/lib/docyard/templates/assets/css/components/search.css +549 -0
  55. data/lib/docyard/templates/assets/css/components/tabs.css +50 -44
  56. data/lib/docyard/templates/assets/css/layout.css +15 -1
  57. data/lib/docyard/templates/assets/css/variables.css +44 -0
  58. data/lib/docyard/templates/assets/js/components/search.js +685 -0
  59. data/lib/docyard/templates/layouts/default.html.erb +14 -2
  60. data/lib/docyard/templates/partials/_code_block.html.erb +50 -2
  61. data/lib/docyard/templates/partials/_heading_anchor.html.erb +1 -1
  62. data/lib/docyard/templates/partials/_prev_next.html.erb +1 -1
  63. data/lib/docyard/templates/partials/_search_modal.html.erb +45 -0
  64. data/lib/docyard/templates/partials/_search_trigger.html.erb +22 -0
  65. data/lib/docyard/utils/html_helpers.rb +14 -0
  66. data/lib/docyard/utils/path_resolver.rb +2 -1
  67. data/lib/docyard/utils/url_helpers.rb +20 -0
  68. data/lib/docyard/version.rb +1 -1
  69. data/lib/docyard.rb +22 -15
  70. metadata +57 -36
  71. data/lib/docyard/components/callout_processor.rb +0 -121
  72. data/lib/docyard/components/code_block_processor.rb +0 -55
  73. data/lib/docyard/components/code_detector.rb +0 -59
  74. data/lib/docyard/components/heading_anchor_processor.rb +0 -34
  75. data/lib/docyard/components/icon_detector.rb +0 -57
  76. data/lib/docyard/components/icon_processor.rb +0 -51
  77. data/lib/docyard/components/table_of_contents_processor.rb +0 -64
  78. data/lib/docyard/components/table_wrapper_processor.rb +0 -18
  79. data/lib/docyard/components/tabs_parser.rb +0 -60
  80. data/lib/docyard/components/tabs_processor.rb +0 -44
  81. data/lib/docyard/routing/resolution_result.rb +0 -31
  82. /data/lib/docyard/{sidebar → navigation/sidebar}/config_parser.rb +0 -0
  83. /data/lib/docyard/{sidebar → navigation/sidebar}/file_system_scanner.rb +0 -0
  84. /data/lib/docyard/{sidebar → navigation/sidebar}/item.rb +0 -0
  85. /data/lib/docyard/{sidebar → navigation/sidebar}/title_extractor.rb +0 -0
  86. /data/lib/docyard/{icons → rendering/icons}/LICENSE.phosphor +0 -0
  87. /data/lib/docyard/{icons.rb → rendering/icons.rb} +0 -0
  88. /data/lib/docyard/{language_mapping.rb → rendering/language_mapping.rb} +0 -0
  89. /data/lib/docyard/{file_watcher.rb → server/file_watcher.rb} +0 -0
  90. /data/lib/docyard/{errors.rb → utils/errors.rb} +0 -0
  91. /data/lib/docyard/{logging.rb → utils/logging.rb} +0 -0
@@ -1,59 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "../language_mapping"
4
-
5
- module Docyard
6
- module Components
7
- class CodeDetector
8
- def self.detect(content)
9
- new(content).detect
10
- end
11
-
12
- def initialize(content)
13
- @content = content
14
- end
15
-
16
- def detect
17
- return nil unless code_only?
18
-
19
- language = extract_language
20
- return nil unless language
21
-
22
- icon_for_language(language)
23
- end
24
-
25
- private
26
-
27
- attr_reader :content
28
-
29
- def code_only?
30
- stripped = content.strip
31
- return false unless stripped.start_with?("```") && stripped.end_with?("```")
32
-
33
- parts = stripped.split("```")
34
- parts.length == 2 && parts[0].empty?
35
- end
36
-
37
- def extract_language
38
- parts = content.strip.split("```")
39
- return nil unless parts[1]
40
-
41
- lines = parts[1].split("\n", 2)
42
- lang_line = lines[0].strip
43
- return nil if lang_line.empty? || lang_line.include?(" ")
44
-
45
- lang_line.downcase
46
- end
47
-
48
- def icon_for_language(language)
49
- if LanguageMapping.terminal_language?(language)
50
- { icon: "terminal-window", source: "phosphor" }
51
- elsif (extension = LanguageMapping.extension_for(language))
52
- { icon: extension, source: "file-extension" }
53
- else
54
- { icon: "file", source: "phosphor" }
55
- end
56
- end
57
- end
58
- end
59
- end
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Docyard
4
- module Components
5
- class HeadingAnchorProcessor < BaseProcessor
6
- self.priority = 30
7
-
8
- def postprocess(html)
9
- add_anchor_links(html)
10
- end
11
-
12
- private
13
-
14
- def add_anchor_links(html)
15
- html.gsub(%r{<(h[2-6])\s+id="([^"]+)">(.*?)</\1>}m) do |_match|
16
- tag = Regexp.last_match(1)
17
- id = Regexp.last_match(2)
18
- content = Regexp.last_match(3)
19
-
20
- anchor_html = render_anchor_link(id)
21
-
22
- "<#{tag} id=\"#{id}\">#{content}#{anchor_html}</#{tag}>"
23
- end
24
- end
25
-
26
- def render_anchor_link(id)
27
- renderer = Renderer.new
28
- renderer.render_partial("_heading_anchor", {
29
- id: id
30
- })
31
- end
32
- end
33
- end
34
- end
@@ -1,57 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "code_detector"
4
-
5
- module Docyard
6
- module Components
7
- class IconDetector
8
- MANUAL_ICON_PATTERN = /^:([a-z0-9-]+):\s*(.+)$/i
9
-
10
- def self.detect(tab_name, tab_content)
11
- new(tab_name, tab_content).detect
12
- end
13
-
14
- def initialize(tab_name, tab_content)
15
- @tab_name = tab_name
16
- @tab_content = tab_content
17
- end
18
-
19
- def detect
20
- manual_icon || auto_detected_icon || no_icon
21
- end
22
-
23
- private
24
-
25
- attr_reader :tab_name, :tab_content
26
-
27
- def manual_icon
28
- return nil unless tab_name.match(MANUAL_ICON_PATTERN)
29
-
30
- {
31
- name: Regexp.last_match(2).strip,
32
- icon: Regexp.last_match(1),
33
- icon_source: "phosphor"
34
- }
35
- end
36
-
37
- def auto_detected_icon
38
- detected = CodeDetector.detect(tab_content)
39
- return nil unless detected
40
-
41
- {
42
- name: tab_name,
43
- icon: detected[:icon],
44
- icon_source: detected[:source]
45
- }
46
- end
47
-
48
- def no_icon
49
- {
50
- name: tab_name,
51
- icon: nil,
52
- icon_source: nil
53
- }
54
- end
55
- end
56
- end
57
- end
@@ -1,51 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "../icons"
4
- require_relative "base_processor"
5
-
6
- module Docyard
7
- module Components
8
- class IconProcessor < BaseProcessor
9
- self.priority = 20
10
-
11
- ICON_PATTERN = /:([a-z][a-z0-9-]*):(?:([a-z]+):)?/i
12
-
13
- def postprocess(html)
14
- segments = split_preserving_code_blocks(html)
15
-
16
- segments.map do |segment|
17
- segment[:type] == :code ? segment[:content] : process_segment(segment[:content])
18
- end.join
19
- end
20
-
21
- private
22
-
23
- def split_preserving_code_blocks(html)
24
- segments = []
25
- current_pos = 0
26
-
27
- html.scan(%r{<(code|pre)[^>]*>.*?</\1>}m) do
28
- match_start = Regexp.last_match.begin(0)
29
- match_end = Regexp.last_match.end(0)
30
-
31
- segments << { type: :text, content: html[current_pos...match_start] } if match_start > current_pos
32
- segments << { type: :code, content: html[match_start...match_end] }
33
-
34
- current_pos = match_end
35
- end
36
-
37
- segments << { type: :text, content: html[current_pos..] } if current_pos < html.length
38
-
39
- segments.empty? ? [{ type: :text, content: html }] : segments
40
- end
41
-
42
- def process_segment(content)
43
- content.gsub(ICON_PATTERN) do
44
- icon_name = Regexp.last_match(1)
45
- weight = Regexp.last_match(2) || "regular"
46
- Icons.render(icon_name, weight) || Regexp.last_match(0)
47
- end
48
- end
49
- end
50
- end
51
- end
@@ -1,64 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Docyard
4
- module Components
5
- class TableOfContentsProcessor < BaseProcessor
6
- self.priority = 35
7
-
8
- def postprocess(html)
9
- headings = extract_headings(html)
10
- Thread.current[:docyard_toc] = headings
11
- html
12
- end
13
-
14
- private
15
-
16
- def extract_headings(html)
17
- headings = []
18
-
19
- html.scan(%r{<(h[2-4])\s+id="([^"]+)">(.*?)</\1>}m) do
20
- level = Regexp.last_match(1)[1].to_i
21
- id = Regexp.last_match(2)
22
- text = strip_html(Regexp.last_match(3))
23
-
24
- headings << {
25
- level: level,
26
- id: id,
27
- text: text
28
- }
29
- end
30
-
31
- build_hierarchy(headings)
32
- end
33
-
34
- def build_hierarchy(headings)
35
- return [] if headings.empty?
36
-
37
- root = []
38
- stack = []
39
-
40
- headings.each do |heading|
41
- heading[:children] = []
42
-
43
- stack.pop while stack.any? && stack.last[:level] >= heading[:level]
44
-
45
- if stack.empty?
46
- root << heading
47
- else
48
- stack.last[:children] << heading
49
- end
50
-
51
- stack << heading
52
- end
53
-
54
- root
55
- end
56
-
57
- def strip_html(text)
58
- text.gsub(%r{<a[^>]*class="heading-anchor"[^>]*>.*?</a>}, "")
59
- .gsub(/<[^>]+>/, "")
60
- .strip
61
- end
62
- end
63
- end
64
- end
@@ -1,18 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Docyard
4
- module Components
5
- class TableWrapperProcessor < BaseProcessor
6
- self.priority = 100
7
-
8
- def postprocess(html)
9
- wrapped = html.gsub(/<table([^>]*)>/) do
10
- attributes = Regexp.last_match(1)
11
- "<div class=\"table-wrapper\"><table#{attributes}>"
12
- end
13
-
14
- wrapped.gsub("</table>", "</table></div>")
15
- end
16
- end
17
- end
18
- end
@@ -1,60 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "icon_detector"
4
- require "kramdown"
5
- require "kramdown-parser-gfm"
6
-
7
- module Docyard
8
- module Components
9
- class TabsParser
10
- def self.parse(content)
11
- new(content).parse
12
- end
13
-
14
- def initialize(content)
15
- @content = content
16
- end
17
-
18
- def parse
19
- sections.filter_map { |section| parse_section(section) }
20
- end
21
-
22
- private
23
-
24
- attr_reader :content
25
-
26
- def sections
27
- content.split(/^==[ \t]+/)
28
- end
29
-
30
- def parse_section(section)
31
- return nil if section.strip.empty?
32
-
33
- parts = section.split("\n", 2)
34
- tab_name = parts[0]&.strip
35
- return nil if tab_name.nil? || tab_name.empty?
36
-
37
- tab_content = parts[1]&.strip || ""
38
- icon_data = IconDetector.detect(tab_name, tab_content)
39
-
40
- {
41
- name: icon_data[:name],
42
- content: render_markdown(tab_content),
43
- icon: icon_data[:icon],
44
- icon_source: icon_data[:icon_source]
45
- }
46
- end
47
-
48
- def render_markdown(markdown_content)
49
- return "" if markdown_content.empty?
50
-
51
- Kramdown::Document.new(
52
- markdown_content,
53
- input: "GFM",
54
- hard_wrap: false,
55
- syntax_highlighter: "rouge"
56
- ).to_html
57
- end
58
- end
59
- end
60
- end
@@ -1,44 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "../renderer"
4
- require_relative "base_processor"
5
- require_relative "tabs_parser"
6
- require "securerandom"
7
-
8
- module Docyard
9
- module Components
10
- class TabsProcessor < BaseProcessor
11
- self.priority = 15
12
-
13
- def preprocess(content)
14
- return content unless content.include?(":::tabs")
15
-
16
- content.gsub(/^:::[ \t]*tabs[ \t]*\n(.*?)^:::[ \t]*$/m) do
17
- process_tabs_block(Regexp.last_match(1))
18
- end
19
- end
20
-
21
- private
22
-
23
- def process_tabs_block(tabs_content)
24
- tabs = TabsParser.parse(tabs_content)
25
- return "" if tabs.empty?
26
-
27
- wrap_in_nomarkdown(render_tabs(tabs))
28
- end
29
-
30
- def render_tabs(tabs)
31
- Renderer.new.render_partial(
32
- "_tabs", {
33
- tabs: tabs,
34
- group_id: SecureRandom.hex(4)
35
- }
36
- )
37
- end
38
-
39
- def wrap_in_nomarkdown(html)
40
- "{::nomarkdown}\n#{html}\n{:/nomarkdown}"
41
- end
42
- end
43
- end
44
- end
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Docyard
4
- module Routing
5
- class ResolutionResult
6
- attr_reader :file_path, :status
7
-
8
- def self.found(file_path)
9
- new(file_path: file_path, status: :found)
10
- end
11
-
12
- def self.not_found
13
- new(file_path: nil, status: :not_found)
14
- end
15
-
16
- def initialize(file_path:, status:)
17
- @file_path = file_path
18
- @status = status
19
- freeze
20
- end
21
-
22
- def found?
23
- status == :found
24
- end
25
-
26
- def not_found?
27
- status == :not_found
28
- end
29
- end
30
- end
31
- end
File without changes
File without changes
File without changes