ace-support-markdown 0.3.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: caaad5b4c68beae09ec7d4e7d6c09b0b0060cb09eeff550d72473edcb315e87e
4
+ data.tar.gz: 150a17fa755dbacf16be654f325fcc9eaa520f2e7f69e77150304aeedb8c3e81
5
+ SHA512:
6
+ metadata.gz: 55dd180392b834db3db26c312f193b68c8ffa2b3ccb9b9099a99ecc365c4b0e3c91153d7329829e88ba323722519727ea62268cc3b9f054beea1d66ce5cced37
7
+ data.tar.gz: ab08ad8cd7290c10c53021121b247c2eec7db7939bdf2f58356fd7f33690623cbc081b611daf60b8823bc1c03ec1a08e742458f95223671e0f1d3fac855c0aa6
data/CHANGELOG.md ADDED
@@ -0,0 +1,121 @@
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.3.0] - 2026-03-23
11
+
12
+ ### Technical
13
+ - Removed phantom `handbook/**/*` glob from gemspec (no handbook directory exists).
14
+
15
+ ## [0.2.2] - 2026-03-22
16
+
17
+ ### Changed
18
+ - Refreshed README structure with an explicit Purpose section and consistent support-library framing.
19
+ - Updated README testing commands to use `ace-test` instead of `bundle exec` invocations.
20
+ - Added a "Part of ACE" footer link to the package README.
21
+
22
+ ## [0.2.1] - 2026-02-23
23
+
24
+ ### Technical
25
+ - Updated internal dependency version constraints to current releases
26
+
27
+ ## [0.2.0] - 2026-01-03
28
+
29
+ ### Changed
30
+ - **BREAKING**: Minimum Ruby version raised to 3.3.0 (was 3.1.0)
31
+ - Standardized gemspec file patterns with deterministic Dir.glob
32
+ - Added MIT LICENSE file
33
+
34
+ ## [0.1.3] - 2025-11-01
35
+
36
+ ### Changed
37
+
38
+ - **Dependency Migration**: Updated to use renamed infrastructure gems
39
+ - Changed dependency from `ace-core` to `ace-support-core`
40
+ - Part of ecosystem-wide naming convention alignment for infrastructure gems
41
+
42
+
43
+ ## [0.1.2] - 2025-10-23
44
+
45
+ ### Improved
46
+
47
+ - **README examples now include educational comments**
48
+ - Added "why" explanations to all 6 real-world examples
49
+ - Comments explain reasoning behind patterns (validation, error handling, etc.)
50
+ - Clarifies ATOM architecture usage and best practices
51
+ - Explains StandardError rescue patterns and ensure block usage
52
+
53
+ - **Example 5 refactored to use begin/rescue/ensure pattern**
54
+ - Replaced multiple rescue blocks with ensure block for rollback
55
+ - Uses success flag to track operation status
56
+ - Cleaner, more maintainable error handling pattern
57
+ - Guarantees cleanup even with non-linear control flow
58
+
59
+ - **Documentation sync strategy documented**
60
+ - Added "Maintaining Documentation" section to README
61
+ - Created comprehensive CONTRIBUTING.md with API sync guidelines
62
+ - Documents the automated README validation approach
63
+ - Provides checklist for API changes and documentation updates
64
+
65
+ ### Added
66
+
67
+ - **Automated README example validation** (`test/integration/readme_examples_test.rb`)
68
+ - 8 test cases validating all README code examples
69
+ - Tests run as part of standard test suite
70
+ - Catches documentation/code mismatches automatically
71
+ - Ensures examples stay in sync with API evolution
72
+ - Validates Quick Start, API Documentation, and Real-World Examples
73
+
74
+ ### Fixed
75
+
76
+ - **Corrected API parameter names in README**
77
+ - Fixed `validate: true` → `validate_before: true` for DocumentEditor.save!()
78
+ - Ensures documentation accurately reflects actual API
79
+ - Caught by new automated README validation tests
80
+
81
+ ## [0.1.1] - 2025-10-23
82
+
83
+ ### Documentation
84
+
85
+ - **Enhanced README with comprehensive real-world examples** (390+ lines)
86
+ - Example 1: Task management system (auto-fixing with backup/validation)
87
+ - Example 2: Documentation updates (bulk operations, nested frontmatter)
88
+ - Example 3: Complex multi-section operations (complete task workflow)
89
+ - Example 4: Safe file writing with custom validation
90
+ - Example 5: Error handling and recovery (rollback, retry logic)
91
+ - Example 6: Batch operations with progress tracking
92
+ - All examples based on actual ace-taskflow and ace-docs implementations
93
+ - Demonstrates error handling patterns, validation rules, and safety features
94
+
95
+ ## [0.1.0] - 2025-10-18
96
+
97
+ ### Added
98
+ - Initial release
99
+ - Frontmatter extraction and serialization atoms
100
+ - Section extraction using Kramdown AST
101
+ - Document validation with hardcoded rules
102
+ - Frontmatter and section editing molecules
103
+ - Safe file writing with backup/rollback
104
+ - Document editor with fluent API
105
+ - Immutable document models
106
+ - Test suite with 100% atom coverage
107
+
108
+ ### Features
109
+ - ATOM architecture (Atoms, Molecules, Organisms, Models)
110
+ - Exact string matching for section identification
111
+ - Atomic file operations with temp file + move pattern
112
+ - Performance: <10ms frontmatter updates, <50ms section edits
113
+ - Zero-corruption design with validation and rollback
114
+
115
+ [Unreleased]: https://github.com/cs3b/ace/compare/v0.2.2...HEAD
116
+ [0.2.2]: https://github.com/cs3b/ace/compare/v0.2.1...v0.2.2
117
+ [0.2.1]: https://github.com/cs3b/ace/compare/v0.2.0...v0.2.1
118
+ [0.2.0]: https://github.com/cs3b/ace/compare/v0.1.2...v0.2.0
119
+ [0.1.2]: https://github.com/cs3b/ace/compare/v0.1.1...v0.1.2
120
+ [0.1.1]: https://github.com/cs3b/ace/compare/v0.1.0...v0.1.1
121
+ [0.1.0]: https://github.com/cs3b/ace/releases/tag/v0.1.0
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,221 @@
1
+ # Contributing to ace-support-markdown
2
+
3
+ Thank you for your interest in contributing to ace-support-markdown! This document provides guidelines for maintaining code quality and documentation accuracy.
4
+
5
+ ## Development Setup
6
+
7
+ ```bash
8
+ git clone https://github.com/your-org/ace-support-markdown.git
9
+ cd ace-support-markdown
10
+ bundle install
11
+ bundle exec rake test
12
+ ```
13
+
14
+ ## Code Architecture
15
+
16
+ This gem follows the **ATOM architecture pattern**:
17
+
18
+ - **Atoms**: Pure functions with no side effects (`lib/ace/support/markdown/atoms/`)
19
+ - **Molecules**: Composed operations (`lib/ace/support/markdown/molecules/`)
20
+ - **Organisms**: High-level orchestration (`lib/ace/support/markdown/organisms/`)
21
+ - **Models**: Immutable data structures (`lib/ace/support/markdown/models/`)
22
+
23
+ ### Design Principles
24
+
25
+ 1. **Immutability**: Models return new instances on transformation
26
+ 2. **Safety First**: All file operations include backup and rollback
27
+ 3. **Validation**: Pre-write and post-write validation prevents corruption
28
+ 4. **Atomicity**: Operations succeed completely or fail completely
29
+ 5. **Clear Errors**: Detailed error messages with actionable information
30
+
31
+ ## Making Changes
32
+
33
+ ### 1. Adding New Features
34
+
35
+ When adding new functionality:
36
+
37
+ 1. **Identify the layer**: Determine if it's an atom, molecule, organism, or model
38
+ 2. **Write tests first**: Add test cases before implementation
39
+ 3. **Implement the feature**: Follow existing patterns and conventions
40
+ 4. **Update documentation**: Add examples to README.md
41
+ 5. **Add README tests**: Create tests in `test/integration/readme_examples_test.rb`
42
+ 6. **Run full test suite**: `bundle exec rake test`
43
+ 7. **Update CHANGELOG.md**: Document the new feature
44
+
45
+ ### 2. Fixing Bugs
46
+
47
+ When fixing bugs:
48
+
49
+ 1. **Write a failing test**: Reproduce the bug in a test case
50
+ 2. **Fix the implementation**: Resolve the issue
51
+ 3. **Verify the fix**: Ensure the test passes
52
+ 4. **Check for regressions**: Run full test suite
53
+ 5. **Update CHANGELOG.md**: Document the fix
54
+
55
+ ### 3. Updating API
56
+
57
+ **IMPORTANT**: API changes require updating both code AND documentation.
58
+
59
+ When modifying the public API:
60
+
61
+ 1. **Update implementation** in `lib/`
62
+ 2. **Update corresponding tests** in `test/`
63
+ 3. **Update README examples** - this is critical!
64
+ 4. **Update README example tests** in `test/integration/readme_examples_test.rb`
65
+ 5. **Run test suite** - README tests will catch doc/code mismatches
66
+ 6. **Update CHANGELOG.md**
67
+ 7. **Consider semver implications**:
68
+ - Breaking changes → MAJOR version bump
69
+ - New features → MINOR version bump
70
+ - Bug fixes → PATCH version bump
71
+
72
+ ### 4. Documentation Sync Strategy
73
+
74
+ **The Problem**: Documentation can become stale as code evolves.
75
+
76
+ **Our Solution**: Automated validation of README examples.
77
+
78
+ The test file `test/integration/readme_examples_test.rb` validates that:
79
+ - Code examples in README.md are syntactically correct
80
+ - Examples work against the current API
81
+ - API behavior matches what's documented
82
+
83
+ **When you change the API:**
84
+ 1. Update the README examples
85
+ 2. Update the README example tests
86
+ 3. Run `bundle exec rake test`
87
+ 4. If tests fail, either:
88
+ - Fix the README (if docs are wrong)
89
+ - Fix the test (if test is wrong)
90
+ - Fix the implementation (if code is wrong)
91
+
92
+ **This ensures documentation never gets out of sync with reality.**
93
+
94
+ ## Testing Guidelines
95
+
96
+ ### Test Structure
97
+
98
+ ```
99
+ test/
100
+ ├── atoms/ # Unit tests for pure functions
101
+ ├── molecules/ # Integration tests for operations
102
+ ├── organisms/ # End-to-end tests for orchestration
103
+ ├── models/ # Tests for data structures
104
+ └── integration/ # Full workflow tests + README validation
105
+ ```
106
+
107
+ ### Writing Tests
108
+
109
+ ```ruby
110
+ class MyFeatureTest < Minitest::Test
111
+ include TestHelpers
112
+
113
+ def setup
114
+ # Setup test environment
115
+ end
116
+
117
+ def teardown
118
+ # Clean up
119
+ end
120
+
121
+ def test_feature_behavior
122
+ # Arrange: Set up test data
123
+ # Act: Execute the feature
124
+ # Assert: Verify behavior
125
+ end
126
+ end
127
+ ```
128
+
129
+ ### Test Coverage Requirements
130
+
131
+ - **Atoms**: 100% coverage (pure functions, easy to test)
132
+ - **Molecules**: 95%+ coverage
133
+ - **Organisms**: 95%+ coverage
134
+ - **Integration**: All major workflows covered
135
+
136
+ Run coverage report:
137
+ ```bash
138
+ bundle exec rake test
139
+ # Coverage report in coverage/index.html
140
+ ```
141
+
142
+ ## Documentation Guidelines
143
+
144
+ ### README Examples
145
+
146
+ Examples should be:
147
+ 1. **Real-world**: Based on actual use cases from ACE gems
148
+ 2. **Complete**: Include setup, execution, and verification
149
+ 3. **Commented**: Explain *why*, not just *what*
150
+ 4. **Tested**: Have corresponding tests in `readme_examples_test.rb`
151
+
152
+ ### Code Comments
153
+
154
+ Add comments that explain:
155
+ - **Why** decisions were made (not what the code does)
156
+ - **Edge cases** and special handling
157
+ - **Performance** considerations
158
+ - **Safety** guarantees
159
+
160
+ Example:
161
+ ```ruby
162
+ # Use ensure block for rollback to guarantee cleanup even if control flow is interrupted
163
+ # This handles early returns, raised exceptions, and other non-linear execution paths
164
+ ensure
165
+ editor.rollback if original_backup && !success_flag
166
+ end
167
+ ```
168
+
169
+ ### CHANGELOG Format
170
+
171
+ Follow [Keep a Changelog](https://keepachangelog.com/) format:
172
+
173
+ ```markdown
174
+ ## [0.1.2] - 2025-10-23
175
+
176
+ ### Added
177
+ - New feature description
178
+
179
+ ### Changed
180
+ - Modified behavior description
181
+
182
+ ### Fixed
183
+ - Bug fix description
184
+ ```
185
+
186
+ ## Pull Request Process
187
+
188
+ 1. **Create a feature branch**: `git checkout -b feature/my-feature`
189
+ 2. **Make your changes**: Follow guidelines above
190
+ 3. **Run tests**: `bundle exec rake test`
191
+ 4. **Update CHANGELOG.md**: Document your changes
192
+ 5. **Commit your changes**: Use clear commit messages
193
+ 6. **Push to your fork**: `git push origin feature/my-feature`
194
+ 7. **Create Pull Request**: With detailed description
195
+
196
+ ### PR Checklist
197
+
198
+ - [ ] Tests added/updated and passing
199
+ - [ ] README updated (if API changed)
200
+ - [ ] README example tests updated (if examples changed)
201
+ - [ ] CHANGELOG.md updated
202
+ - [ ] Code follows ATOM architecture
203
+ - [ ] Comments explain *why*, not *what*
204
+ - [ ] No breaking changes (or clearly documented)
205
+
206
+ ## Code Style
207
+
208
+ Follow Ruby community conventions:
209
+ - 2 spaces for indentation
210
+ - Snake_case for methods and variables
211
+ - CamelCase for classes and modules
212
+ - Descriptive names over short names
213
+ - Limit line length to 120 characters
214
+
215
+ ## Questions?
216
+
217
+ - Open an issue for bugs or feature requests
218
+ - Start a discussion for questions or ideas
219
+ - Check existing issues and PRs first
220
+
221
+ Thank you for contributing to ace-support-markdown!
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 ACE Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ <div align="center">
2
+ <h1> ACE - Support Markdown </h1>
3
+
4
+ Safe, composable markdown editing tools for ACE libraries and docs.
5
+
6
+ <img src="https://raw.githubusercontent.com/cs3b/ace/main/docs/brand/AgenticCodingEnvironment.Logo.XS.jpg" alt="ACE Logo" width="480">
7
+ <br><br>
8
+
9
+ <a href="https://rubygems.org/gems/ace-support-markdown"><img alt="Gem Version" src="https://img.shields.io/gem/v/ace-support-markdown.svg" /></a>
10
+ <a href="https://www.ruby-lang.org"><img alt="Ruby" src="https://img.shields.io/badge/Ruby-3.2+-CC342D?logo=ruby" /></a>
11
+ <a href="https://opensource.org/licenses/MIT"><img alt="License: MIT" src="https://img.shields.io/badge/License-MIT-blue.svg" /></a>
12
+
13
+ </div>
14
+
15
+ > Works with: Claude Code, Codex CLI, OpenCode, Gemini CLI, pi-agent, and more.
16
+
17
+ `ace-support-markdown` provides atomic document editing with frontmatter (YAML metadata blocks at the top of markdown files) and section-level updates while preserving document integrity. It is the shared editing layer that packages like [ace-task](../ace-task) and [ace-docs](../ace-docs) depend on for safe, repeatable content mutations.
18
+
19
+ ## Use Cases
20
+
21
+ **Safely update task and docs content** - use frontmatter and section editing primitives to apply metadata and content changes without risking accidental corruption during automated or agent-driven edits.
22
+
23
+ **Generate documents programmatically** - build reproducible markdown output with builder-like APIs used by [ace-task](../ace-task) for spec files and [ace-docs](../ace-docs) for generated documentation.
24
+
25
+ **Preserve history during edits** - maintain backup and rollback safety for write-heavy operations so that tools like [`ace-task`](../ace-task) can update specs confidently in batch workflows.
26
+
27
+ ---
28
+
29
+ Part of [ACE](https://github.com/cs3b/ace)
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/*_test.rb"]
10
+ t.warning = false
11
+ end
12
+
13
+ task default: :test
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ace
4
+ module Support
5
+ module Markdown
6
+ module Atoms
7
+ # Pure function to validate markdown documents and frontmatter
8
+ # v0.1.0: Uses hardcoded validation rules
9
+ # v0.2.0: Will support JSON Schema validation (see task 080)
10
+ class DocumentValidator
11
+ # Validate complete markdown content (frontmatter + body)
12
+ # @param content [String] The complete markdown content
13
+ # @param rules [Hash] Optional validation rules
14
+ # @return [Hash] Result with :valid (Boolean), :errors (Array), :warnings (Array)
15
+ def self.validate(content, rules: {})
16
+ return invalid_result(["Empty content"]) if content.nil? || content.empty?
17
+
18
+ errors = []
19
+ warnings = []
20
+
21
+ # Parse frontmatter
22
+ extractor_result = FrontmatterExtractor.extract(content)
23
+
24
+ unless extractor_result[:valid]
25
+ errors.concat(extractor_result[:errors])
26
+ return {valid: false, errors: errors, warnings: warnings}
27
+ end
28
+
29
+ # Validate frontmatter structure
30
+ fm_errors = validate_frontmatter(extractor_result[:frontmatter], rules)
31
+ errors.concat(fm_errors)
32
+
33
+ # Validate body exists
34
+ if extractor_result[:body].nil? || extractor_result[:body].strip.empty?
35
+ warnings << "Empty body content"
36
+ end
37
+
38
+ {
39
+ valid: errors.empty?,
40
+ errors: errors,
41
+ warnings: warnings
42
+ }
43
+ end
44
+
45
+ # Validate only frontmatter hash
46
+ # @param frontmatter [Hash] The frontmatter data
47
+ # @param rules [Hash] Optional validation rules
48
+ # @return [Hash] Result with :valid (Boolean), :errors (Array)
49
+ def self.validate_frontmatter(frontmatter, rules = {})
50
+ errors = []
51
+
52
+ unless frontmatter.is_a?(Hash)
53
+ errors << "Frontmatter must be a hash"
54
+ return errors
55
+ end
56
+
57
+ # Apply required fields validation
58
+ if rules[:required_fields]
59
+ rules[:required_fields].each do |field|
60
+ unless frontmatter.key?(field) || frontmatter.key?(field.to_s)
61
+ errors << "Missing required field: #{field}"
62
+ end
63
+ end
64
+ end
65
+
66
+ # Apply field type validation
67
+ if rules[:field_types]
68
+ rules[:field_types].each do |field, expected_type|
69
+ value = frontmatter[field] || frontmatter[field.to_s]
70
+ next unless value
71
+
72
+ unless value.is_a?(expected_type)
73
+ errors << "Field '#{field}' must be #{expected_type}, got #{value.class}"
74
+ end
75
+ end
76
+ end
77
+
78
+ # Apply enum validation
79
+ if rules[:enums]
80
+ rules[:enums].each do |field, allowed_values|
81
+ value = frontmatter[field] || frontmatter[field.to_s]
82
+ next unless value
83
+
84
+ unless allowed_values.include?(value)
85
+ errors << "Field '#{field}' must be one of #{allowed_values.join(", ")}, got '#{value}'"
86
+ end
87
+ end
88
+ end
89
+
90
+ errors
91
+ end
92
+
93
+ # Validate that content can be round-trip parsed
94
+ # @param content [String] The markdown content
95
+ # @return [Boolean] true if content can be safely parsed and rebuilt
96
+ def self.can_round_trip?(content)
97
+ return false if content.nil? || content.empty?
98
+
99
+ begin
100
+ # Extract frontmatter
101
+ result = FrontmatterExtractor.extract(content)
102
+ return false unless result[:valid]
103
+
104
+ # Serialize back
105
+ serialized = FrontmatterSerializer.serialize(
106
+ result[:frontmatter],
107
+ body: result[:body]
108
+ )
109
+
110
+ serialized[:valid]
111
+ rescue
112
+ false
113
+ end
114
+ end
115
+
116
+ private
117
+
118
+ def self.invalid_result(errors)
119
+ {
120
+ valid: false,
121
+ errors: errors,
122
+ warnings: []
123
+ }
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+
5
+ module Ace
6
+ module Support
7
+ module Markdown
8
+ module Atoms
9
+ # Pure function to extract YAML frontmatter from markdown content
10
+ # Separates frontmatter and body content for safe editing
11
+ class FrontmatterExtractor
12
+ # Extract frontmatter from markdown content
13
+ # @param content [String] The markdown content with optional frontmatter
14
+ # @return [Hash] Result with :frontmatter (Hash), :body (String), :valid (Boolean), :errors (Array)
15
+ def self.extract(content)
16
+ return empty_result if content.nil? || content.empty?
17
+
18
+ # Check if content starts with YAML frontmatter delimiter
19
+ unless content.start_with?("---\n")
20
+ return {
21
+ frontmatter: {},
22
+ body: content,
23
+ valid: false,
24
+ errors: ["No frontmatter found"]
25
+ }
26
+ end
27
+
28
+ # Find the ending delimiter (searching from position 4 to skip the opening ---)
29
+ end_index = content.index("\n---\n", 4)
30
+
31
+ unless end_index
32
+ return {
33
+ frontmatter: {},
34
+ body: content[4..-1] || "",
35
+ valid: false,
36
+ errors: ["Missing closing '---' delimiter for frontmatter"]
37
+ }
38
+ end
39
+
40
+ # Extract YAML content (between delimiters) and body content (after delimiters)
41
+ yaml_content = content[4...end_index]
42
+ body_content = content[(end_index + 5)..-1] || ""
43
+
44
+ # Parse YAML with safe_load
45
+ begin
46
+ frontmatter = YAML.safe_load(
47
+ yaml_content,
48
+ permitted_classes: [Date, Time, Symbol],
49
+ permitted_symbols: [],
50
+ aliases: true
51
+ ) || {}
52
+
53
+ # Ensure frontmatter is a hash
54
+ unless frontmatter.is_a?(Hash)
55
+ return {
56
+ frontmatter: {},
57
+ body: body_content,
58
+ valid: false,
59
+ errors: ["Frontmatter must be a hash/object, got #{frontmatter.class}"]
60
+ }
61
+ end
62
+
63
+ {
64
+ frontmatter: frontmatter,
65
+ body: body_content,
66
+ valid: true,
67
+ errors: []
68
+ }
69
+ rescue Psych::SyntaxError => e
70
+ {
71
+ frontmatter: {},
72
+ body: body_content,
73
+ valid: false,
74
+ errors: ["YAML syntax error: #{e.message}"]
75
+ }
76
+ end
77
+ end
78
+
79
+ # Extract only the frontmatter hash
80
+ # @param content [String] The markdown content
81
+ # @return [Hash] The frontmatter data or empty hash
82
+ def self.frontmatter_only(content)
83
+ result = extract(content)
84
+ result[:frontmatter]
85
+ end
86
+
87
+ # Extract only the body content without frontmatter
88
+ # @param content [String] The markdown content
89
+ # @return [String] The body content
90
+ def self.body_only(content)
91
+ result = extract(content)
92
+ result[:body]
93
+ end
94
+
95
+ # Check if content has valid frontmatter
96
+ # @param content [String] The markdown content
97
+ # @return [Boolean] true if valid frontmatter exists
98
+ def self.has_frontmatter?(content)
99
+ result = extract(content)
100
+ result[:valid]
101
+ end
102
+
103
+ private
104
+
105
+ def self.empty_result
106
+ {
107
+ frontmatter: {},
108
+ body: "",
109
+ valid: false,
110
+ errors: ["Empty content"]
111
+ }
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end