Skip to content

Commit 597881a

Browse files
authored
feat: add config validation workflow and editor schema hints (#286)
* feat(devx): add config validation workflow * feat(config): add YAML schema hints for editors * fix(devx): pin prettier and harden config validation * fix(ci): skip schema-only changed config fetch tests * docs: update readme * Fix RuboCop CI warnings
1 parent 9744aa8 commit 597881a

60 files changed

Lines changed: 259 additions & 29 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ruby.yml

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,16 @@ jobs:
1111
runs-on: ubuntu-latest
1212

1313
steps:
14-
- uses: actions/checkout@v2
15-
- uses: actions/setup-node@v2
14+
- uses: actions/checkout@v4
15+
- uses: actions/setup-node@v4
1616
with:
1717
node-version: "20"
1818

1919
- name: Install dependencies
20-
run: |
21-
sudo apt-get install -yqq yamllint
20+
run: sudo apt-get install -yqq yamllint
21+
22+
- name: Install Node dependencies
23+
run: npm ci --ignore-scripts --no-fund --no-audit
2224

2325
- name: Set up Ruby
2426
uses: ruby/setup-ruby@v1
@@ -29,6 +31,9 @@ jobs:
2931
- name: Run linters
3032
run: make lint
3133

34+
- name: Validate configs
35+
run: make validate
36+
3237
test:
3338
strategy:
3439
fail-fast: false
@@ -38,7 +43,7 @@ jobs:
3843
runs-on: ubuntu-latest
3944

4045
steps:
41-
- uses: actions/checkout@v2
46+
- uses: actions/checkout@v4
4247

4348
- name: Set up Ruby
4449
uses: ruby/setup-ruby@v1
@@ -53,7 +58,7 @@ jobs:
5358
runs-on: ubuntu-latest
5459

5560
steps:
56-
- uses: actions/checkout@v2
61+
- uses: actions/checkout@v4
5762

5863
- name: Set up Ruby
5964
uses: ruby/setup-ruby@v1

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
/spec/reports/
88
/tmp/
99
/node_modules/
10+
/schema/
1011

1112
# rspec failure tracking
1213
.rspec_status

.rubocop.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
require:
1+
plugins:
22
- rubocop-rspec
33
- rubocop-performance
44

.vscode/extensions.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"recommendations": [
3+
"redhat.vscode-yaml"
4+
]
5+
}

.vscode/settings.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"yaml.schemas": {
3+
"https://raw.githubusercontent.com/html2rss/html2rss/refs/heads/master/schema/html2rss-config.schema.json": "lib/html2rss/configs/**/*.yml"
4+
}
5+
}

Makefile

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
1-
default: lint test
1+
default: lint validate test
22

33
lint:
44
yamllint lib/html2rss/configs/ .github/
55
bundle exec rubocop -P -f quiet
6-
npx prettier --check lib/**/*.yml .github/**/*.yml README.md
6+
./node_modules/.bin/prettier --check lib/**/*.yml .github/**/*.yml README.md
7+
8+
validate:
9+
bundle exec ruby bin/validate_configs
10+
11+
schema:
12+
mkdir -p schema
13+
bundle exec html2rss schema --write schema/html2rss-config.schema.json
714

815
test:
916
bundle exec rspec
@@ -46,4 +53,4 @@ restore-tests:
4653

4754
lintfix:
4855
bundle exec rubocop -a
49-
npx prettier --write lib/**/*.yml .github/**/*.yml README.md
56+
./node_modules/.bin/prettier --write lib/**/*.yml .github/**/*.yml README.md

README.md

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ This repository contains `html2rss` feed configurations for many websites.
2222

2323
## Dynamic Parameters
2424

25-
Configs must include a `parameters` section to define default values for dynamic parameters:
25+
Parameterized configs should include a `parameters` section with default values:
2626

2727
```yaml
2828
parameters:
@@ -38,7 +38,30 @@ channel:
3838
# ... rest of config
3939
```
4040

