1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
//! A fast linter for changelogs in the Keep a Changelog format.
//!
//! ```rust
//! use notabene::{Linter, parse};
//! let s = "# Changelog ...";
//! let changelog = parse(&s);
//! let diagnostics = Linter::default().lint(&changelog);
//! ```
//!
//! This crate provides a three-step workflow for working with changelogs:
//!
//! 1. **Parse** a changelog document into a structured type.
//! 2. **Lint** the structured changelog to produce **diagnostics**.
//! 3. Optional. **Locate** the diagnostics to map them to line and column numbers.
//!
//! The examples below use this minimal, invalid changelog:
//!
//! ```markdown
//! # Changelog
//!
//! ## [Unreleased]
//!
//! ### Added
//!
//! * Add foo ([#12345])
//!
//! [Unreleased]: https://example.org/
//! ```
//!
//! This changelog is invalid because the `[#12345]` link label has no corresponding reference
//! definition.
//!
//! # Parse
//!
//! Parse a changelog with [`parse`][].
//!
//! ```rust
//! use notabene::parse;
//! let s = r#"# Changelog
//!
//! ### [Unreleased]
//!
//! #### Added
//!
//! * Add foo ([#12345])
//!
//! [Unreleased]: https://example.org/
//! "#;
//! let changelog = parse(&s);
//! ```
//!
//! There are two ways to represent a changelog:
//!
//! * [`ParsedChangelog`](crate::changelog::ParsedChangelog), a borrowed version of the data
//! * [`OwnedChangelog`](crate::changelog::OwnedChangelog), an owned version of the data
//!
//! Parsing is nearly a zero-copy operation and returns a `ParsedChangelog`. This version also
//! includes location information about elements in the changelog for use in the linter. Use
//! [`ParsedChangelog::to_owned`](crate::changelog::ParsedChangelog::to_owned) if you need owned data.
//!
//! This crate uses a trait-based API to access the changelog data of either type. The most
//! convenient way to import the traits is to import the prelude:
//!
//! ```rust
//! # use notabene::parse;
//! use notabene::prelude::*;
//! # let s = r#"# Changelog
//! #
//! # ## [Unreleased]
//! #
//! # ### Added
//! #
//! # * Add foo ([#12345])
//! #
//! # [Unreleased]: https://example.org/
//! # "#;
//! # let changelog = parse(&s);
//!
//! assert_eq!(changelog.title(), Some("Changelog"));
//! let unreleased = changelog.unreleased().unwrap();
//! assert_eq!(unreleased.changes()[0].kind(), "Added");
//! assert_eq!(unreleased.changes()[0].items().collect::<Vec<_>>()[0], "Add foo ([#12345])");
//! ```
//!
//! # Lint
//!
//! Use [`Linter`] to lint a `ParsedChangelog`. [`Linter::default`] returns a linter configured
//! with the default set of rules.
//!
//! ```rust
//! # use notabene::{Linter, parse};
//! # use notabene::prelude::*;
//! # let s = r#"# Changelog
//! #
//! # ## [Unreleased]
//! #
//! # ### Added
//! #
//! # * Add foo ([#12345])
//! #
//! # [Unreleased]: https://example.org/
//! # "#;
//! # let changelog = parse(&s);
//! let diagnostics = Linter::default().lint(&changelog);
//! ```
//!
//! The linter reports the invalid link as a [`Diagnostic`].
//!
//! ```
//! use notabene::Rule;
//! # use notabene::{Linter, parse};
//! # use notabene::prelude::*;
//! # let s = r#"# Changelog
//! #
//! # ## [Unreleased]
//! #
//! # ### Added
//! #
//! # * Add foo ([#12345])
//! #
//! # [Unreleased]: https://example.org/
//! # "#;
//! # let changelog = parse(&s);
//! # let diagnostics = Linter::default().lint(&changelog);
//! assert_eq!(diagnostics[0].rule, Rule::UndefinedLinkReference);
//! assert_eq!(diagnostics[0].rule.code(), "E300");
//! ```
//!
//! Use [`Linter::new`] to create a linter with a custom set of rules.
//!
//! ```rust
//! use notabene::RuleSet;
//! # use notabene::Rule;
//! # use notabene::{Linter, parse, prelude::*};
//! # let s = r#"# Changelog
//! #
//! # ## [Unreleased]
//! #
//! # ### Added
//! #
//! # * Add foo ([#12345])
//! #
//! # [Unreleased]: https://example.org/
//! # "#;
//! # let changelog = parse(&s);
//! // This ruleset excludes the rule above.
//! let ruleset = RuleSet::new([Rule::MissingTitle, Rule::MissingUnreleased]);
//! let linter = Linter::new(&ruleset);
//! let diagnostics = linter.lint(&changelog);
//! assert_eq!(diagnostics, vec![]);
//! ```
//!
//! # Locate
//!
//! By default, diagnostics report the *spans* in the source document that matched a [`Rule`].
//! A [`Span`](crate::span::Span) represents a range of byte offsets in the source document.
//! The [`Position`](crate::span::Position) type includes the line and column corresponding to the
//! offset.
//!
//! Use [`ParsedChangelog::locate_all`](crate::changelog::ParsedChangelog::locate_all) to convert a
//! collection of `Diagnostic<Span>` to a `Vec<Diagnostic<Position>>`.
//!
//! ```
//! # use notabene::{Linter, parse, prelude::*};
//! # let s = r#"# Changelog
//! #
//! # ## [Unreleased]
//! #
//! # ### Added
//! #
//! # * Add foo ([#12345])
//! #
//! # [Unreleased]: https://example.org/
//! # "#;
//! # let changelog = parse(&s);
//! # let diagnostics = Linter::default().lint(&changelog);
//! assert_eq!(diagnostics[0].range(), Some(52..60));
//!
//! let diagnostics = changelog.locate_all(&diagnostics);
//! assert_eq!((diagnostics[0].range()), (Some(52..60)));
//! assert_eq!((diagnostics[0].line(), diagnostics[0].column()), (Some(7), Some(12)));
//! ```
pub
pub
pub
pub
pub
pub
pub use Diagnostic;
pub use Linter;
pub use parse;
pub use Rule;
pub use RuleSet;