docyard 0.0.1 → 0.2.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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +31 -0
  3. data/.github/ISSUE_TEMPLATE/feature_request.md +19 -0
  4. data/.github/pull_request_template.md +14 -0
  5. data/.github/workflows/ci.yml +49 -0
  6. data/.rubocop.yml +38 -0
  7. data/CHANGELOG.md +52 -0
  8. data/CONTRIBUTING.md +55 -0
  9. data/README.md +174 -3
  10. data/lib/docyard/asset_handler.rb +64 -0
  11. data/lib/docyard/cli.rb +34 -0
  12. data/lib/docyard/constants.rb +23 -0
  13. data/lib/docyard/errors.rb +54 -0
  14. data/lib/docyard/file_watcher.rb +42 -0
  15. data/lib/docyard/initializer.rb +76 -0
  16. data/lib/docyard/logging.rb +43 -0
  17. data/lib/docyard/markdown.rb +61 -0
  18. data/lib/docyard/rack_application.rb +81 -0
  19. data/lib/docyard/renderer.rb +61 -0
  20. data/lib/docyard/router.rb +31 -0
  21. data/lib/docyard/routing/resolution_result.rb +31 -0
  22. data/lib/docyard/server.rb +91 -0
  23. data/lib/docyard/sidebar/file_system_scanner.rb +77 -0
  24. data/lib/docyard/sidebar/renderer.rb +110 -0
  25. data/lib/docyard/sidebar/title_extractor.rb +25 -0
  26. data/lib/docyard/sidebar/tree_builder.rb +59 -0
  27. data/lib/docyard/sidebar_builder.rb +50 -0
  28. data/lib/docyard/templates/assets/css/code.css +214 -0
  29. data/lib/docyard/templates/assets/css/components.css +258 -0
  30. data/lib/docyard/templates/assets/css/layout.css +273 -0
  31. data/lib/docyard/templates/assets/css/main.css +10 -0
  32. data/lib/docyard/templates/assets/css/markdown.css +199 -0
  33. data/lib/docyard/templates/assets/css/reset.css +59 -0
  34. data/lib/docyard/templates/assets/css/typography.css +97 -0
  35. data/lib/docyard/templates/assets/css/variables.css +114 -0
  36. data/lib/docyard/templates/assets/js/reload.js +98 -0
  37. data/lib/docyard/templates/assets/js/theme.js +193 -0
  38. data/lib/docyard/templates/errors/404.html.erb +16 -0
  39. data/lib/docyard/templates/errors/500.html.erb +25 -0
  40. data/lib/docyard/templates/layouts/default.html.erb +51 -0
  41. data/lib/docyard/templates/markdown/core-concepts/file-structure.md.erb +61 -0
  42. data/lib/docyard/templates/markdown/core-concepts/markdown.md.erb +90 -0
  43. data/lib/docyard/templates/markdown/getting-started/installation.md.erb +43 -0
  44. data/lib/docyard/templates/markdown/getting-started/introduction.md.erb +30 -0
  45. data/lib/docyard/templates/markdown/getting-started/quick-start.md.erb +56 -0
  46. data/lib/docyard/templates/markdown/index.md.erb +86 -0
  47. data/lib/docyard/templates/partials/_icons.html.erb +11 -0
  48. data/lib/docyard/templates/partials/_nav_group.html.erb +7 -0
  49. data/lib/docyard/templates/partials/_nav_item.html.erb +3 -0
  50. data/lib/docyard/templates/partials/_nav_leaf.html.erb +1 -0
  51. data/lib/docyard/templates/partials/_nav_list.html.erb +3 -0
  52. data/lib/docyard/templates/partials/_nav_section.html.erb +6 -0
  53. data/lib/docyard/templates/partials/_sidebar.html.erb +6 -0
  54. data/lib/docyard/templates/partials/_sidebar_footer.html.erb +11 -0
  55. data/lib/docyard/utils/path_resolver.rb +30 -0
  56. data/lib/docyard/utils/text_formatter.rb +22 -0
  57. data/lib/docyard/version.rb +1 -1
  58. data/lib/docyard.rb +20 -2
  59. metadata +169 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a189b17059ce7d65d2bf10e3f920f193a1441cc2505a67b2acb9b9e4e99f5d94
