diff --git a/specs/.prettierignore b/specs/.prettierignore new file mode 100644 index 00000000..fad770cd --- /dev/null +++ b/specs/.prettierignore @@ -0,0 +1,2 @@ +# VsCode local history +.history diff --git a/specs/.prettierrc.toml b/specs/.prettierrc.toml new file mode 100644 index 00000000..2582a496 --- /dev/null +++ b/specs/.prettierrc.toml @@ -0,0 +1,11 @@ +tabWidth = 2 +useTabs = false +printWidth = 90 +# Avoid re-formatting existing text. +# All new text SHOULD be formatted with semantic newlines. +# We SHOULD revisit this setting in the future. +proseWrap = "preserve" +# Only use quotes in supported config file formats where necessary. +quoteProps = "as-needed" +# Force all newlines to line-feed-only. +endOfLine = "lf" diff --git a/specs/README.md b/specs/README.md new file mode 100644 index 00000000..8fbac31e --- /dev/null +++ b/specs/README.md @@ -0,0 +1,87 @@ +[//]: # "Copyright Amazon.com Inc. or its affiliates. All Rights Reserved." +[//]: # "SPDX-License-Identifier: CC-BY-SA-4.0" + +# Duvet Specification + +## Overview + +This directory contains the specification for Duvet. +The primary goal of this specification is to define a standard, +language independent, description of the Duvet features. +It serves as the source of truth for the features that make up Duvet +and the details of their behavior. + +## Editing + +We use `prettier` to maintain consistent formatting. +Our CI will stop PRs that do not match our formatting requirements, +but to easily apply them, +run `./ci/prettify.sh write`. +If you want to check them without writing, +run `./ci/prettify.sh check`. + +We prefer authors adhere to [semantic line breaks](https://sembr.org/), +but we do not enforce it. + +## Proposals and Changes + +Proposals for new features or changes to the current features of Duvet should be +authored in the proposals' directory, following the format used by the +[AWS Encryption SDK Specification](https://github.com/awslabs/aws-encryption-sdk-specification/blob/a2ba123eb42b863bba1babf412af374018b35c0c/proposals/2020-06-26_decrypt-max-header-size-max-body-size/proposal.md). + +Accepted proposals will change the Duvet Specification, +but should still be documented as a change as is done by the +[AWS Encryption SDK Specification](https://github.com/awslabs/aws-encryption-sdk-specification/tree/master/changes). + +## Exporting to RFC and TOML + +### Extract `compliance` from Specification + +The Specification is written in Markdown. +Currently, Duvet needs RFC formatted text and TOML files. +As such, we have a tool that extracts the RFC spec +and supporting TOML files from the Markdown. + +### Running `extract` + +The entire specification may be extracted at once. Run: + +``` +./util/specification_extract.sh +``` + +### Installing dependencies + +The utility/script `util/specification_extract.sh` depends on four run +times: `node`, `python`, `ruby`, and `rust`. +(No, this is not ideal, but +Duvet is pushing the "spec to code" boundary; +we are ahead of the tooling.) + +#### Set Up Python & `xml2rfc` + +Follow +[AWS Crypto Tools Getting Started with Python instructions](https://github.com/aws/crypto-tools/blob/master/getting-started/python/README.md#local-development-setup) +to install `pyenv`. + +Then, in this repository, run +`pyenv local 3.9.7; pyenv exec python -m pip install xml2rfc==3.5.0 markupsafe==2.0.1`. + +#### Set up `kramdown-rfc2629` + +This is the Ruby dependency. Unfortunately, we have not figured out +a good way of installing this, so we do a bad way: + +``` +sudo gem install kramdown-rfc2629 +``` + +#### Node + +Follow +[Installing Node.js with `nvm` macOS by Daniel Schildt](https://gist.github.com/d2s/372b5943bce17b964a79) +to get `nvm` and `node` working. + +#### Rust + +Installing Duvet will install rust. diff --git a/specs/ci/prettify.sh b/specs/ci/prettify.sh new file mode 100755 index 00000000..09353e2c --- /dev/null +++ b/specs/ci/prettify.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +case "${1}" in + write) + npx prettier --config .prettierrc.toml --write -- '**/*.md' !./history/* + ;; + check) + npx prettier --config .prettierrc.toml --check -- '**/*.md' + ;; + *) + echo "mode required!" + echo "${0} [write/check]" + exit 1 + ;; +esac diff --git a/specs/duvet-specification.md b/specs/duvet-specification.md new file mode 100644 index 00000000..0c379641 --- /dev/null +++ b/specs/duvet-specification.md @@ -0,0 +1,485 @@ +[//]: # "Copyright Amazon.com Inc. or its affiliates. All Rights Reserved." +[//]: # "SPDX-License-Identifier: CC-BY-SA-4.0" + +# Duvet specification + +## Version + +0.2.0 + +### Changelog + +- 0.2.0 + + - Initial record + +- 0.1.0 + + - "Specless" Rust Implementation + +## Overview + +This document introduces and describes Duvet. + +Any implementation of Duvet MUST follow this specification. + +## Introduction + +Duvet is an application to build confidence that your software is correct. +The first step in correct software is to document what correct behavior is. +This document is called a specification. +A specification can be a design document or an RFC. +This specification document describes an application’s behaviors. +It includes which steps are important, in what order, and why. +Duvet lets you annotate your code with text from your specification. +This helps clarify what a specific implementation should be doing and why. +Any part of the specification can be an annotation. +However, Duvet treats RFC 2119 keywords in your specification as requirement key-words that must be annotated. + +Duvet reads files you designate as specifications and files you designate as part of your software. +It matches the annotations in your software to your specification. Duvet will then report on these matches. +Are there annotations in your source that do not exist in your specification? +Does every cited requirement from your specification have a test? +This report can either be a pass/fail for CI or an interactive report for development and code review. + +### Conventions used in this document + +The keywords +"MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" +in this document are to be interpreted as described in [RFC2119](https://tools.ietf.org/html/rfc2119). + +# Structures + +This following sections describe the common Duvet structures and their behavior. + +## Specification + +A specification is a document, like this, that defines correct behavior. +This behavior is defined in regular human language. + +## Section + +The top-level header for requirements is the name of a section. +After the section's header, follows the body. +Requirements defined inside the body MUST be associated to the immediate section in which they are defined. +This means requirements have one and only one section that they are associated with. +A header MUST NOT itself be a requirement. + +A section MUST be indexable by combining different levels of naming. +This means that Duvet needs to be able to locate it uniquely within a specification. +A good example of a section is a header in an HTML or Markdown document. + +## Requirement + +Any complete sentence containing at least one RFC 2119 keyword MUST be treated as a requirement. +A requirement MAY contain multiple RFC 2119 keywords. +A requirement MUST be terminated by one of the following: + +- period (.) +- exclamation point (!) +- list +- table + +In the case of the requirement being terminated by a list, +the text proceeding the list MUST be concatenated +with each element of the list to form a requirement. +Taking the above list as an example, +Duvet is required to be able to recognize 4 different ways +to group text into requirements. +List elements MAY have RFC 2119 keywords, +this is the same as regular sentences with multiple keywords. +Sublists MUST be treated as if the parent item were terminated by the sublist. +List elements MAY contain a period (.) or exclamation point (!) +and this punctuation MUST NOT terminate the requirement by +excluding the following elements from the list of requirements. + +In the case of the requirement being terminated by a table, +the text proceeding the table SHOULD be concatenated +with each row of the table to form a requirement. +Table cells MAY have RFC 2119 keywords, +this is the same as regular sentences with multiple keywords. +Table cells MAY contain a period (.) or exclamation point (!) +and this punctuation MUST NOT terminate the requirement +by excluding the following rows from the table of requirements. + +### Restrictive Requirement Parsing + +Older versions of Duvet were more restrictive in parsing requirements. +They did not treat elements of lists or rows in a table as individual elements. +For backwards compatibility Duvet MUST support +this older simpler form of requirement identification. +Any complete sentence containing at least one RFC 2119 keyword MUST be treated as a requirement. +A requirement MAY contain multiple RFC 2119 keywords. +A requirement MUST be terminated by one of the following: + +- period (.) +- exclamation point (!) +- an empty blank line + +The main distinction between this legacy and regular requirement identification +is that there is no sugar for lists or tables. +For a given a specification Duvet MUST use one way to identify requirements. + +### Formats + +Duvet MUST be able to parse specifications formatted as: + +- Markdown +- IETF style RFCs as text files. + +#### Requirements to TOML + +Duvet SHOULD be able to record parsed requirements into Toml Files. + +These Toml features supports users of Duvet who do not author the specifications they are implementing. +As such, they need to customize the extracted requirements, +modifying the content of requirements or adding/removing requirements. + +## Annotation + +Annotations are references to a text from a section in a specification, +written as comment in the source code and test code. +Annotations are generally stored as formatted comments in source within a project. + +### Meta + +The default identifier for the meta part in source documents MUST be //= followed by a single space. +This identifier of meta parts MUST be configurable. + +### Meta: Location + +The first line of the meta part identifies the location of the content, +it MUST be parsed as a URL. +All parts of the URL other than a URL fragment MUST be optional +and MUST identify the specification that contains this section and content. +The URL MUST contain a URL fragment that uniquely identifies +the section that contains this content. +If the URL only contains a URL fragment +then this content only exists as an annotation. +The scope of such a fragment +is defined by all the files given to Duvet analyze. +Formatting such comments for Duvet +lets Duvet track these implementation specific requirements +and require tests/evidence of their correctness. + +The Meta: Locations for Annotations targeting specifications +written in Markdown will NOT be identical to Locations targeting specifications written in IETF. + +### Meta: Key/Values parsing + +After the [Meta: Location](#meta-location) all additional meta data is a series of name/value pairs. +The name MUST be the characters between the meta identifier and the first `=`. +The value MUST be all characters after the first `=`. +If consecutive duplicate names exist in a meta section +the values MUST be concatenated with a `\n`. +### Meta: Type + +If the meta part is a single line then the type MUST be citation. +The type MUST be a valid annotation type string: + +- citation +- test +- untestable +- exception +- implication +- todo + +### Meta: Reason + +The reason tag MUST start with `reason=`. + +### Annotation Types + +Annotation types give meaning to the way the thing being annotated relate to the content. +Each type is listed here with its intended usage. + +- Citation: The implementation of what is described in the content. +- Test: A test or test vector that verifies that an implementation honors what is described in the content. + These tests are ideally negative. + i.e. Counter examples to the description are attempted and fail. +- Untestable: The implementation that can not be tested. + Some runtimes, languages, or constructions make the idea described in the content untestable. + Additional protections against random bit flips is a good example. +- Deviation: An implementation that differs from what is described in the content. + The implementation may have proceeded the specification for example. +- Exception: A part of a specification that is not implemented. + This can include optional or legacy features. + Exceptions have an optional reason field. +- Implication: A construction that is correct by construction i.e. it can not fail. + For example take a requirement that a function take a specific set of arguments. + In a static strongly typed language the arguments of a function can not change. + So an implication could be a good choice to express that the implementation satisfies this requirement. +- TODO: The suggested location for the implementation. +- feature +- tracking-issue + +### Content + +A one or more line meta part MUST be followed by at least a one line content part. +The default identifier for the content part in software documents +MUST be `//#` followed by a single space. +This identifier of content parts MUST be configurable. +All content part lines MUST be consecutive. + +## Matching + +Duvet needs to be able to match annotation content. +Both to other annotations and to specifications. +This matching is used to report on requirements. + +### Matching annotation and specification requirement + +For an annotation to match a specification +the annotation's content MUST exist in the specification's section +identified by the annotation's meta location URL. +The match between the annotation content and the specification text +MUST be case-sensitive but MUST NOT be white space sensitive +and MUST uniquely identify text in the specification. +For simple text in a paragraph this means just identifying +the annotation's content is a substring of the text in the specification's section. +Elements of a list MUST NOT be matched by their order within the list. +This means that an annotation may contain a list +that is a subset of the elements in the specification. +Rows of a table MUST NOT be matched by their order within the table. +This means that an annotation MAY contain a table that is a subset of the rows in the specification. + +## Matching Labels + +Duvet MUST analyze the matching annotations, generating Matching Labels. +Matching Labels MAY be exclusive. +Duvet MUST label requirements matched to annotations as follows: + +### Implemented + +A specification requirement MUST be labeled `Implemented` +if there exists at least one matching annotation of type: + +- citation +- untestable +- implication + +### Attested + +A specification requirement MUST be labeled `Attested` +if there exists at least one matching annotation of type + +- test +- untestable +- implication + +### Excused + +A specification requirement MUST be labeled `Excused` +and MUST only be labeled `Excused` if there exists +a matching annotation of type `exception` and the annotation has a `reason`. + +### Unexcused + +A specification requirement MUST be labeled `Unexcused` +and MUST only be labeled `Unexcused` if there exists +a matching annotation of type `exception` +and the annotation does NOT have a `reason`. + +## Report + +Duvet's report shows how your project conforms to specifications. +This lets you bound the correctness of your project. +As you annotate the code in your project +Duvet's report creates links between the implementation, +the specification, +and attestations. + +Duvet’s report aids customers in annotating their code. + +### Status + +Duvet MUST analyze the matching labels for every requirement; +the result of this analysis is the requirement’s Status. +Requirement Statuses MUST be exclusive. + +The Requirement Statuses MUST be: + +- Complete - The requirement MUST have both the labels `Implemented` and `Attested` +- Missing Proof - The requirement MUST only have the label `Implemented` +- Excused - The requirement MUST only have the label `Excused` +- Missing Implementation - The requirement MUST only have the label `Attested` +- Not started - The requirement MUST NOT have any labels +- Missing Reason - The requirement MUST have the label `Unexcused` +- Duvet Error - The requirements matching labels MUST be invalid. + +[//]: # "TODO: Should `Duvet Error` trigger a warning/exception?" +[//]: # "TODO: Should `Duvet Error` cause a Fail?" + +### Pass/Fail + +For Duvet to pass the Status of every “MUST” and “MUST NOT” requirement MUST be Complete or Excused.
+Duvet MUST return `0` for Pass. Duvet SHOULD print a success message.
+Duvet MUST NOT return `0` for Fail. Duvet SHOULD print a failure message.
+ +### Report Summary + +The report summary shows high level information about the linkage between annotations and specifications. +It MUST have all a link for each included specifications. +It MUST have all a link for annotations that do not match any included specifications. +It MUST have all a link for annotations not associated with any specifications. +For each link it MUST have a table summarizing +the matrix of requirements crossed annotation types, +and include totals for both sides. + +### Specification Overview + +The specification overview shows more detailed information about the specific specification. +It MUST have a table summarizing the matrix of requirements across annotation types, +and include totals for both sides. +It MUST show a table with a row for each requirement. +The table MUST have a column for: + +- Section within the specification - it would be heading in the markdown, section name and number for ietf docs +- Requirement key word - key word defined in rfc2119 +- Status +- Text - The requirement text + +### Specification Section + +The specification section shows the specific specification text and how this links to annotation. +It MUST show all text from the section. +It MUST highlight the text for every requirement. +It MUST highlight the text that matches any annotation. +Any highlighted text MUST have a mouse over that shows its annotation information. +Clicking on any highlighted text MUST bring up a popup that shows: + +- The requirement level +- The text +- List of quick links to add the text to a Duvet comment for every annotation type +- If annotations exist, relative links to these files. This link SHOULD include the line number. + +Selecting any text of the specification +and right-clicking on it +MUST bring up a popup for the selected text that shows: + +- The text +- List of quick links to add the text to a Duvet comment for every annotation type +- If annotations exist, relative links to these files. This link SHOULD include the line number. + +It MUST show a table with a row for each requirement included in this section. +The table MUST have a column for: + +- Section within the specification +- Requirement key word - key word defined in rfc2119 +- Status +- Text - The requirement text + +# Behaviors + +Duvet MUST support a [CI Behavior](#ci-behavior). + +Duvet SHOULD support a [Requirement to TOML Behavior](#record-requirements-as-toml-behavior). + +[//]: # "TODO: Define all of behaviors input and output in one place" +[//]: # "TODO: Describe how Duvet should handle exceptions" + +## CI Behavior + +The following sections describe +how Duvet parses specifications and implementations +to generate a report and a pass/fail status appropriate +for continuous integration (CI) usage. + +Implementations of Duvet MUST implement this behavior. + +This MUST be the default execution of Duvet. + +This behavior MUST accept a configuration file. + +### Parse Specifications + +Duvet MUST accept one or more groups of file patterns that describe the paths to the specifications files. + +These file pattern groups MUST specify which [specification format](#formats) they are in. + +For each file pattern group, +for each file pattern in the group, +Duvet MUST attempt to parse as a [specification](#specification) any files +discovered on this file pattern +as if they were in the file pattern groups' [specification format](#formats). + +Failure to parse a file MUST NOT halt Duvet. + +Failure to parse a file SHOULD yield a warning. + +#### Specifications as TOML + +In addition to parsing Markdown and RFC (`.txt`) files as specifications, +Duvet SHOULD accept one or more file patterns that describe the paths to `.toml` files. + +Duvet SHOULD interpret each directory containing one or more TOML files as a [specification](#specification). + +See [Sections as TOML](#sections-as-toml). + +### Extract Sections + +Duvet MUST extract [sections](#section) from [specifications](#specification). + +#### Sections as TOML + +If Duvet has interpreted TOML directories as [specifications](#specification), +Duvet SHOULD interpret each TOML file in a directory +as a [section](#section) of that directories' specification. + +See [Requirements from TOML](#requirements-from-toml). + +### Extract Requirements. + +Duvet MUST extract [Requirements](#requirement) from [Sections](#section). + +#### Requirements from TOML + +If Duvet has interpreted TOML files as a [section](#section), +for every [array of tables](https://toml.io/en/v1.0.0#array-of-tables) in the TOML file, +Duvet SHOULD extract a [requirement](#requirement). + +### Parse Implementation + +Duvet MUST accept one or more file pattern groups that describe the paths to the implementation files. + +Each file pattern group MAY be associated with an annotation identifier tuple, +which MUST be used when parsing files from the file pattern group. + +Otherwise, the default annotation identifiers MUST be used for that file pattern group. + +For each file pattern group, +for each file pattern in the group, +for every file found via a file pattern, +Duvet MUST extract [annotations](#annotation) form that file +with the group's annotation identifiers. + +Failure to parse a file MUST NOT halt Duvet. + +Failure to parse a file SHOULD yield a warning. + +Duvet MUST attempt to match these [annotations](#annotation) to [requirements](#requirement) +as described in [Matching](#matching). + +Even if a match is not found, +Duvet MUST record every [annotation](#annotation). + +### Generate Report + +Duvet MUST analyze every [requirement](#requirement) extracted, +generating and validating matching labels as described in [Matching Labels](#matching-labels). + +Then, Duvet MUST determine every [requirement's](#requirement) [Status](#status). + +Duvet MUST generate an HTML report as described in [report](#report). + +### Pass or Fail + +Duvet MUST Pass or Fail as described in [Pass/Fail](#Pass/Fail). + +## Record Requirements as TOML Behavior + +Duvet SHOULD support requirement to Toml extraction as a separate utility that MAY be invoked outside normal execution. + +See Requirements from TOML, Sections as TOML, and Specifications as TOML. + +[//]: # "TODO: Flesh this out" diff --git a/specs/util/extract.js b/specs/util/extract.js new file mode 100755 index 00000000..161b12ea --- /dev/null +++ b/specs/util/extract.js @@ -0,0 +1,168 @@ +#!/usr/bin/env node +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/* This is some sugar to produce compliance TOML + * from existing markdown. + * It uses `cargo-compliance`, `xml2rfc` and `kramdown`. + * + */ + +const { extname, basename, resolve, dirname, join, relative } = require("path"); +const { execSync } = require("child_process"); +const { + readFileSync, + writeFileSync, + statSync, + constants, + mkdirSync, +} = require("fs"); +const ext = ".md"; +const pathToComplianceRoot = `${relative(process.cwd(), `${__dirname}/../compliance`)}`; + +needs( + () => execSync("which kramdown-rfc2629"), + "kramdown-rfc2629 needs to be installed, try `gem install kramdown-rfc2629 -v 1.5.21`" +); +needs( + () => execSync("which xml2rfc"), + "xml2rfc needs to be installed, try `pip install xml2rfc==3.5.0`" +); +needs( + () => execSync("which duvet"), + "duvet needs to be installed, try `util/install-duvet`" +); + +/* May need to change this to a better parser... + * When run with a shebang + * the argv list will start, + * with the path to node + * then the path to this script. + * So the 3rd element ([2]) + * is the first user parameter. + */ +process.argv.slice(2).map(extract_needs).map(extract); + +function extract(filePath) { + const fileName = basename(filePath, ext); + const tmpdir = require("os").tmpdir(); + const markdownSpecFile = resolve(tmpdir, `${fileName}${ext}`); + const xmlRfcFile = resolve(tmpdir, `${fileName}.xml`); + const complianceSpec = join(pathToComplianceRoot, dirname(filePath), `${fileName}.txt`); + const complianceDir = join(pathToComplianceRoot, dirname(filePath), fileName); + + // Create the root compliance directory if it doesn't exist + try { + statSync(pathToComplianceRoot).isDirectory(); + } catch (ex) { + mkdirSync(pathToComplianceRoot, { recursive: true }); + } + + /* + 1. Get the file name without extension + 2. Add the RFC crap to a new tmp file + 3. kramdown + 4. xml2rfc + 5. cargo-compliance extract + */ + + // Write the spec file with the header and footer + writeFileSync( + markdownSpecFile, + [header(fileName), readFileSync(filePath, { encoding: "utf8" }), footer()].join("\n"), + { encoding: "utf8" } + ); + + // Convert the markdown file from RFC XML + execSync(["kramdown-rfc2629", "-3", markdownSpecFile, ">", xmlRfcFile].join(" "), {stdio: 'inherit'}); + + // An existing spec may exists, clean up first + try { + // This will throw if the directory does not exist + statSync(complianceDir).isDirectory(); + // If the directory exists, remove it. Nothing could go wrong... :( + execSync(["rm", "-rf", complianceDir].join(" ")); + } catch (ex) { + // If the directory does not exist, that is OK + needs(ex.errno === -2, "Unknown error"); + } + + // make sure the compliance directory exists + mkdirSync(complianceDir, { recursive: true }); + + // Convert the RFC XML to a ietf rfc + execSync(["xml2rfc", + "-P", xmlRfcFile, + "-s", "'Too long line found'", // Suppress warnings about long table line length + "-s", "'Total table width'", // Suppress warnings about overall table width + "-o", complianceSpec + ].join(" "), {stdio: 'inherit'}); + + const args = ["duvet", "extract", `${complianceSpec}`, "-o", "compliance"]; + // extract the specification + execSync(args.join(" "), { encoding: 'utf8', stdio: 'inherit'}); +} + +function extract_needs(filePath) { + needs(extname(filePath) === ext, `Unsupported ext ${ext}`); + needs( + () => statSync(filePath).isFile(), + `Specification file ${filePath} does not exist.` + ); + return filePath; +} + +// The `ipr: none` is particularly important +// this removed boilerplate (especially Copyright) +function header(docName) { + return `--- +title: ${docName} +abbrev: ${docName} +docname: ${docName} +category: info +ipr: none +area: General +workgroup: AWS Crypto Tools +keyword: INTERNAL-ONLY +stand_alone: yes +pi: [toc, sortrefs, symrefs] +author: + - + ins: Amazon AWS + name: Amazon AWS + organization: Amazon AWS + email: cryptools+rfc@amazon.com +normative: + RFC2119: +informative: +--- abstract +The ${docName} specification for the AWS Encryption SDK. +--- middle +# Conventions and Definitions +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", +"SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this +document are to be interpreted as described in BCP 14 {{RFC2119}} {{!RFC8174}} +when, and only when, they appear in all capitals, as shown here + `; +} + +function footer() { + return `--- back +# Acknowledgments +{:numbered="false"} +`; +} + +function needs(condition, errorMessage) { + if (typeof condition === "function") { + try { + needs(condition(), errorMessage); + } catch (ex) { + throw new Error(errorMessage); + } + } + + if (!condition) { + throw new Error(errorMessage); + } +} diff --git a/specs/util/specification_extract.sh b/specs/util/specification_extract.sh new file mode 100755 index 00000000..70a89f51 --- /dev/null +++ b/specs/util/specification_extract.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +SCRIPT_SOURCE=$(dirname "$0") + +FILES=("$SCRIPT_SOURCE"/../duvet-specification.md) + +echo "Extracting ${#FILES[@]} files:" + +for FILE in "${FILES[@]}" +do + RESOLVED="$(cd "$(dirname "$FILE")"; pwd -P)/$(basename "$FILE")" + echo "Extracting: $RESOLVED" + "$SCRIPT_SOURCE"/extract.js "$FILE" +done