objective_elements 1.1.1 → 2.0.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 +4 -4
- data/.github/workflows/ci.yml +44 -0
- data/.github/workflows/release.yml +28 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +3 -0
- data/AGENTS.md +35 -0
- data/CHANGELOG.md +75 -0
- data/Gemfile +6 -3
- data/README.md +164 -129
- data/Rakefile +10 -2
- data/bin/console +4 -7
- data/docs/adr/0001-ruby-version-support-policy.md +68 -0
- data/docs/agents/domain.md +51 -0
- data/docs/agents/issue-tracker.md +45 -0
- data/docs/agents/triage-labels.md +15 -0
- data/lib/objective_elements/double_tag.rb +6 -4
- data/lib/objective_elements/html_attributes.rb +24 -17
- data/lib/objective_elements/shelf_tag.rb +4 -2
- data/lib/objective_elements/single_tag.rb +6 -4
- data/lib/objective_elements/version.rb +3 -1
- data/lib/objective_elements.rb +7 -0
- data/mise.toml +2 -0
- data/objective_elements.gemspec +14 -7
- metadata +23 -69
- data/Gemfile.lock +0 -41
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a8c175b8c8ee5cb41bcdc79101e658706bc82c930ba3bbb64675cf805375297d
|
|
4
|
+
data.tar.gz: 234cba6e82854442b3780df1cde7d0b394d5acca7b649c695d8e697379eb213f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: be1b67a8c7df385802f168ed9684453d378343c56180da10859be98d545e3ac934f56092f27573992c18cea56e7fd2a91fc2d7fc715dc486609d07186f15f7d9
|
|
7
|
+
data.tar.gz: b2220420f60c50ea537ac4bfeb94e6287452c38b820f7c9d59c16458ef00d2dc7b653e5802868cc330d4ad4742bceb2d570b55f1c237313cdece0cca33acc234
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
spec:
|
|
10
|
+
name: rspec (ruby ${{ matrix.ruby }})
|
|
11
|
+
runs-on: ${{ matrix.runs-on || 'ubuntu-latest' }}
|
|
12
|
+
continue-on-error: ${{ matrix.experimental || false }}
|
|
13
|
+
|
|
14
|
+
strategy:
|
|
15
|
+
fail-fast: false
|
|
16
|
+
matrix:
|
|
17
|
+
ruby: ["3.2", "3.3", "3.4", "4.0"]
|
|
18
|
+
include:
|
|
19
|
+
# setup-ruby has no 3.0 build for images newer than 22.04.
|
|
20
|
+
- ruby: "3.0"
|
|
21
|
+
runs-on: ubuntu-22.04
|
|
22
|
+
- ruby: ruby-head
|
|
23
|
+
experimental: true
|
|
24
|
+
|
|
25
|
+
steps:
|
|
26
|
+
- uses: actions/checkout@v4
|
|
27
|
+
- uses: ruby/setup-ruby@v1
|
|
28
|
+
with:
|
|
29
|
+
ruby-version: ${{ matrix.ruby }}
|
|
30
|
+
bundler-cache: true
|
|
31
|
+
- run: bundle exec rspec
|
|
32
|
+
|
|
33
|
+
rubocop:
|
|
34
|
+
name: rubocop
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
|
|
37
|
+
steps:
|
|
38
|
+
- uses: actions/checkout@v4
|
|
39
|
+
- uses: ruby/setup-ruby@v1
|
|
40
|
+
with:
|
|
41
|
+
# Matches the version mise.toml pins, so local and CI lint agree.
|
|
42
|
+
ruby-version: "3.4"
|
|
43
|
+
bundler-cache: true
|
|
44
|
+
- run: bundle exec rubocop
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
# Publishes whatever version.rb says on the selected branch. Needed for
|
|
7
|
+
# v2.0.0, whose tag predates this workflow. `rake release` skips tagging when
|
|
8
|
+
# the tag already exists, so this is safe to re-run.
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
release:
|
|
13
|
+
name: push to rubygems
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
|
|
16
|
+
permissions:
|
|
17
|
+
contents: read
|
|
18
|
+
# Required for the OIDC token rubygems.org trades for a scoped API key.
|
|
19
|
+
id-token: write
|
|
20
|
+
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v4
|
|
23
|
+
- uses: ruby/setup-ruby@v1
|
|
24
|
+
with:
|
|
25
|
+
# Matches the version mise.toml pins.
|
|
26
|
+
ruby-version: "3.4"
|
|
27
|
+
bundler-cache: true
|
|
28
|
+
- uses: rubygems/release-gem@v1
|
data/.gitignore
CHANGED
data/.rubocop.yml
ADDED
data/AGENTS.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
This project is a small ruby gem for building HTML tags.
|
|
4
|
+
|
|
5
|
+
## Commits
|
|
6
|
+
|
|
7
|
+
- All AI assisted commits should have a trailer: `Assisted-by: <model>`
|
|
8
|
+
- [Scoped commits](https://scopedcommits.com/), not conventional commits. Short version:
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
<scope>: <description>
|
|
12
|
+
|
|
13
|
+
[optional body]
|
|
14
|
+
|
|
15
|
+
[optional trailer(s)]
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
- <scope> — the subsystem, area, or module that the commit touches
|
|
19
|
+
- <description> — a short description of the changes made
|
|
20
|
+
- [optional body] — detailed information about the changes
|
|
21
|
+
- [optional trailer(s)] — additional metadata about the commit
|
|
22
|
+
|
|
23
|
+
## Agent skills
|
|
24
|
+
|
|
25
|
+
### Issue tracker
|
|
26
|
+
|
|
27
|
+
Issues live in GitHub Issues for `rbuchberger/objective_elements`, managed via the `gh` CLI. See `docs/agents/issue-tracker.md`.
|
|
28
|
+
|
|
29
|
+
### Triage labels
|
|
30
|
+
|
|
31
|
+
The five canonical triage roles, each using its default label string. See `docs/agents/triage-labels.md`.
|
|
32
|
+
|
|
33
|
+
### Domain docs
|
|
34
|
+
|
|
35
|
+
Single-context: `CONTEXT.md` and `docs/adr/` at the repo root. See `docs/agents/domain.md`.
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [2.0.0] - 2026-08-12
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **Breaking:** `required_ruby_version` is now `>= 3.0`, with no upper bound.
|
|
13
|
+
See [ADR 0001](docs/adr/0001-ruby-version-support-policy.md) for the support
|
|
14
|
+
policy and the April 2027 expiry of the 3.0 floor.
|
|
15
|
+
- All source files now declare `# frozen_string_literal: true`.
|
|
16
|
+
- `ShelfTag#initialize` calls `super`, so a ShelfTag now has a `nil` element and
|
|
17
|
+
an empty `HTMLAttributes` rather than no attributes at all.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- `HTMLAttributes#to_s` no longer mutates the attribute hash. Previously an
|
|
22
|
+
empty attribute gained an empty string on every render, so repeated calls
|
|
23
|
+
accumulated trailing spaces.
|
|
24
|
+
- `HTMLAttributes#to_s`, `SingleTag#opening_tag`, and `DoubleTag#to_a` built
|
|
25
|
+
their output by mutating string literals, which raised `FrozenError` under
|
|
26
|
+
frozen string literals.
|
|
27
|
+
- `bin/console` required the gem's pre-rename name and could not start.
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- GitHub Actions CI: rspec on Ruby 3.0, 3.2, 3.3, 3.4, and 4.0, plus a
|
|
32
|
+
`ruby-head` leg that is allowed to fail, and rubocop on 3.4.
|
|
33
|
+
- Gemspec metadata URIs, and `rubygems_mfa_required`.
|
|
34
|
+
- `mise.toml`, pinning Ruby 3.4 for development.
|
|
35
|
+
- This changelog. Earlier entries are transcribed from the README's old
|
|
36
|
+
"Changes" section.
|
|
37
|
+
|
|
38
|
+
### Removed
|
|
39
|
+
|
|
40
|
+
- `bundler`, `pry`, and `solargraph` as declared development dependencies, and
|
|
41
|
+
`.solargraph.yml`. Install those locally instead.
|
|
42
|
+
- `Gemfile.lock`, which is now gitignored — one lockfile can't serve a Ruby
|
|
43
|
+
3.0 through 4.0 matrix.
|
|
44
|
+
|
|
45
|
+
## [1.1.2] - 2020-01-16
|
|
46
|
+
|
|
47
|
+
### Changed
|
|
48
|
+
|
|
49
|
+
- Gemspec and dependency housekeeping. No functional changes.
|
|
50
|
+
|
|
51
|
+
## [1.1.1] - 2019-11-14
|
|
52
|
+
|
|
53
|
+
### Fixed
|
|
54
|
+
|
|
55
|
+
- Duplicate attribute keys passed at the same time no longer overwrite each
|
|
56
|
+
other's values.
|
|
57
|
+
|
|
58
|
+
## [1.1.0] - 2019-02-08
|
|
59
|
+
|
|
60
|
+
### Added
|
|
61
|
+
|
|
62
|
+
- `ShelfTag`, for creating siblings without a parent element.
|
|
63
|
+
|
|
64
|
+
## [1.0.0] - 2018-10-23
|
|
65
|
+
|
|
66
|
+
### Changed
|
|
67
|
+
|
|
68
|
+
- **Breaking:** attributes syntax changed significantly. `.add_attributes`
|
|
69
|
+
became `.attributes <<`; see the README's usage section.
|
|
70
|
+
|
|
71
|
+
[unreleased]: https://github.com/rbuchberger/objective_elements/compare/v1.1.2...HEAD
|
|
72
|
+
[1.1.2]: https://github.com/rbuchberger/objective_elements/compare/v1.1.1...v1.1.2
|
|
73
|
+
[1.1.1]: https://github.com/rbuchberger/objective_elements/compare/v1.1.0...v1.1.1
|
|
74
|
+
[1.1.0]: https://github.com/rbuchberger/objective_elements/compare/v1.0.0...v1.1.0
|
|
75
|
+
[1.0.0]: https://github.com/rbuchberger/objective_elements/releases/tag/v1.0.0
|
data/Gemfile
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
source 'https://rubygems.org'
|
|
4
4
|
|
|
5
|
-
# Specify your gem's dependencies in elements.gemspec
|
|
6
5
|
gemspec
|
|
6
|
+
|
|
7
|
+
gem 'rake', '~> 13.0'
|
|
8
|
+
gem 'rspec', '~> 3.13'
|
|
9
|
+
gem 'rubocop', '~> 1.81'
|
data/README.md
CHANGED
|
@@ -18,7 +18,7 @@ you only need sometimes, it turns into a horrible mess.
|
|
|
18
18
|
|
|
19
19
|
The problem, of course, is that building long, complex, varying blocks of text with string
|
|
20
20
|
concatenation and interpolation is fragile, unreadable, and painful. You know this, but you're not
|
|
21
|
-
going to write an entirely new class or pull in some big new dependency just for 10 lines of HTML,
|
|
21
|
+
going to write an entirely new class or pull in some big new dependency just for 10 lines of HTML,
|
|
22
22
|
so instead you hammer through it and end up with code like this:
|
|
23
23
|
|
|
24
24
|
```ruby
|
|
@@ -29,7 +29,8 @@ picture_tag = "<picture>\n"\
|
|
|
29
29
|
"#{html_attr_string}>\n"\"#{markdown_escape * 2}</picture>\n"
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
or this:
|
|
32
|
+
or this:
|
|
33
|
+
|
|
33
34
|
```ruby
|
|
34
35
|
def build_li(this_item_data, icon_location, label)
|
|
35
36
|
li = " <li#{@attributes['li']}>"
|
|
@@ -98,43 +99,46 @@ div = p.add_parent DoubleTag.new 'div'
|
|
|
98
99
|
|
|
99
100
|
## Changes
|
|
100
101
|
|
|
101
|
-
|
|
102
|
-
Add `ShelfTag`, which is useful when you want to create siblings without a parent element.
|
|
103
|
-
|
|
104
|
-
### 1.0.0
|
|
105
|
-
Attributes syntax has changed pretty significantly. Find `.add_attributes` & replace `.attributes << `
|
|
106
|
-
will get you most of the way there, but you should read over the usage section again.
|
|
102
|
+
See [CHANGELOG.md](CHANGELOG.md).
|
|
107
103
|
|
|
108
104
|
## Installation
|
|
109
105
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
106
|
+
```ruby
|
|
107
|
+
# Gemfile
|
|
108
|
+
|
|
109
|
+
gem 'objective_elements', '~> 2.0'
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
# Anywhere else:
|
|
114
|
+
|
|
115
|
+
require 'objective_elements'
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Supported Rubies
|
|
119
|
+
|
|
120
|
+
Ruby 3.0 and up, with no upper bound. The policy is to support every non-EOL Ruby, plus EOL versions
|
|
121
|
+
still shipped in a supported Ubuntu LTS — which is the only reason the floor is as low as 3.0. It
|
|
122
|
+
rises to 3.2 when Ubuntu 22.04 leaves standard support in April 2027. See
|
|
123
|
+
[ADR 0001](docs/adr/0001-ruby-version-support-policy.md).
|
|
124
|
+
|
|
122
125
|
## Terminology
|
|
123
126
|
|
|
124
127
|
```
|
|
125
128
|
<p class="stumpy">Hello</p>
|
|
126
129
|
|a| b | c | d |
|
|
127
130
|
```
|
|
128
|
-
- a - element
|
|
129
|
-
- b - attributes
|
|
130
|
-
- a+b - opening tag
|
|
131
|
-
- c - content
|
|
132
|
-
- d - closing tag
|
|
133
131
|
|
|
132
|
+
- a - element
|
|
133
|
+
- b - attributes
|
|
134
|
+
- a+b - opening tag
|
|
135
|
+
- c - content
|
|
136
|
+
- d - closing tag
|
|
134
137
|
|
|
135
138
|
## Usage
|
|
136
139
|
|
|
137
140
|
For Attribute & SingleTag examples, we'll use this image tag:
|
|
141
|
+
|
|
138
142
|
```ruby
|
|
139
143
|
|
|
140
144
|
img = SingleTag.new 'img', attributes: 'src="angry-baby.jpg"'
|
|
@@ -146,34 +150,39 @@ A `SingleTag` is a self-closing tag, meaning it has no content and no closing ta
|
|
|
146
150
|
the other kind.
|
|
147
151
|
|
|
148
152
|
### Attributes
|
|
153
|
+
|
|
149
154
|
Attributes are their own class, and can be accessed by the `.attributes` method on both single and
|
|
150
155
|
double tags. Important methods:
|
|
151
156
|
|
|
152
|
-
|
|
157
|
+
`<< (attribute)`
|
|
158
|
+
|
|
153
159
|
- Example: `p.attributes << 'alt="he does not look happy"'`
|
|
154
160
|
- Example: `p.attributes << { alt: "he does not look happy" }`
|
|
155
161
|
- Add new attributes, can accept a hash or a string. Hash keys will be converted
|
|
156
|
-
to symbols if they are not already, and values will be split on spaces into an array if they are not
|
|
157
|
-
already. Attributes can also be given as a string in the standard HTML syntax (`class="myclass"
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
+
to symbols if they are not already, and values will be split on spaces into an array if they are not
|
|
163
|
+
already. Attributes can also be given as a string in the standard HTML syntax (`class="myclass" id="my-id"`). **Every other method which adds attributes in some way, calls this method**. This
|
|
164
|
+
means that any time you are adding attributes, you can use any format which this method understands.
|
|
165
|
+
|
|
166
|
+
`.delete(attribute)`
|
|
167
|
+
|
|
162
168
|
- Example: `img.attributes.delete 'src'`
|
|
163
169
|
- Example: `img.attributes.delete :src`
|
|
164
170
|
- Example: `img.attributes.delete [:src, 'alt']`
|
|
165
171
|
- Delete one or more attributes. Accepts a string, symbol, or an array of
|
|
166
|
-
strings and/or symbols.
|
|
172
|
+
strings and/or symbols.
|
|
173
|
+
|
|
174
|
+
`.replace(attribute)`
|
|
167
175
|
|
|
168
|
-
`.replace(attribute)`
|
|
169
176
|
- Example: `img.attributes.replace 'src="happy-baby.jpg"'`
|
|
170
177
|
- Replaces one or more attributes and values individually.
|
|
171
178
|
|
|
172
|
-
`.content[:attribute_name]
|
|
179
|
+
`.content[:attribute_name] =`
|
|
180
|
+
|
|
173
181
|
- Example: `img.attributes.content[:src] = 'dirty-baby.jpg'` < This just broke everything.
|
|
174
|
-
- Don't do it. Use `.replace`, `.(attribute name)
|
|
182
|
+
- Don't do it. Use `.replace`, `.(attribute name) =`, or `<<`
|
|
183
|
+
|
|
184
|
+
`.content[:attribute_name]`
|
|
175
185
|
|
|
176
|
-
`.content[:attribute_name]`
|
|
177
186
|
- Example: `img.attributes[:src] # returns 'angry-baby.jpg`
|
|
178
187
|
- Retrieve the content for a given attribute, as an array of strings. Must be a symbol. You'll
|
|
179
188
|
- mostly need this when you don't know which attribute you need ahead of
|
|
@@ -181,18 +190,20 @@ strings and/or symbols.
|
|
|
181
190
|
|
|
182
191
|
**There is a shorthand for the next two methods. Keep reading.**
|
|
183
192
|
|
|
184
|
-
`.(attribute_name)`
|
|
193
|
+
`.(attribute_name)`
|
|
194
|
+
|
|
185
195
|
- Example: `img.attributes.src # 'angry-baby.jpg'`
|
|
186
196
|
- Convenience method/syntactic sugar: Returns the value of a given attribute name, as a
|
|
187
197
|
- space-separated string. This relies on method_missing, which means that any overlap with
|
|
188
|
-
already existing methods won't work.
|
|
198
|
+
already existing methods won't work.
|
|
189
199
|
- **You can't access `class` or `method` html attributes this way, because
|
|
190
200
|
basic objects in ruby already have those methods.**
|
|
191
201
|
- **Ruby methods can't have dashes in them.** This means `p.data-awesomeness` won't work.
|
|
192
202
|
|
|
193
|
-
`.(attribute_name) = value`
|
|
203
|
+
`.(attribute_name) = value`
|
|
204
|
+
|
|
194
205
|
- Example: `img.attributes.src = 'happy-baby.jpg'`
|
|
195
|
-
- Same as above. Similar to `.replace(attribute)`. Interestingly, `method
|
|
206
|
+
- Same as above. Similar to `.replace(attribute)`. Interestingly, `method =` and `class =` both
|
|
196
207
|
work (`.class` is defined on the basic object class, but `.class=` is not.). Still, you
|
|
197
208
|
probably shouldn't use them.
|
|
198
209
|
|
|
@@ -203,36 +214,43 @@ strings and/or symbols.
|
|
|
203
214
|
|
|
204
215
|
### SingleTag Properties:
|
|
205
216
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
217
|
+
#### element
|
|
218
|
+
|
|
219
|
+
- String
|
|
220
|
+
- Mandatory
|
|
221
|
+
- Which type of tag, such as 'hr' or 'img'
|
|
222
|
+
|
|
223
|
+
#### attributes
|
|
224
|
+
|
|
225
|
+
- Instance of the class described above.
|
|
213
226
|
|
|
214
227
|
### SingleTag Methods (that you care about)
|
|
215
228
|
|
|
216
229
|
`SingleTag.new(element, attributes: nil)`
|
|
217
230
|
|
|
218
|
-
`.to_s`
|
|
231
|
+
`.to_s`
|
|
232
|
+
|
|
219
233
|
- Example: `img.to_s`
|
|
220
234
|
- The big one. Returns your HTML as a string, nondestructively.
|
|
221
235
|
|
|
222
|
-
`.add_parent(DoubleTag)`
|
|
236
|
+
`.add_parent(DoubleTag)`
|
|
237
|
+
|
|
223
238
|
- Example: `img.add_parent DoubleTag.new 'picture'`
|
|
224
239
|
- returns supplied DoubleTag, with self added as a child.
|
|
225
240
|
|
|
226
|
-
`.attributes`
|
|
241
|
+
`.attributes`
|
|
242
|
+
|
|
227
243
|
- Example: `img.attributes`
|
|
228
244
|
- attr_reader for HTML attributes. This is how you can access any attribute method
|
|
229
|
-
described above.
|
|
245
|
+
described above.
|
|
246
|
+
|
|
247
|
+
`.reset_attributes`
|
|
230
248
|
|
|
231
|
-
`.reset_attributes`
|
|
232
249
|
- Example: `img.reset_attributes`
|
|
233
250
|
- Removes all attributes.
|
|
234
251
|
|
|
235
|
-
`.attributes=(new)`
|
|
252
|
+
`.attributes=(new)`
|
|
253
|
+
|
|
236
254
|
- Example: `img.attributes = 'src="grumpy-baby.jpg" id="muddy"'`
|
|
237
255
|
- Equivalent to `reset_attributes` and `.attributes << new`
|
|
238
256
|
|
|
@@ -241,40 +259,43 @@ described above.
|
|
|
241
259
|
|
|
242
260
|
### DoubleTag Properties:
|
|
243
261
|
|
|
244
|
-
|
|
245
262
|
#### `DoubleTag` Inherits all of `SingleTag`'s properties and methods, and adds content and a
|
|
263
|
+
|
|
246
264
|
closing tag.
|
|
247
265
|
|
|
248
|
-
|
|
266
|
+
#### content
|
|
249
267
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
268
|
+
- Array
|
|
269
|
+
- Optional
|
|
270
|
+
- Contains anything (but probably just strings and tags. Anything else will be turned into a
|
|
271
|
+
string with `.to_s`, which is an alias for `.inspect` most of the time).
|
|
272
|
+
- Each element in the array corresponds to at least one line of HTML
|
|
273
|
+
- Multiline child tags will get as many lines as they need (like you'd expect).
|
|
274
|
+
- Child elements are not rendered until the parent is rendered, meaning you can access and
|
|
275
|
+
modify them after defining a parent.
|
|
276
|
+
- add with `.add_content`, or modify the content array directly.
|
|
259
277
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
278
|
+
#### oneline
|
|
279
|
+
|
|
280
|
+
- Boolean
|
|
281
|
+
- optional, defaults to false.
|
|
282
|
+
- When true, the entire element and its content will be rendered as a single line. Useful for
|
|
283
|
+
anchor tags and list items.
|
|
265
284
|
|
|
266
285
|
### DoubleTag Methods (that you care about)
|
|
267
286
|
|
|
268
|
-
For DoubleTag Examples, we're working with a div tag:
|
|
287
|
+
For DoubleTag Examples, we're working with a div tag:
|
|
269
288
|
|
|
270
289
|
```
|
|
271
290
|
div = DoubleTag.new 'div'
|
|
272
291
|
```
|
|
273
292
|
|
|
274
|
-
`DoubleTag.new(element, attributes: {}, oneline: false, content: [])`
|
|
293
|
+
`DoubleTag.new(element, attributes: {}, oneline: false, content: [])`
|
|
294
|
+
|
|
275
295
|
- You can initialize it with content.
|
|
276
296
|
|
|
277
|
-
`add_content(anything)`
|
|
297
|
+
`add_content(anything)`
|
|
298
|
+
|
|
278
299
|
- Example: `div.add_content 'example text!'`
|
|
279
300
|
- Example: `div.add_content ['splits', 'across', 'lines']`
|
|
280
301
|
- Example: `div.add_content img # image tag from earlier`
|
|
@@ -300,91 +321,105 @@ tag.
|
|
|
300
321
|
Convert it into an actual tag with `.add_parent(DoubleTag)`
|
|
301
322
|
|
|
302
323
|
## Configuration
|
|
303
|
-
|
|
304
|
-
Indentation is defined by the `indent` method on the DoubleTag class, which is two escaped
|
|
305
|
-
spaces by default ("\ \ "). If you'd like to change it:
|
|
306
324
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
325
|
+
Indentation is defined by the `indent` method on the DoubleTag class, which is two escaped
|
|
326
|
+
spaces by default ("\ \ "). If you'd like to change it:
|
|
327
|
+
|
|
328
|
+
1. Make a new class, inherit from DoubleTag.
|
|
329
|
+
2. Override `indent` with whatever you want.
|
|
330
|
+
3. Use your new class instead of DoubleTag.
|
|
331
|
+
|
|
332
|
+
Example:
|
|
310
333
|
|
|
311
|
-
|
|
334
|
+
```ruby
|
|
312
335
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
require 'objective_elements'
|
|
336
|
+
require 'objective_elements'
|
|
316
337
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
end
|
|
338
|
+
class MyDoubleTag < DoubleTag
|
|
339
|
+
def indent
|
|
340
|
+
# 4 escaped spaces:
|
|
341
|
+
"\ \ \ \ "
|
|
322
342
|
end
|
|
343
|
+
end
|
|
323
344
|
|
|
324
345
|
MyDoubleTag.new('p', content: 'hello').to_s
|
|
325
346
|
# <p>
|
|
326
347
|
# hello
|
|
327
348
|
# </p>
|
|
328
349
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
## Limitations
|
|
332
|
-
|
|
333
|
-
* It doesn't know a single HTML element on its own, so it does nothing to ensure your HTML is valid.
|
|
334
|
-
|
|
335
|
-
* A parent tag can't put siblings on the same line. You can either do this (with `oneline: true` on
|
|
336
|
-
the strong tag):
|
|
350
|
+
```
|
|
337
351
|
|
|
338
|
-
|
|
352
|
+
## Limitations
|
|
339
353
|
|
|
340
|
-
|
|
341
|
-
Here is some
|
|
342
|
-
<strong>strong</strong>
|
|
343
|
-
text.
|
|
344
|
-
</p>
|
|
354
|
+
- It doesn't know a single HTML element on its own, so it does nothing to ensure your HTML is valid.
|
|
345
355
|
|
|
346
|
-
|
|
347
|
-
|
|
356
|
+
- A parent tag can't put siblings on the same line. You can either do this (with `oneline: true` on
|
|
357
|
+
the strong tag):
|
|
348
358
|
|
|
349
|
-
|
|
359
|
+
```html
|
|
360
|
+
<p>
|
|
361
|
+
Here is some
|
|
362
|
+
<strong>strong</strong>
|
|
363
|
+
text.
|
|
364
|
+
</p>
|
|
365
|
+
```
|
|
350
366
|
|
|
351
|
-
|
|
352
|
-
Here is some
|
|
353
|
-
<strong>
|
|
354
|
-
strong
|
|
355
|
-
</strong>
|
|
356
|
-
text.
|
|
357
|
-
</p>
|
|
367
|
+
or this (default behavior):
|
|
358
368
|
|
|
359
|
-
|
|
360
|
-
|
|
369
|
+
```html
|
|
370
|
+
<p>
|
|
371
|
+
Here is some
|
|
372
|
+
<strong>
|
|
373
|
+
strong
|
|
374
|
+
</strong>
|
|
375
|
+
text.
|
|
376
|
+
</p>
|
|
377
|
+
```
|
|
361
378
|
|
|
362
|
-
|
|
379
|
+
But you can't do this without string interpolation or something:
|
|
363
380
|
|
|
364
|
-
|
|
381
|
+
```html
|
|
382
|
+
<p>
|
|
383
|
+
Here is some
|
|
384
|
+
<strong>strong</strong>
|
|
385
|
+
text.
|
|
386
|
+
</p>
|
|
387
|
+
```
|
|
365
388
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
source code layout.
|
|
389
|
+
This doesn't affect how the browser will render it, but it might bug you if you're particular about
|
|
390
|
+
source code layout.
|
|
369
391
|
|
|
370
|
-
|
|
392
|
+
- If you set 'oneline: true' on a parent DoubleTag, but not all its children DoubleTags, the output
|
|
371
393
|
will not be pretty. I advise against it. Handling this situation is on the TODO list.
|
|
372
394
|
|
|
373
|
-
|
|
395
|
+
- It doesn't wrap long lines of text, and it doesn't indent text with newlines embedded. It's on the
|
|
374
396
|
TODO list.
|
|
375
397
|
|
|
376
|
-
##
|
|
398
|
+
## Development
|
|
399
|
+
|
|
400
|
+
Ruby is managed with [mise](https://mise.jdx.dev/); `mise.toml` pins 3.4, which is the version CI
|
|
401
|
+
lints on, so local rubocop can't disagree with CI. Any supported Ruby will run the tests.
|
|
402
|
+
|
|
403
|
+
```sh
|
|
404
|
+
mise install # optional; only if you use mise
|
|
405
|
+
bundle install
|
|
406
|
+
bundle exec rake # rspec + rubocop
|
|
407
|
+
```
|
|
408
|
+
[Scoped commits](https://scopedcommits.com/)
|
|
377
409
|
|
|
378
|
-
|
|
379
|
-
same.
|
|
410
|
+
## AI Policy
|
|
380
411
|
|
|
381
|
-
|
|
382
|
-
reflect it.
|
|
412
|
+
The use of LLM powered coding tools is permitted so long as:
|
|
383
413
|
|
|
384
|
-
|
|
414
|
+
* Any commits with any AI generated code have a trailer: `Assisted-by: <model>`
|
|
415
|
+
* All changes are read completely and reviewed thoroughly by a knowledgable human before merging to
|
|
416
|
+
master. This has always been the case, but it is especially true for AI-generated code.
|
|
417
|
+
* Pull request authors are expected to have done the same before submitting.
|
|
385
418
|
|
|
386
|
-
|
|
387
|
-
|
|
419
|
+
If you don't want to run AI generated code, you can use versions prior to `2.0.0`. You are likely
|
|
420
|
+
using this project as a dependency of jekyll_picture_tag; it is pinned to OE `1.x` for JPT versions
|
|
421
|
+
prior to `3.0.0` which carries a similar policy change. In other words, If you use the non-ai
|
|
422
|
+
version of JPT, you'll get the non-ai version of this.
|
|
388
423
|
|
|
389
424
|
## License
|
|
390
425
|
|
data/Rakefile
CHANGED
|
@@ -1,2 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'bundler/gem_tasks'
|
|
4
|
+
require 'rubocop/rake_task'
|
|
5
|
+
require 'rspec/core/rake_task'
|
|
6
|
+
|
|
7
|
+
RuboCop::RakeTask.new
|
|
8
|
+
RSpec::Core::RakeTask.new(:spec)
|
|
9
|
+
|
|
10
|
+
task default: %i[spec rubocop]
|
data/bin/console
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
4
|
+
require 'bundler/setup'
|
|
5
|
+
require 'objective_elements'
|
|
5
6
|
|
|
6
7
|
# You can add fixtures and/or initialization code here to make experimenting
|
|
7
8
|
# with your gem easier. You can also use a different console, if you like.
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
# require "pry"
|
|
11
|
-
# Pry.start
|
|
12
|
-
|
|
13
|
-
require "irb"
|
|
10
|
+
require 'irb'
|
|
14
11
|
IRB.start(__FILE__)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# 1. Ruby version support policy
|
|
2
|
+
|
|
3
|
+
Date: 2026-08-11
|
|
4
|
+
|
|
5
|
+
## Status
|
|
6
|
+
|
|
7
|
+
Accepted. The Ruby 3.0 floor expires April 2027 — see "Consequences".
|
|
8
|
+
|
|
9
|
+
## Context
|
|
10
|
+
|
|
11
|
+
The gem sat dormant long enough that its tooling and its implicit Ruby support
|
|
12
|
+
window had both drifted. Reviving it meant answering a question that had never
|
|
13
|
+
been written down: which Rubies does this gem promise to work on?
|
|
14
|
+
|
|
15
|
+
Two failure modes were on the table. Setting the floor too high strands users on
|
|
16
|
+
distro-packaged Rubies they don't control. Setting an upper bound strands
|
|
17
|
+
everyone the moment a new Ruby ships — which is exactly the state
|
|
18
|
+
`jekyll_picture_tag` is in, uninstallable on Ruby 4.0 because of its own
|
|
19
|
+
`< 4.0` cap.
|
|
20
|
+
|
|
21
|
+
This gem is ~100 lines, has no runtime dependencies, and uses no recent syntax.
|
|
22
|
+
The cost of supporting an old Ruby is close to zero; the cost of an upper bound
|
|
23
|
+
is a broken install.
|
|
24
|
+
|
|
25
|
+
## Decision
|
|
26
|
+
|
|
27
|
+
`required_ruby_version = '>= 3.0'`, with no upper bound.
|
|
28
|
+
|
|
29
|
+
The rule behind that number: **support every non-EOL Ruby, plus EOL versions
|
|
30
|
+
still shipped in a supported Ubuntu LTS.**
|
|
31
|
+
|
|
32
|
+
| Ruby | Status (Aug 2026) | Ubuntu LTS |
|
|
33
|
+
| ---- | ----------------- | --------------------- |
|
|
34
|
+
| 4.0 | supported | — |
|
|
35
|
+
| 3.4 | supported | — |
|
|
36
|
+
| 3.3 | supported | 26.04 |
|
|
37
|
+
| 3.2 | EOL 2026-03 | 24.04 (to Apr 2029) |
|
|
38
|
+
| 3.0 | EOL 2024-04 | 22.04 (to Apr 2027) |
|
|
39
|
+
|
|
40
|
+
No upper bound. A dependency-free library has nothing to break against a new
|
|
41
|
+
Ruby that a cap would protect against, and a cap guarantees breakage the day
|
|
42
|
+
that Ruby ships.
|
|
43
|
+
|
|
44
|
+
CI tests the full supported range — 3.0, 3.2, 3.3, 3.4, 4.0 — plus a
|
|
45
|
+
`continue-on-error` `ruby-head` leg for early warning. The 3.0 leg pins
|
|
46
|
+
`ubuntu-22.04`, because `setup-ruby` publishes no 3.0 build for newer images.
|
|
47
|
+
Rubocop runs once, on 3.4, matching the version `mise.toml` pins so local lint
|
|
48
|
+
and CI lint cannot disagree. No JRuby or TruffleRuby: no runtime dependencies
|
|
49
|
+
and no C extensions means nothing platform-specific to catch.
|
|
50
|
+
|
|
51
|
+
## Consequences
|
|
52
|
+
|
|
53
|
+
The floor is user-visible metadata, so raising it is a breaking change. That is
|
|
54
|
+
what makes this release 2.0.0.
|
|
55
|
+
|
|
56
|
+
Ruby 3.0 is here **only** because Ubuntu 22.04 ships it. **Revisit in April
|
|
57
|
+
2027**, when 22.04 leaves standard support: at that point the floor should rise
|
|
58
|
+
to 3.2 (Ubuntu 24.04's Ruby, supported until April 2029) in the next major
|
|
59
|
+
version.
|
|
60
|
+
|
|
61
|
+
Applying the rule mechanically means the floor moves on Ubuntu LTS's schedule
|
|
62
|
+
rather than upstream Ruby's, which is slower than most gems — deliberately.
|
|
63
|
+
That is the point: the users least able to upgrade Ruby are the ones on a
|
|
64
|
+
distro-packaged one.
|
|
65
|
+
|
|
66
|
+
Out of scope, and tracked separately in that repo: `jekyll_picture_tag`'s own
|
|
67
|
+
`< 4.0` cap, and its `objective_elements ~> 1.1` constraint. Both need their own
|
|
68
|
+
support decision.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Domain Docs
|
|
2
|
+
|
|
3
|
+
How the engineering skills should consume this repo's domain documentation when exploring the codebase.
|
|
4
|
+
|
|
5
|
+
## Before exploring, read these
|
|
6
|
+
|
|
7
|
+
- **`CONTEXT.md`** at the repo root, or
|
|
8
|
+
- **`CONTEXT-MAP.md`** at the repo root if it exists — it points at one `CONTEXT.md` per context. Read each one relevant to the topic.
|
|
9
|
+
- **`docs/adr/`** — read ADRs that touch the area you're about to work in. In multi-context repos, also check `src/<context>/docs/adr/` for context-scoped decisions.
|
|
10
|
+
|
|
11
|
+
If any of these files don't exist, **proceed silently**. Don't flag their absence; don't suggest creating them upfront. The `/domain-modeling` skill (reached via `/grill-with-docs` and `/improve-codebase-architecture`) creates them lazily when terms or decisions actually get resolved.
|
|
12
|
+
|
|
13
|
+
## File structure
|
|
14
|
+
|
|
15
|
+
Single-context repo (most repos):
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
/
|
|
19
|
+
├── CONTEXT.md
|
|
20
|
+
├── docs/adr/
|
|
21
|
+
│ ├── 0001-event-sourced-orders.md
|
|
22
|
+
│ └── 0002-postgres-for-write-model.md
|
|
23
|
+
└── src/
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Multi-context repo (presence of `CONTEXT-MAP.md` at the root):
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
/
|
|
30
|
+
├── CONTEXT-MAP.md
|
|
31
|
+
├── docs/adr/ ← system-wide decisions
|
|
32
|
+
└── src/
|
|
33
|
+
├── ordering/
|
|
34
|
+
│ ├── CONTEXT.md
|
|
35
|
+
│ └── docs/adr/ ← context-specific decisions
|
|
36
|
+
└── billing/
|
|
37
|
+
├── CONTEXT.md
|
|
38
|
+
└── docs/adr/
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Use the glossary's vocabulary
|
|
42
|
+
|
|
43
|
+
When your output names a domain concept (in an issue title, a refactor proposal, a hypothesis, a test name), use the term as defined in `CONTEXT.md`. Don't drift to synonyms the glossary explicitly avoids.
|
|
44
|
+
|
|
45
|
+
If the concept you need isn't in the glossary yet, that's a signal — either you're inventing language the project doesn't use (reconsider) or there's a real gap (note it for `/domain-modeling`).
|
|
46
|
+
|
|
47
|
+
## Flag ADR conflicts
|
|
48
|
+
|
|
49
|
+
If your output contradicts an existing ADR, surface it explicitly rather than silently overriding:
|
|
50
|
+
|
|
51
|
+
> _Contradicts ADR-0007 (event-sourced orders) — but worth reopening because…_
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Issue tracker: GitHub
|
|
2
|
+
|
|
3
|
+
Issues and specs for this repo live as GitHub issues. Use the `gh` CLI for all operations.
|
|
4
|
+
|
|
5
|
+
## Conventions
|
|
6
|
+
|
|
7
|
+
- **Create an issue**: `gh issue create --title "..." --body "..."`. Use a heredoc for multi-line bodies.
|
|
8
|
+
- **Read an issue**: `gh issue view <number> --comments`, filtering comments by `jq` and also fetching labels.
|
|
9
|
+
- **List issues**: `gh issue list --state open --json number,title,body,labels,comments --jq '[.[] | {number, title, body, labels: [.labels[].name], comments: [.comments[].body]}]'` with appropriate `--label` and `--state` filters.
|
|
10
|
+
- **Comment on an issue**: `gh issue comment <number> --body "..."`
|
|
11
|
+
- **Apply / remove labels**: `gh issue edit <number> --add-label "..."` / `--remove-label "..."`
|
|
12
|
+
- **Close**: `gh issue close <number> --comment "..."`
|
|
13
|
+
|
|
14
|
+
Infer the repo from `git remote -v` — `gh` does this automatically when run inside a clone.
|
|
15
|
+
|
|
16
|
+
## Pull requests as a triage surface
|
|
17
|
+
|
|
18
|
+
**PRs as a request surface: no.** _(Set to `yes` if this repo treats external PRs as feature requests; `/triage` reads this flag.)_
|
|
19
|
+
|
|
20
|
+
When set to `yes`, PRs run through the same labels and states as issues, using the `gh pr` equivalents:
|
|
21
|
+
|
|
22
|
+
- **Read a PR**: `gh pr view <number> --comments` and `gh pr diff <number>` for the diff.
|
|
23
|
+
- **List external PRs for triage**: `gh pr list --state open --json number,title,body,labels,author,authorAssociation,comments` then keep only `authorAssociation` of `CONTRIBUTOR`, `FIRST_TIME_CONTRIBUTOR`, or `NONE` (drop `OWNER`/`MEMBER`/`COLLABORATOR`).
|
|
24
|
+
- **Comment / label / close**: `gh pr comment`, `gh pr edit --add-label`/`--remove-label`, `gh pr close`.
|
|
25
|
+
|
|
26
|
+
GitHub shares one number space across issues and PRs, so a bare `#42` may be either — resolve with `gh pr view 42` and fall back to `gh issue view 42`.
|
|
27
|
+
|
|
28
|
+
## When a skill says "publish to the issue tracker"
|
|
29
|
+
|
|
30
|
+
Create a GitHub issue.
|
|
31
|
+
|
|
32
|
+
## When a skill says "fetch the relevant ticket"
|
|
33
|
+
|
|
34
|
+
Run `gh issue view <number> --comments`.
|
|
35
|
+
|
|
36
|
+
## Wayfinding operations
|
|
37
|
+
|
|
38
|
+
Used by `/wayfinder`. The **map** is a single issue with **child** issues as tickets.
|
|
39
|
+
|
|
40
|
+
- **Map**: a single issue labelled `wayfinder:map`, holding the Notes / Decisions-so-far / Fog body. `gh issue create --label wayfinder:map`.
|
|
41
|
+
- **Child ticket**: an issue linked to the map as a GitHub sub-issue (`gh api` on the sub-issues endpoint). Where sub-issues aren't enabled, add the child to a task list in the map body and put `Part of #<map>` at the top of the child body. Labels: `wayfinder:<type>` (`research`/`prototype`/`grilling`/`task`). Once claimed, the ticket is assigned to the driving dev.
|
|
42
|
+
- **Blocking**: GitHub's **native issue dependencies** — the canonical, UI-visible representation. Add an edge with `gh api --method POST repos/<owner>/<repo>/issues/<child>/dependencies/blocked_by -F issue_id=<blocker-db-id>`, where `<blocker-db-id>` is the blocker's numeric **database id** (`gh api repos/<owner>/<repo>/issues/<n> --jq .id`, _not_ the `#number` or `node_id`). GitHub reports `issue_dependencies_summary.blocked_by` (open blockers only — the live gate). Where dependencies aren't available, fall back to a `Blocked by: #<n>, #<n>` line at the top of the child body. A ticket is unblocked when every blocker is closed.
|
|
43
|
+
- **Frontier query**: list the map's open children (`gh issue list --state open`, scoped to the map's sub-issues / task list), drop any with an open blocker (`issue_dependencies_summary.blocked_by > 0`, or an open issue in the `Blocked by` line) or an assignee; first in map order wins.
|
|
44
|
+
- **Claim**: `gh issue edit <n> --add-assignee @me` — the session's first write.
|
|
45
|
+
- **Resolve**: `gh issue comment <n> --body "<answer>"`, then `gh issue close <n>`, then append a context pointer (gist + link) to the map's Decisions-so-far.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Triage Labels
|
|
2
|
+
|
|
3
|
+
The skills speak in terms of five canonical triage roles. This file maps those roles to the actual label strings used in this repo's issue tracker.
|
|
4
|
+
|
|
5
|
+
| Label in mattpocock/skills | Label in our tracker | Meaning |
|
|
6
|
+
| -------------------------- | -------------------- | ---------------------------------------- |
|
|
7
|
+
| `needs-triage` | `needs-triage` | Maintainer needs to evaluate this issue |
|
|
8
|
+
| `needs-info` | `needs-info` | Waiting on reporter for more information |
|
|
9
|
+
| `ready-for-agent` | `ready-for-agent` | Fully specified, ready for an AFK agent |
|
|
10
|
+
| `ready-for-human` | `ready-for-human` | Requires human implementation |
|
|
11
|
+
| `wontfix` | `wontfix` | Will not be actioned |
|
|
12
|
+
|
|
13
|
+
When a skill mentions a role (e.g. "apply the AFK-ready triage label"), use the corresponding label string from this table.
|
|
14
|
+
|
|
15
|
+
Edit the right-hand column to match whatever vocabulary you actually use.
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# Non-Self-Closing tag. Can have content, but doesn't have to.
|
|
2
4
|
class DoubleTag < SingleTag
|
|
3
5
|
attr_accessor :oneline
|
|
@@ -29,23 +31,23 @@ class DoubleTag < SingleTag
|
|
|
29
31
|
|
|
30
32
|
def to_a
|
|
31
33
|
lines = content.map { |c| build_content_line c }
|
|
32
|
-
lines = lines.flatten.map { |l|
|
|
34
|
+
lines = lines.flatten.map { |l| oneline ? l : indent + l }
|
|
33
35
|
lines.unshift(opening_tag).push(closing_tag)
|
|
34
36
|
end
|
|
35
37
|
|
|
36
38
|
def to_s
|
|
37
|
-
to_a.join(oneline ? '' : "\n")
|
|
39
|
+
"#{to_a.join(oneline ? '' : "\n")}\n"
|
|
38
40
|
end
|
|
39
41
|
|
|
40
42
|
private
|
|
41
43
|
|
|
42
44
|
def build_content_line(element)
|
|
43
45
|
# Since DoubleTag inherits from SingleTag, it will slurp up those too.
|
|
44
|
-
element.is_a?(SingleTag) ? element.to_a : element.to_s
|
|
46
|
+
element.is_a?(SingleTag) ? element.to_a : element.to_s
|
|
45
47
|
end
|
|
46
48
|
|
|
47
49
|
def indent
|
|
48
|
-
|
|
50
|
+
' '
|
|
49
51
|
end
|
|
50
52
|
|
|
51
53
|
def closing_tag
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# Represents standard HTML attributes, such as class="myclass"
|
|
2
4
|
class HTMLAttributes
|
|
3
5
|
attr_reader :content
|
|
6
|
+
|
|
4
7
|
def initialize(new = nil)
|
|
5
8
|
@content = {}
|
|
6
9
|
self << new
|
|
@@ -11,12 +14,9 @@ class HTMLAttributes
|
|
|
11
14
|
end
|
|
12
15
|
|
|
13
16
|
def to_s
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
return_string.strip
|
|
17
|
+
# An attribute with no values joins to an empty string, which is the
|
|
18
|
+
# correct format (alt="", for example).
|
|
19
|
+
@content.map { |k, v| "#{k}=\"#{v.join ' '}\"" }.join(' ')
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
# This is the only way we add new attributes. Flexible about what you give
|
|
@@ -86,29 +86,36 @@ class HTMLAttributes
|
|
|
86
86
|
add_hash hashify_input new_string
|
|
87
87
|
end
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
# Input: Keys are attribute names (either strings or symbols), values are
|
|
90
|
+
# attribute values (either a string or an array of strings)
|
|
91
|
+
def add_hash(new_hash)
|
|
90
92
|
formatted_new = {}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
+
|
|
94
|
+
new_hash.each_pair do |k, v|
|
|
95
|
+
v = v.split if v.is_a? String
|
|
96
|
+
|
|
93
97
|
formatted_new[k.to_sym] = v
|
|
94
98
|
end
|
|
95
99
|
|
|
96
|
-
@content.merge!
|
|
97
|
-
oldval.concat
|
|
100
|
+
@content.merge!(formatted_new) do |_key, oldval, newval|
|
|
101
|
+
oldval.concat(newval)
|
|
98
102
|
end
|
|
103
|
+
|
|
99
104
|
self
|
|
100
105
|
end
|
|
101
106
|
|
|
102
107
|
def hashify_input(new_string)
|
|
103
108
|
# looking for something like:
|
|
104
|
-
# 'class="something something-else" id="my-id"'
|
|
109
|
+
# 'class="something something-else" id="my-id" alt=""'
|
|
105
110
|
new_hash = {}
|
|
106
|
-
new_string.scan(/ ?([^="]+)="([^"]
|
|
107
|
-
#
|
|
108
|
-
|
|
109
|
-
|
|
111
|
+
new_string.scan(/ ?([^="]+)="([^"]*)"/).each do |match|
|
|
112
|
+
# Returns something like:
|
|
113
|
+
# [['class','something something-else'],['id','my-id'],['alt', '']]
|
|
114
|
+
|
|
115
|
+
key, val = *match
|
|
116
|
+
|
|
110
117
|
if new_hash[key]
|
|
111
|
-
new_hash[key] <<
|
|
118
|
+
new_hash[key] << " #{val}"
|
|
112
119
|
else
|
|
113
120
|
new_hash[key] = val
|
|
114
121
|
end
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# This is a nonexistent HTML tag, which holds content like a double tag but has
|
|
2
4
|
# no opening or closing tag. This allows us to group siblings without adding an
|
|
3
5
|
# extra parent to the markup.
|
|
4
6
|
class ShelfTag < DoubleTag
|
|
7
|
+
# A shelf has no element of its own, so it never renders an opening tag.
|
|
5
8
|
def initialize(content: nil, oneline: false)
|
|
6
|
-
|
|
7
|
-
self.content = content
|
|
9
|
+
super(nil, content: content, oneline: oneline)
|
|
8
10
|
end
|
|
9
11
|
|
|
10
12
|
def to_a
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# Collection of HTML element tags
|
|
2
4
|
# Describes a basic, self-closing HTML tag.
|
|
3
5
|
class SingleTag
|
|
@@ -34,7 +36,7 @@ class SingleTag
|
|
|
34
36
|
|
|
35
37
|
# Renders our HTML.
|
|
36
38
|
def to_s
|
|
37
|
-
opening_tag
|
|
39
|
+
"#{opening_tag}\n"
|
|
38
40
|
end
|
|
39
41
|
|
|
40
42
|
# Allows us to work with attributes as methods:
|
|
@@ -53,8 +55,8 @@ class SingleTag
|
|
|
53
55
|
private
|
|
54
56
|
|
|
55
57
|
def opening_tag
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
return "<#{@element}>" if @attributes.empty?
|
|
59
|
+
|
|
60
|
+
"<#{@element} #{@attributes}>"
|
|
59
61
|
end
|
|
60
62
|
end
|
data/lib/objective_elements.rb
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Author: Robert Buchberger <robert@buchberger.cc>
|
|
4
|
+
#
|
|
5
|
+
# This module provides a few helpful classes for generating HTML using simple
|
|
6
|
+
# Ruby. Its goal is to be lightweight, and more focused than the general-purpose
|
|
7
|
+
# nature of nokogiri.
|
|
1
8
|
module ObjectiveElements
|
|
2
9
|
require_relative 'objective_elements/single_tag'
|
|
3
10
|
require_relative 'objective_elements/double_tag'
|
data/mise.toml
ADDED
data/objective_elements.gemspec
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
lib = File.expand_path('lib', __dir__)
|
|
2
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
3
5
|
require 'objective_elements/version'
|
|
@@ -6,12 +8,22 @@ Gem::Specification.new do |spec|
|
|
|
6
8
|
spec.name = 'objective_elements'
|
|
7
9
|
spec.version = ObjectiveElements::VERSION
|
|
8
10
|
spec.authors = ['Robert Buchberger']
|
|
9
|
-
spec.email = ['robert@
|
|
11
|
+
spec.email = ['robert@buchberger.cc']
|
|
10
12
|
|
|
11
|
-
spec.summary = 'Build HTML
|
|
13
|
+
spec.summary = 'Build HTML with simple ruby.'
|
|
12
14
|
spec.homepage = 'https://github.com/rbuchberger/objective_elements'
|
|
13
15
|
spec.license = 'MIT'
|
|
14
16
|
|
|
17
|
+
spec.required_ruby_version = '>= 3.0'
|
|
18
|
+
|
|
19
|
+
spec.metadata = {
|
|
20
|
+
'homepage_uri' => spec.homepage,
|
|
21
|
+
'source_code_uri' => spec.homepage,
|
|
22
|
+
'bug_tracker_uri' => "#{spec.homepage}/issues",
|
|
23
|
+
'changelog_uri' => "#{spec.homepage}/blob/master/CHANGELOG.md",
|
|
24
|
+
'rubygems_mfa_required' => 'true'
|
|
25
|
+
}
|
|
26
|
+
|
|
15
27
|
# Specify which files should be added to the gem when it is released. The
|
|
16
28
|
# `git ls-files -z` loads the files in the RubyGem that have been added into
|
|
17
29
|
# git.
|
|
@@ -23,9 +35,4 @@ Gem::Specification.new do |spec|
|
|
|
23
35
|
spec.bindir = 'exe'
|
|
24
36
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
25
37
|
spec.require_paths = ['lib']
|
|
26
|
-
|
|
27
|
-
spec.add_development_dependency 'bundler', '~> 1.16'
|
|
28
|
-
spec.add_development_dependency 'pry', '~>0.11.3'
|
|
29
|
-
spec.add_development_dependency 'rake', '~> 10.0'
|
|
30
|
-
spec.add_development_dependency 'rspec', '~>3.8.0'
|
|
31
38
|
end
|
metadata
CHANGED
|
@@ -1,98 +1,53 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: objective_elements
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 2.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Robert Buchberger
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
12
|
-
dependencies:
|
|
13
|
-
- !ruby/object:Gem::Dependency
|
|
14
|
-
name: bundler
|
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
|
16
|
-
requirements:
|
|
17
|
-
- - "~>"
|
|
18
|
-
- !ruby/object:Gem::Version
|
|
19
|
-
version: '1.16'
|
|
20
|
-
type: :development
|
|
21
|
-
prerelease: false
|
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
-
requirements:
|
|
24
|
-
- - "~>"
|
|
25
|
-
- !ruby/object:Gem::Version
|
|
26
|
-
version: '1.16'
|
|
27
|
-
- !ruby/object:Gem::Dependency
|
|
28
|
-
name: pry
|
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
|
30
|
-
requirements:
|
|
31
|
-
- - "~>"
|
|
32
|
-
- !ruby/object:Gem::Version
|
|
33
|
-
version: 0.11.3
|
|
34
|
-
type: :development
|
|
35
|
-
prerelease: false
|
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
-
requirements:
|
|
38
|
-
- - "~>"
|
|
39
|
-
- !ruby/object:Gem::Version
|
|
40
|
-
version: 0.11.3
|
|
41
|
-
- !ruby/object:Gem::Dependency
|
|
42
|
-
name: rake
|
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
|
44
|
-
requirements:
|
|
45
|
-
- - "~>"
|
|
46
|
-
- !ruby/object:Gem::Version
|
|
47
|
-
version: '10.0'
|
|
48
|
-
type: :development
|
|
49
|
-
prerelease: false
|
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
-
requirements:
|
|
52
|
-
- - "~>"
|
|
53
|
-
- !ruby/object:Gem::Version
|
|
54
|
-
version: '10.0'
|
|
55
|
-
- !ruby/object:Gem::Dependency
|
|
56
|
-
name: rspec
|
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
|
58
|
-
requirements:
|
|
59
|
-
- - "~>"
|
|
60
|
-
- !ruby/object:Gem::Version
|
|
61
|
-
version: 3.8.0
|
|
62
|
-
type: :development
|
|
63
|
-
prerelease: false
|
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
-
requirements:
|
|
66
|
-
- - "~>"
|
|
67
|
-
- !ruby/object:Gem::Version
|
|
68
|
-
version: 3.8.0
|
|
69
|
-
description:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
70
12
|
email:
|
|
71
|
-
- robert@
|
|
13
|
+
- robert@buchberger.cc
|
|
72
14
|
executables: []
|
|
73
15
|
extensions: []
|
|
74
16
|
extra_rdoc_files: []
|
|
75
17
|
files:
|
|
18
|
+
- ".github/workflows/ci.yml"
|
|
19
|
+
- ".github/workflows/release.yml"
|
|
76
20
|
- ".gitignore"
|
|
21
|
+
- ".rubocop.yml"
|
|
22
|
+
- AGENTS.md
|
|
23
|
+
- CHANGELOG.md
|
|
77
24
|
- Gemfile
|
|
78
|
-
- Gemfile.lock
|
|
79
25
|
- LICENSE.txt
|
|
80
26
|
- README.md
|
|
81
27
|
- Rakefile
|
|
82
28
|
- bin/console
|
|
83
29
|
- bin/setup
|
|
30
|
+
- docs/adr/0001-ruby-version-support-policy.md
|
|
31
|
+
- docs/agents/domain.md
|
|
32
|
+
- docs/agents/issue-tracker.md
|
|
33
|
+
- docs/agents/triage-labels.md
|
|
84
34
|
- lib/objective_elements.rb
|
|
85
35
|
- lib/objective_elements/double_tag.rb
|
|
86
36
|
- lib/objective_elements/html_attributes.rb
|
|
87
37
|
- lib/objective_elements/shelf_tag.rb
|
|
88
38
|
- lib/objective_elements/single_tag.rb
|
|
89
39
|
- lib/objective_elements/version.rb
|
|
40
|
+
- mise.toml
|
|
90
41
|
- objective_elements.gemspec
|
|
91
42
|
homepage: https://github.com/rbuchberger/objective_elements
|
|
92
43
|
licenses:
|
|
93
44
|
- MIT
|
|
94
|
-
metadata:
|
|
95
|
-
|
|
45
|
+
metadata:
|
|
46
|
+
homepage_uri: https://github.com/rbuchberger/objective_elements
|
|
47
|
+
source_code_uri: https://github.com/rbuchberger/objective_elements
|
|
48
|
+
bug_tracker_uri: https://github.com/rbuchberger/objective_elements/issues
|
|
49
|
+
changelog_uri: https://github.com/rbuchberger/objective_elements/blob/master/CHANGELOG.md
|
|
50
|
+
rubygems_mfa_required: 'true'
|
|
96
51
|
rdoc_options: []
|
|
97
52
|
require_paths:
|
|
98
53
|
- lib
|
|
@@ -100,15 +55,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
100
55
|
requirements:
|
|
101
56
|
- - ">="
|
|
102
57
|
- !ruby/object:Gem::Version
|
|
103
|
-
version: '0'
|
|
58
|
+
version: '3.0'
|
|
104
59
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
105
60
|
requirements:
|
|
106
61
|
- - ">="
|
|
107
62
|
- !ruby/object:Gem::Version
|
|
108
63
|
version: '0'
|
|
109
64
|
requirements: []
|
|
110
|
-
rubygems_version: 3.
|
|
111
|
-
signing_key:
|
|
65
|
+
rubygems_version: 3.6.9
|
|
112
66
|
specification_version: 4
|
|
113
|
-
summary: Build HTML
|
|
67
|
+
summary: Build HTML with simple ruby.
|
|
114
68
|
test_files: []
|
data/Gemfile.lock
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
PATH
|
|
2
|
-
remote: .
|
|
3
|
-
specs:
|
|
4
|
-
objective_elements (1.1.0)
|
|
5
|
-
|
|
6
|
-
GEM
|
|
7
|
-
remote: https://rubygems.org/
|
|
8
|
-
specs:
|
|
9
|
-
coderay (1.1.2)
|
|
10
|
-
diff-lcs (1.3)
|
|
11
|
-
method_source (0.9.0)
|
|
12
|
-
pry (0.11.3)
|
|
13
|
-
coderay (~> 1.1.0)
|
|
14
|
-
method_source (~> 0.9.0)
|
|
15
|
-
rake (10.5.0)
|
|
16
|
-
rspec (3.8.0)
|
|
17
|
-
rspec-core (~> 3.8.0)
|
|
18
|
-
rspec-expectations (~> 3.8.0)
|
|
19
|
-
rspec-mocks (~> 3.8.0)
|
|
20
|
-
rspec-core (3.8.0)
|
|
21
|
-
rspec-support (~> 3.8.0)
|
|
22
|
-
rspec-expectations (3.8.1)
|
|
23
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
|
24
|
-
rspec-support (~> 3.8.0)
|
|
25
|
-
rspec-mocks (3.8.0)
|
|
26
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
|
27
|
-
rspec-support (~> 3.8.0)
|
|
28
|
-
rspec-support (3.8.0)
|
|
29
|
-
|
|
30
|
-
PLATFORMS
|
|
31
|
-
ruby
|
|
32
|
-
|
|
33
|
-
DEPENDENCIES
|
|
34
|
-
bundler (~> 1.16)
|
|
35
|
-
objective_elements!
|
|
36
|
-
pry (~> 0.11.3)
|
|
37
|
-
rake (~> 10.0)
|
|
38
|
-
rspec (~> 3.8.0)
|
|
39
|
-
|
|
40
|
-
BUNDLED WITH
|
|
41
|
-
1.17.2
|