4
- data.tar.gz: f08c2fc0fb50a8e8c4f52bda72f14b122e196ad82b474977111292115e56c80b
3
+ metadata.gz: 0614d285d89d4f529607c5e4bdb50c823cce769a5628b90100234c9513f98690
4
+ data.tar.gz: 2f7c134bca20f17766e54fb4bc3b02b602e735cf4d8c7e11f83f9158773bec22
5
5
  SHA512:
6
- metadata.gz: c0dc6551e43449d96f38fff9c0bf444bd8b233013be3af84690e2bf0c7db7795b75e9af9c02425e003a2ca2704aa7177452b90faebcc4bf599392afca17a34cc
7
- data.tar.gz: 12ea961b5b7acb9da1ba15eaac76b7e62f5bbb96bf8039f83cfe4ec0a002cf641f25333eda7824c7e76a90cbda3fd5c87eafe3ed7b3477b93f8b29e6fc92f546
6
+ metadata.gz: 1259470e5245ff3ffc8796144a1e48fae46d851b44ca2c6cbeda0e8f83c782ebb815cfdfe0bdb3fa36fc25744a050859df44e5451376c8dadb4601ad9c3e2e98
7
+ data.tar.gz: 90281b4af9dd20425d26d6ea14214de5a79237ddeec9038546b9142819e363051850c64942a1b56459e5e8e1c509d08a36b323c2e01e3bacd4353fc39755915a
@@ -0,0 +1,31 @@
1
+ ---
2
+ name: Bug Report
3
+ about: Report a bug
4
+ title: ''
5
+ labels: bug
6
+ assignees: ''
7
+ ---
8
+
9
+ ## Description
10
+
11
+ <!-- Clear description of the bug -->
12
+
13
+ ## Steps to Reproduce
14
+
15
+ 1.
16
+ 2.
17
+ 3.
18
+
19
+ ## Expected Behavior
20
+
21
+ <!-- What should happen -->
22
+
23
+ ## Actual Behavior
24
+
25
+ <!-- What actually happens -->
26
+
27
+ ## Environment
28
+
29
+ - Ruby version: <!-- `ruby -v` -->
30
+ - Docyard version: <!-- `gem list docyard` -->
31
+ - OS: <!-- macOS, Linux, etc. -->
@@ -0,0 +1,19 @@
1
+ ---
2
+ name: Feature Request
3
+ about: Suggest a feature
4
+ title: ''
5
+ labels: enhancement
6
+ assignees: ''
7
+ ---
8
+
9
+ ## Description
10
+
11
+ <!-- What feature would you like? -->
12
+
13
+ ## Use Case
14
+
15
+ <!-- Why is this useful? When would you use it? -->
16
+
17
+ ## Implementation Ideas
18
+
19
+ <!-- Optional: How might this work? -->
@@ -0,0 +1,14 @@
1
+ ## Description
2
+
3
+ <!-- What does this PR do? -->
4
+
5
+ ## Changes
6
+
7
+ -
8
+ -
9
+
10
+ ## Checklist
11
+
12
+ - [ ] Tests added/updated
13
+ - [ ] CHANGELOG.md updated
14
+ - [ ] README.md updated (if needed)
@@ -0,0 +1,49 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ name: Ruby ${{ matrix.ruby }} - Test
12
+ runs-on: ubuntu-latest
13
+ strategy:
14
+ fail-fast: false
15
+ matrix:
16
+ ruby: ['3.2', '3.3', '3.4', 'head']
17
+ continue-on-error: ${{ matrix.ruby == 'head' }}
18
+
19
+ steps:
20
+ - name: Checkout code
21
+ uses: actions/checkout@v4
22
+
23
+ - name: Set up Ruby
24
+ uses: ruby/setup-ruby@v1
25
+ with:
26
+ ruby-version: ${{ matrix.ruby }}
27
+ bundler-cache: true
28
+
29
+ - name: Run tests
30
+ run: bundle exec rspec
31
+ env:
32
+ RUBYOPT: '-W0'
33
+
34
+ lint:
35
+ name: Rubocop
36
+ runs-on: ubuntu-latest
37
+
38
+ steps:
39
+ - name: Checkout code
40
+ uses: actions/checkout@v4
41
+
42
+ - name: Set up Ruby
43
+ uses: ruby/setup-ruby@v1
44
+ with:
45
+ ruby-version: '3.3'
46
+ bundler-cache: true
47
+
48
+ - name: Run rubocop
49
+ run: bundle exec rubocop
data/.rubocop.yml ADDED
@@ -0,0 +1,38 @@
1
+ plugins:
2
+ - rubocop-rspec
3
+ - rubocop-rake
4
+
5
+ AllCops:
6
+ NewCops: enable
7
+ TargetRubyVersion: 3.2
8
+ Exclude:
9
+ - 'vendor/**/*'
10
+ - 'spec/fixtures/**/*'
11
+ - 'tmp/**/*'
12
+
13
+ Metrics/BlockLength:
14
+ Exclude:
15
+ - 'spec/**/*'
16
+ - '*.gemspec'
17
+
18
+ RSpec/ExampleLength:
19
+ Max: 10
20
+
21
+ Metrics/ClassLength:
22
+ Max: 150
23
+
24
+ Metrics/MethodLength:
25
+ Max: 15
26
+
27
+ Style/Documentation:
28
+ Enabled: false
29
+
30
+ Layout/LineLength:
31
+ Max: 120
32
+
33
+ Style/StringLiterals:
34
+ Enabled: true
35
+ EnforcedStyle: double_quotes
36
+
37
+ Layout/MultilineMethodCallIndentation:
38
+ EnforcedStyle: indented
data/CHANGELOG.md ADDED
@@ -0,0 +1,52 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.2.0] - 2025-11-08
11
+
12
+ ### Added
13
+ - Automatic sidebar navigation with nested folders
14
+ - Collapsible sidebar sections with active page highlighting
15
+ - Sidebar scroll position persistence
16
+ - Modern responsive theme with mobile hamburger menu
17
+ - Structured logging system with configurable levels
18
+ - Custom error classes for better error handling
19
+ - Constants module for shared application values
20
+ - Utility modules for path resolution and text formatting
21
+ - Improved initial templates with nested docs structure
22
+
23
+ ### Changed
24
+ - Refactored sidebar rendering to use partial templates
25
+ - Modular CSS architecture (split into variables, reset, typography, components, layout)
26
+ - Enhanced router with routing resolution result pattern
27
+
28
+ ### Removed
29
+ - Legacy syntax.css in favor of modular code.css
30
+
31
+ ## [0.1.0] - 2025-11-04
32
+
33
+ ### Added
34
+ - Hot reload
35
+ - GitHub Flavored Markdown support (tables, task lists, strikethrough)
36
+ - Syntax highlighting for 100+ languages via Rouge
37
+ - YAML frontmatter for page metadata
38
+ - Customizable 404/500 error templates
39
+ - CLI commands: `docyard init`, `docyard serve`
40
+ - File watcher for live reload
41
+ - Directory traversal protection in asset handler
42
+
43
+ ## [0.0.1] - 2025-11-03
44
+
45
+ ### Added
46
+ - Initial gem structure
47
+ - Project scaffolding
48
+
49
+ [Unreleased]: https://github.com/sanifhimani/docyard/compare/v0.2.0...HEAD
50
+ [0.2.0]: https://github.com/sanifhimani/docyard/compare/v0.1.0...v0.2.0
51
+ [0.1.0]: https://github.com/sanifhimani/docyard/compare/v0.0.1...v0.1.0
52
+ [0.0.1]: https://github.com/sanifhimani/docyard/releases/tag/v0.0.1
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,55 @@
1
+ # Contributing
2
+
3
+ Thanks for wanting to contribute to Docyard!
4
+
5
+ ## Development Setup
6
+
7
+ ```bash
8
+ git clone https://github.com/YOUR_USERNAME/docyard.git
9
+ cd docyard
10
+ bundle install
11
+
12
+ # Run tests
13
+ bundle exec rspec
14
+ bundle exec rubocop
15
+
16
+ # Test locally
17
+ ./bin/docyard init
18
+ ./bin/docyard serve
19
+ ```
20
+
21
+ ## Pull Requests
22
+
23
+ 1. Fork the repo and create a branch from `main`
24
+ 2. Write tests for new features
25
+ 3. Run `bundle exec rspec && bundle exec rubocop`
26
+ 4. Update README/CHANGELOG if needed
27
+ 5. Use conventional commits: `feat:`, `fix:`, `refactor:`, `docs:`, `test:`
28
+
29
+ ## Architecture
30
+
31
+ ```
32
+ lib/docyard/
33
+ cli.rb # CLI
34
+ server.rb # Server lifecycle
35
+ rack_application.rb # HTTP handling
36
+ router.rb # URL → file mapping
37
+ renderer.rb # Markdown → HTML
38
+ markdown.rb # Parsing
39
+ file_watcher.rb # Live reload
40
+ asset_handler.rb # Static assets
41
+ ```
42
+
43
+ ## Code Style
44
+
45
+ - Maintain >85% test coverage
46
+ - Keep methods focused and readable
47
+ - Add tests for new features
48
+
49
+ ## Questions?
50
+
51
+ Open an issue.
52
+
53
+ ---
54
+
55
+ Thanks for contributing! 🚀
data/README.md CHANGED
@@ -1,14 +1,185 @@
1
1
  # Docyard
