specifications

Specification and standard documents
git clone git://git.finwo.net/misc/specifications
Log | Files | Refs | README | LICENSE

commit da4daf0ded43ba9492357b65545f424ecb2ae5ea
parent 9327772128bae1733a71254a8f66b123475cedd9
Author: finwo <finwo@pm.me>
Date:   Fri, 17 Aug 2018 14:52:22 +0200

Working on the spec compiler

Diffstat:
M.gitignore | 13+++++++++++--
Acompile.php | 163+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acomposer.json | 14++++++++++++++
Asrc/0000.md | 136+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/RFC2119.md | 4++++
5 files changed, 328 insertions(+), 2 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1,5 +1,13 @@ -# Created by https://www.gitignore.io/api/osx,linux,windows,intellij +# Created by https://www.gitignore.io/api/osx,linux,windows,intellij,composer + +### Composer ### +composer.phar +/vendor/ + +# Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control +# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file +# composer.lock ### Intellij ### # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm @@ -143,6 +151,7 @@ $RECYCLE.BIN/ *.lnk -# End of https://www.gitignore.io/api/osx,linux,windows,intellij +# End of https://www.gitignore.io/api/osx,linux,windows,intellij,composer /.gtm/ /.idea/ +composer.lock diff --git a/compile.php b/compile.php @@ -0,0 +1,163 @@ +<?php + +if(!defined('DS')) define('DS',DIRECTORY_SEPARATOR); +require(__DIR__.DS.'vendor'.DS.'autoload.php'); +$params = array(); +@array_shift($argv); +@parse_str(@implode('&',$argv), $params); + +function perror($msg=null) { + if(is_null($msg)) exit(1); + if(is_string($msg)) { + echo $msg, PHP_EOL; + } else { + try { + echo json_encode($msg, JSON_PRETTY_PRINT), PHP_EOL; + } catch(Exception $e) { + // Do nothing + } + } + exit(1); +} + +function flatten( $data, $prefix = '', &$output = [] ) { + foreach($data as $key => $value) { + if(is_array($value)) { pack($value,$prefix.$key.'.',$output); } + else { $output[$prefix.$key] = $value; } + } + return $output; +} +function unflatten( $flatArray, &$output = [] ) { + static $accessor = null; + $accessor = $accessor ?: new \Finwo\PropertyAccessor\PropertyAccessor(true); + foreach ($flatArray as $key => $value ) $accessor->set($output,$key,$value,'.'); + return $output; +} + +function configFile( $file, &$output = [] ) { + $fd = null; + if(is_array($file)) $file = implode(DS,$file); + if(is_string($file) && file_exists($file)) { $fd = fopen($file,'r'); $file = null; } + if(is_string($file)) { + $fd = fopen('php://temp','r+'); + fwrite($fd,$file); + fseek($fd,0); + $file = null; + } + if(!$fd) return null; + while(!feof($fd)) { // Loop until we're out of file + $line = fgets($fd); // Fetch the line we're about to process + $line = @array_shift(str_getcsv($line,'#')); // Strip comments + $line = trim($line); // Trim line (we don't care about whitespace) + if(strlen($line)==0) continue; // Blank line = skip + $line = array_map('trim',str_getcsv($line,':')); // Split key & value + if(count($line)!=2) continue; // Only 1 key & 1 value supported + $output[strtolower($line[0])]=$line[1]; // Flat keys + } + fclose($fd); + return $output; +} + +function fcopy($src,$dst=null) { + if(is_null($dst)) $dst = fopen('php://temp','c+'); + while(!feof($src)) fwrite($dst,fread($src,1024)?:''); + return $dst; +} + +// Make sure we get the minimum params +if(!isset($params['spec'])) perror('Spec missing from the parameters'); +$spec = $params['spec']; + +// Ensure the said spec has a src +if(!file_exists('src'.DS.$spec.'.md')) perror('Given source does not exist'); +$filename = implode(DS,[__DIR__,'src',$spec.'.md']); + +// Fetch the file's headers +$fd = fopen($filename,'r+'); +$headers = ''; +while(!feof($fd)) { + $line = trim(fgets($fd)); + if(!$line) break; + $headers .= $line . "\n"; +} +$headers = unflatten(configFile($headers)); + +class Pipe { + protected $processor = null; + protected $next = null; + public function __construct( $processor, Pipe $next = null ) { + if(is_array($processor)) { + $this->processor = array_shift($processor); + if(count($processor)) $this->next = new Pipe($processor); + } else { + $this->processor = $processor; + $this->next = $next; + } + } + public function write( $chunk ) { + if(is_resource($this->processor)) return fwrite($this->processor,$chunk); + if(is_callable($this->processor)) return call_user_func($this->processor,$chunk,$this->next); + return false; + } +} +function normalize_newlines( $chunk, Pipe $next ) { + $next->write(str_replace(["\r\n","\r"],"\n",$chunk)); +} +function inclusions( $chunk, Pipe $next ) { + if(substr($chunk,0,1)=='<') { + $fd = fopen('src'.DS.substr(trim($chunk),1).'.md','r'); + while(!feof($fd)) $next->write(fgets($fd)); + fclose($fd); + } else { + $next->write($chunk); + } +} + +// Build the processing pipe +$outfd = fopen('spec'.DS.'spec'.$spec.'.txt','c+'); +ftruncate($outfd,0); +$pipe = new Pipe([ + 'normalize_newlines', + 'inclusions', + $outfd, +]); + +// Run data through the pipe +var_dump($pipe); +while(!feof($fd)) $pipe->write(fgets($fd)); +var_dump($pipe); + +//// Handle file inclusions +//$temp_fd = fopen('php://temp','c+'); +//while(!feof($process_fd)) { +// $line = fgets($process_fd); +// if(substr($line,0,1)==='<') { +// $fd = fopen('src'.DS.trim(substr($line, 1)) . '.md', 'r'); +// var_dump('src'.DS.trim(substr($line, 1)) . '.md'); +// fcopy($fd, $temp_fd); +// fclose($fd); +// } else { +// fwrite($temp_fd,$line); +// } +//} +// +//// Rewind again +//fseek($process_fd,0); +//ftruncate($process_fd,0); +//fseek($temp_fd,0); +//fcopy($temp_fd,$process_fd); +//fseek($process_fd,0); +//fseek($temp_fd,0); +//ftruncate($temp_fd,0); +// +// +// +// +// +//echo stream_get_contents($process_fd); +// +// +////var_dump($config); +////var_dump($contents); +//var_dump($headers); +//var_dump($params); diff --git a/composer.json b/composer.json @@ -0,0 +1,14 @@ +{ + "name": "ratus/specifications", + "require": { + "finwo/property-accessor": "^0.1.4" + }, + "license": "CC-By 4.0", + "authors": [ + { + "name": "Robin Bron", + "email": "robin@finwo.nl" + } + ], + "minimum-stability": "stable" +} diff --git a/src/0000.md b/src/0000.md @@ -0,0 +1,136 @@ +Date: 2018-08-15 +author: Robin Bron <robin@finwo.nl> +organization: Ratus B.V. + +# Specification Format + +## Conventions + +<RFC2119 + +## Character encoding + +Plain-text files for specifications MUST use the `CP437` standard with the +exclusion of character code 0x0A which represents a line feed as specified in +`RFC20`. + +## Line definition + +A line of text is a sequence of 0 or more characters followed by a line feed +character. For the sake of and clarity, the ending line feed character is part +of the line. + +Lines MUST NOT exceed 72 characters in length, including the ending line feed +character. A line is called a blank line if it consists of only a line feed +charachter. + +### Line numbering + +To ensure the following page dimension section is clear, we need to define how +lines are numbered. + +Assuming a document is in digital format and has a length of greater than +0 bytes, the first character in the document is part of line 0. Numbering lines +from 0 instead of 1 gives us an advantage of clarity in the next section. + +## Pages + +A page is a sequence of 60 lines. That means for every line number n, the line +is the start of a new page when $$ n mod 60 = 0 $$. + +### Page header + +The first line of a page SHOULD consist of a left-aligned spec number +indicator, a centered (short) document title and a right-aligned publishing +date (see [Document header][document header]). The second line of a page MUST +always be blank, excluding the first page of the document. + +### Page footer + +The last line of a page MUST consist of a left-align last name of the author +and a right-aligned page number between square brackets. The second-to-last +line of a page must be blank, just like the second line of a page. + +## Paragraphs + +A paragraph is a sequence of consecutive lines containing characters other than +only a line feed. Paragraphs are separated by either a blank line or a page +break. Paragraphs MUST NOT span multiple pages, limiting their size to 56 +lines. + +## Document header + +The first lines of the first page of a specification document SHALL always +contain left-aligned description headers (see +[Descriptive header][descriptive header]) and right-aligned author +identification and a right-aligned publishing date. + +After the initial lines (see [Descriptive header][descriptive header] through +[Publish date][publish date]), the document title is REQUIRED to be written on +the first page of the document. For it's specification, see section +[document title][document title]. + +Further information on the first page should give a quick description of the +contents of the document. + +### Descriptive header + +Each descriptive header is made up of a key and a value. Whitespace is not +allowed in both the key and the value. Whitespace can only be included in the +value by wrapping the value in quote characters. + +The key of the header consists of all characters of the line up to the first +semicolon, excluding the semicolon itself and omitting all white-space +characters. + +The value of the header starts at the first non-whitespace character after the +first semicolon of the line. If the first character is a quote, the value ends +at the next quote in the line. If the first character is not a quote, the value +ends at the next whitespace character. + +### Short author identification + +In order to allow authors to take some credit and to track who has written +what, the author's name MUST be added right-aligned on the first line of the +first page of the document. To prevent mixing notations between documents, the +names SHOULD be written as only the first letters of all given names in +capitals, separated by dots, a space and the Family name starting with a +capital. When written by a group with a name, the short author identification +string SHOULD state the group's name. + +### Publish date + +Because a document is unlikely to have been written within a day, a publish +date is simply the month's name starting with a capital followed by the year, +both following the Gregorian calendar. + +## Document footer + +The document SHOULD close, starting on a new page, with all informative +resources which were used to write the document, noting their keyword and +document title. When possible, a URL to the resource SHOULD be included. + +After the informative resources, the document SHOULD end with one or several +pages dedicated to the information of the author(s) and if possible their +contact information. + +## Section titles + +Section titles SHOULD be a short text about the subject the section describes. +Whether it is simply the keyword of what it explains, a problem statement or +other type of text is up to the author as long as it's relevant to the +section's body. + +A section title MUST start with a capital character & MUST NOT contain any +other capital letters, excluding where they are required in names or +abbreviations. + +## Document title + +The title of the document should clearly state the main subject of the document +and it's contents. Each word of the document title must start with a capital +character when noted as the title of the document. + +On the first page of the document, the title should be centered horizontally +and have at least 2 blank lines both above and below it. The document title +SHOULD be as close to the document's descriptive headers. diff --git a/src/RFC2119.md b/src/RFC2119.md @@ -0,0 +1,4 @@ +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 `RFC2119` when, and only when, +they appear in all capitals, as shown here.