ace-lint 0.25.0 → 0.27.3
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 +4 -4
- data/.ace-defaults/lint/config.yml +3 -2
- data/CHANGELOG.md +78 -0
- data/README.md +4 -2
- data/lib/ace/lint/cli/commands/lint.rb +71 -8
- data/lib/ace/lint/molecules/kramdown_formatter.rb +76 -9
- data/lib/ace/lint/molecules/markdown_linter.rb +171 -50
- data/lib/ace/lint/molecules/markdown_surgical_fixer.rb +247 -0
- data/lib/ace/lint/organisms/auto_fix_orchestrator.rb +382 -0
- data/lib/ace/lint/organisms/lint_orchestrator.rb +35 -3
- data/lib/ace/lint/version.rb +1 -1
- data/lib/ace/lint.rb +1 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8d08aca270559c7f2cb97ffab6f7d0cc28fab077a58b51763d7d9ac0e223a22b
|
|
4
|
+
data.tar.gz: 305b0142f0f4b89104559f0079f98f3286e41fd8876ec236c24e3bce838f5df7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 033a583ec905b507983508780c806a13bbb054ac6423c3c7619279c84aebf85bb60db054b53ad7d7f4f887b9a4f004006de67d1edf90952aeaa706bedeacc5ef
|
|
7
|
+
data.tar.gz: b444718179323bb8e2c1b6617605a54349e22e170c02b81add84b76c282c036ccad901edba5aa0386b85515a95d29ea63331c13d50fb0da5f9d7b77b6e9b1fc8
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# ace-lint general configuration
|
|
2
2
|
# Place this in .ace/lint/config.yml in your project
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
#
|
|
4
|
+
lint:
|
|
5
|
+
# Default model for --auto-fix-with-agent when --model is not provided
|
|
6
|
+
doctor_agent_model: "gemini:flash-latest@yolo"
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,84 @@ The format is based on [Keep a Changelog][1], and this project adheres to [Seman
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.27.3] - 2026-03-26
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- Corrected `--auto-fix --dry-run` file-count reporting to count unique lint result file paths instead of parsing formatted issue strings.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- Extracted deterministic and agent-assisted auto-fix orchestration from the lint CLI command into `Organisms::AutoFixOrchestrator`.
|
|
16
|
+
- Centralized `ace-llm` test load-path setup in `test_helper` for agent-fix command tests.
|
|
17
|
+
|
|
18
|
+
### Technical
|
|
19
|
+
- Documented the frontmatter-prefix reconstruction contract in `MarkdownSurgicalFixer`.
|
|
20
|
+
- Marked internal structural-change guardrail helpers in `KramdownFormatter` as private class methods.
|
|
21
|
+
|
|
22
|
+
## [0.27.2] - 2026-03-26
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
- Made `--auto-fix-with-agent` apply concrete file edits from model output and fail fast when no editable changes are returned.
|
|
26
|
+
- Prevented stale pre-agent lint errors from being carried into post-agent validation results.
|
|
27
|
+
- Hardened markdown formatting guardrails to treat any HTML attribute-count change (increase or decrease) as structural risk.
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
- Added an explicit warning before agent-assisted fixes that full file contents are sent to the selected model.
|
|
31
|
+
- Added payload-size limits for agent-fix prompt file content to avoid oversized requests.
|
|
32
|
+
- Refactored markdown fence-state handling to use a shared fence-aware line iterator across linter and fixer paths.
|
|
33
|
+
|
|
34
|
+
### Technical
|
|
35
|
+
- Added non-dry-run command tests for agent fix mode success and no-edit failure behavior.
|
|
36
|
+
- Added formatter coverage for HTML attribute-removal structural change detection.
|
|
37
|
+
|
|
38
|
+
## [0.27.1] - 2026-03-26
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
- Preserved markdown link destinations containing nested parentheses during typography-safe surgical fixes.
|
|
42
|
+
- Hardened agent prompt construction for `--auto-fix-with-agent` by using dynamic code fences that remain valid when file content already contains fenced blocks.
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
- Updated `--auto-fix --dry-run` preview wording to avoid implying every issue is deterministically fixable.
|
|
46
|
+
- Aligned `--auto-fix` exit behavior with normal lint semantics: warning-only results now exit successfully while error results still fail.
|
|
47
|
+
|
|
48
|
+
### Technical
|
|
49
|
+
- Optimized `run_auto_fix` to skip redundant fix-mode passes for non-fixable file types while preserving final lint validation.
|
|
50
|
+
|
|
51
|
+
## [0.27.0] - 2026-03-26
|
|
52
|
+
|
|
53
|
+
### Added
|
|
54
|
+
- Added deterministic repair flags to `ace-lint`: `--auto-fix`, `--auto-fix-with-agent`, `--dry-run` (`-n`), and `--model`.
|
|
55
|
+
- Added agent-assisted lint repair flow that builds a structured prompt with remaining violations and full file content for affected files.
|
|
56
|
+
- Added command-level coverage for auto-fix dry-run, alias parity, warning precedence, and exit semantics.
|
|
57
|
+
|
|
58
|
+
### Fixed
|
|
59
|
+
- Fixed auto-fix exit behavior to return non-zero when violations remain after deterministic repair.
|
|
60
|
+
- Fixed help and docs drift by aligning CLI docs/examples and E2E fix-mode guidance with the new auto-fix contract.
|
|
61
|
+
|
|
62
|
+
### Changed
|
|
63
|
+
- Changed `--fix` semantics to an alias of `--auto-fix` (deterministic fix then re-lint).
|
|
64
|
+
- Changed auto-fix modes to ignore `--format` with an explicit warning.
|
|
65
|
+
- Added `lint.doctor_agent_model` default configuration for agent-assisted repair model selection.
|
|
66
|
+
|
|
67
|
+
### Technical
|
|
68
|
+
- Expanded task specification verification checklist evidence for the auto-fix and agent-assisted workflow implementation.
|
|
69
|
+
|
|
70
|
+
## [0.26.0] - 2026-03-26
|
|
71
|
+
|
|
72
|
+
### Added
|
|
73
|
+
- Added `MarkdownSurgicalFixer` for markdown-family `--fix` operations that apply targeted line edits without full document reserialization.
|
|
74
|
+
- Added structural guardrails for markdown `--format` to skip unsafe kramdown rewrites when frontmatter, code-block, table, or HTML-attribute drift is detected.
|
|
75
|
+
|
|
76
|
+
### Fixed
|
|
77
|
+
- Fixed markdown style checks to ignore heading/list spacing checks inside fenced code blocks.
|
|
78
|
+
- Added trailing whitespace detection for markdown style validation.
|
|
79
|
+
|
|
80
|
+
### Changed
|
|
81
|
+
- Changed markdown-family `--fix` behavior (`markdown`, `skill`, `workflow`, `agent`) to surgical edits, with `--fix --format` executing surgical fix first then guarded format.
|
|
82
|
+
- Updated CLI/help and usage docs to describe surgical `--fix` semantics and guarded `--format` behavior.
|
|
83
|
+
|
|
84
|
+
### Technical
|
|
85
|
+
- Expanded ace-lint tests for surgical fixer behavior, formatter guardrails, and orchestrator ordering.
|
|
86
|
+
|
|
9
87
|
## [0.25.0] - 2026-03-23
|
|
10
88
|
|
|
11
89
|
### Fixed
|
data/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
Ruby-native linting for markdown, YAML, and Ruby with no Node.js or Python runtime required.
|
|
5
5
|
|
|
6
|
-
<img src="
|
|
6
|
+
<img src="../docs/brand/AgenticCodingEnvironment.Logo.XS.jpg" alt="ACE Logo" width="480">
|
|
7
7
|
<br><br>
|
|
8
8
|
|
|
9
9
|
<a href="https://rubygems.org/gems/ace-lint"><img alt="Gem Version" src="https://img.shields.io/gem/v/ace-lint.svg" /></a>
|
|
@@ -24,13 +24,15 @@
|
|
|
24
24
|
|
|
25
25
|
1. Run lint checks across markdown, YAML, Ruby, and frontmatter validators in one pass.
|
|
26
26
|
2. Apply safe auto-fixes with `--fix` for markdown and Ruby style issues.
|
|
27
|
+
For markdown-family files, `--fix` is surgical (line-level) and does not reserialize structure.
|
|
28
|
+
Use `--format` when you explicitly want a guarded kramdown rewrite.
|
|
27
29
|
3. Report results with colorized pass/fail output, using configuration cascade from CLI flags through project and user defaults.
|
|
28
30
|
|
|
29
31
|
## Use Cases
|
|
30
32
|
|
|
31
33
|
**Validate markdown, YAML, and Ruby in one pass** - run [`ace-lint`](docs/usage.md) for doc and code lint checks without mixing Node/Python tooling into Ruby projects. Use `/as-lint-run` for the full agent-driven lint workflow.
|
|
32
34
|
|
|
33
|
-
**Apply low-risk formatting fixes before review** - use `--fix` to automatically clean markdown and Ruby style issues prior to manual review. Run `/as-lint-fix-issue-from` to fix specific issues identified in lint reports.
|
|
35
|
+
**Apply low-risk formatting fixes before review** - use `--fix` to automatically clean markdown and Ruby style issues prior to manual review. For markdown, fixes are surgical to preserve frontmatter/code blocks/tables/HTML. Run `/as-lint-fix-issue-from` to fix specific issues identified in lint reports.
|
|
34
36
|
|
|
35
37
|
**Standardize lint behavior across teams and repos** - rely on user/project/default cascade settings from [ace-support-config](../ace-support-config) for consistent validator sets and output modes.
|
|
36
38
|
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "ace/support/cli"
|
|
4
|
+
require_relative "../../atoms/type_detector"
|
|
4
5
|
require_relative "../../atoms/validator_registry"
|
|
5
6
|
require_relative "../../organisms/lint_orchestrator"
|
|
7
|
+
require_relative "../../organisms/auto_fix_orchestrator"
|
|
6
8
|
require_relative "../../organisms/result_reporter"
|
|
7
9
|
require_relative "../../organisms/report_generator"
|
|
8
10
|
require_relative "../../organisms/lint_doctor"
|
|
@@ -18,6 +20,12 @@ module Ace
|
|
|
18
20
|
class Lint < Ace::Support::Cli::Command
|
|
19
21
|
include Ace::Support::Cli::Base
|
|
20
22
|
|
|
23
|
+
AGENT_FIX_FILE_BLOCK_START = "<<ACE_FILE:".freeze
|
|
24
|
+
AGENT_FIX_FILE_BLOCK_END = "<<ACE_END_FILE>>".freeze
|
|
25
|
+
AGENT_FIX_NO_CHANGES = "<<ACE_NO_CHANGES>>".freeze
|
|
26
|
+
AGENT_FIX_MAX_FILE_BYTES = 200_000
|
|
27
|
+
AGENT_FIX_MAX_TOTAL_BYTES = 1_000_000
|
|
28
|
+
|
|
21
29
|
desc <<~DESC.strip
|
|
22
30
|
Lint markdown, YAML, Ruby, and frontmatter files
|
|
23
31
|
|
|
@@ -42,8 +50,11 @@ module Ace
|
|
|
42
50
|
# Examples shown in help output
|
|
43
51
|
example [
|
|
44
52
|
"README.md # Auto-detect type from extension",
|
|
45
|
-
"--fix README.md
|
|
46
|
-
"
|
|
53
|
+
"--auto-fix README.md # Deterministic auto-fix then re-lint",
|
|
54
|
+
"--fix README.md # Alias for --auto-fix",
|
|
55
|
+
"--auto-fix --dry-run README.md # Preview fixes without writing",
|
|
56
|
+
"--auto-fix-with-agent README.md # Auto-fix then agent for remaining issues",
|
|
57
|
+
"docs/**/*.md --format # Format with guarded kramdown",
|
|
47
58
|
"**/*.rb --validators standardrb,rubocop # Multiple validators",
|
|
48
59
|
"--doctor # Diagnose lint configuration",
|
|
49
60
|
"--doctor-verbose # Diagnose with all details"
|
|
@@ -54,8 +65,13 @@ module Ace
|
|
|
54
65
|
argument :files, required: false, type: :array, desc: "Files to lint"
|
|
55
66
|
|
|
56
67
|
# Method options (maintaining parity with Thor implementation)
|
|
57
|
-
option :
|
|
58
|
-
option :
|
|
68
|
+
option :auto_fix, type: :boolean, aliases: %w[-f --fix], desc: "Deterministic auto-fix, then re-lint"
|
|
69
|
+
option :auto_fix_with_agent, type: :boolean,
|
|
70
|
+
desc: "Run --auto-fix, then launch agent for remaining issues"
|
|
71
|
+
option :dry_run, type: :boolean, aliases: %w[-n],
|
|
72
|
+
desc: "Preview auto-fixes without modifying files"
|
|
73
|
+
option :model, type: :string, desc: "Provider:model for agent-assisted fix"
|
|
74
|
+
option :format, type: :boolean, desc: "Format markdown with guarded kramdown"
|
|
59
75
|
option :type, type: :string, aliases: %w[-t], desc: "File type (markdown, yaml, ruby, frontmatter)"
|
|
60
76
|
option :line_width, type: :integer, desc: "Line width for formatting (default: 120)"
|
|
61
77
|
option :validators, type: :string, desc: "Comma-separated list of validators (e.g., standardrb,rubocop)"
|
|
@@ -116,7 +132,22 @@ module Ace
|
|
|
116
132
|
lint_options = prepare_options(clean_options)
|
|
117
133
|
|
|
118
134
|
# Lint files
|
|
119
|
-
|
|
135
|
+
if auto_fix_mode?(clean_options)
|
|
136
|
+
warn_format_ignored_if_needed(clean_options)
|
|
137
|
+
auto_fix_orchestrator = build_auto_fix_orchestrator(
|
|
138
|
+
orchestrator,
|
|
139
|
+
lint_options: lint_options,
|
|
140
|
+
options: clean_options
|
|
141
|
+
)
|
|
142
|
+
if clean_options[:dry_run]
|
|
143
|
+
auto_fix_orchestrator.run_dry_run(expanded_paths)
|
|
144
|
+
return
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
results = auto_fix_orchestrator.run(expanded_paths)
|
|
148
|
+
else
|
|
149
|
+
results = orchestrator.lint_files(expanded_paths, options: lint_options)
|
|
150
|
+
end
|
|
120
151
|
|
|
121
152
|
# Generate report unless --no-report flag is set
|
|
122
153
|
report_dir = nil
|
|
@@ -138,8 +169,16 @@ module Ace
|
|
|
138
169
|
verbose = !clean_options[:quiet]
|
|
139
170
|
Organisms::ResultReporter.report(results, verbose: verbose, report_dir: report_dir, report_files: report_files)
|
|
140
171
|
|
|
141
|
-
# Raise on
|
|
142
|
-
if
|
|
172
|
+
# Raise on remaining issues
|
|
173
|
+
if auto_fix_mode?(clean_options)
|
|
174
|
+
remaining_errors = results.sum(&:error_count)
|
|
175
|
+
if remaining_errors.positive?
|
|
176
|
+
raise Ace::Support::Cli::Error.new(
|
|
177
|
+
"#{remaining_errors} error violation(s) remain after auto-fix",
|
|
178
|
+
exit_code: 1
|
|
179
|
+
)
|
|
180
|
+
end
|
|
181
|
+
elsif results.any?(&:failed?)
|
|
143
182
|
failed_count = results.count(&:failed?)
|
|
144
183
|
exit_code = Organisms::ResultReporter.exit_code(results)
|
|
145
184
|
raise Ace::Support::Cli::Error.new("#{failed_count} file(s) had lint errors", exit_code: exit_code)
|
|
@@ -289,7 +328,7 @@ module Ace
|
|
|
289
328
|
prepared[:type] = options[:type].to_sym if options[:type]
|
|
290
329
|
|
|
291
330
|
# Fix/format options
|
|
292
|
-
prepared[:fix] =
|
|
331
|
+
prepared[:fix] = true if auto_fix_mode?(options)
|
|
293
332
|
prepared[:format] = options[:format] if options[:format]
|
|
294
333
|
|
|
295
334
|
# Validators option (comma-separated list)
|
|
@@ -311,6 +350,30 @@ module Ace
|
|
|
311
350
|
def parse_validators(validators_str)
|
|
312
351
|
validators_str.split(",").map { |v| v.strip.downcase.to_sym }
|
|
313
352
|
end
|
|
353
|
+
|
|
354
|
+
def auto_fix_mode?(options)
|
|
355
|
+
options[:auto_fix] || options[:auto_fix_with_agent]
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def warn_format_ignored_if_needed(options)
|
|
359
|
+
return unless options[:format]
|
|
360
|
+
|
|
361
|
+
warn "[ace-lint] Warning: --format is ignored when using --auto-fix or --auto-fix-with-agent"
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
def build_auto_fix_orchestrator(orchestrator, lint_options:, options:)
|
|
365
|
+
Organisms::AutoFixOrchestrator.new(
|
|
366
|
+
orchestrator: orchestrator,
|
|
367
|
+
lint_options: lint_options,
|
|
368
|
+
with_agent: options[:auto_fix_with_agent],
|
|
369
|
+
model: options[:model],
|
|
370
|
+
file_block_start: AGENT_FIX_FILE_BLOCK_START,
|
|
371
|
+
file_block_end: AGENT_FIX_FILE_BLOCK_END,
|
|
372
|
+
no_changes_marker: AGENT_FIX_NO_CHANGES,
|
|
373
|
+
max_file_bytes: AGENT_FIX_MAX_FILE_BYTES,
|
|
374
|
+
max_total_bytes: AGENT_FIX_MAX_TOTAL_BYTES
|
|
375
|
+
)
|
|
376
|
+
end
|
|
314
377
|
end
|
|
315
378
|
end
|
|
316
379
|
end
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../atoms/kramdown_parser"
|
|
4
|
+
require_relative "../atoms/frontmatter_extractor"
|
|
5
|
+
require_relative "markdown_linter"
|
|
4
6
|
|
|
5
7
|
module Ace
|
|
6
8
|
module Lint
|
|
@@ -10,21 +12,35 @@ module Ace
|
|
|
10
12
|
# Format markdown file in place
|
|
11
13
|
# @param file_path [String] Path to markdown file
|
|
12
14
|
# @param options [Hash] Kramdown options
|
|
13
|
-
# @
|
|
14
|
-
|
|
15
|
+
# @param guardrails [Boolean] Enable structural safety checks before write
|
|
16
|
+
# @return [Hash] Result with :success, :formatted, :errors, :warnings
|
|
17
|
+
def self.format_file(file_path, options: {}, guardrails: false)
|
|
15
18
|
content = File.read(file_path)
|
|
16
19
|
result = format_content(content, options: options)
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
return {success: false, formatted: false, errors: result[:errors], warnings: []} unless result[:success]
|
|
22
|
+
|
|
23
|
+
formatted_content = result[:formatted_content]
|
|
24
|
+
return {success: true, formatted: false, errors: [], warnings: []} if formatted_content == content
|
|
25
|
+
|
|
26
|
+
if guardrails
|
|
27
|
+
structural_changes = detect_structural_changes(content, formatted_content)
|
|
28
|
+
if structural_changes.any?
|
|
29
|
+
return {
|
|
30
|
+
success: true,
|
|
31
|
+
formatted: false,
|
|
32
|
+
errors: [],
|
|
33
|
+
warnings: ["Skipped formatting due to structural change risk: #{structural_changes.join(", ")}"]
|
|
34
|
+
}
|
|
35
|
+
end
|
|
23
36
|
end
|
|
37
|
+
|
|
38
|
+
File.write(file_path, formatted_content)
|
|
39
|
+
{success: true, formatted: true, errors: [], warnings: []}
|
|
24
40
|
rescue Errno::ENOENT
|
|
25
|
-
{success: false, formatted: false, errors: ["File not found: #{file_path}"]}
|
|
41
|
+
{success: false, formatted: false, errors: ["File not found: #{file_path}"], warnings: []}
|
|
26
42
|
rescue => e
|
|
27
|
-
{success: false, formatted: false, errors: ["Error formatting file: #{e.message}"]}
|
|
43
|
+
{success: false, formatted: false, errors: ["Error formatting file: #{e.message}"], warnings: []}
|
|
28
44
|
end
|
|
29
45
|
|
|
30
46
|
# Format markdown content
|
|
@@ -60,6 +76,57 @@ module Ace
|
|
|
60
76
|
|
|
61
77
|
result[:formatted_content] != content
|
|
62
78
|
end
|
|
79
|
+
|
|
80
|
+
def self.detect_structural_changes(original, formatted)
|
|
81
|
+
changes = []
|
|
82
|
+
changes << "frontmatter" if frontmatter_changed?(original, formatted)
|
|
83
|
+
changes << "code blocks" if fence_count_changed?(original, formatted)
|
|
84
|
+
changes << "tables" if table_row_count_changed?(original, formatted)
|
|
85
|
+
changes << "html attributes" if html_attributes_changed?(original, formatted)
|
|
86
|
+
changes
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def self.frontmatter_changed?(original, formatted)
|
|
90
|
+
original_frontmatter = Atoms::FrontmatterExtractor.extract(original)
|
|
91
|
+
formatted_frontmatter = Atoms::FrontmatterExtractor.extract(formatted)
|
|
92
|
+
|
|
93
|
+
original_frontmatter[:has_frontmatter] != formatted_frontmatter[:has_frontmatter] ||
|
|
94
|
+
original_frontmatter[:frontmatter].to_s != formatted_frontmatter[:frontmatter].to_s
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def self.fence_count_changed?(original, formatted)
|
|
98
|
+
fence_count(original) != fence_count(formatted)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def self.fence_count(content)
|
|
102
|
+
content.lines.count { |line| line.match?(MarkdownLinter::FENCE_PATTERN) }
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def self.table_row_count_changed?(original, formatted)
|
|
106
|
+
table_row_count(original) != table_row_count(formatted)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def self.table_row_count(content)
|
|
110
|
+
content.lines.count { |line| line.match?(/^\s*\|.*\|\s*$/) }
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def self.html_attributes_changed?(original, formatted)
|
|
114
|
+
html_attribute_count(formatted) != html_attribute_count(original)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def self.html_attribute_count(content)
|
|
118
|
+
content.scan(/<([A-Za-z][A-Za-z0-9:-]*)([^>]*)>/).sum do |_tag_name, attributes|
|
|
119
|
+
attributes.scan(/\s+[A-Za-z_:][\w:.-]*(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'=<>`]+))?/).size
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
private_class_method :frontmatter_changed?,
|
|
124
|
+
:fence_count_changed?,
|
|
125
|
+
:fence_count,
|
|
126
|
+
:table_row_count_changed?,
|
|
127
|
+
:table_row_count,
|
|
128
|
+
:html_attributes_changed?,
|
|
129
|
+
:html_attribute_count
|
|
63
130
|
end
|
|
64
131
|
end
|
|
65
132
|
end
|