2
2
 
3
+ [![CI](https://github.com/sanifhimani/docyard/actions/workflows/ci.yml/badge.svg)](https://github.com/sanifhimani/docyard/actions/workflows/ci.yml)
4
+ [![Gem Version](https://badge.fury.io/rb/docyard.svg)](https://badge.fury.io/rb/docyard)
5
+
3
6
  > Documentation generator for Ruby
4
7
 
5
- ## What is this?
8
+ **Early development** - Core features work, but missing search and build command. See [roadmap](#roadmap).
9
+
10
+ ## Features
11
+
12
+ - **Sidebar navigation** - Automatic sidebar with nested folders and collapsible sections
13
+ - **Hot reload** - Changes appear instantly while you write
14
+ - **GitHub Flavored Markdown** - Tables, task lists, strikethrough
15
+ - **Syntax highlighting** - 100+ languages via Rouge
16
+ - **YAML frontmatter** - Add metadata to your pages
17
+ - **Customizable error pages** - Make 404/500 pages your own
18
+
19
+ ## Quick Start
20
+
21
+ ```bash
22
+ # Install the gem
23
+ gem install docyard
24
+
25
+ # Create a new docs project
26
+ mkdir my-docs && cd my-docs
27
+ docyard init
6
28
 
7
- Building a modern documentation generator for Ruby. Zero config, convention over configuration.
29
+ # Start the dev server
30
+ docyard serve
31
+
32
+ # Visit http://localhost:4200
33
+ ```
8
34
 
9
35
  ## Installation
10
36
 
11
- Not published yet. Building in public - follow along!
37
+ Add to your Gemfile:
38
+
39
+ ```ruby
40
+ gem 'docyard'
41
+ ```
42
+
43
+ Or install directly:
44
+
45
+ ```bash
46
+ gem install docyard
47
+ ```
48
+
49
+ ## Usage
50
+
51
+ ### Initialize a New Project
52
+
53
+ ```bash
54
+ docyard init
55
+ ```
56
+
57
+ This creates:
58
+ ```
59
+ docs/
60
+ index.md # Home page
61
+ getting-started/
62
+ introduction.md # Getting started guide
63
+ installation.md # Installation instructions
64
+ quick-start.md # Quick start guide
65
+ core-concepts/
66
+ file-structure.md # File structure guide
67
+ markdown.md # Markdown guide
68
+ ```
69
+
70
+ ### Start Development Server
71
+
72
+ ```bash
73
+ docyard serve
74
+
75
+ # Custom port and host
76
+ docyard serve --port 3000 --host 0.0.0.0
77
+ ```
78
+
79
+ ### Writing Docs
80
+
81
+ Create markdown files in the `docs/` directory:
82
+
83
+ ```markdown
84
+ ---
85
+ title: Getting Started
86
+ ---
87
+
88
+ # Getting Started
89
+
90
+ \`\`\`ruby
91
+ class User
92
+ def initialize(name)
93
+ @name = name
94
+ end
95
+ end
96
+ \`\`\`
97
+ ```
98
+
99
+ ### Frontmatter
100
+
101
+ Add YAML frontmatter to customize page metadata:
102
+
103
+ ```yaml
104
+ ---
105
+ title: My Page Title
106
+ description: Page description
107
+ ---
108
+ ```
109
+
110
+ Currently supported:
111
+ - `title` - Page title (shown in `<title>` tag)
112
+
113
+ ### Linking Between Pages
114
+
115
+ Write links with `.md` extension, they'll be automatically cleaned:
116
+
117
+ ```markdown
118
+ [Getting Started](./getting-started.md) → /getting-started
119
+ [Guide](./guide/index.md) → /guide
120
+ ```
121
+
122
+ ### Directory Structure
123
+
124
+ ```
125
+ docs/
126
+ index.md # / (root)
127
+ getting-started.md # /getting-started
128
+ guide/
129
+ index.md # /guide
130
+ setup.md # /guide/setup
131
+ advanced.md # /guide/advanced
132
+ ```
133
+
134
+ ## Architecture
135
+
136
+ Clean separation of concerns:
137
+
138
+ ```
139
+ lib/docyard/
140
+ cli.rb # Command-line interface (Thor)
141
+ initializer.rb # Project scaffolding (init command)
142
+ server.rb # Server lifecycle (WEBrick, signals)
143
+ rack_application.rb # HTTP request handling (routing, rendering)
144
+ router.rb # URL → file path mapping
145
+ renderer.rb # Markdown → HTML conversion
146
+ markdown.rb # Markdown parsing & frontmatter extraction
147
+ file_watcher.rb # Live reload file monitoring
148
+ asset_handler.rb # Static asset serving
149
+ ```
150
+
151
+ Each class has a single, clear responsibility. Easy to test, easy to extend.
152
+
153
+ ## Development
154
+
155
+ ```bash
156
+ git clone https://github.com/sanifhimani/docyard.git
157
+ cd docyard
158
+ bundle install
159
+
160
+ # Run tests
161
+ bundle exec rspec
162
+
163
+ # Run linter
164
+ bundle exec rubocop
165
+
166
+ # Use local version
167
+ ./bin/docyard init
168
+ ./bin/docyard serve
169
+ ```
170
+
171
+ ## Roadmap
172
+
173
+ **Next up:**
174
+ - Dark mode with theme toggle
175
+ - Markdown components (callouts, code groups, collapsible sections)
176
+ - Icon system
177
+ - Client-side search
178
+ - Static site generation (`docyard build`)
179
+
180
+ ## Contributing
181
+
182
+ See [CONTRIBUTING.md](CONTRIBUTING.md)
12
183
 
13
184
  ## License
14
185
 
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docyard
4
+ class AssetHandler
5
+ ASSETS_PATH = File.join(__dir__, "templates", "assets")
6
+
7
+ CONTENT_TYPES = {
8
+ ".css" => "text/css; charset=utf-8",
9
+ ".js" => "application/javascript; charset=utf-8",
10
+ ".png" => "image/png",
11
+ ".jpg" => "image/jpeg",
12
+ ".jpeg" => "image/jpeg",
13
+ ".svg" => "image/svg+xml",
14
+ ".woff" => "font/woff2",
15
+ ".woff2" => "font/woff2",
16
+ ".ico" => "image/x-icon"
17
+ }.freeze
18
+
19
+ def serve(request_path)
20
+ asset_path = extract_asset_path(request_path)
21
+
22
+ return forbidden_response if directory_traversal?(asset_path)
23
+
24
+ file_path = build_file_path(asset_path)
25
+ return not_found_response unless File.file?(file_path)
26
+
27
+ serve_file(file_path)
28
+ end
29
+
30
+ private
31
+
32
+ def extract_asset_path(request_path)
33
+ request_path.delete_prefix("/assets/")
34
+ end
35
+
36
+ def directory_traversal?(path)
37
+ path.include?("..")
38
+ end
39
+
40
+ def build_file_path(asset_path)
41
+ File.join(ASSETS_PATH, asset_path)
42
+ end
43
+
44
+ def serve_file(file_path)
45
+ content = File.read(file_path)
46
+ content_type = detect_content_type(file_path)
47
+
48
+ [200, { "Content-Type" => content_type }, [content]]
49
+ end
50
+
51
+ def detect_content_type(file_path)
52
+ extension = File.extname(file_path)
53
+ CONTENT_TYPES.fetch(extension, "application/octet-stream")
54
+ end
55
+
56
+ def forbidden_response
57
+ [403, { "Content-Type" => "text/plain" }, ["403 Forbidden"]]
58
+ end
59
+
60
+ def not_found_response
61
+ [404, { "Content-Type" => "text/plain" }, ["404 Not Found"]]
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+
5
+ module Docyard
6
+ class CLI < Thor
7
+ def self.exit_on_failure?
8
+ true
9
+ end
10
+
11
+ desc "version", "Show docyard version"
12
+ def version
13
+ puts "docyard #{Docyard::VERSION}"
14
+ end
15
+
16
+ desc "init", "Initialize a new docyard project"
17
+ def init
18
+ initializer = Docyard::Initializer.new
19
+ exit(1) unless initializer.run
20
+ end
21
+
22
+ desc "serve", "Start the development server"
23
+ method_option :port, type: :numeric, default: 4200, aliases: "-p", desc: "Port to run the server on"
24
+ method_option :host, type: :string, default: "localhost", aliases: "-h", desc: "Host to bind the server to"
25
+ def serve
26
+ require_relative "server"
27
+ server = Docyard::Server.new(
28
+ port: options[:port],
29
+ host: options[:host]
30
+ )
31
+ server.start
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docyard
4
+ module Constants
5
+ CONTENT_TYPE_HTML = "text/html; charset=utf-8"
6
+ CONTENT_TYPE_JSON = "application/json; charset=utf-8"
7
+ CONTENT_TYPE_CSS = "text/css; charset=utf-8"
8
+ CONTENT_TYPE_JS = "application/javascript; charset=utf-8"
9
+
10
+ RELOAD_ENDPOINT = "/_docyard/reload"
11
+ ASSETS_PREFIX = "/assets/"
12
+
13
+ INDEX_FILE = "index"
14
+ INDEX_TITLE = "Home"
15
+
16
+ MARKDOWN_EXTENSION = ".md"
17
+ HTML_EXTENSION = ".html"
18
+
19
+ STATUS_OK = 200
20
+ STATUS_NOT_FOUND = 404
21
+ STATUS_INTERNAL_ERROR = 500
22
+ end
23
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docyard
4
+ class Error < StandardError; end
5
+
6
+ class FileNotFoundError < Error
7
+ attr_reader :path
8
+
9
+ def initialize(path)
10
+ @path = path
11
+ super("File not found: #{path}")
12
+ end
13
+ end
14
+
15
+ class InvalidPathError < Error; end
16
+
17
+ class MarkdownParseError < Error
18
+ attr_reader :file_path, :original_error
19
+
20
+ def initialize(file_path, original_error)
21
+ @file_path = file_path
22
+ @original_error = original_error
23
+ super("Failed to parse markdown file #{file_path}: #{original_error.message}")
24
+ end
25
+ end
26
+
27
+ class TemplateRenderError < Error
28
+ attr_reader :template_path, :original_error
29
+
30
+ def initialize(template_path, original_error)
31
+ @template_path = template_path
32
+ @original_error = original_error
33
+ super("Failed to render template #{template_path}: #{original_error.message}")
34
+ end
35
+ end
36
+
37
+ class ReloadCheckError < Error
38
+ attr_reader :original_error
39
+
40
+ def initialize(original_error)
41
+ @original_error = original_error
42
+ super("Reload check failed: #{original_error.message}")
43
+ end
44
+ end
45
+
46
+ class AssetNotFoundError < Error
47
+ attr_reader :asset_path
48
+
49
+ def initialize(asset_path)
50
+ @asset_path = asset_path
51
+ super("Asset not found: #{asset_path}")
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "listen"
4
+
5
+ module Docyard
6
+ class FileWatcher
7
+ attr_reader :last_modified_time
8
+
9
+ def initialize(docs_path)
10
+ @docs_path = docs_path
11
+ @last_modified_time = Time.now
12
+ @listener = nil
13
+ end
14
+
15
+ def start
16
+ @listener = Listen.to(@docs_path, only: /\.md$/) do |modified, added, removed|
17
+ handle_changes(modified, added, removed)
18
+ end
19
+
20
+ @listener.start
21
+ end
22
+
23
+ def stop
24
+ @listener&.stop
25
+ rescue StandardError => e
26
+ Docyard.logger.error "Error stopping file watcher: #{e.class} - #{e.message}"
27
+ end
28
+
29
+ def changed_since?(timestamp)
30
+ @last_modified_time > timestamp
31
+ end
32
+
33
+ private
34
+
35
+ def handle_changes(modified, added, removed)
36
+ return if modified.empty? && added.empty? && removed.empty?
37
+
38
+ @last_modified_time = Time.now
39
+ Docyard.logger.info "Files changed, triggering reload..."
40
+ end
41
+ end
42
+ end