41-
The `type` field specifies the parameter type (currently only `string` is supported), and `default` provides the default value when no parameter is explicitly provided.
41+
The `type` field specifies the parameter type (currently only `string` is supported), and `default` provides the default value used by this repository's validation and fetch tests.
42+
43+
Notes:
44+
45+
- Only configs that use `%<param>s` placeholders need a `parameters` section.
46+
- Callers can still override those defaults at runtime with `html2rss feed ... --params ...`.
47+
- Dynamic substitution applies to `channel` and `headers`; selectors are not parameterized by this feature.
48+
49+
## Validation
50+
51+
Use both schema-aware editing and runtime validation before committing:
52+
53+
```bash
54+
# Validate all configs with the runtime validator
55+
make validate
56+
57+
# Validate a single config directly
58+
bundle exec html2rss validate lib/html2rss/configs/github.com/releases.yml
59+
60+
# Export the current JSON Schema locally for editor use
61+
make schema
62+
```
63+
64+
The JSON Schema is useful for editor autocompletion and basic structural checks. Runtime validation remains authoritative for merged defaults and cross-field rules.
4265

4366
## Testing
4467

@@ -55,12 +78,33 @@ make test-config CONFIG=github.com/releases.yml
5578
make test-domain DOMAIN=github.com
5679
```
5780

58-
**Adding new configs**: Just create the YAML file and run tests. No spec file needed.
81+
**Adding new configs**: Create the YAML file, run `make validate`, then run the generated tests. No dedicated spec file is needed.
5982

6083
**Config folder convention**: Place configs under the registrable domain folder (e.g., `example.com/` or `bbc.co.uk/`). Legacy subdomain folders (e.g., `news.example.com/`) are allowed but not preferred.
6184

85+
## Editor Setup (JSON Schema)
86+
87+
Get inline validation and autocompletion when editing configs in your IDE.
88+
All config files already carry the schema modeline at the top:
89+
90+
```yaml
91+
# yaml-language-server: $schema=https://raw.githubusercontent.com/html2rss/html2rss/refs/heads/master/schema/html2rss-config.schema.json
92+
```
93+
94+
Any editor with [yaml-language-server](https://github.com/redhat-developer/yaml-language-server)
95+
support (VS Code + [YAML extension](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml),
96+
Neovim, Helix, …) will automatically pick up the schema when opening a config file.
97+
98+
The included `.vscode/settings.json` additionally associates the schema with all
99+
configs via a glob pattern, so new files get validation before the modeline is added.
100+
101+
> `make schema` writes `schema/html2rss-config.schema.json` locally — useful for
102+
> offline editing or when you want to pin against the currently installed gem version.
103+
62104
## Documentation
63105

64-
- [Main Documentation](https://html2rss.github.io/html2rss-configs/)
106+
- [Selectors Reference](https://html2rss.github.io/ruby-gem/reference/selectors/)
107+
- [Dynamic Parameters](https://html2rss.github.io/ruby-gem/how-to/dynamic-parameters/)
108+
- [CLI Reference](https://html2rss.github.io/ruby-gem/reference/cli-reference/)
65109
- [Contributing Guide](https://html2rss.github.io/get-involved/contributing)
66110
- [Sponsorship Page](https://html2rss.github.io/get-involved/sponsoring)

bin/rspec_changed_configs

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,34 @@
11
#!/usr/bin/env ruby
22
# frozen_string_literal: true
33

4-
changed_files = `git diff --name-only origin/master | grep 'lib/html2rss/configs/.*/.*.yml'`.split("\n")
5-
6-
if changed_files.any?
7-
# Use dynamic test file with environment variable to filter changed configs
8-
config_names = changed_files.map { |file| file.sub('lib/html2rss/configs/', '') }
9-
10-
# Test each changed config individually
11-
config_names.each do |config_name|
12-
puts "Testing changed config: #{config_name}"
13-
system("bundle exec rspec --example '#{config_name}' --tag fetch spec/html2rss/configs_dynamic_spec.rb")
14-
end
15-
else
16-
puts 'No changed config files found'
4+
CONFIG_PATH = %r{\Alib/html2rss/configs/.+/.+\.yml\z}
5+
SCHEMA_MODELINE = /\A[+-]# yaml-language-server: \$schema=/
6+
7+
def comment_only_change?(file)
8+
diff_lines = `git diff --unified=0 origin/master -- #{file}`.lines
9+
changed_lines = diff_lines.select { |line| line.start_with?('+', '-') }
10+
content_lines = changed_lines.reject { |line| line.start_with?('+++', '---') }
11+
12+
content_lines.any? && content_lines.all? { |line| line.match?(SCHEMA_MODELINE) }
13+
end
14+
15+
changed_files = `git diff --name-only origin/master`
16+
.split("\n")
17+
.grep(CONFIG_PATH)
18+
19+
test_files = changed_files.reject { |file| comment_only_change?(file) }
20+
21+
if test_files.empty?
22+
puts 'No changed config files require fetch tests'
1723
exit 0
1824
end
25+
26+
failed = false
27+
28+
test_files.each do |file|
29+
config_name = file.sub('lib/html2rss/configs/', '')
30+
puts "Testing changed config: #{config_name}"
31+
failed ||= !system("bundle exec rspec --example '#{config_name}' --tag fetch spec/html2rss/configs_dynamic_spec.rb")
32+
end
33+
34+
exit 1 if failed

