prosereflect 0.2.0 → 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 +4 -4
- data/.github/workflows/docs.yml +63 -0
- data/.github/workflows/links.yml +97 -0
- data/.gitignore +4 -0
- data/.rubocop_todo.yml +61 -75
- data/README.adoc +2 -0
- data/docs/Gemfile +10 -0
- data/docs/INDEX.adoc +45 -0
- data/docs/_advanced/index.adoc +15 -0
- data/docs/_advanced/schema.adoc +112 -0
- data/docs/_advanced/step-map.adoc +66 -0
- data/docs/_advanced/steps.adoc +88 -0
- data/docs/_advanced/test-builder.adoc +61 -0
- data/docs/_advanced/transform.adoc +92 -0
- data/docs/_config.yml +174 -0
- data/docs/_features/html-input.adoc +69 -0
- data/docs/_features/html-output.adoc +45 -0
- data/docs/_features/index.adoc +15 -0
- data/docs/_features/marks.adoc +86 -0
- data/docs/_features/node-types.adoc +124 -0
- data/docs/_features/user-mentions.adoc +47 -0
- data/docs/_guides/custom-nodes.adoc +107 -0
- data/docs/_guides/index.adoc +13 -0
- data/docs/_guides/round-trip-html.adoc +91 -0
- data/docs/_guides/serialization.adoc +109 -0
- data/docs/_pages/index.adoc +67 -0
- data/docs/_reference/document-api.adoc +49 -0
- data/docs/_reference/index.adoc +14 -0
- data/docs/_reference/node-api.adoc +79 -0
- data/docs/_reference/schema-api.adoc +95 -0
- data/docs/_reference/transform-api.adoc +77 -0
- data/docs/_understanding/document-model.adoc +65 -0
- data/docs/_understanding/fragment.adoc +52 -0
- data/docs/_understanding/index.adoc +14 -0
- data/docs/_understanding/resolved-position.adoc +53 -0
- data/docs/_understanding/slice.adoc +54 -0
- data/docs/lychee.toml +63 -0
- data/lib/prosereflect/blockquote.rb +9 -0
- data/lib/prosereflect/bullet_list.rb +25 -19
- data/lib/prosereflect/code_block.rb +1 -5
- data/lib/prosereflect/fragment.rb +249 -0
- data/lib/prosereflect/horizontal_rule.rb +9 -0
- data/lib/prosereflect/image.rb +9 -0
- data/lib/prosereflect/input/html.rb +96 -0
- data/lib/prosereflect/node.rb +141 -3
- data/lib/prosereflect/ordered_list.rb +2 -0
- data/lib/prosereflect/output/html.rb +227 -0
- data/lib/prosereflect/parser.rb +9 -0
- data/lib/prosereflect/resolved_pos.rb +256 -0
- data/lib/prosereflect/schema/attribute.rb +57 -0
- data/lib/prosereflect/schema/content_match.rb +656 -0
- data/lib/prosereflect/schema/fragment.rb +166 -0
- data/lib/prosereflect/schema/mark.rb +121 -0
- data/lib/prosereflect/schema/mark_type.rb +130 -0
- data/lib/prosereflect/schema/node.rb +236 -0
- data/lib/prosereflect/schema/node_type.rb +274 -0
- data/lib/prosereflect/schema/schema_main.rb +190 -0
- data/lib/prosereflect/schema/spec.rb +92 -0
- data/lib/prosereflect/schema.rb +39 -0
- data/lib/prosereflect/text.rb +24 -0
- data/lib/prosereflect/transform/attr_step.rb +157 -0
- data/lib/prosereflect/transform/insert_step.rb +115 -0
- data/lib/prosereflect/transform/mapping.rb +82 -0
- data/lib/prosereflect/transform/mark_step.rb +269 -0
- data/lib/prosereflect/transform/replace_around_step.rb +181 -0
- data/lib/prosereflect/transform/replace_step.rb +157 -0
- data/lib/prosereflect/transform/slice.rb +91 -0
- data/lib/prosereflect/transform/step.rb +89 -0
- data/lib/prosereflect/transform/step_map.rb +126 -0
- data/lib/prosereflect/transform/structure.rb +120 -0
- data/lib/prosereflect/transform/transform.rb +341 -0
- data/lib/prosereflect/transform.rb +26 -0
- data/lib/prosereflect/version.rb +1 -1
- data/lib/prosereflect.rb +3 -0
- data/spec/fixtures/documents/formatted_text.yaml +14 -0
- data/spec/fixtures/documents/heading_paragraph.yaml +16 -0
- data/spec/fixtures/documents/lists_doc.yaml +32 -0
- data/spec/fixtures/documents/mixed_content.yaml +40 -0
- data/spec/fixtures/documents/nested_doc.yaml +20 -0
- data/spec/fixtures/documents/simple_doc.yaml +6 -0
- data/spec/fixtures/documents/table_doc.yaml +32 -0
- data/spec/fixtures/documents/transform_test.yaml +14 -0
- data/spec/fixtures/schema/custom_schema.rb +37 -0
- data/spec/fixtures/schema/test_schema.rb +46 -0
- data/spec/fixtures/test_builder/helpers.rb +212 -0
- data/spec/prosereflect/document_spec.rb +1 -1
- data/spec/prosereflect/fragment_spec.rb +273 -0
- data/spec/prosereflect/input/html_spec.rb +197 -1
- data/spec/prosereflect/node_spec.rb +128 -0
- data/spec/prosereflect/output/whitespace_spec.rb +248 -0
- data/spec/prosereflect/parser/round_trip_spec.rb +472 -0
- data/spec/prosereflect/resolved_pos_spec.rb +74 -0
- data/spec/prosereflect/schema/conftest.rb +68 -0
- data/spec/prosereflect/schema/content_match_spec.rb +237 -0
- data/spec/prosereflect/schema/mark_spec.rb +274 -0
- data/spec/prosereflect/schema/mark_type_spec.rb +86 -0
- data/spec/prosereflect/schema/node_type_spec.rb +142 -0
- data/spec/prosereflect/schema/schema_spec.rb +194 -0
- data/spec/prosereflect/test_builder/marks_spec.rb +127 -0
- data/spec/prosereflect/transform/equivalence_spec.rb +487 -0
- data/spec/prosereflect/transform/mapping_spec.rb +226 -0
- data/spec/prosereflect/transform/replace_spec.rb +832 -0
- data/spec/prosereflect/transform/replace_step_spec.rb +157 -0
- data/spec/prosereflect/transform/slice_spec.rb +48 -0
- data/spec/prosereflect/transform/step_map_spec.rb +70 -0
- data/spec/prosereflect/transform/step_spec.rb +211 -0
- data/spec/prosereflect/transform/structure_spec.rb +98 -0
- data/spec/prosereflect/transform/transform_spec.rb +238 -0
- data/spec/spec_helper.rb +1 -0
- metadata +90 -2
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
title: Schema API
|
|
4
|
+
parent: Reference
|
|
5
|
+
nav_order: 4
|
|
6
|
+
---
|
|
7
|
+
= Schema API
|
|
8
|
+
|
|
9
|
+
== Prosereflect::Schema
|
|
10
|
+
|
|
11
|
+
=== Constructor
|
|
12
|
+
|
|
13
|
+
`Schema.new(nodes_spec:, marks_spec:, top_node: nil)`
|
|
14
|
+
|
|
15
|
+
[source,ruby]
|
|
16
|
+
----
|
|
17
|
+
schema = Prosereflect::Schema.new(
|
|
18
|
+
nodes_spec: {
|
|
19
|
+
"doc" => { content: "block+" },
|
|
20
|
+
"paragraph" => { content: "inline*", group: "block" },
|
|
21
|
+
"text" => { group: "inline" }
|
|
22
|
+
},
|
|
23
|
+
marks_spec: { "bold" => {}, "italic" => {} }
|
|
24
|
+
)
|
|
25
|
+
----
|
|
26
|
+
|
|
27
|
+
=== Methods
|
|
28
|
+
|
|
29
|
+
[cols="1,3"]
|
|
30
|
+
|===
|
|
31
|
+
| Method | Description
|
|
32
|
+
|
|
33
|
+
| `node_type(name)` | Get NodeType by name (raises on unknown)
|
|
34
|
+
| `mark_type(name)` | Get MarkType by name (raises on unknown)
|
|
35
|
+
| `top_node_type` | The top-level NodeType (usually "doc")
|
|
36
|
+
| `node(type, attrs, content, marks)` | Create a validated node
|
|
37
|
+
| `text(text, marks)` | Create a text node
|
|
38
|
+
| `mark(type, attrs)` | Create a mark
|
|
39
|
+
| `node_from_json(json)` | Deserialize node from JSON
|
|
40
|
+
| `mark_from_json(json)` | Deserialize mark from JSON
|
|
41
|
+
|===
|
|
42
|
+
|
|
43
|
+
== Schema::NodeType
|
|
44
|
+
|
|
45
|
+
[cols="1,3"]
|
|
46
|
+
|===
|
|
47
|
+
| Method | Description
|
|
48
|
+
|
|
49
|
+
| `name` | Node type name
|
|
50
|
+
| `attrs` | Hash of Attribute definitions
|
|
51
|
+
| `schema` | Parent Schema
|
|
52
|
+
| `content_match` | ContentMatch for valid children
|
|
53
|
+
| `is_block?` | Whether a block node
|
|
54
|
+
| `is_inline?` | Whether an inline node
|
|
55
|
+
| `is_textblock?` | Whether a block with inline content
|
|
56
|
+
| `is_leaf?` | Whether a leaf node (no children)
|
|
57
|
+
| `is_atom?` | Whether atomic (leaf or atom flag)
|
|
58
|
+
| `text?` | Whether text type
|
|
59
|
+
| `in_group?(name)` | Whether in a named group
|
|
60
|
+
| `create(attrs, content, marks)` | Create node (no validation)
|
|
61
|
+
| `create_checked(attrs, content, marks)` | Create with content validation
|
|
62
|
+
| `create_and_fill(attrs, content, marks)` | Create with default content
|
|
63
|
+
| `valid_content?(fragment)` | Check content validity
|
|
64
|
+
| `allows_mark_type?(mark_type)` | Check if mark type is allowed
|
|
65
|
+
|===
|
|
66
|
+
|
|
67
|
+
== Schema::MarkType
|
|
68
|
+
|
|
69
|
+
[cols="1,3"]
|
|
70
|
+
|===
|
|
71
|
+
| Method | Description
|
|
72
|
+
|
|
73
|
+
| `name` | Mark type name
|
|
74
|
+
| `rank` | Ordering rank
|
|
75
|
+
| `schema` | Parent Schema
|
|
76
|
+
| `create(attrs)` | Create a Schema::Mark instance
|
|
77
|
+
| `excluded` | Set of excluded mark types
|
|
78
|
+
|===
|
|
79
|
+
|
|
80
|
+
== Schema::ContentMatch
|
|
81
|
+
|
|
82
|
+
[cols="1,3"]
|
|
83
|
+
|===
|
|
84
|
+
| Method | Description
|
|
85
|
+
|
|
86
|
+
| `valid_end` | Whether this is a valid terminal state
|
|
87
|
+
| `match_type(node_type)` | Match a node type, returns next state or nil
|
|
88
|
+
| `match_fragment(fragment)` | Match a full fragment
|
|
89
|
+
| `fill_before(after:, to_end:)` | Compute filler content
|
|
90
|
+
| `find_wrapping(target_type)` | Find wrapping for content
|
|
91
|
+
| `inline_content?` | Whether accepts inline content
|
|
92
|
+
| `default_type` | Default NodeType
|
|
93
|
+
| `edge_count` | Number of edges
|
|
94
|
+
| `edge(n)` | Edge at index
|
|
95
|
+
|===
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
title: Transform API
|
|
4
|
+
parent: Reference
|
|
5
|
+
nav_order: 3
|
|
6
|
+
---
|
|
7
|
+
= Transform API
|
|
8
|
+
|
|
9
|
+
== Prosereflect::Transform::Transform
|
|
10
|
+
|
|
11
|
+
=== Constructor
|
|
12
|
+
|
|
13
|
+
`Transform.new(doc)` -- create a transform chain for the given document.
|
|
14
|
+
|
|
15
|
+
=== Step-adding Methods
|
|
16
|
+
|
|
17
|
+
[cols="1,3"]
|
|
18
|
+
|===
|
|
19
|
+
| Method | Description
|
|
20
|
+
|
|
21
|
+
| `add_mark(from, to, mark)` | Add a mark to range
|
|
22
|
+
| `remove_mark(from, to, mark)` | Remove a mark from range
|
|
23
|
+
| `insert(pos, content)` | Insert content at position
|
|
24
|
+
| `delete(from, to)` | Delete content in range
|
|
25
|
+
| `replace(from, to, slice)` | Replace range with slice
|
|
26
|
+
| `replace_with(from, to, *nodes)` | Replace range with nodes
|
|
27
|
+
| `set_node_attribute(pos, attrs)` | Set attributes on node at pos
|
|
28
|
+
| `set_doc_attribute(attrs)` | Set document-level attributes
|
|
29
|
+
| `split(pos, depth)` | Split at position
|
|
30
|
+
| `join(pos, depth)` | Join nodes at position
|
|
31
|
+
| `lift(range, target)` | Lift content out of wrapper
|
|
32
|
+
| `wrap(range, wrappers)` | Wrap content in new nodes
|
|
33
|
+
|===
|
|
34
|
+
|
|
35
|
+
=== Access Methods
|
|
36
|
+
|
|
37
|
+
[cols="1,3"]
|
|
38
|
+
|===
|
|
39
|
+
| Method | Description
|
|
40
|
+
|
|
41
|
+
| `doc` | Apply steps and return resulting document
|
|
42
|
+
| `steps` | Array of accumulated Step objects
|
|
43
|
+
| `mapping` | The Mapping tracking position changes
|
|
44
|
+
| `maps` | Array of StepMap objects
|
|
45
|
+
| `size` | Number of accumulated steps
|
|
46
|
+
| `empty?` | Whether no steps are accumulated
|
|
47
|
+
|===
|
|
48
|
+
|
|
49
|
+
=== Other Methods
|
|
50
|
+
|
|
51
|
+
[cols="1,3"]
|
|
52
|
+
|===
|
|
53
|
+
| Method | Description
|
|
54
|
+
|
|
55
|
+
| `apply` | Apply all steps (returns self)
|
|
56
|
+
| `rollback` | Undo the last step
|
|
57
|
+
| `clone` | Create a new Transform with same doc
|
|
58
|
+
| `can_apply?` | Check if all steps can be applied
|
|
59
|
+
|===
|
|
60
|
+
|
|
61
|
+
== Step Types
|
|
62
|
+
|
|
63
|
+
All inherit from `Prosereflect::Transform::Step`.
|
|
64
|
+
|
|
65
|
+
* `ReplaceStep` -- replace range with slice
|
|
66
|
+
* `ReplaceAroundStep` -- replace around a gap
|
|
67
|
+
* `AddMarkStep` -- add mark to range
|
|
68
|
+
* `RemoveMarkStep` -- remove mark from range
|
|
69
|
+
* `AttrStep` -- set node attributes
|
|
70
|
+
* `InsertStep` -- insert content at position
|
|
71
|
+
* `DeleteStep` -- delete content in range
|
|
72
|
+
|
|
73
|
+
== Supporting Types
|
|
74
|
+
|
|
75
|
+
* `Slice` -- document slice with open boundaries
|
|
76
|
+
* `StepMap` -- position mapping for a single step
|
|
77
|
+
* `Mapping` -- composed position mapping across multiple steps
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
title: Document Model
|
|
4
|
+
parent: Understanding
|
|
5
|
+
nav_order: 1
|
|
6
|
+
---
|
|
7
|
+
= Document Model
|
|
8
|
+
|
|
9
|
+
prosereflect models a ProseMirror document as a tree of Node objects. Every node has a `type`, optional `attrs`, optional `marks`, and optional `content` (child nodes).
|
|
10
|
+
|
|
11
|
+
== Node Hierarchy
|
|
12
|
+
|
|
13
|
+
----
|
|
14
|
+
Document
|
|
15
|
+
├── Paragraph
|
|
16
|
+
│ ├── Text (with marks)
|
|
17
|
+
│ └── Text
|
|
18
|
+
├── Heading (level: 1-6)
|
|
19
|
+
│ └── Text
|
|
20
|
+
├── Table
|
|
21
|
+
│ ├── TableRow
|
|
22
|
+
│ │ ├── TableHeader
|
|
23
|
+
│ │ └── TableCell
|
|
24
|
+
│ │ └── Paragraph
|
|
25
|
+
│ └── TableRow
|
|
26
|
+
├── BulletList / OrderedList
|
|
27
|
+
│ └── ListItem
|
|
28
|
+
│ └── Paragraph
|
|
29
|
+
├── Blockquote
|
|
30
|
+
│ └── Paragraph
|
|
31
|
+
├── CodeBlockWrapper
|
|
32
|
+
│ └── CodeBlock
|
|
33
|
+
├── Image
|
|
34
|
+
├── HorizontalRule
|
|
35
|
+
└── ...
|
|
36
|
+
----
|
|
37
|
+
|
|
38
|
+
== Node Size
|
|
39
|
+
|
|
40
|
+
Each node has a `node_size` that represents its footprint in the document position space:
|
|
41
|
+
|
|
42
|
+
* **Non-text nodes**: `1` (opening token) + sum of children's `node_size`
|
|
43
|
+
* **Text nodes**: `text.length + 1`
|
|
44
|
+
|
|
45
|
+
Example: A document with one paragraph containing "hi" has `node_size` of 5:
|
|
46
|
+
|
|
47
|
+
[source,ruby]
|
|
48
|
+
----
|
|
49
|
+
doc.node_size # 1 (doc) + 1 (para open) + 3 ("hi" + 1) = 5
|
|
50
|
+
----
|
|
51
|
+
|
|
52
|
+
== Positions
|
|
53
|
+
|
|
54
|
+
Positions are integer offsets into the document tree. Position 0 is before the first child of the root. Each non-text node contributes its opening token (position +1) and its content. Text nodes contribute `text.length + 1`.
|
|
55
|
+
|
|
56
|
+
[source,ruby]
|
|
57
|
+
----
|
|
58
|
+
# doc(p("abc"))
|
|
59
|
+
# Position 0: before doc content
|
|
60
|
+
# Position 1: before paragraph content
|
|
61
|
+
# Position 2: before "a"
|
|
62
|
+
# Position 3: between "a" and "b"
|
|
63
|
+
# Position 4: between "b" and "c"
|
|
64
|
+
# Position 5: after "c" (end of text)
|
|
65
|
+
----
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
title: Fragment
|
|
4
|
+
parent: Understanding
|
|
5
|
+
nav_order: 2
|
|
6
|
+
---
|
|
7
|
+
= Fragment
|
|
8
|
+
|
|
9
|
+
`Prosereflect::Fragment` represents a flat sequence of nodes. It is the primary data structure for node content and slice content.
|
|
10
|
+
|
|
11
|
+
== Creating Fragments
|
|
12
|
+
|
|
13
|
+
[source,ruby]
|
|
14
|
+
----
|
|
15
|
+
# From an array of nodes
|
|
16
|
+
frag = Prosereflect::Fragment.new([node1, node2])
|
|
17
|
+
|
|
18
|
+
# Empty fragment
|
|
19
|
+
frag = Prosereflect::Fragment.empty
|
|
20
|
+
|
|
21
|
+
# From arbitrary content
|
|
22
|
+
frag = Prosereflect::Fragment.from(node)
|
|
23
|
+
frag = Prosereflect::Fragment.from([node1, node2])
|
|
24
|
+
frag = Prosereflect::Fragment.from(existing_fragment) # returns as-is
|
|
25
|
+
----
|
|
26
|
+
|
|
27
|
+
== Core Methods
|
|
28
|
+
|
|
29
|
+
* `size` -- total node_size of all contained nodes
|
|
30
|
+
* `empty?` -- whether the fragment has no nodes
|
|
31
|
+
* `length` / `count` -- number of nodes
|
|
32
|
+
* `to_a` -- convert to a plain array
|
|
33
|
+
* `each` -- iterate over nodes
|
|
34
|
+
* `[](index)` -- access node by index
|
|
35
|
+
|
|
36
|
+
== Manipulation
|
|
37
|
+
|
|
38
|
+
* `append(other)` -- concatenate two fragments (returns new Fragment)
|
|
39
|
+
* `cut(from, to)` -- extract a sub-fragment within position range
|
|
40
|
+
* `replace_child(index, replacement)` -- replace a node at index
|
|
41
|
+
|
|
42
|
+
== Traversal
|
|
43
|
+
|
|
44
|
+
* `nodes_between(from, to, &block)` -- iterate nodes within position range, callback receives `(node, position)`
|
|
45
|
+
* `descendants(&block)` -- iterate all descendant nodes
|
|
46
|
+
* `text_between(from, to)` -- extract text between positions
|
|
47
|
+
|
|
48
|
+
== Comparison
|
|
49
|
+
|
|
50
|
+
* `eq?(other)` -- structural equality check
|
|
51
|
+
* `find_diff_start(other)` -- find first position where two fragments differ
|
|
52
|
+
* `find_diff_end(other)` -- find last position where two fragments differ
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
title: Understanding
|
|
4
|
+
nav_order: 4
|
|
5
|
+
has_children: true
|
|
6
|
+
---
|
|
7
|
+
= Understanding
|
|
8
|
+
|
|
9
|
+
This section explains the core concepts behind prosereflect's document model.
|
|
10
|
+
|
|
11
|
+
* x:document-model[Document Model] -- Node hierarchy and content model
|
|
12
|
+
* x:fragment[Fragment] -- Flat content collections
|
|
13
|
+
* x:resolved-position[Resolved Positions] -- Position resolution in the document tree
|
|
14
|
+
* x:slice[Slice] -- Document slices with open boundaries
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
title: Resolved Positions
|
|
4
|
+
parent: Understanding
|
|
5
|
+
nav_order: 3
|
|
6
|
+
---
|
|
7
|
+
= Resolved Positions
|
|
8
|
+
|
|
9
|
+
`Prosereflect::ResolvedPos` represents a document position that has been resolved to a specific location in the document tree. Resolving a position gives you information about the surrounding context.
|
|
10
|
+
|
|
11
|
+
== Creating a ResolvedPos
|
|
12
|
+
|
|
13
|
+
[source,ruby]
|
|
14
|
+
----
|
|
15
|
+
doc = Prosereflect::Parser.parse_document(data)
|
|
16
|
+
resolved = doc.resolve(3)
|
|
17
|
+
----
|
|
18
|
+
|
|
19
|
+
== Properties
|
|
20
|
+
|
|
21
|
+
* `pos` -- the absolute document position
|
|
22
|
+
* `depth` -- how deep in the tree this position is (0 = root)
|
|
23
|
+
* `parent` -- the parent node at current depth
|
|
24
|
+
* `parent_offset` -- offset within the parent node (`pos - start`)
|
|
25
|
+
|
|
26
|
+
== Depth-based Access
|
|
27
|
+
|
|
28
|
+
These methods accept an optional `depth` parameter (default: current depth):
|
|
29
|
+
|
|
30
|
+
* `node(depth)` -- node at the given depth
|
|
31
|
+
* `index(depth)` -- index within parent at depth
|
|
32
|
+
* `start(depth)` -- start position of node at depth
|
|
33
|
+
* `end_(depth)` -- end position of node at depth
|
|
34
|
+
|
|
35
|
+
== Boundary Checks
|
|
36
|
+
|
|
37
|
+
* `block?` -- whether the parent is a block node
|
|
38
|
+
* `inline?` -- whether the parent is an inline node
|
|
39
|
+
* `text_block?` -- whether the parent is a text block
|
|
40
|
+
* `start_of_parent?` -- whether at start of parent
|
|
41
|
+
* `end_of_parent?` -- whether at end of parent
|
|
42
|
+
* `before?` -- whether at the start of the current node
|
|
43
|
+
* `after?` -- whether at the end of the current node
|
|
44
|
+
|
|
45
|
+
== Comparing Positions
|
|
46
|
+
|
|
47
|
+
* `shared_depth(other)` -- the deepest depth shared with another position
|
|
48
|
+
* `block_range(other)` -- create a NodeRange between two positions
|
|
49
|
+
|
|
50
|
+
== Marks
|
|
51
|
+
|
|
52
|
+
* `marks` -- marks at this position
|
|
53
|
+
* `marks_between(from, to, marks)` -- collect marks between positions
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
title: Slice
|
|
4
|
+
parent: Understanding
|
|
5
|
+
nav_order: 4
|
|
6
|
+
---
|
|
7
|
+
= Slice
|
|
8
|
+
|
|
9
|
+
`Prosereflect::Transform::Slice` represents a contiguous portion of a document that can be inserted, deleted, or moved. Slices track "open" boundaries for proper node joining during replacements.
|
|
10
|
+
|
|
11
|
+
== Creating Slices
|
|
12
|
+
|
|
13
|
+
[source,ruby]
|
|
14
|
+
----
|
|
15
|
+
# From a fragment
|
|
16
|
+
slice = Prosereflect::Transform::Slice.new(fragment)
|
|
17
|
+
|
|
18
|
+
# With open boundaries
|
|
19
|
+
slice = Prosereflect::Transform::Slice.new(fragment, open_start: 1, open_end: 1)
|
|
20
|
+
|
|
21
|
+
# Empty slice
|
|
22
|
+
slice = Prosereflect::Transform::Slice.empty
|
|
23
|
+
----
|
|
24
|
+
|
|
25
|
+
== Open Boundaries
|
|
26
|
+
|
|
27
|
+
`open_start` and `open_end` indicate how many levels of the slice's first/last nodes are "open" (not included in the content). This is used when splitting or joining nodes during transforms:
|
|
28
|
+
|
|
29
|
+
* `open_start = 0` -- the first node is complete
|
|
30
|
+
* `open_start = 1` -- the first node is open at the top (its content extends beyond the slice)
|
|
31
|
+
|
|
32
|
+
== Properties
|
|
33
|
+
|
|
34
|
+
* `content` -- the `Fragment` containing the slice's nodes
|
|
35
|
+
* `open_start` -- number of open levels at the start
|
|
36
|
+
* `open_end` -- number of open levels at the end
|
|
37
|
+
* `size` -- total size including open boundaries (`content_size + open_start + open_end`)
|
|
38
|
+
* `content_size` -- size of just the content fragment
|
|
39
|
+
* `empty?` -- whether the slice has no content and no open boundaries
|
|
40
|
+
|
|
41
|
+
== Methods
|
|
42
|
+
|
|
43
|
+
* `cut(from, to)` -- return a sub-slice
|
|
44
|
+
* `eq?(other)` -- structural equality check
|
|
45
|
+
|
|
46
|
+
== Use in Transforms
|
|
47
|
+
|
|
48
|
+
Slices are used as the replacement content in `ReplaceStep` and `ReplaceAroundStep`:
|
|
49
|
+
|
|
50
|
+
[source,ruby]
|
|
51
|
+
----
|
|
52
|
+
tx = Prosereflect::Transform::Transform.new(doc)
|
|
53
|
+
tx.replace(2, 5, Prosereflect::Transform::Slice.new(fragment))
|
|
54
|
+
----
|
data/docs/lychee.toml
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Lychee Link Checker Configuration
|
|
2
|
+
# For Prosereflect Documentation
|
|
3
|
+
# https://github.com/lycheeverse/lychee
|
|
4
|
+
|
|
5
|
+
# Cache results to avoid re-checking same URLs
|
|
6
|
+
cache = true
|
|
7
|
+
max_cache_age = "1d"
|
|
8
|
+
|
|
9
|
+
# Check both source files and built site
|
|
10
|
+
include_verbatim = true
|
|
11
|
+
|
|
12
|
+
# File types to check (regex patterns)
|
|
13
|
+
include = [
|
|
14
|
+
"_site/**/*.html",
|
|
15
|
+
".*\\.adoc$",
|
|
16
|
+
".*\\.md$"
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
# Excluded paths
|
|
20
|
+
exclude = [
|
|
21
|
+
".git",
|
|
22
|
+
".github",
|
|
23
|
+
"node_modules",
|
|
24
|
+
"vendor",
|
|
25
|
+
".bundle",
|
|
26
|
+
".sass-cache",
|
|
27
|
+
".jekyll-cache",
|
|
28
|
+
"_site/.jekyll-cache",
|
|
29
|
+
"Gemfile.lock"
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
# Link checking behavior
|
|
33
|
+
max_redirects = 10
|
|
34
|
+
max_retries = 3
|
|
35
|
+
timeout = 30
|
|
36
|
+
|
|
37
|
+
# Accept status codes
|
|
38
|
+
accept = [
|
|
39
|
+
"100..=103", # Informational
|
|
40
|
+
"200..=299", # Success
|
|
41
|
+
"429" # Too Many Requests (retry handled by max_retries)
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
# User agent to identify ourselves
|
|
45
|
+
user_agent = "lychee/prosereflect-docs-link-checker"
|
|
46
|
+
|
|
47
|
+
# Check HTTP, HTTPS, and file:// schemes
|
|
48
|
+
scheme = ["https", "http", "file"]
|
|
49
|
+
|
|
50
|
+
# Handle different link types
|
|
51
|
+
include_mail = false # Don't check mailto: links
|
|
52
|
+
|
|
53
|
+
# Maximum concurrent requests
|
|
54
|
+
max_concurrency = 10
|
|
55
|
+
|
|
56
|
+
# Verbose output for debugging
|
|
57
|
+
verbose = "warn"
|
|
58
|
+
|
|
59
|
+
# Require HTTPS where possible
|
|
60
|
+
require_https = false # Don't enforce
|
|
61
|
+
|
|
62
|
+
# Index files for directory URLs
|
|
63
|
+
index_files = ["index.html"]
|
|
@@ -74,6 +74,15 @@ module Prosereflect
|
|
|
74
74
|
attrs.delete("citation")
|
|
75
75
|
end
|
|
76
76
|
|
|
77
|
+
def to_h
|
|
78
|
+
hash = super
|
|
79
|
+
if hash["attrs"]&.key?("citation") && hash["attrs"]["citation"].nil?
|
|
80
|
+
hash["attrs"].delete("citation")
|
|
81
|
+
hash.delete("attrs") if hash["attrs"].empty?
|
|
82
|
+
end
|
|
83
|
+
hash
|
|
84
|
+
end
|
|
85
|
+
|
|
77
86
|
def add_paragraph(text)
|
|
78
87
|
paragraph = Paragraph.new
|
|
79
88
|
paragraph.add_text(text)
|
|
@@ -19,22 +19,24 @@ module Prosereflect
|
|
|
19
19
|
|
|
20
20
|
def initialize(attributes = {})
|
|
21
21
|
attributes[:content] ||= []
|
|
22
|
-
|
|
22
|
+
# Only apply default if attrs key is completely absent
|
|
23
|
+
unless attributes.key?(:attrs) || attributes.key?("attrs")
|
|
24
|
+
attributes[:attrs] = { "bullet_style" => nil }
|
|
25
|
+
end
|
|
23
26
|
super
|
|
24
27
|
end
|
|
25
28
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
@bullet_style || attrs&.[]("bullet_style")
|
|
29
|
+
# Use *args to distinguish between create (no args) and create(nil)
|
|
30
|
+
# create with no args -> defaults applied
|
|
31
|
+
# create(nil) from parser -> no defaults, attrs explicitly nil
|
|
32
|
+
def self.create(*args)
|
|
33
|
+
if args.empty?
|
|
34
|
+
# No attrs provided - let initialize apply defaults
|
|
35
|
+
new(type: PM_TYPE)
|
|
36
|
+
else
|
|
37
|
+
attrs = args[0]
|
|
38
|
+
new({ type: PM_TYPE, attrs: attrs })
|
|
39
|
+
end
|
|
38
40
|
end
|
|
39
41
|
|
|
40
42
|
def add_item(text)
|
|
@@ -73,12 +75,16 @@ module Prosereflect
|
|
|
73
75
|
end.join("\n")
|
|
74
76
|
end
|
|
75
77
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
78
|
+
def bullet_style=(value)
|
|
79
|
+
@bullet_style = value
|
|
80
|
+
return if value.nil?
|
|
81
|
+
|
|
82
|
+
self.attrs ||= {}
|
|
83
|
+
attrs["bullet_style"] = value
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def bullet_style
|
|
87
|
+
@bullet_style || attrs&.[]("bullet_style")
|
|
82
88
|
end
|
|
83
89
|
end
|
|
84
90
|
end
|
|
@@ -82,12 +82,8 @@ module Prosereflect
|
|
|
82
82
|
|
|
83
83
|
def to_h
|
|
84
84
|
hash = super
|
|
85
|
-
hash["attrs"] = {
|
|
86
|
-
"content" => content,
|
|
87
|
-
"language" => language,
|
|
88
|
-
}
|
|
85
|
+
hash["attrs"] = { "language" => language }
|
|
89
86
|
hash["attrs"]["line_numbers"] = line_numbers if line_numbers
|
|
90
|
-
hash.delete("content")
|
|
91
87
|
hash
|
|
92
88
|
end
|
|
93
89
|
|