Skip to content

Commit 2e299d0

Browse files
authored
docs: set up guide (#155)
1 parent e689f3a commit 2e299d0

File tree

14 files changed

+463
-2
lines changed

14 files changed

+463
-2
lines changed

.github/workflows/guide.yml

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
name: guide
2+
3+
on:
4+
push:
5+
branches: ["main"]
6+
pull_request:
7+
branches: ["main"]
8+
9+
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
10+
permissions:
11+
contents: read
12+
pages: write
13+
id-token: write
14+
15+
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
16+
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
17+
concurrency:
18+
group: "pages"
19+
cancel-in-progress: false
20+
21+
jobs:
22+
build:
23+
runs-on: ubuntu-20.04
24+
permissions:
25+
contents: write
26+
steps:
27+
- name: Checkout
28+
uses: actions/[email protected]
29+
30+
- name: Install rust toolchain
31+
id: toolchain
32+
run: |
33+
rustup toolchain install stable --profile minimal
34+
rustup override set stable
35+
36+
- name: Install mdBook
37+
uses: camshaft/install@v1
38+
with:
39+
crate: mdbook
40+
41+
- name: Install taplo
42+
uses: camshaft/install@v1
43+
with:
44+
crate: taplo-cli
45+
bins: "taplo"
46+
47+
- name: Install typos
48+
uses: camshaft/install@v1
49+
with:
50+
crate: typos-cli
51+
bins: "typos"
52+
53+
- name: Setup cache
54+
uses: camshaft/rust-cache@v1
55+
56+
- name: Build book
57+
env:
58+
MDBOOK_OUTPUT__HTML__SITE_url: "/duvet"
59+
run: cargo xtask guide
60+
61+
- name: Setup Pages
62+
id: pages
63+
if: github.event_name == 'push'
64+
uses: actions/configure-pages@v5
65+
66+
- name: Upload artifact
67+
uses: actions/upload-pages-artifact@v3
68+
with:
69+
path: ./guide/build
70+
71+
# Deployment job
72+
deploy:
73+
if: github.event_name == 'push'
74+
environment:
75+
name: github-pages
76+
url: ${{ steps.deployment.outputs.page_url }}
77+
runs-on: ubuntu-latest
78+
needs: build
79+
steps:
80+
- name: Deploy to GitHub Pages
81+
id: deployment
82+
uses: actions/deploy-pages@v4

duvet/src/project.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,8 @@ impl Project {
113113
}
114114
}
115115