bin/setup

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,33 @@
11
#!/usr/bin/env bash
22
set -euo pipefail
33
IFS=$'\n\t'
4-
set -vx
54

5+
echo "==> Installing Ruby dependencies..."
66
bundle install
77

8-
# Do any other automated setup that you need to do here
8+
echo
9+
echo "==> Installing Node dependencies (required for make lint)..."
10+
npm install --ignore-scripts --no-fund --no-audit
11+
12+
echo
13+
echo "==> Checking system tools..."
14+
if command -v yamllint &>/dev/null; then
15+
echo " yamllint: ok ($(yamllint --version))"
16+
else
17+
echo " yamllint: not found"
18+
echo " Install with one of:"
19+
echo " brew install yamllint"
20+
echo " pip install yamllint"
21+
fi
22+
23+
echo
24+
echo "==> Setup complete. Common commands:"
25+
echo
26+
echo " make # lint + validate + test"
27+
echo " make lint # yamllint, rubocop, prettier"
28+
echo " make validate # validate all configs against the html2rss schema"
29+
echo " make test # run rspec"
30+
echo
31+
echo " make test-config CONFIG=github.com/releases.yml # test a single config"
32+
echo " make test-domain DOMAIN=github.com # test all configs for a domain"
33+
echo " make schema # write schema locally (optional)"

bin/validate_configs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#!/usr/bin/env ruby
2+
# frozen_string_literal: true
3+
4+
require 'bundler/setup'
5+
require 'html2rss'
6+
require 'yaml'
7+
8+
files = Dir['lib/html2rss/configs/**/*.yml']
9+
failed = []
10+
11+
files.each do |file|
12+
config = YAML.safe_load_file(file, symbolize_names: true)
13+
result = Html2rss::Config.validate(config)
14+
15+
if result.success?
16+
puts "ok #{file}"
17+
else
18+
puts "FAIL #{file}"
19+
result.errors.to_h.each do |key, messages|
20+
Array(messages).each { |msg| warn " #{key}: #{msg}" }
21+
end
22+
failed << file
23+
end
24+
rescue Psych::Exception => error
25+
puts "FAIL #{file}"
26+
warn " parse: #{error.message}"
27+
failed << file
28+
rescue StandardError => error
29+
puts "FAIL #{file}"
30+
warn " validation: #{error.class}: #{error.message}"
31+
failed << file
32+
end
33+
34+
puts
35+
if failed.empty?
36+
puts "#{files.size} configs validated successfully."
37+
else
38+
warn "#{failed.size}/#{files.size} configs failed validation."
39+
exit 1
40+
end

0 commit comments

Comments
 (0)