rsyntaxtree 2.1.0 → 2.2.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/.dockerignore +5 -0
- data/CHANGELOG.md +18 -0
- data/CODE_OF_CONDUCT.md +85 -0
- data/CONTRIBUTING.md +89 -0
- data/README.md +11 -17
- data/bin/rsyntaxtree +3 -6
- data/lib/rsyntaxtree/notation_examples.md +42 -16
- data/lib/rsyntaxtree/string_parser.rb +74 -8
- data/lib/rsyntaxtree/version.rb +1 -1
- data/lib/rsyntaxtree.rb +114 -13
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fe4cd7a8b28ffa968f99ff228e3a182a64d420e0405cd27e120b5f4ff02fe48e
|
|
4
|
+
data.tar.gz: 23a64934f448707de2b55cad4c206d65fab18aa7d6c1cd3aab0b1871efd9be1e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2dfd53e10a62187649fb3d7285fa38189ef25cf56d6af7ceea27fba3cc7358b1f7d528db6466607061120fab61a2ff7bd9b84fa0b989e4313faaac306af42235
|
|
7
|
+
data.tar.gz: 7399787ea179bdd17910fa9695cc292a18cb5b8e9b3948e943bf32fd52fd05f89e6ec0e2792138197d56c839b9960bab678489a976590667eca9b4dd80679029
|
data/.dockerignore
ADDED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.2.0] - 2026-09-01
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
- Validation reports every error of a stage, not just the first one found.
|
|
7
|
+
An input with three bad labels lists all three in the `errors` array of
|
|
8
|
+
the JSON diagnosis, so one round of fixes covers them. The stages are
|
|
9
|
+
ordered — options, bracket structure, labels, whole-tree checks — and a
|
|
10
|
+
mistake stops the later stages, with a `note` saying that fixing what is
|
|
11
|
+
listed may reveal more; nothing is reported whose appearance is an
|
|
12
|
+
artifact of an earlier mistake. Duplicate failures collapse to one entry
|
|
13
|
+
and the list stops at twenty. The schema is unchanged
|
|
14
|
+
(`rsyntaxtree.error/1`): the same array, more of it.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- `RSGenerator.diagnose(text, options)` returns the full diagnosis as a
|
|
18
|
+
hash — what `--validate` prints — for programmatic callers.
|
|
19
|
+
`check_data` keeps its contract of raising the first error.
|
|
20
|
+
|
|
3
21
|
## [2.1.0] - 2026-08-29
|
|
4
22
|
|
|
5
23
|
### Changed
|
data/CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
|
|
2
|
+
# Contributor Covenant Code of Conduct
|
|
3
|
+
|
|
4
|
+
## Our Pledge
|
|
5
|
+
|
|
6
|
+
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
|
|
7
|
+
|
|
8
|
+
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
|
|
9
|
+
|
|
10
|
+
## Our Standards
|
|
11
|
+
|
|
12
|
+
Examples of behavior that contributes to a positive environment for our community include:
|
|
13
|
+
|
|
14
|
+
* Demonstrating empathy and kindness toward other people
|
|
15
|
+
* Being respectful of differing opinions, viewpoints, and experiences
|
|
16
|
+
* Giving and gracefully accepting constructive feedback
|
|
17
|
+
* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
|
|
18
|
+
* Focusing on what is best not just for us as individuals, but for the overall community
|
|
19
|
+
|
|
20
|
+
Examples of unacceptable behavior include:
|
|
21
|
+
|
|
22
|
+
* The use of sexualized language or imagery, and sexual attention or advances of any kind
|
|
23
|
+
* Trolling, insulting or derogatory comments, and personal or political attacks
|
|
24
|
+
* Public or private harassment
|
|
25
|
+
* Publishing others' private information, such as a physical or email address, without their explicit permission
|
|
26
|
+
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
|
27
|
+
|
|
28
|
+
## Enforcement Responsibilities
|
|
29
|
+
|
|
30
|
+
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
|
|
31
|
+
|
|
32
|
+
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
|
|
33
|
+
|
|
34
|
+
## Scope
|
|
35
|
+
|
|
36
|
+
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
|
|
37
|
+
|
|
38
|
+
## Enforcement
|
|
39
|
+
|
|
40
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at <yohasebe@gmail.com>. All complaints will be reviewed and investigated promptly and fairly.
|
|
41
|
+
|
|
42
|
+
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
|
|
43
|
+
|
|
44
|
+
## Enforcement Guidelines
|
|
45
|
+
|
|
46
|
+
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
|
|
47
|
+
|
|
48
|
+
### 1. Correction
|
|
49
|
+
|
|
50
|
+
**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
|
|
51
|
+
|
|
52
|
+
**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
|
|
53
|
+
|
|
54
|
+
### 2. Warning
|
|
55
|
+
|
|
56
|
+
**Community Impact**: A violation through a single incident or series of actions.
|
|
57
|
+
|
|
58
|
+
**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
|
|
59
|
+
|
|
60
|
+
### 3. Temporary Ban
|
|
61
|
+
|
|
62
|
+
**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
|
|
63
|
+
|
|
64
|
+
**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
|
|
65
|
+
|
|
66
|
+
### 4. Permanent Ban
|
|
67
|
+
|
|
68
|
+
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
|
|
69
|
+
|
|
70
|
+
**Consequence**: A permanent ban from any sort of public interaction within the community.
|
|
71
|
+
|
|
72
|
+
## Attribution
|
|
73
|
+
|
|
74
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
|
75
|
+
|
|
76
|
+
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
|
77
|
+
|
|
78
|
+
For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations].
|
|
79
|
+
|
|
80
|
+
[homepage]: https://www.contributor-covenant.org
|
|
81
|
+
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
|
82
|
+
[Mozilla CoC]: https://github.com/mozilla/diversity
|
|
83
|
+
[FAQ]: https://www.contributor-covenant.org/faq
|
|
84
|
+
[translations]: https://www.contributor-covenant.org/translations
|
|
85
|
+
|
data/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Contributing to RSyntaxTree
|
|
2
|
+
|
|
3
|
+
This page explains how to report a problem, propose a change, and get a
|
|
4
|
+
development environment running.
|
|
5
|
+
|
|
6
|
+
## Reporting bugs
|
|
7
|
+
|
|
8
|
+
Open an issue at <https://github.com/yohasebe/rsyntaxtree/issues> with:
|
|
9
|
+
|
|
10
|
+
- the input text (the bracket notation) and the options you used
|
|
11
|
+
- what you expected and what you got — for drawing problems, attach the image
|
|
12
|
+
- your platform and how you run RSyntaxTree (gem, Docker, or the
|
|
13
|
+
[web interface](https://yohasebe.com/rsyntaxtree))
|
|
14
|
+
|
|
15
|
+
If the input fails to draw, include the output of:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
rsyntaxtree --validate "your input here"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
It reports the problem in a structured form that is usually enough to
|
|
22
|
+
diagnose the issue.
|
|
23
|
+
|
|
24
|
+
## Suggesting features
|
|
25
|
+
|
|
26
|
+
Open an issue describing what you want to draw and, ideally, a hand-drawn or
|
|
27
|
+
published example of the intended output. The
|
|
28
|
+
[gallery](https://yohasebe.github.io/rsyntaxtree/examples) shows the range of
|
|
29
|
+
figures the notation covers today; a suggestion that names the linguistic
|
|
30
|
+
framework it serves is easier to evaluate than one described purely in terms
|
|
31
|
+
of graphics.
|
|
32
|
+
|
|
33
|
+
Not every good idea belongs in RSyntaxTree. The project deliberately keeps
|
|
34
|
+
the notation small, so proposals are weighed against what they add for
|
|
35
|
+
users who do not need them.
|
|
36
|
+
|
|
37
|
+
## Setting up for development
|
|
38
|
+
|
|
39
|
+
System libraries first (Pango for text measurement, librsvg for rasterizing):
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Debian/Ubuntu
|
|
43
|
+
apt install libpango1.0-dev librsvg2-dev libgirepository1.0-dev gobject-introspection
|
|
44
|
+
|
|
45
|
+
# macOS
|
|
46
|
+
brew install pkg-config pango librsvg gobject-introspection
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The tests measure text with the Noto fonts, so install them too — the
|
|
50
|
+
[Fonts](https://yohasebe.github.io/rsyntaxtree/documentation#fonts) section
|
|
51
|
+
of the manual lists the package names for each platform.
|
|
52
|
+
|
|
53
|
+
Then:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
git clone https://github.com/yohasebe/rsyntaxtree.git
|
|
57
|
+
cd rsyntaxtree
|
|
58
|
+
bundle install
|
|
59
|
+
bundle exec rake test
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The tests should pass before and after your change.
|
|
63
|
+
|
|
64
|
+
## Making changes
|
|
65
|
+
|
|
66
|
+
- Every behavioral change needs a test that fails without it.
|
|
67
|
+
- The committed gallery figures are generated inside Docker
|
|
68
|
+
(`rake docker_build && rake docker_generate`) so that font differences
|
|
69
|
+
between machines do not rewrite them. Do not regenerate them with a local
|
|
70
|
+
`rake generate`; if your change intentionally alters figures, say so in
|
|
71
|
+
the pull request and the maintainer will regenerate them.
|
|
72
|
+
- The manual lives in `docs/documentation.md` (English) and
|
|
73
|
+
`docs/documentation_ja.md` (Japanese). If your change affects the
|
|
74
|
+
notation or an option, update at least the English manual; the reference
|
|
75
|
+
the gem installs (`lib/rsyntaxtree/notation_core.md`) and the LLM payload
|
|
76
|
+
(`rake llm_payload`) must be regenerated when examples or the manual
|
|
77
|
+
change — the test suite tells you when they are stale.
|
|
78
|
+
|
|
79
|
+
## Pull requests
|
|
80
|
+
|
|
81
|
+
Fork, branch, and open a pull request against `master`. A good pull request
|
|
82
|
+
explains the problem before the solution and stays small enough to review.
|
|
83
|
+
CI runs the test suite on Linux and an installation check on macOS; both
|
|
84
|
+
must pass.
|
|
85
|
+
|
|
86
|
+
## Questions
|
|
87
|
+
|
|
88
|
+
For anything that does not fit an issue, contact the maintainer:
|
|
89
|
+
Yoichiro Hasebe <yohasebe@gmail.com>.
|
data/README.md
CHANGED
|
@@ -135,23 +135,17 @@ See [Fonts](https://yohasebe.github.io/rsyntaxtree/documentation#install-fonts-f
|
|
|
135
135
|
gem install rsyntaxtree
|
|
136
136
|
```
|
|
137
137
|
|
|
138
|
-
### macOS
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
```bash
|
|
151
|
-
gem install rsyntaxtree
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
Alternatively, use the [Docker image](https://hub.docker.com/r/yohasebe/rsyntaxtree) or the [web interface](https://yohasebe.com/rsyntaxtree).
|
|
138
|
+
### macOS
|
|
139
|
+
|
|
140
|
+
With the libraries in place (`brew install pkg-config pango librsvg
|
|
141
|
+
gobject-introspection`), a plain `gem install rsyntaxtree` builds the native
|
|
142
|
+
extensions without further options — CI verifies this on every change. If an
|
|
143
|
+
older setup still fails to build `gobject-introspection`, `cairo-gobject` or
|
|
144
|
+
`gio2`, install those three first with
|
|
145
|
+
`-- --with-ldflags="-Wl,-undefined,dynamic_lookup"` appended, then install
|
|
146
|
+
RSyntaxTree; or skip the build entirely with the
|
|
147
|
+
[Docker image](https://hub.docker.com/r/yohasebe/rsyntaxtree) or the
|
|
148
|
+
[web interface](https://yohasebe.com/rsyntaxtree).
|
|
155
149
|
|
|
156
150
|
## Usage
|
|
157
151
|
|
data/bin/rsyntaxtree
CHANGED
|
@@ -214,12 +214,9 @@ data = RSyntaxTree::FormatConverter.to_bracket(data) if data
|
|
|
214
214
|
# Validate without drawing or writing anything: the diagnosis goes to stdout
|
|
215
215
|
# as JSON and the exit code carries the verdict.
|
|
216
216
|
if opts[:validate]
|
|
217
|
-
diagnosis =
|
|
218
|
-
RSyntaxTree::RSGenerator.
|
|
219
|
-
|
|
220
|
-
rescue RSTError => e
|
|
221
|
-
{ "schema" => "rsyntaxtree.error/1" }.merge(e.to_h)
|
|
222
|
-
end
|
|
217
|
+
diagnosis = { "schema" => "rsyntaxtree.error/1" }.merge(
|
|
218
|
+
RSyntaxTree::RSGenerator.diagnose(data, string_opts)
|
|
219
|
+
)
|
|
223
220
|
puts JSON.pretty_generate(diagnosis)
|
|
224
221
|
exit(diagnosis["ok"] ? 0 : 1)
|
|
225
222
|
end
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
RSyntaxTree examples:
|
|
1
|
+
RSyntaxTree examples: 81 trees, every one verified to draw.
|
|
2
2
|
|
|
3
3
|
Each is the input behind a figure in the gallery at https://yohasebe.github.io/rsyntaxtree/examples.
|
|
4
4
|
The settings line names the options the gallery records for that figure;
|
|
@@ -2586,30 +2586,56 @@ VAL\t#(SPR\t⟨<>⟩#)#)#)
|
|
|
2586
2586
|
VAL\t#(SPR\t⟨<>*NP*<>⟩#)#)#) [barks]]]
|
|
2587
2587
|
```
|
|
2588
2588
|
|
|
2589
|
-
## 085 — A tree on a tilted plane
|
|
2589
|
+
## 085 — A tree on a tilted plane, with a movement path
|
|
2590
2590
|
|
|
2591
2591
|
Category: General
|
|
2592
2592
|
Settings: fontstyle=noto-serif shear=20 vheight=1.0
|
|
2593
2593
|
|
|
2594
2594
|
```
|
|
2595
|
-
[
|
|
2596
|
-
[NP
|
|
2597
|
-
|
|
2598
|
-
[
|
|
2599
|
-
|
|
2600
|
-
[VP
|
|
2601
|
-
[V put]
|
|
2602
|
-
[NP
|
|
2603
|
-
[D the]
|
|
2604
|
-
[N book]
|
|
2605
|
-
]
|
|
2606
|
-
[PP
|
|
2607
|
-
[P on]
|
|
2595
|
+
[CP
|
|
2596
|
+
[NP what+>1]
|
|
2597
|
+
[C'
|
|
2598
|
+
[C did]
|
|
2599
|
+
[TP
|
|
2608
2600
|
[NP
|
|
2609
2601
|
[D the]
|
|
2610
|
-
[N
|
|
2602
|
+
[N man]
|
|
2603
|
+
]
|
|
2604
|
+
[VP
|
|
2605
|
+
[V'
|
|
2606
|
+
[V put]
|
|
2607
|
+
[NP *t*+1]
|
|
2608
|
+
]
|
|
2609
|
+
[PP
|
|
2610
|
+
[P on]
|
|
2611
|
+
[NP
|
|
2612
|
+
[D the]
|
|
2613
|
+
[N table]
|
|
2614
|
+
]
|
|
2615
|
+
]
|
|
2611
2616
|
]
|
|
2612
2617
|
]
|
|
2613
2618
|
]
|
|
2614
2619
|
]
|
|
2615
2620
|
```
|
|
2621
|
+
|
|
2622
|
+
## 086 — Feature structures on a tilted plane
|
|
2623
|
+
|
|
2624
|
+
Category: General
|
|
2625
|
+
Settings: color=none fontstyle=noto-serif leafstyle=nothing shear=20 vheight=1.5
|
|
2626
|
+
|
|
2627
|
+
```
|
|
2628
|
+
[#(*phrase*\
|
|
2629
|
+
---\
|
|
2630
|
+
SYN\t#(HEAD\t|1|#)#)
|
|
2631
|
+
[#(*word*\
|
|
2632
|
+
---\
|
|
2633
|
+
SYN\t#(HEAD\t#(*noun*\
|
|
2634
|
+
---\
|
|
2635
|
+
AGR\t|2|#(NUM\t*pl*#)#)#)#) [dogs]]
|
|
2636
|
+
[#(*word*\
|
|
2637
|
+
---\
|
|
2638
|
+
SYN\t#(HEAD\t|1|#(*verb*\
|
|
2639
|
+
---\
|
|
2640
|
+
AGR\t|2|#)#)#) [bark]]]
|
|
2641
|
+
```
|
|
@@ -15,9 +15,23 @@ require_relative 'utils'
|
|
|
15
15
|
module RSyntaxTree
|
|
16
16
|
class StringParser
|
|
17
17
|
attr_accessor :data, :elist, :pos, :id, :level
|
|
18
|
+
attr_reader :collected_errors
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
# In collect mode, reporting stops here but the walk does not: labels
|
|
21
|
+
# past this many failures are still checked (the tree must stay whole)
|
|
22
|
+
# without adding to the list.
|
|
23
|
+
COLLECTED_ERRORS_LIMIT = 20
|
|
24
|
+
|
|
25
|
+
# What stands in for a label that would not parse, when collecting.
|
|
26
|
+
# Any label that always parses will do; what matters is that it keeps
|
|
27
|
+
# the tree's shape.
|
|
28
|
+
PLACEHOLDER_LABEL = "x"
|
|
29
|
+
|
|
30
|
+
def initialize(str, fontset, fontsize, global, collect_errors: false)
|
|
20
31
|
@global = global
|
|
32
|
+
@collect_errors = collect_errors
|
|
33
|
+
@collected_errors = []
|
|
34
|
+
@more_errors = false
|
|
21
35
|
# Clean up the data a little to make processing easier
|
|
22
36
|
# repeated newlines => a newline
|
|
23
37
|
string = str.gsub(/[\n\r]+/m, "\n")
|
|
@@ -113,6 +127,41 @@ module RSyntaxTree
|
|
|
113
127
|
restore_rule_names_without_a_rule
|
|
114
128
|
end
|
|
115
129
|
|
|
130
|
+
def more_errors?
|
|
131
|
+
@more_errors
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# One element, or its recorded failure. Outside collect mode this is
|
|
135
|
+
# exactly the Element.new it wraps. In collect mode an RSTError is
|
|
136
|
+
# recorded and a placeholder element stands in, so the walk continues
|
|
137
|
+
# and every bad label is seen. The placeholder keeps the tree's shape —
|
|
138
|
+
# parenthood, childlessness, level — which is all a later label's
|
|
139
|
+
# verdict can depend on; what it cannot keep (a path marker the broken
|
|
140
|
+
# label carried) only matters to the whole-tree checks, and those do
|
|
141
|
+
# not run while collected errors stand.
|
|
142
|
+
def element_or_recorded_failure(id, parent, level, names_a_rule = false)
|
|
143
|
+
yield
|
|
144
|
+
rescue RSTError => e
|
|
145
|
+
raise unless @collect_errors
|
|
146
|
+
|
|
147
|
+
record_failure(e)
|
|
148
|
+
Element.new(id, parent, PLACEHOLDER_LABEL, level, @fontset, @fontsize, @global, names_a_rule)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# The same mistake in two copies of a label is one thing to fix, so a
|
|
152
|
+
# failure is recorded once per (code, label, position). Past the limit
|
|
153
|
+
# the list stops growing and only the fact that there was more is kept.
|
|
154
|
+
def record_failure(error)
|
|
155
|
+
key = [error.code, error.label, error.position]
|
|
156
|
+
return if @collected_errors.any? { |c| [c.code, c.label, c.position] == key }
|
|
157
|
+
|
|
158
|
+
if @collected_errors.length >= COLLECTED_ERRORS_LIMIT
|
|
159
|
+
@more_errors = true
|
|
160
|
+
else
|
|
161
|
+
@collected_errors << error
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
116
165
|
# A rule name names the step that produced a node from its daughters. A node
|
|
117
166
|
# with no daughters is the product of no step, so what looked like a name is
|
|
118
167
|
# a column of the label like any other, and it goes back.
|
|
@@ -128,8 +177,17 @@ module RSyntaxTree
|
|
|
128
177
|
next unless e.children.empty?
|
|
129
178
|
next if e.label_with_rule_name.nil?
|
|
130
179
|
|
|
131
|
-
|
|
132
|
-
|
|
180
|
+
# In collect mode the failed restoration is recorded and the element
|
|
181
|
+
# kept as it is; the tree stays whole either way.
|
|
182
|
+
restored = begin
|
|
183
|
+
Element.new(e.id, e.parent, e.label_with_rule_name,
|
|
184
|
+
e.level, @fontset, @fontsize, @global)
|
|
185
|
+
rescue RSTError => err
|
|
186
|
+
raise unless @collect_errors
|
|
187
|
+
|
|
188
|
+
record_failure(err)
|
|
189
|
+
next
|
|
190
|
+
end
|
|
133
191
|
restored.children = e.children
|
|
134
192
|
restored.type = e.type
|
|
135
193
|
@elist.elements[i] = restored
|
|
@@ -226,7 +284,9 @@ module RSyntaxTree
|
|
|
226
284
|
# Check for escaped square brackets
|
|
227
285
|
if token =~ /\A\\\[/ || token =~ /\A\\\]/
|
|
228
286
|
# Treat escaped brackets as regular text
|
|
229
|
-
element =
|
|
287
|
+
element = element_or_recorded_failure(@id, parent, @level) do
|
|
288
|
+
Element.new(@id, parent, token, @level, @fontset, @fontsize, @global)
|
|
289
|
+
end
|
|
230
290
|
@id += 1
|
|
231
291
|
@elist.add(element)
|
|
232
292
|
else
|
|
@@ -241,7 +301,7 @@ module RSyntaxTree
|
|
|
241
301
|
tl = token_r.length
|
|
242
302
|
parts[1] = token_r[spaceat, tl - spaceat].join
|
|
243
303
|
|
|
244
|
-
element =
|
|
304
|
+
element = element_or_recorded_failure(@id, parent, @level, true) do
|
|
245
305
|
Element.new(@id, parent, parts[0], @level, @fontset, @fontsize, @global, true)
|
|
246
306
|
rescue RSTError => e
|
|
247
307
|
# The first raw space splits a token into the node's label
|
|
@@ -277,11 +337,15 @@ module RSyntaxTree
|
|
|
277
337
|
@elist.add(element)
|
|
278
338
|
newparent = element.id
|
|
279
339
|
|
|
280
|
-
element =
|
|
340
|
+
element = element_or_recorded_failure(@id, @id - 1, @level + 1) do
|
|
341
|
+
Element.new(@id, @id - 1, parts[1], @level + 1, @fontset, @fontsize, @global)
|
|
342
|
+
end
|
|
281
343
|
@id += 1
|
|
282
344
|
else
|
|
283
345
|
joined = token_r.join
|
|
284
|
-
element =
|
|
346
|
+
element = element_or_recorded_failure(@id, parent, @level, true) do
|
|
347
|
+
Element.new(@id, parent, joined, @level, @fontset, @fontsize, @global, true)
|
|
348
|
+
end
|
|
285
349
|
@id += 1
|
|
286
350
|
newparent = element.id
|
|
287
351
|
end
|
|
@@ -291,7 +355,9 @@ module RSyntaxTree
|
|
|
291
355
|
end
|
|
292
356
|
else
|
|
293
357
|
if token.strip != ""
|
|
294
|
-
element =
|
|
358
|
+
element = element_or_recorded_failure(@id, parent, @level) do
|
|
359
|
+
Element.new(@id, parent, token, @level, @fontset, @fontsize, @global)
|
|
360
|
+
end
|
|
295
361
|
@id += 1
|
|
296
362
|
@elist.add(element)
|
|
297
363
|
end
|
data/lib/rsyntaxtree/version.rb
CHANGED
data/lib/rsyntaxtree.rb
CHANGED
|
@@ -139,14 +139,20 @@ class RSTError < StandardError
|
|
|
139
139
|
# the top, rather than repeated on every error.
|
|
140
140
|
REFERENCE = "rsyntaxtree --notation, or https://yohasebe.github.io/rsyntaxtree/llms-full.txt"
|
|
141
141
|
|
|
142
|
+
# One error as the hash the JSON diagnosis carries; to_h wraps a single
|
|
143
|
+
# one and diagnose collects many.
|
|
144
|
+
def error_entry
|
|
145
|
+
{ "code" => code.to_s,
|
|
146
|
+
"message" => message,
|
|
147
|
+
"label" => label,
|
|
148
|
+
"position" => position,
|
|
149
|
+
"hint" => hint,
|
|
150
|
+
"retryable" => retryable }.compact
|
|
151
|
+
end
|
|
152
|
+
|
|
142
153
|
def to_h
|
|
143
154
|
{ "ok" => false,
|
|
144
|
-
"errors" => [
|
|
145
|
-
"message" => message,
|
|
146
|
-
"label" => label,
|
|
147
|
-
"position" => position,
|
|
148
|
-
"hint" => hint,
|
|
149
|
-
"retryable" => retryable }.compact],
|
|
155
|
+
"errors" => [error_entry],
|
|
150
156
|
"reference" => REFERENCE }
|
|
151
157
|
end
|
|
152
158
|
end
|
|
@@ -185,13 +191,13 @@ module RSyntaxTree
|
|
|
185
191
|
|
|
186
192
|
if REMOVED_OPTIONS.key?(key)
|
|
187
193
|
raise RSTError.new(+"Error: option '#{key}' was removed in RSyntaxTree 2.0",
|
|
188
|
-
code: :invalid_option,
|
|
194
|
+
code: :invalid_option, label: key.to_s,
|
|
189
195
|
hint: "Use #{REMOVED_OPTIONS[key]} instead.",
|
|
190
196
|
retryable: false)
|
|
191
197
|
end
|
|
192
198
|
if OPTION_VALUES.key?(key) && !OPTION_VALUES[key].include?(value.to_s)
|
|
193
199
|
raise RSTError.new(+"Error: invalid value for option '#{key}': #{value.inspect}",
|
|
194
|
-
code: :invalid_option,
|
|
200
|
+
code: :invalid_option, label: key.to_s,
|
|
195
201
|
hint: "'#{key}' must be one of: #{OPTION_VALUES[key].join(', ')}.",
|
|
196
202
|
retryable: false)
|
|
197
203
|
end
|
|
@@ -202,14 +208,14 @@ module RSyntaxTree
|
|
|
202
208
|
if NUMERIC_RANGES.key?(key) && !value.is_a?(Numeric) &&
|
|
203
209
|
value.to_s.strip !~ /\A-?(\d+(\.\d+)?|\.\d+)\z/
|
|
204
210
|
raise RSTError.new(+"Error: invalid value for option '#{key}': #{value.inspect}",
|
|
205
|
-
code: :invalid_option,
|
|
211
|
+
code: :invalid_option, label: key.to_s,
|
|
206
212
|
hint: "'#{key}' takes a number.",
|
|
207
213
|
retryable: false)
|
|
208
214
|
end
|
|
209
215
|
if NUMERIC_RANGES.key?(key) && !NUMERIC_RANGES[key].cover?(value.to_f)
|
|
210
216
|
range = NUMERIC_RANGES[key]
|
|
211
217
|
raise RSTError.new(+"Error: invalid value for option '#{key}': #{value.inspect}",
|
|
212
|
-
code: :invalid_option,
|
|
218
|
+
code: :invalid_option, label: key.to_s,
|
|
213
219
|
hint: "'#{key}' must be in the range of #{range.begin}-#{range.end}.",
|
|
214
220
|
retryable: false)
|
|
215
221
|
end
|
|
@@ -288,7 +294,7 @@ module RSyntaxTree
|
|
|
288
294
|
else
|
|
289
295
|
unless COLOR_NAMES.include?(v.downcase) || v =~ /\A#(\h{3}|\h{6})\z/
|
|
290
296
|
raise RSTError.new(+"Error: invalid value for option 'shear_plane': #{value.inspect}",
|
|
291
|
-
code: :invalid_option,
|
|
297
|
+
code: :invalid_option, label: "shear_plane",
|
|
292
298
|
hint: "'shear_plane' is on, off, a colour name, " \
|
|
293
299
|
"or a hex colour of 3 or 6 digits.",
|
|
294
300
|
retryable: false)
|
|
@@ -333,7 +339,7 @@ module RSyntaxTree
|
|
|
333
339
|
# the combination is refused rather than approximated.
|
|
334
340
|
if @params[:derivation] == true && @params[:direction] == "ltr"
|
|
335
341
|
raise RSTError.new(+"Error: a derivation cannot be drawn left to right",
|
|
336
|
-
code: :invalid_option,
|
|
342
|
+
code: :invalid_option, label: "derivation",
|
|
337
343
|
hint: "A derivation runs down the page. Use direction ttb or btt, " \
|
|
338
344
|
"or turn derivation off.",
|
|
339
345
|
retryable: false)
|
|
@@ -346,7 +352,7 @@ module RSyntaxTree
|
|
|
346
352
|
# them. Refused for the same reason as left to right.
|
|
347
353
|
if @params[:derivation] == true && @params[:hide_default_connectors] == true
|
|
348
354
|
raise RSTError.new(+"Error: a derivation's rules cannot be hidden",
|
|
349
|
-
code: :invalid_option,
|
|
355
|
+
code: :invalid_option, label: "derivation",
|
|
350
356
|
hint: "The rules are what a derivation is drawn with, not a " \
|
|
351
357
|
"connector added to it. Turn off hide default connectors, " \
|
|
352
358
|
"or turn derivation off.",
|
|
@@ -448,6 +454,101 @@ module RSyntaxTree
|
|
|
448
454
|
end
|
|
449
455
|
end
|
|
450
456
|
|
|
457
|
+
# Every option error at once. The constructor is the only judge of an
|
|
458
|
+
# option, and its rules are not written out a second time here: it
|
|
459
|
+
# stops at its first complaint, so it is asked again with the option
|
|
460
|
+
# it complained about set aside, until it accepts what is left or
|
|
461
|
+
# names nothing to set aside. Each round removes one option, so the
|
|
462
|
+
# loop is as bounded as the option list.
|
|
463
|
+
def self.option_errors(params)
|
|
464
|
+
remaining = params.reject { |k, _| k.to_sym == :data }
|
|
465
|
+
errors = []
|
|
466
|
+
loop do
|
|
467
|
+
begin
|
|
468
|
+
new(remaining.merge(data: "[A a]"))
|
|
469
|
+
break
|
|
470
|
+
rescue RSTError => e
|
|
471
|
+
errors << e
|
|
472
|
+
key = e.label
|
|
473
|
+
break if key.nil? || remaining.keys.none? { |k| k.to_s == key }
|
|
474
|
+
|
|
475
|
+
remaining = remaining.reject { |k, _| k.to_s == key }
|
|
476
|
+
end
|
|
477
|
+
end
|
|
478
|
+
errors
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
# Parse in collect mode: every label that will not parse, and every
|
|
482
|
+
# rule name that turns out to have no rule behind it, recorded instead
|
|
483
|
+
# of raised. The walk is the real one — the same tokens, the same
|
|
484
|
+
# judgments — so what this reports and what drawing rejects cannot
|
|
485
|
+
# drift apart. Returns the errors and whether the list was cut short.
|
|
486
|
+
def collect_input_errors
|
|
487
|
+
sp = StringParser.new(@params[:data].gsub('&', '&'), @params[:fontset],
|
|
488
|
+
@params[:fontsize], @global, collect_errors: true)
|
|
489
|
+
sp.parse
|
|
490
|
+
[sp.collected_errors, sp.more_errors?]
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
NOTE_OPTIONS = "Not every option could be read, so the input itself has " \
|
|
494
|
+
"not been checked yet; fixing the options may reveal more."
|
|
495
|
+
NOTE_STRUCTURE = "The bracket structure could not be read, so the labels " \
|
|
496
|
+
"have not been checked yet; fixing it may reveal more."
|
|
497
|
+
NOTE_LABELS = "Whole-tree checks (paths, output limits) run only once " \
|
|
498
|
+
"every label reads, so fixing these may reveal more."
|
|
499
|
+
NOTE_TRUNCATED = " Only the first #{StringParser::COLLECTED_ERRORS_LIMIT} " \
|
|
500
|
+
"problems are listed."
|
|
501
|
+
|
|
502
|
+
# The whole diagnosis at once, as the hash the CLI prints: every error
|
|
503
|
+
# of the first stage that finds any, not just the first error found.
|
|
504
|
+
# Stages are ordered so that nothing is reported whose appearance is an
|
|
505
|
+
# artifact of an earlier mistake — a missing bracket shifts every token
|
|
506
|
+
# after it, an unreadable hyphen option changes what counts as markup —
|
|
507
|
+
# and when a stage stops the walk, the note says that fixing what is
|
|
508
|
+
# listed may reveal more. check_data keeps its contract (raise the
|
|
509
|
+
# first error) for callers that want a verdict rather than a list.
|
|
510
|
+
def self.diagnose(text, params = {})
|
|
511
|
+
errors = []
|
|
512
|
+
note = nil
|
|
513
|
+
if text.to_s == ""
|
|
514
|
+
errors << RSTError.new(+"Error: input text is empty", code: :empty_input, retryable: false)
|
|
515
|
+
else
|
|
516
|
+
begin
|
|
517
|
+
errors = option_errors(params)
|
|
518
|
+
if errors.any?
|
|
519
|
+
note = NOTE_OPTIONS
|
|
520
|
+
else
|
|
521
|
+
StringParser.valid?(text)
|
|
522
|
+
gen = new(params.merge(data: text))
|
|
523
|
+
collected, truncated = gen.collect_input_errors
|
|
524
|
+
if collected.any?
|
|
525
|
+
errors = collected
|
|
526
|
+
note = NOTE_LABELS
|
|
527
|
+
note += NOTE_TRUNCATED if truncated
|
|
528
|
+
else
|
|
529
|
+
gen.validate!
|
|
530
|
+
end
|
|
531
|
+
end
|
|
532
|
+
rescue RSTError => e
|
|
533
|
+
errors << e
|
|
534
|
+
note = NOTE_STRUCTURE if %i[empty_brackets unbalanced_brackets].include?(e.code)
|
|
535
|
+
rescue StandardError => e
|
|
536
|
+
# The same promise check_data makes: a defect anywhere in here —
|
|
537
|
+
# the option probing included — is still a verdict of "no", in
|
|
538
|
+
# the same shape, not a backtrace.
|
|
539
|
+
errors << RSTError.new(+"Error: input could not be processed (#{e.class})",
|
|
540
|
+
code: :internal_error, retryable: false)
|
|
541
|
+
end
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
return { "ok" => true } if errors.empty?
|
|
545
|
+
|
|
546
|
+
result = { "ok" => false, "errors" => errors.map(&:error_entry) }
|
|
547
|
+
result["note"] = note if note
|
|
548
|
+
result["reference"] = RSTError::REFERENCE
|
|
549
|
+
result
|
|
550
|
+
end
|
|
551
|
+
|
|
451
552
|
# Generate, and throw the result away. Parsing alone leaves out the
|
|
452
553
|
# checks that only happen once the tree is laid out — a movement path
|
|
453
554
|
# with one end, a line with three — so validation that stopped at the
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rsyntaxtree
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yoichiro Hasebe
|
|
@@ -115,8 +115,11 @@ executables:
|
|
|
115
115
|
extensions: []
|
|
116
116
|
extra_rdoc_files: []
|
|
117
117
|
files:
|
|
118
|
+
- ".dockerignore"
|
|
118
119
|
- CHANGELOG.md
|
|
119
120
|
- CITATION.cff
|
|
121
|
+
- CODE_OF_CONDUCT.md
|
|
122
|
+
- CONTRIBUTING.md
|
|
120
123
|
- LICENSE
|
|
121
124
|
- README.md
|
|
122
125
|
- bin/rsyntaxtree
|