116-
/// Set of options that are preserved for backwards compatibility but either
117-
/// don't do anything or are undocumented
116+
// Set of options that are preserved for backwards compatibility but either
117+
// don't do anything or are undocumented
118118
#[derive(Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Parser)]
119119
struct Deprecated {
120120
#[clap(long, short = 'p', hide = true)]

guide/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
build/
2+
command/

guide/book.toml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[book]
2+
authors = ["Cameron Bytheway"]
3+
language = "en"
4+
multilingual = false
5+
src = "src"
6+
title = "Duvet"
7+
8+
[build]
9+
build-dir = "build"
10+
11+
[output.html]
12+
copy-fonts = true
13+
git-repository-url = "https://github.com/awslabs/duvet"
14+
edit-url-template = "https://github.com/awslabs/duvet/edit/main/book/{path}"
15+
16+
[output.html.playground]
17+
editable = false
18+
copyable = true
19+
copy-js = true
20+
line-numbers = false
21+
runnable = false

guide/src/SUMMARY.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Summary
2+
3+
- [Introduction](./introduction.md)
4+
- [Configuration](./config.md)
5+
- [Specifications](./specifications.md)
6+
- [Annotations](./annotations.md)
7+
- [Reports](./reports.md)
8+
- [Commands]()
9+
- [init](./command/init.md)
10+
- [extract](./command/extract.md)
11+
- [report](./command/report.md)

guide/src/annotations.md

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# Annotations
2+
3+
Duvet scans source code for special comments containing references to specification text. By default, the comment style is the following:
4+
5+
```rust
6+
//= https://www.rfc-editor.org/rfc/rfc2324#section-2.1.1
7+
//# A coffee pot server MUST accept both the BREW and POST method
8+
//# equivalently.
9+
```
10+
11+
If the default comment style is not compatible with the language being used, it can be changed in the [configuration](./config.md) with the `comment-style` field.
12+
13+
The default type of annotation is `implementation`, meaning the reference is implementing the cited text. The type of annotation can be changed with the `type` parameter. Duvet supports the following annotation types:
14+
15+
## `implementation`
16+
17+
The source code is aiming to implement the cited text from the specification. This is the default annotation type.
18+
19+
## `test`
20+
21+
The source code is aiming to test that the program implements the cited text correctly.
22+
23+
```rust
24+
//= https://www.rfc-editor.org/rfc/rfc2324#section-2.1.1
25+
//= type=test
26+
//# A coffee pot server MUST accept both the BREW and POST method
27+
//# equivalently.
28+
#[test]
29+
fn my_test() {
30+
// TODO
31+
}
32+
```
33+
34+
## `implication`
35+
36+
The source code is both implementing and testing the cited text. This can be useful for requirements that are correct by construction. For example, let's say our specification says the following:
37+
38+
```
39+
# Section
40+
41+
The function MUST return a 64-bit integer.
42+
```
43+
44+
In a strongly-typed language, this requirement is being both implemented and tested by the compiler.
45+
46+
```rust
47+
//= my-spec.md#section
48+
//= type=implication
49+
//# The function MUST return a 64-bit integer.
50+
fn the_function() -> u64 {
51+
42
52+
}
53+
```
54+
55+
## `exception`
56+
57+
The source code has defined an exception for a requirement and is explicitly choosing not to implement it. This could be for various reasons. For example, let's consider the following specification:
58+
59+
```
60+
# Section
61+
62+
Implementations MAY panic on invalid arguments.
63+
```
64+
65+
In our example here, we've chosen _not_ to panic, but instead return an error. Annotations with the `exception` type can optionally provide a reason as to why the requirement is not being implemented.
66+
67+
```rust
68+
//= my-spec.md#section
69+
//= type=exception
70+
//= reason=We prefer to return errors that can be handled by the caller.
71+
//# Implementations MAY panic on invalid arguments.
72+
fn the_function() -> Result<u64, Error> {
73+
// implementation here
74+
}
75+
```
76+
77+
## `todo`
78+
79+
Some requirements may not be currently implemented but are on the product's roadmap. Such requirements can be annotated with the `todo` type to indicate this. Optionally, the annotation can provide a tracking issue for more context/updates.
80+
81+
```rust
82+
//= my-spec.md#section
83+
//= type=todo
84+
//= tracking-issue=1234
85+
//# Implementations SHOULD do this thing.
86+
```
87+
88+
## `spec`
89+
90+
The `spec` annotation type provides a way to annotate additional text in a specification that does not use the key words from [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119), but is still considered as providing a requirement.
91+
92+
```
93+
# Section
94+
95+
It's really important that implementations validate untrusted input.
96+
```
97+
98+
```rust
99+
//= my-spec.md#section
100+
//= type=spec
101+
//= level=MUST
102+
//# It's really important that implementations validate untrusted input.
103+
```
104+
105+
Additionally, Duvet also supports defining these requirements in `toml`:
106+
107+
```toml
108+
[[spec]]
109+
target = "my-spec.md#section"
110+
level = "MUST"
111+
quote = '''
112+
It's really important that implementations validate untrusted input.
113+
'''
114+
```

guide/src/config.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Configuration
2+
3+
Configuration files are written in the [TOML format](https://toml.io/). The following is a quick overview of all settings:
4+
5+
```toml
6+
{{#include example-config.toml}}
7+
```

guide/src/example-config.toml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Specifies the version of the config
2+
'$schema' = "https://awslabs.github.io/duvet/config/v0.4.json"
3+
4+
[[source]]
5+
pattern = "src/**/*.rs" # Lists all of the source files to scan
6+
7+
[[source]]
8+
pattern = "test/**/*.rs"
9+
type = "test" # Sets the default annotation type
10+
11+
[[source]]
12+
pattern = "src/**/*.py"
13+
type = "implementation"
14+
# Sets the comment style for this group
15+
comment-style = { meta = "##=", content = "##%" }
16+
17+
# Defines a required specification
18+
[[specification]]
19+
source = "https://www.rfc-editor.org/rfc/rfc2324" # URL to the specification
20+
21+
[[specification]]
22+
source = "https://www.rfc-editor.org/rfc/rfc9000" # URL to the specification
23+
format = "ietf" # Specifies the format
24+
25+
[[specification]]
26+
source = "my-specification.md" # Sets the local path to a specification
27+
28+
# Loads additional requirement files. By default it includes:
29+
# * ".duvet/requirements/**/*.toml",
30+
# * ".duvet/todos/**/*.toml",
31+
# * ".duvet/exceptions/**/*.toml",
32+
[[requirement]]
33+
pattern = ".duvet/implications/**/*.toml"
34+
35+
[report.html]
36+
enabled = true # Enables the HTML report
37+
path = ".duvet/reports/report.html" # Sets the path to the report output
38+
issue-link = "https://github.com/awslabs/duvet/issues" # Configures issue creation links
39+
blob-link = "https://github.com/awslabs/duvet/blob/main" # Configures source file links
40+
41+
[report.json]
42+
enabled = true # Enables the JSON report
43+
path = ".duvet/reports/report.html" # Sets the path to the report output
44+
45+
[report.snapshot]
46+
enabled = true # Enables the snapshot report
47+
path = ".duvet/snapshot.txt" # Sets the path to the report output

guide/src/introduction.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Introduction
2+
3+
Duvet is a tool that establishes a bidirectional link between implementation and specification. This practice is called [requirements traceability](https://en.wikipedia.org/wiki/Requirements_traceability), which is defined as:
4+
5+
> the ability to describe and follow the life of a requirement in both a forwards and backwards direction (i.e., from its origins, through its development and specification, to its subsequent deployment and use, and through periods of ongoing refinement and iteration in any of these phases)
6+
7+
## Quick Start
8+
9+
Before getting started, Duvet requires a [rust toolchain](https://www.rust-lang.org/tools/install).
10+
11+
1. Install command
12+
13+
```console
14+
$ cargo install duvet --locked
15+
```
16+
17+
2. Initialize repository
18+
19+
In this example, we are using Rust. However, Duvet can be used with any language.
20+
21+
```console
22+
$ duvet init --lang-rust --specification https://www.rfc-editor.org/rfc/rfc2324
23+
```
24+
25+
3. Add a implementation comment in the project
26+
27+
```rust
28+
// src/lib.rs
29+
30+
//= https://www.rfc-editor.org/rfc/rfc2324#section-2.1.1
31+
//# A coffee pot server MUST accept both the BREW and POST method
32+
//# equivalently.
33+
```
34+
35+
4. Generate a report
36+
37+
```console
38+
$ duvet report
39+
```

guide/src/reports.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Reports
2+
3+
Duvet provides a `report` command to provide insight into requirement coverage for a project. Each report has its own [configuration](./config.md).
4+
5+
## HTML
6+
7+
The `html` report is enabled by default. It's rendered in a browser and makes it easy to explore all of the specifications being annotated and provides statuses for each requirement. Additionally, the specifications are highlighted with links back to the project's source code, which establishes a bidirectional link between source and specification.
8+
9+
<!-- TODO provide an example link to a report, ideally the Duvet spec report -->
10+
11+
## Snapshot
12+
13+
The `snapshot` report provides a mechanism for projects to ensure requirement coverage does not change without explicit approvals. It accomplishes this by writing a simple text file to `.duvet/snapshot.txt` that can be checked against a derived snapshot in the project's CI. If the snapshot stored in the repo doesn't match the derived snapshot, we know there was an unintentional change in requirement coverage and the CI job fails.
14+
15+
```console
16+
$ duvet report --ci
17+
EXIT: Some(1)
18+
Extracting requirements
19+
Extracted requirements from 1 specifications
20+
Scanning sources
21+
Scanned 1 sources
22+
Parsing annotations
23+
Parsed 1 annotations
24+
Loading specifications
25+
Loaded 1 specifications
26+
Mapping sections
27+
Mapped 1 sections
28+
Matching references
29+
Matched 1 references
30+
Sorting references
31+
Sorted 1 references
32+
Writing .duvet/snapshot.txt
33+
34+
Differences detected in .duvet/snapshot.txt:
35+
36+
@@ -1 +1,3 @@
37+
SPECIFICATION: [Section](my-spec.md)
38+
+ SECTION: [Section](#section)
39+
+ TEXT[implementation]: here is a spec
40+
41+
× .duvet/snapshot.txt
42+
╰─▶ Report snapshot does not match with CI mode enabled.
43+
```
44+
45+
This is what is known as a "snapshot test". Note that in order for this to work, the `snapshot.txt` file needs to be checked in to the source code's version control system, which ensures that it always tracks the state of the code.

0 commit comments

Comments
 (0)