├── packages ├── repl │ ├── .npmignore │ ├── package.json │ ├── Makefile │ ├── CHANGELOG.md │ ├── LICENCE │ ├── src │ │ ├── index.js │ │ ├── ast.js │ │ ├── terminal-display.js │ │ └── browser.js │ └── README.md ├── interface │ ├── .npmignore │ ├── CONTRIBUTORS │ ├── src │ │ ├── annotations │ │ │ ├── index.js │ │ │ └── built-ins.js │ │ ├── assertions.js │ │ ├── stability.js │ │ └── primitives.js │ ├── test │ │ └── src │ │ │ └── auto-examples.js │ ├── Makefile │ ├── package.json │ ├── LICENCE │ ├── README.md │ └── CHANGELOG.md ├── mocha-bridge │ ├── .npmignore │ ├── CONTRIBUTORS │ ├── CHANGELOG.md │ ├── package.json │ ├── Makefile │ ├── test │ │ └── src │ │ │ └── index.js │ ├── LICENCE │ ├── src │ │ └── index.js │ └── README.md ├── babel-plugin-assertion-comments │ ├── .npmignore │ ├── CONTRIBUTORS │ ├── package.json │ ├── Makefile │ ├── CHANGELOG.md │ ├── LICENCE │ ├── README.md │ └── src │ │ └── index.js └── babel-plugin-metamagical-comments │ ├── .npmignore │ ├── CONTRIBUTORS │ ├── package.json │ ├── Makefile │ ├── CHANGELOG.md │ └── README.md ├── experimental ├── markdown │ ├── .npmignore │ ├── bin │ │ └── metamagical-markdown │ ├── package.json │ ├── LICENCE │ ├── README.md │ └── src │ │ ├── cli.js │ │ └── index.js ├── mkdocs │ ├── .npmignore │ ├── package.json │ ├── LICENCE │ └── src │ │ └── index.js ├── sphinx │ ├── .npmignore │ ├── test.js │ ├── package.json │ ├── LICENCE │ ├── README.md │ └── src │ │ ├── rst.js │ │ └── index.js ├── static-tree │ ├── .npmignore │ ├── package.json │ ├── LICENCE │ └── src │ │ └── index.js └── static-docs │ ├── CHANGELOG.md │ ├── src │ ├── formatters │ │ ├── index.js │ │ ├── json.js │ │ └── html.js │ ├── index.js │ └── staticalise.js │ ├── package.json │ ├── Makefile │ ├── LICENCE │ └── resources │ └── html │ ├── static │ └── prism.css │ └── default-theme.styl ├── .eslintrc.json ├── .eslintignore ├── siren.png ├── CONTRIBUTORS ├── Images └── repl.png ├── .gitignore ├── .babelrc ├── Scripts └── run.sh ├── .travis.yml ├── package.json ├── ROADMAP.org ├── LICENCE ├── tools └── generate-docs.js ├── Makefile ├── .gitlabels ├── CODE_OF_CONDUCT.md └── README.md /packages/repl/.npmignore: -------------------------------------------------------------------------------- 1 | /src/ -------------------------------------------------------------------------------- /experimental/markdown/.npmignore: -------------------------------------------------------------------------------- 1 | /src/ -------------------------------------------------------------------------------- /experimental/mkdocs/.npmignore: -------------------------------------------------------------------------------- 1 | /src/ -------------------------------------------------------------------------------- /experimental/sphinx/.npmignore: -------------------------------------------------------------------------------- 1 | /src/ -------------------------------------------------------------------------------- /packages/interface/.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | -------------------------------------------------------------------------------- /packages/mocha-bridge/.npmignore: -------------------------------------------------------------------------------- 1 | src/ -------------------------------------------------------------------------------- /experimental/static-tree/.npmignore: -------------------------------------------------------------------------------- 1 | /src/ -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "origamitower" 3 | } 4 | -------------------------------------------------------------------------------- /packages/babel-plugin-assertion-comments/.npmignore: -------------------------------------------------------------------------------- 1 | /src/ -------------------------------------------------------------------------------- /packages/babel-plugin-metamagical-comments/.npmignore: -------------------------------------------------------------------------------- 1 | /src/ -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /packages/**/lib/ 2 | /packages/**/test/ 3 | node_modules/ -------------------------------------------------------------------------------- /siren.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robotlolita/metamagical/HEAD/siren.png -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | - Quildreen Motta (http://robotlolita.me) 2 | -------------------------------------------------------------------------------- /Images/repl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robotlolita/metamagical/HEAD/Images/repl.png -------------------------------------------------------------------------------- /experimental/markdown/bin/metamagical-markdown: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('../lib/cli')(); -------------------------------------------------------------------------------- /packages/interface/CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | - Quildreen Motta (http://robotlolita.me) -------------------------------------------------------------------------------- /packages/mocha-bridge/CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | - Quildreen Motta (http://robotlolita.me) -------------------------------------------------------------------------------- /packages/babel-plugin-assertion-comments/CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | - Quildreen Motta (http://robotlolita.me) -------------------------------------------------------------------------------- /packages/babel-plugin-metamagical-comments/CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | - Quildreen Motta (http://robotlolita.me) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.tern-port 3 | /packages/**/lib/ 4 | /packages/**/test/spec/ 5 | /experimental/**/lib/ 6 | /experimental/**/test/spec/ 7 | /experimental/static-docs/resources/html/*.css 8 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"], 3 | "plugins": [ 4 | "transform-metamagical-comments", 5 | "transform-assertion-comments", 6 | "transform-function-bind", 7 | "transform-object-rest-spread", 8 | "transform-async-to-generator" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /Scripts/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | COMMAND=$1 3 | PACKAGES=$2 4 | 5 | ROOT="$(pwd)" 6 | 7 | for package in $PACKAGES; do 8 | if [ -f "${package}/Makefile" ]; then 9 | cd "$package" 10 | make "$COMMAND" || exit 1 11 | cd "$ROOT" 12 | fi 13 | done 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4" 4 | - "5" 5 | - "6" 6 | env: 7 | - MM_EXPERIMENTAL_PACKAGES=false 8 | - MM_EXPERIMENTAL_PACKAGES=true 9 | 10 | matrix: 11 | allow_failures: 12 | env: 13 | - MM_EXPERIMENTAL_PACKAGES=true 14 | 15 | install: make install 16 | script: make test 17 | -------------------------------------------------------------------------------- /experimental/sphinx/test.js: -------------------------------------------------------------------------------- 1 | require('babel-polyfill'); 2 | var s = require('./')(require('../interface')); 3 | var d = require('folktale/core/adt').data; 4 | 5 | function show(p) { 6 | p.then( 7 | v => console.log(v.content), 8 | e => console.log('ERROR:\n', e, '\n', e.stack) 9 | ) 10 | } 11 | 12 | show(s.generateOne(d)) 13 | -------------------------------------------------------------------------------- /experimental/static-docs/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | All of the changes to the `metamagical-static-docs` project 4 | are recorded in this file. Changes are ordered from most recent to oldest 5 | versions. 6 | 7 | (This will eventually be autogenerated) 8 | 9 | 10 | ## 0.x 11 | 12 | ### [0.1.0] - 2016-09-03 13 | 14 | Initial release with usable HTML static documentation. -------------------------------------------------------------------------------- /packages/mocha-bridge/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | All of the changes to the `metamagical-mocha-bridge` project are recorded in 4 | this file. Changes are ordered from most recent to oldest versions. 5 | 6 | (This will eventually be autogenerated) 7 | 8 | 9 | ## 0.x 10 | 11 | ### [0.3.0] - 2016-05-27 12 | 13 | The first release of the Mocha bridge library supports defining 14 | Mocha tests from named objects annotated with examples. -------------------------------------------------------------------------------- /packages/interface/src/annotations/index.js: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------- 2 | // 3 | // This source file is part of the Meta:Magical project. 4 | // 5 | // See LICENCE for licence information. 6 | // See CONTRIBUTORS for the list of contributors to the project. 7 | // 8 | //--------------------------------------------------------------------- 9 | 10 | module.exports = (meta) => require('./built-ins')(meta); -------------------------------------------------------------------------------- /experimental/static-docs/src/formatters/index.js: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------- 2 | // 3 | // This source file is part of the Meta:Magical project. 4 | // 5 | // See LICENCE for licence information. 6 | // See CONTRIBUTORS for the list of contributors to the project. 7 | // 8 | //--------------------------------------------------------------------- 9 | 10 | module.exports = { 11 | json: require('./json'), 12 | html: require('./html') 13 | }; 14 | -------------------------------------------------------------------------------- /packages/babel-plugin-assertion-comments/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "babel-plugin-transform-assertion-comments", 3 | "version": "0.4.0", 4 | "description": "Compile assertion comments to runtime assertions.", 5 | "license": "MIT", 6 | "main": "./lib/index.js", 7 | "keywords": [ 8 | "babel-plugin" 9 | ], 10 | "dependencies": { 11 | "babel-generator": "^6.7.7", 12 | "babel-template": "^6.7.0", 13 | "babel-traverse": "^6.8.0", 14 | "babylon": "^6.7.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /experimental/static-tree/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "metamagical-static-tree", 3 | "version": "0.2.0", 4 | "description": "Generates a static tree from a root object.", 5 | "main": "./lib/index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/origamitower/metamagical.git" 9 | }, 10 | "author": "Quildreen Motta", 11 | "license": "MIT", 12 | "bugs": { 13 | "url": "https://github.com/origamitower/metamagical/issues" 14 | }, 15 | "dependencies": { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/babel-plugin-metamagical-comments/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "babel-plugin-transform-metamagical-comments", 3 | "version": "0.15.0", 4 | "description": "Compile meta:magical doc-comments to runtime object annotations.", 5 | "license": "MIT", 6 | "main": "./lib/index.js", 7 | "keywords": [ 8 | "babel-plugin" 9 | ], 10 | "dependencies": { 11 | "babel-generator": "^6.8.0", 12 | "babel-template": "^6.8.0", 13 | "babylon": "^6.8.0", 14 | "js-yaml": "^3.6.0", 15 | "marked": "^0.3.5" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /experimental/mkdocs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "metamagical-mkdocs", 3 | "version": "0.2.0", 4 | "description": "Generates Mkdocs documentation from documentation metadata.", 5 | "main": "./lib/index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/origamitower/metamagical.git" 9 | }, 10 | "author": "Quildreen Motta", 11 | "license": "MIT", 12 | "bugs": { 13 | "url": "https://github.com/origamitower/metamagical/issues" 14 | }, 15 | "dependencies": { 16 | "marked": "^0.3.5" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /experimental/static-docs/src/formatters/json.js: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------- 2 | // 3 | // This source file is part of the Meta:Magical project. 4 | // 5 | // See LICENCE for licence information. 6 | // See CONTRIBUTORS for the list of contributors to the project. 7 | // 8 | //--------------------------------------------------------------------- 9 | 10 | const toJSON = (entities, options) => { 11 | return { 12 | filename: 'entities.json', 13 | content: entities 14 | }; 15 | }; 16 | 17 | module.exports = toJSON; 18 | -------------------------------------------------------------------------------- /packages/mocha-bridge/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "metamagical-mocha-bridge", 3 | "version": "0.4.0", 4 | "description": "Allows defining Mocha test cases from Meta:Magical examples.", 5 | "main": "./lib/index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/origamitower/metamagical.git" 9 | }, 10 | "keywords": [ 11 | "mocha", 12 | "testing" 13 | ], 14 | "author": "Quildreen Motta", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/origamitower/metamagical/issues" 18 | }, 19 | "peerDependencies": { 20 | "metamagical-interface": "3.x" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "license": "MIT", 4 | "devDependencies": { 5 | "babel-cli": "^6.7.7", 6 | "babel-eslint": "^6.0.4", 7 | "babel-plugin-transform-assertion-comments": "^0.2.3", 8 | "babel-plugin-transform-async-to-generator": "^6.8.0", 9 | "babel-plugin-transform-function-bind": "^6.8.0", 10 | "babel-plugin-transform-metamagical-comments": "^0.12.0", 11 | "babel-plugin-transform-object-rest-spread": "^6.8.0", 12 | "babel-preset-es2015": "^6.6.0", 13 | "eslint": "^2.9.0", 14 | "eslint-config-origamitower": "^1.1.0", 15 | "mocha": "^2.5.3" 16 | }, 17 | "dependencies": {} 18 | } 19 | -------------------------------------------------------------------------------- /packages/repl/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "metamagical-repl", 3 | "version": "0.3.0", 4 | "description": "A REPL browser for Meta:Magical.", 5 | "main": "./lib/index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/origamitower/metamagical.git" 9 | }, 10 | "author": "Quildreen Motta", 11 | "license": "MIT", 12 | "bugs": { 13 | "url": "https://github.com/origamitower/metamagical/issues" 14 | }, 15 | "dependencies": { 16 | "chalk": "^1.1.3", 17 | "folktale": "^2.0.0-alpha1", 18 | "marked": "^0.3.5", 19 | "marked-terminal": "^1.6.1", 20 | "refinable": "^1.0.2" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ROADMAP.org: -------------------------------------------------------------------------------- 1 | * Interface 2 | ** TODO Track overriden methods 3 | ** TODO Track related methods (see also, etc) 4 | ** TODO Interactive tutorials 5 | ** TODO Type definitions and querying 6 | ** TODO Use a superset of Markdown (currently too limited) 7 | * Assertions 8 | ** TODO Support [1, ..._, 2] 9 | ** TODO Support `throws` 10 | * Meta:Magical Doc Comments 11 | ** TODO Infer metadata from AUTHORS/CONTRIBUTORS file 12 | ** TODO Parse short repository URLs from package.json 13 | ** TODO Parse author metadata 14 | * New applications 15 | ** TODO An Electron-based Browser app 16 | * New libraries 17 | ** TODO Common annotations as packages (for built-ins, Node, etc) 18 | -------------------------------------------------------------------------------- /packages/interface/test/src/auto-examples.js: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------- 2 | // 3 | // This source file is part of the Meta:Magical project. 4 | // 5 | // See LICENCE for licence information. 6 | // See CONTRIBUTORS for the list of contributors to the project. 7 | // 8 | //---------------------------------------------------------------------- 9 | 10 | // This module runs all of the example-based tests defined in the 11 | // documentation for this module. 12 | const metamagical = require('../../'); 13 | const defineTests = require('../../../mocha-bridge')(metamagical, describe, it); 14 | 15 | defineTests(metamagical); 16 | 17 | -------------------------------------------------------------------------------- /packages/repl/Makefile: -------------------------------------------------------------------------------- 1 | bin := ../../node_modules/.bin 2 | babel := $(bin)/babel 3 | lint := $(bin)/eslint 4 | mocha := $(bin)/mocha 5 | 6 | .DEFAULT_GOAL: build 7 | 8 | build: 9 | @echo "-- Compiling metamagical-interface" 10 | $(babel) src/ --source-map inline \ 11 | --out-dir lib \ 12 | $$BABEL_OPTIONS 13 | 14 | clean: 15 | rm -rf lib/ 16 | 17 | lint: 18 | $(eslint) . 19 | 20 | test: build 21 | exit 0 22 | 23 | publish: clean-test 24 | npm publish 25 | 26 | install: 27 | npm install 28 | 29 | clean-test: 30 | $(MAKE) clean 31 | rm -rf node_modules 32 | npm install 33 | $(MAKE) test 34 | 35 | .PHONY: build clean lint test clean-test install publish 36 | -------------------------------------------------------------------------------- /experimental/sphinx/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "metamagical-sphinx", 3 | "version": "0.1.1", 4 | "description": "Generates Sphinx documentation from documentation metadata.", 5 | "main": "./lib/index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/origamitower/metamagical.git" 9 | }, 10 | "author": "Quildreen Motta", 11 | "license": "MIT", 12 | "bugs": { 13 | "url": "https://github.com/origamitower/metamagical/issues" 14 | }, 15 | "dependencies": { 16 | "babel-generator": "^6.8.0", 17 | "babel-polyfill": "^6.8.0", 18 | "babel-types": "^6.8.1", 19 | "babylon": "^6.8.0", 20 | "folktale": "2.0.0-alpha1", 21 | "marked": "^0.3.5" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/babel-plugin-assertion-comments/Makefile: -------------------------------------------------------------------------------- 1 | bin := ../../node_modules/.bin 2 | babel := $(bin)/babel 3 | lint := $(bin)/eslint 4 | mocha := $(bin)/mocha 5 | 6 | .DEFAULT_GOAL: build 7 | 8 | build: 9 | @echo "-- Compiling Babel plugin: assertion-comments" 10 | $(babel) src/ --source-map inline \ 11 | --out-dir lib \ 12 | $$BABEL_OPTIONS 13 | 14 | clean: 15 | rm -rf lib/ 16 | 17 | lint: 18 | $(eslint) . 19 | 20 | test: build 21 | exit 0 22 | 23 | publish: clean-test 24 | npm publish 25 | 26 | install: 27 | npm install 28 | 29 | clean-test: 30 | $(MAKE) clean 31 | rm -rf node_modules 32 | npm install 33 | $(MAKE) test 34 | 35 | .PHONY: build clean lint test clean-test install publish 36 | -------------------------------------------------------------------------------- /packages/babel-plugin-metamagical-comments/Makefile: -------------------------------------------------------------------------------- 1 | bin := ../../node_modules/.bin 2 | babel := $(bin)/babel 3 | lint := $(bin)/eslint 4 | mocha := $(bin)/mocha 5 | 6 | .DEFAULT_GOAL: build 7 | 8 | build: 9 | @echo "-- Compiling Babel plugin: metamagical-comments" 10 | $(babel) src/ --source-map inline \ 11 | --out-dir lib \ 12 | $$BABEL_OPTIONS 13 | 14 | clean: 15 | rm -rf lib/ 16 | 17 | lint: 18 | $(eslint) . 19 | 20 | test: build 21 | exit 0 22 | 23 | publish: clean-test 24 | npm publish 25 | 26 | install: 27 | npm install 28 | 29 | clean-test: 30 | $(MAKE) clean 31 | rm -rf node_modules 32 | npm install 33 | $(MAKE) test 34 | 35 | .PHONY: build clean lint test clean-test install publish 36 | -------------------------------------------------------------------------------- /packages/interface/Makefile: -------------------------------------------------------------------------------- 1 | bin := ../../node_modules/.bin 2 | babel := $(bin)/babel 3 | lint := $(bin)/eslint 4 | mocha := $(bin)/mocha 5 | 6 | .DEFAULT_GOAL: build 7 | 8 | build: 9 | @echo "-- Compiling metamagical-interface" 10 | $(babel) src/ --source-map inline \ 11 | --out-dir lib \ 12 | $$BABEL_OPTIONS 13 | 14 | clean: 15 | rm -rf lib/ 16 | 17 | lint: 18 | $(eslint) . 19 | 20 | test: build 21 | $(babel) test/src --source-map inline --out-dir test/spec 22 | $(mocha) --reporter spec --uid bdd test/spec 23 | 24 | publish: clean-test 25 | npm publish 26 | 27 | install: 28 | npm install 29 | 30 | clean-test: 31 | $(MAKE) clean 32 | rm -rf node_modules 33 | npm install 34 | $(MAKE) test 35 | 36 | .PHONY: build clean lint test clean-test install publish 37 | -------------------------------------------------------------------------------- /packages/mocha-bridge/Makefile: -------------------------------------------------------------------------------- 1 | bin := ../../node_modules/.bin 2 | babel := $(bin)/babel 3 | lint := $(bin)/eslint 4 | mocha := $(bin)/mocha 5 | 6 | .DEFAULT_GOAL: build 7 | 8 | build: 9 | @echo "-- Compiling metamagical-mocha-bridge" 10 | $(babel) src/ --source-map inline \ 11 | --out-dir lib \ 12 | $$BABEL_OPTIONS 13 | 14 | clean: 15 | rm -rf lib/ 16 | 17 | lint: 18 | $(eslint) . 19 | 20 | test: build 21 | $(babel) test/src --source-map inline --out-dir test/spec 22 | $(mocha) --reporter spec --uid bdd test/spec 23 | 24 | publish: clean-test 25 | npm publish 26 | 27 | install: 28 | npm install 29 | 30 | clean-test: 31 | $(MAKE) clean 32 | rm -rf node_modules 33 | npm install 34 | $(MAKE) test 35 | 36 | .PHONY: build clean lint test clean-test install publish 37 | -------------------------------------------------------------------------------- /experimental/markdown/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "metamagical-markdown", 3 | "version": "0.1.0", 4 | "description": "Compiles Markdown to JS modules annotated with Meta:Magical.", 5 | "main": "./lib/index.js", 6 | "bin": { 7 | "metamagical-markdown": "bin/metamagical-markdown" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/origamitower/metamagical.git" 12 | }, 13 | "author": "Quildreen Motta", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/origamitower/metamagical/issues" 17 | }, 18 | "dependencies": { 19 | "babel-core": "^6.8.0", 20 | "babel-generator": "^6.8.0", 21 | "babel-template": "^6.8.0", 22 | "babel-types": "^6.8.1", 23 | "babylon": "^6.8.0", 24 | "docopt": "^0.6.2", 25 | "js-yaml": "^3.6.0", 26 | "marked": "^0.3.5" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /experimental/static-docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "metamagical-static-docs", 3 | "version": "0.1.0", 4 | "description": "Generates static documentation from Meta:Magical data", 5 | "main": "./lib/index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/origamitower/metamagical.git" 9 | }, 10 | "author": "Quildreen Motta", 11 | "license": "MIT", 12 | "bugs": { 13 | "url": "https://github.com/origamitower/metamagical/issues" 14 | }, 15 | "dependencies": { 16 | "folktale": "^2.0.0-alpha3", 17 | "html-element": "^2.1.1", 18 | "hyperscript": "^2.0.2", 19 | "marked": "^0.3.6", 20 | "mkdirp": "^0.5.1", 21 | "node-uuid": "^1.4.7" 22 | }, 23 | "devDependencies": { 24 | "jumper-skirt": "^0.1.0", 25 | "jumperskirt": "^0.1.9", 26 | "nib": "^1.1.2", 27 | "stylus": "^0.54.5" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /experimental/static-docs/Makefile: -------------------------------------------------------------------------------- 1 | bin := ../../node_modules/.bin 2 | babel := $(bin)/babel 3 | lint := $(bin)/eslint 4 | mocha := $(bin)/mocha 5 | 6 | .DEFAULT_GOAL: build 7 | 8 | resources: 9 | ./node_modules/.bin/stylus -I node_modules/nib/lib -I node_modules/jumperskirt/stylus -o resources/html/ resources/html/ 10 | 11 | build: resources 12 | @echo "-- Compiling metamagical-static-tree" 13 | $(babel) src/ --source-map inline \ 14 | --out-dir lib \ 15 | $$BABEL_OPTIONS 16 | 17 | clean: 18 | rm -rf lib/ 19 | 20 | lint: 21 | $(eslint) . 22 | 23 | test: build 24 | $(babel) test/src --source-map inline --out-dir test/spec 25 | $(mocha) --reporter spec --uid bdd test/spec 26 | 27 | publish: clean-test 28 | npm publish 29 | 30 | install: 31 | npm install 32 | 33 | clean-test: 34 | $(MAKE) clean 35 | rm -rf node_modules 36 | npm install 37 | $(MAKE) test 38 | 39 | .PHONY: build clean lint test clean-test install publish resources 40 | -------------------------------------------------------------------------------- /packages/mocha-bridge/test/src/index.js: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------- 2 | // 3 | // This source file is part of the Meta:Magical project. 4 | // 5 | // See LICENCE for licence information. 6 | // See CONTRIBUTORS for the list of contributors to the project. 7 | // 8 | //---------------------------------------------------------------------- 9 | 10 | var called = false; 11 | 12 | const metamagical = require('../../../interface'); 13 | const defineTests = require('../../')(metamagical, describe, it); 14 | const assert = require('assert'); 15 | 16 | function double(n) { 17 | return n + n; 18 | } 19 | 20 | double[Symbol.for('@@meta:magical')] = { 21 | name: 'double', 22 | examples: [_ => { 23 | assert(double(10) === 20); 24 | assert(double(15) === 30); 25 | called = true; 26 | }] 27 | } 28 | 29 | defineTests(double); 30 | 31 | describe('Mocha-Bridge', () => { 32 | it('Examples were called', () => { 33 | assert(called); 34 | }) 35 | }); 36 | -------------------------------------------------------------------------------- /packages/babel-plugin-assertion-comments/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | All of the changes to the `babel-plugin-transform-assertion-comments` project 4 | are recorded in this file. Changes are ordered from most recent to oldest 5 | versions. 6 | 7 | (This will eventually be autogenerated) 8 | 9 | 10 | ## 0.x 11 | 12 | ### [0.3.1] - 2016-09-17 13 | 14 | #### Bug fixes 15 | 16 | - The `$ASSERT(a == b)` form was not compiling special expectations. This 17 | version allows records and arrays to be provided for either a or b. 18 | 19 | #### Other 20 | 21 | - Better error messages. 22 | 23 | 24 | ### [0.3.0] - 2016-09-05 25 | 26 | #### New Features 27 | 28 | - Support `$ASSERT(actual == expected)` and `$ASSERT(expr)` forms. 29 | These are better for using in tests and stuff. 30 | 31 | 32 | ### [0.2.3] - 2016-05-26 33 | 34 | The first stable release of the Assertion comments plugin supports 35 | assertions for structural equality using Setoids, partial Arrays 36 | and partial Records. 37 | 38 | -------------------------------------------------------------------------------- /packages/interface/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "metamagical-interface", 3 | "version": "3.5.0", 4 | "description": "An interface for attaching metadata to JavaScript objects.", 5 | "main": "./lib/index.js", 6 | "scripts": { 7 | "test": "make test" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/origamitower/metamagical.git" 12 | }, 13 | "keywords": [ 14 | "metadata", 15 | "documentation" 16 | ], 17 | "author": "Quildreen Motta ", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/origamitower/metamagical/issues" 21 | }, 22 | "homepage": "https://github.com/origamitower/metamagical#readme", 23 | "devDependencies": {}, 24 | "dependencies": { 25 | "data.maybe": "^1.2.2", 26 | "refinable": "^1.0.2" 27 | }, 28 | "metamagical": { 29 | "copyright": "(c) 2016 Quildreen Motta", 30 | "maintainers": [ 31 | "Quildreen Motta (http://robotlolita.me/)" 32 | ] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/repl/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | All of the changes to the `metamagical-interface` project are recorded in 4 | this file. Changes are ordered from most recent to oldest versions. 5 | 6 | (This will eventually be autogenerated) 7 | 8 | 9 | ## 0.x 10 | 11 | ### [0.3.0] - 2016-07-08 12 | 13 | - Made `summary` shorter, moving inherited properties away. 14 | 15 | - Added `.properties()` to list all properties in an object. 16 | 17 | - `summary` now returns a longer partial doc, rather than a short synopsis. 18 | 19 | - Show hierarchy in the summary. 20 | 21 | 22 | ### [0.2.0] - 2016-05-27 23 | 24 | The first release of the REPL browser allows one to browse annotated 25 | objects in the Node REPL. 26 | 27 | This includes the Browser object, which can inspect source code, 28 | documentation, and a summary for the object. And a TerminalDisplay 29 | object that tells the Browser how to display the information. 30 | 31 | Displays are configurable, and need only to provide a `.show` method 32 | that defines how to render the Browser AST. -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | The MIT Licence (MIT) 2 | 3 | Copyright (c) 2016 Quildreen Motta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /packages/repl/LICENCE: -------------------------------------------------------------------------------- 1 | The MIT Licence (MIT) 2 | 3 | Copyright (c) 2016 Quildreen Motta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /experimental/mkdocs/LICENCE: -------------------------------------------------------------------------------- 1 | The MIT Licence (MIT) 2 | 3 | Copyright (c) 2016 Quildreen Motta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /experimental/sphinx/LICENCE: -------------------------------------------------------------------------------- 1 | The MIT Licence (MIT) 2 | 3 | Copyright (c) 2016 Quildreen Motta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /experimental/markdown/LICENCE: -------------------------------------------------------------------------------- 1 | The MIT Licence (MIT) 2 | 3 | Copyright (c) 2016 Quildreen Motta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /experimental/static-docs/LICENCE: -------------------------------------------------------------------------------- 1 | The MIT Licence (MIT) 2 | 3 | Copyright (c) 2016 Quildreen Motta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /experimental/static-tree/LICENCE: -------------------------------------------------------------------------------- 1 | The MIT Licence (MIT) 2 | 3 | Copyright (c) 2016 Quildreen Motta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /packages/interface/LICENCE: -------------------------------------------------------------------------------- 1 | The MIT Licence (MIT) 2 | 3 | Copyright (c) 2016 Quildreen Motta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /packages/mocha-bridge/LICENCE: -------------------------------------------------------------------------------- 1 | The MIT Licence (MIT) 2 | 3 | Copyright (c) 2016 Quildreen Motta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /packages/babel-plugin-assertion-comments/LICENCE: -------------------------------------------------------------------------------- 1 | The MIT Licence (MIT) 2 | 3 | Copyright (c) 2016 Quildreen Motta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /packages/interface/src/assertions.js: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------- 2 | // 3 | // This source file is part of the Meta:Magical project. 4 | // 5 | // See LICENCE for licence information. 6 | // See CONTRIBUTORS for the list of contributors to the project. 7 | // 8 | //--------------------------------------------------------------------- 9 | 10 | /*~ 11 | * Assertions used by other modules. 12 | * 13 | * --- 14 | * name : module assertions 15 | * module : metamagical-interface/lib/assertions 16 | * category : Assertions 17 | * platforms: 18 | * - ECMAScript 3 19 | */ 20 | module.exports = { 21 | /*~ 22 | * Makes sure that `value` is an object. 23 | * 24 | * --- 25 | * category : Assertions 26 | * stability : stable 27 | * 28 | * throws: 29 | * TypeError: when the value isn't an object. 30 | * 31 | * type: | 32 | * (Any) => None :: throws TypeError 33 | */ 34 | assertObject(value) { 35 | if (Object(value) !== value) { 36 | const kind = value === null ? 'null' 37 | : value === undefined ? 'undefined' 38 | : /* otherwise */ `a primitive value (${JSON.stringify(value)})`; 39 | 40 | throw new TypeError(`Meta:Magical can only associate meta-data with objects, but you're trying to use ${kind}`); 41 | } 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /tools/generate-docs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var metamagical = require('../packages/interface'); 6 | var generateTree = require('../packages/mkdocs')(metamagical).generateTree; 7 | var tree = require('../packages/static-tree'); 8 | 9 | var root = path.join(__dirname, '../documentation/docs'); 10 | 11 | 12 | function exists(path) { 13 | try { 14 | fs.accessSync(path); 15 | return true; 16 | } catch (e) { 17 | return false; 18 | } 19 | } 20 | 21 | function mkdir(path) { 22 | if (!exists(path)) { 23 | console.log('Creating directory', path); 24 | fs.mkdirSync(path); 25 | } 26 | } 27 | 28 | function mkdirp(pathString) { 29 | if (!exists(pathString)) { 30 | var parent = path.join(pathString, '..'); 31 | if (!exists(parent)) { 32 | mkdirp(parent); 33 | } 34 | mkdir(pathString); 35 | } 36 | } 37 | 38 | function write(pathString, data) { 39 | mkdirp(path.dirname(pathString)); 40 | fs.writeFileSync(pathString, data, 'utf8'); 41 | } 42 | 43 | function p(pathString) { 44 | return path.resolve(root, pathString); 45 | } 46 | 47 | var data = tree(metamagical, 'metamagical-interface', require('../packages/interface'), { 48 | skipUndocumented: true 49 | }); 50 | var files = generateTree(data.tree, { references: data.references }); 51 | 52 | files.forEach(function(file) { 53 | console.log(': Generating', file.filename); 54 | write(p(file.filename), file.content); 55 | }); 56 | -------------------------------------------------------------------------------- /experimental/static-docs/src/index.js: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------- 2 | // 3 | // This source file is part of the Meta:Magical project. 4 | // 5 | // See LICENCE for licence information. 6 | // See CONTRIBUTORS for the list of contributors to the project. 7 | // 8 | //---------------------------------------------------------------------- 9 | 10 | // --[ Dependencies ]--------------------------------------------------- 11 | const mkdirp = require('mkdirp').sync; 12 | const fs = require('fs'); 13 | const path = require('path'); 14 | 15 | const formatters = require('./formatters'); 16 | const { makeStatic } = require('./staticalise'); 17 | 18 | 19 | // --[ Helpers ]------------------------------------------------------- 20 | const write = (file, data) => fs.writeFileSync(file, data); 21 | 22 | 23 | // --[ Implementation ]------------------------------------------------ 24 | const generate = (files, options) => { 25 | const outputDirectory = options.outputDirectory || '.'; 26 | const verbose = options.verbose || false; 27 | 28 | files.forEach(file => { 29 | const filename = path.join(outputDirectory, file.filename); 30 | if (verbose) { 31 | console.log('- Writing ', filename); 32 | } 33 | mkdirp(path.dirname(filename)); 34 | write(filename, file.content); 35 | }); 36 | }; 37 | 38 | 39 | // --[ Exports ]------------------------------------------------------- 40 | module.exports = { 41 | generate, 42 | makeStatic, 43 | formatters 44 | }; 45 | -------------------------------------------------------------------------------- /packages/repl/src/index.js: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------- 2 | // 3 | // This source file is part of the Meta:Magical project. 4 | // 5 | // See LICENCE for licence information. 6 | // See CONTRIBUTORS for the list of contributors to the project. 7 | // 8 | //--------------------------------------------------------------------- 9 | 10 | // --[ Dependencies ]-------------------------------------------------- 11 | const Browser = require('./browser'); 12 | 13 | 14 | // --[ Module ]-------------------------------------------------------- 15 | 16 | /*~ 17 | * Constructs a Meta:Magical browser. 18 | * 19 | * The Browser is parameterised over a Meta:Magical interface, so before 20 | * you can browse objects you must provide the proper interface: 21 | * 22 | * const Interface = require('metamagical-interface'); 23 | * const Browser = require('metamagical-repl')(Interface); 24 | * 25 | * // Browser can now be used to inspect objects: 26 | * Browser.for(yourObject).summary(); 27 | * 28 | * // The browser starts inspecting itself, so you can look at 29 | * // its properties easily: 30 | * Browser.summary(); 31 | * Browser.forProperty("for").documentation() 32 | * 33 | * --- 34 | * category : Constructing 35 | * stability : experimental 36 | * type: | 37 | * (Interface) => Browser 38 | */ 39 | module.exports = function(metamagical) { 40 | return Browser.for(metamagical.for(Browser)); 41 | }; 42 | 43 | module.exports.AST = require('./ast'); 44 | module.exports.Browser = require('./browser'); 45 | module.exports.TerminalDisplay = require('./terminal-display'); 46 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: help 2 | help: 3 | @echo "--[ CONFIGURATION ]------------------------------------------" 4 | @echo "You may configure the build with the following environment" 5 | @echo "variables:" 6 | @echo "" 7 | @echo " MM_EXPERIMENTAL_PACKAGES ..... Include experimental packages" 8 | @echo "" 9 | @echo "" 10 | @echo "--[ AVAILABLE TASKS ]----------------------------------------" 11 | @echo "" 12 | @echo " install .............. Installs all dependencies" 13 | @echo " lint ................. Lints selected packages" 14 | @echo " build ................ Builds selected packages" 15 | @echo " test ................. Tests selected packages" 16 | @echo " clean-test ........... Re-installs, re-builds, and runs tests" 17 | @echo " clean ................ Removes build artifacts from selected packages" 18 | @echo "" 19 | 20 | 21 | # ---------------------------------------------------------------------- 22 | bin := $(shell npm bin) 23 | babel := $(bin)/babel 24 | eslint := $(bin)/eslint 25 | mocha := $(bin)/mocha 26 | 27 | 28 | # -- [ CONFIGURATION ] ------------------------------------------------- 29 | ifndef MM_PACKAGES 30 | ifeq ($(MM_EXPERIMENTAL_PACKAGES),true) 31 | export MM_PACKAGES = $(wildcard packages/* experimental/*) 32 | else 33 | export MM_PACKAGES = $(wildcard packages/*) 34 | endif 35 | endif 36 | 37 | 38 | # -- [ TASKS ] --------------------------------------------------------- 39 | .PHONY: lint 40 | lint: 41 | ./Scripts/run.sh lint "$$MM_PACKAGES" 42 | 43 | .PHONY: build 44 | build: 45 | ./Scripts/run.sh build "$$MM_PACKAGES" 46 | 47 | .PHONY: test 48 | test: 49 | $(MAKE) build 50 | ./Scripts/run.sh test "$$MM_PACKAGES" 51 | 52 | .PHONY: clean-test 53 | clean-test: 54 | ./Scripts/run.sh clean-test "$$MM_PACKAGES" 55 | 56 | .PHONY: clean 57 | clean: 58 | ./Scripts/run.sh clean "$$MM_PACKAGES" 59 | 60 | .PHONY: install 61 | install: 62 | npm install 63 | ./Scripts/run.sh install "$$MM_PACKAGES" 64 | -------------------------------------------------------------------------------- /.gitlabels: -------------------------------------------------------------------------------- 1 | # This file describes meta-data associated with Git commits 2 | # that can be processed by tools. 3 | # 4 | # See 5 | 6 | # --[ Nature of the change ]------------------------------------------- 7 | # These labels give a summary of the kind of change that was made. 8 | # They may be combined, although it's usually better to have separated 9 | # commits introducing them. 10 | 11 | - (new) Introduces new functionality or features. 12 | - (fix) Fixes the behaviour of exiting features. 13 | - (doc documentation) Introduces documentation. 14 | - (test) Introduces new tests or improves existing ones. 15 | - (perf performance) Introduces performance optimisations. 16 | - (re refactor) Changes structure, but doesn't introduce new features nor fixes bugs. 17 | - (build) Changes to the build scripts 18 | 19 | # --[ Qualifiers ]----------------------------------------------------- 20 | # Changes may be qualified 21 | 22 | - (!! important) The commit introduces substantial changes. 23 | - (break) The change breaks an existing feature 24 | - (- minor) The change is rather minor and may be ignored in most cases 25 | - (release) A new public release 26 | 27 | # --[ Scopes ]--------------------------------------------------------- 28 | # Because this is a multi-package repository, sometimes changes apply 29 | # to only a few of these scopes. These labels exist for that reason 30 | 31 | - (scoped) The scope of changes. 32 | - (iface interface) The Meta:Magical interface. 33 | - (repl) The REPL browser 34 | - (mocha) The Mocha bridge 35 | - (mmdoc babel-comments) The Meta:Magical comments babel plugin 36 | - (assert babel-assertion) The assertion comments plugin 37 | 38 | # The packages below are experimental 39 | - (mdjs markdown) The markdown→JS compiler 40 | - (mkdocs) The mkdocs generator 41 | - (sphinx) The Sphinx generator 42 | - (tree) The static-tree library 43 | - (static) The static-docs library 44 | -------------------------------------------------------------------------------- /experimental/sphinx/README.md: -------------------------------------------------------------------------------- 1 | # Meta:Magical Sphinx 2 | 3 | [![NPM version](https://img.shields.io/npm/v/metamagical-sphinx.svg?style=flat-square)](https://npmjs.org/package/metamagical-sphinx) 4 | ![Licence](https://img.shields.io/npm/l/metamagical-sphinx.svg?style=flat-square&label=licence) 5 | ![Stability: Stable](https://img.shields.io/badge/stability-experimental-orange.svg?style=flat-square) 6 | 7 | Generates Sphinx documentation from Meta:Magical annotations. 8 | 9 | 10 | ## Example 11 | 12 | ( ... ) 13 | 14 | 15 | ## Installing 16 | 17 | The only supported way to install the transform plugin is through [npm][]. 18 | 19 | > **NOTE** 20 | > If you don't have npm yet, you'll need to install [Node.js][]. All newer 21 | > versions (4+) of Node come with npm. 22 | 23 | ```shell 24 | $ npm install metamagical-sphinx 25 | ``` 26 | 27 | [npm]: https://www.npmjs.com/ 28 | [Node.js]: nodejs.org 29 | 30 | 31 | 32 | ## Support 33 | 34 | If you think you've found a bug in the project, or want to voice your 35 | frustration about using it (maybe the documentation isn't clear enough? Maybe 36 | it takes too much effort to use?), feel free to open a new issue in the 37 | [Github issue tracker](https://github.com/origamitower/metamagical/issues). 38 | 39 | Pull Requests are welcome. By submitting a Pull Request you agree with releasing 40 | your code under the MIT licence. 41 | 42 | You can join the [Gitter Channel](https://gitter.im/origamitower/discussion) for 43 | quick support. You may also contact the author directly through 44 | [email](mailto:queen@robotlolita.me), or 45 | [Twitter](https://twitter.com/robotlolita). 46 | 47 | Note that all interactions in this project are subject to Origami Tower's 48 | [Code of Conduct](https://github.com/origamitower/metamagical/blob/master/CODE_OF_CONDUCT.md). 49 | 50 | 51 | ## Licence 52 | 53 | Meta:Magical is copyright (c) Quildreen Motta 2016, and released under the MIT licence. See the `LICENCE` file in this repository for detailed information. 54 | -------------------------------------------------------------------------------- /packages/mocha-bridge/src/index.js: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------- 2 | // 3 | // This source file is part of the Meta:Magical project. 4 | // 5 | // See LICENCE for licence information. 6 | // See CONTRIBUTORS for the list of contributors to the project. 7 | // 8 | //---------------------------------------------------------------------- 9 | 10 | 11 | function isObject(object) { 12 | return Object(object) === object; 13 | } 14 | 15 | function groupBy(xs, f) { 16 | let groups = new Map(); 17 | 18 | xs.forEach(x => { 19 | const key = f(x); 20 | const values = groups.get(key) || []; 21 | values.push(x); 22 | groups.set(key, values); 23 | }); 24 | 25 | return [...groups.entries()]; 26 | } 27 | 28 | function exampleDescription(example) { 29 | return example.name ? example.name.replace(/_/, ' ') 30 | : /* otherwise */ 'Meta:Magical examples'; 31 | } 32 | 33 | 34 | module.exports = (meta, describe, it) => (object) => { 35 | let visited = new Set(); 36 | const _ = meta.fields; 37 | 38 | function isProvided(kind, value) { 39 | if (kind !== 'getter') { 40 | return isObject(value); 41 | } else { 42 | return !meta.for(value).get(_.isRequired).getOrElse(false); 43 | } 44 | } 45 | 46 | function defineTests(object) { 47 | if (!isObject(object) || visited.has(object)) return; 48 | 49 | const m = meta.for(object); 50 | 51 | visited.add(object); 52 | 53 | m.get(_.examples).chain(examples => { 54 | const exampleGroups = groupBy(examples, exampleDescription); 55 | exampleGroups.forEach(([heading, functions]) => { 56 | it(heading, () => functions.reduce((p, f) => p.then(_ => f.call()), Promise.resolve())); 57 | }); 58 | }); 59 | 60 | m.properties().forEach(({ category, members }) => { 61 | describe(`(${category})`, () => { 62 | members.forEach(({ name, kind, value }) => { 63 | if (isProvided(kind, value)) { 64 | describe(`.${name}`, () => defineTests(value)); 65 | } 66 | }); 67 | }); 68 | }); 69 | } 70 | 71 | meta.for(object).get(_.name).chain(name => { 72 | describe(`Examples in ${name}`, () => { 73 | defineTests(object); 74 | }); 75 | }); 76 | }; 77 | -------------------------------------------------------------------------------- /experimental/markdown/README.md: -------------------------------------------------------------------------------- 1 | # Meta:Magical Markdown 2 | 3 | [![NPM version](https://img.shields.io/npm/v/metamagical-markdown.svg?style=flat-square)](https://npmjs.org/package/metamagical-markdown) 4 | ![Licence](https://img.shields.io/npm/l/metamagical-markdown.svg?style=flat-square&label=licence) 5 | ![Stability: Stable](https://img.shields.io/badge/stability-experimental-orange.svg?style=flat-square) 6 | 7 | Compiles Markdown files to JS modules annotated with Meta:Magical. 8 | 9 | 10 | ## Example 11 | 12 | Write your Markdown file, and optionally add meta-data using an YAML document: 13 | 14 | ```md 15 | --- 16 | category: Tutorials 17 | --- 18 | 19 | # Heading 20 | 21 | Text goes here 22 | ``` 23 | 24 | Compile it: 25 | 26 | ```shell 27 | metamagical-markdown file.md 28 | ``` 29 | 30 | Get: 31 | 32 | ```js 33 | module.exports = { 34 | [Symbol.for('@@meta:magical')] = { 35 | category: 'Tutorials', 36 | name: 'Heading', 37 | documentation: '# Heading\n\nText goes here' 38 | } 39 | }; 40 | ``` 41 | 42 | 43 | ## Installing 44 | 45 | The only supported way to install the transform plugin is through [npm][]. 46 | 47 | > **NOTE** 48 | > If you don't have npm yet, you'll need to install [Node.js][]. All newer 49 | > versions (4+) of Node come with npm. 50 | 51 | ```shell 52 | $ npm install metamagical-markdown 53 | ``` 54 | 55 | [npm]: https://www.npmjs.com/ 56 | [Node.js]: nodejs.org 57 | 58 | 59 | 60 | ## Support 61 | 62 | If you think you've found a bug in the project, or want to voice your 63 | frustration about using it (maybe the documentation isn't clear enough? Maybe 64 | it takes too much effort to use?), feel free to open a new issue in the 65 | [Github issue tracker](https://github.com/origamitower/metamagical/issues). 66 | 67 | Pull Requests are welcome. By submitting a Pull Request you agree with releasing 68 | your code under the MIT licence. 69 | 70 | You can join the [Gitter Channel](https://gitter.im/origamitower/discussion) for 71 | quick support. You may also contact the author directly through 72 | [email](mailto:queen@robotlolita.me), or 73 | [Twitter](https://twitter.com/robotlolita). 74 | 75 | Note that all interactions in this project are subject to Origami Tower's 76 | [Code of Conduct](https://github.com/origamitower/metamagical/blob/master/CODE_OF_CONDUCT.md). 77 | 78 | 79 | ## Licence 80 | 81 | Meta:Magical is copyright (c) Quildreen Motta 2016, and released under the MIT licence. See the `LICENCE` file in this repository for detailed information. 82 | -------------------------------------------------------------------------------- /packages/repl/src/ast.js: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------- 2 | // 3 | // This source file is part of the Meta:Magical project. 4 | // 5 | // See LICENCE for licence information. 6 | // See CONTRIBUTORS for the list of contributors to the project. 7 | // 8 | //--------------------------------------------------------------------- 9 | 10 | // --[ Dependencies ]-------------------------------------------------- 11 | const { data } = require('folktale/core/adt'); 12 | 13 | 14 | // --[ ADT Definition ]------------------------------------------------ 15 | 16 | /*~ 17 | * The AST used by displays to present information. 18 | * 19 | * --- 20 | * isModule : true 21 | * stability : experimental 22 | * category : Representing output 23 | */ 24 | const AST = data('metamagical-browser:ADT', { 25 | 26 | // ---[ Base nodes ]------------------------------------------------- 27 | Nil() { 28 | return {}; 29 | }, 30 | 31 | Text(text) { 32 | return { text }; 33 | }, 34 | 35 | 36 | // ---[ Structure ]-------------------------------------------------- 37 | Sequence(items) { 38 | return { items }; 39 | }, 40 | 41 | Block(indentation, items) { 42 | return { indentation, items }; 43 | }, 44 | 45 | Title(level, content) { 46 | return { level, content }; 47 | }, 48 | 49 | Subtitle(level, content) { 50 | return { level, content }; 51 | }, 52 | 53 | Quote(content) { 54 | return { content }; 55 | }, 56 | 57 | Code(language, source) { 58 | return { language, source }; 59 | }, 60 | 61 | List(items) { 62 | return { items }; 63 | }, 64 | 65 | Table(headings, rows) { 66 | return { headings, rows }; 67 | }, 68 | 69 | HorizontalLine(size = 72) { 70 | return { size }; 71 | }, 72 | 73 | // ---[ Inline formatting ]------------------------------------------ 74 | Strong(content) { 75 | return { content }; 76 | }, 77 | 78 | Emphasis(content) { 79 | return { content }; 80 | }, 81 | 82 | Good(content) { 83 | return { content }; 84 | }, 85 | 86 | Error(content) { 87 | return { content }; 88 | }, 89 | 90 | Warning(content) { 91 | return { content }; 92 | }, 93 | 94 | Detail(content) { 95 | return { content }; 96 | }, 97 | 98 | Label(content) { 99 | return { content }; 100 | }, 101 | 102 | // TODO: this should be replaced by nodes in this ast, but we don't have a Markdown parser rn 103 | Markdown(text) { 104 | return { text }; 105 | } 106 | 107 | }); 108 | 109 | 110 | // --[ Exports ]------------------------------------------------------- 111 | module.exports = AST; -------------------------------------------------------------------------------- /packages/interface/src/annotations/built-ins.js: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------- 2 | // 3 | // This source file is part of the Meta:Magical project. 4 | // 5 | // See LICENCE for licence information. 6 | // See CONTRIBUTORS for the list of contributors to the project. 7 | // 8 | //--------------------------------------------------------------------- 9 | 10 | module.exports = (meta) => { 11 | meta.for(Object).update({ 12 | module: '(built-in)', 13 | platforms: ['ECMAScript'], 14 | name: 'Object' 15 | }); 16 | 17 | meta.for(Object.prototype).update({ 18 | module: '(built-in)', 19 | platforms: ['ECMAScript'], 20 | name: 'Object.prototype' 21 | }); 22 | 23 | meta.for(Function).update({ 24 | module: '(built-in)', 25 | platforms: ['ECMAScript'], 26 | name: 'Function.prototype' 27 | }); 28 | 29 | meta.for(String).update({ 30 | module: '(built-in)', 31 | platforms: ['ECMAScript'], 32 | name: 'String' 33 | }); 34 | 35 | meta.for(String.prototype).update({ 36 | module: '(built-in)', 37 | platforms: ['ECMAScript'], 38 | name: 'String.prototype' 39 | }); 40 | 41 | meta.for(Number).update({ 42 | module: '(built-in)', 43 | platforms: ['ECMAScript'], 44 | name: 'Number' 45 | }); 46 | 47 | meta.for(Number.prototype).update({ 48 | module: '(built-in)', 49 | platforms: ['ECMAScript'], 50 | name: 'Number.prototype' 51 | }); 52 | 53 | meta.for(Boolean).update({ 54 | module: '(built-in)', 55 | platforms: ['ECMAScript'], 56 | name: 'Boolean' 57 | }); 58 | 59 | meta.for(Boolean.prototype).update({ 60 | module: '(built-in)', 61 | platforms: ['ECMAScript'], 62 | name: 'Boolean.prototype' 63 | }); 64 | 65 | meta.for(RegExp).update({ 66 | module: '(built-in)', 67 | platforms: ['ECMAScript'], 68 | name: 'RegExp' 69 | }); 70 | 71 | meta.for(RegExp.prototype).update({ 72 | module: '(built-in)', 73 | platforms: ['ECMAScript'], 74 | name: 'RegExp.prototype' 75 | }); 76 | 77 | meta.for(Date).update({ 78 | module: '(built-in)', 79 | platforms: ['ECMAScript'], 80 | name: 'Date' 81 | }); 82 | 83 | meta.for(Date.prototype).update({ 84 | module: '(built-in)', 85 | platforms: ['ECMAScript'], 86 | name: 'Date.prototype' 87 | }); 88 | 89 | meta.for(Array).update({ 90 | module: '(built-in)', 91 | platforms: ['ECMAScript'], 92 | name: 'Array' 93 | }); 94 | 95 | meta.for(Array.prototype).update({ 96 | module: '(built-in)', 97 | platforms: ['ECMAScript'], 98 | name: 'Array.prototype' 99 | }) 100 | }; 101 | -------------------------------------------------------------------------------- /packages/repl/README.md: -------------------------------------------------------------------------------- 1 | # The REPL Browser 2 | 3 | [![Chat on Gitter](https://img.shields.io/gitter/room/origamitower/discussion.svg?style=flat-square)](https://gitter.im/origamitower/discussion) 4 | [![Build status](https://img.shields.io/travis/origamitower/metamagical/master.svg?style=flat-square)](https://travis-ci.org/origamitower/metamagical) 5 | [![NPM version](https://img.shields.io/npm/v/metamagical-repl.svg?style=flat-square)](https://npmjs.org/package/metamagical-repl) 6 | ![Licence](https://img.shields.io/npm/l/metamagical-repl.svg?style=flat-square&label=licence) 7 | ![Stability: Experimental](https://img.shields.io/badge/stability-experimental-orange.svg?style=flat-square) 8 | 9 | The Meta:Magical REPL browser allows one to explore annotated objects from 10 | Node's REPL. 11 | 12 | ![](https://raw.github.com/origamitower/metamagical/master/Images/repl.png) 13 | 14 | 15 | ## Installing 16 | 17 | The only supported way to install the transform plugin is through [npm][]. 18 | 19 | > **NOTE** 20 | > If you don't have npm yet, you'll need to install [Node.js][]. All newer 21 | > versions (4+) of Node come with npm. 22 | 23 | ```shell 24 | $ npm install metamagical-repl 25 | ``` 26 | 27 | [npm]: https://www.npmjs.com/ 28 | [Node.js]: nodejs.org 29 | 30 | 31 | ## Documentation 32 | 33 | To learn about the REPL browser, you can use the REPL browser itself! Before you can do 34 | that you'll need to download and build this project: 35 | 36 | ```shell 37 | git clone https://github.com/origamitower/metamagical.git 38 | cd metamagical 39 | npm install # install build tooling 40 | make all # compiles all sub projects 41 | ``` 42 | 43 | Then launch a Node REPL to browse the project: 44 | 45 | ```js 46 | node> var Interface = require('./packages/interface') 47 | node> var browser = require('./packages/repl')(Interface) 48 | node> browser.summary() 49 | ``` 50 | 51 | ## Support 52 | 53 | If you think you've found a bug in the project, or want to voice your 54 | frustration about using it (maybe the documentation isn't clear enough? Maybe 55 | it takes too much effort to use?), feel free to open a new issue in the 56 | [Github issue tracker](https://github.com/origamitower/metamagical/issues). 57 | 58 | Pull Requests are welcome. By submitting a Pull Request you agree with releasing 59 | your code under the MIT licence. 60 | 61 | You can join the [Gitter Channel](https://gitter.im/origamitower/discussion) for quick support. 62 | You can contact the author over [email](mailto:queen@robotlolita.me), or 63 | [Twitter](https://twitter.com/robotlolita). 64 | 65 | Note that all interactions in this project are subject to Origami Tower's 66 | [Code of Conduct](https://github.com/origamitower/metamagical/blob/master/CODE_OF_CONDUCT.md). 67 | 68 | 69 | ## Licence 70 | 71 | Meta:Magical is copyright (c) Quildreen Motta, 2016, and released under the MIT 72 | licence. See the `LICENCE` file in this repository for detailed information. 73 | -------------------------------------------------------------------------------- /experimental/markdown/src/cli.js: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------- 2 | // 3 | // This source file is part of the Meta:Magical project. 4 | // 5 | // See LICENCE for licence information. 6 | // See CONTRIBUTORS for the list of contributors to the project. 7 | // 8 | //---------------------------------------------------------------------- 9 | 10 | const doc = ` 11 | metamagical-markdown — Compiles Markdown to annotated JavaScript modules. 12 | 13 | Usage: 14 | metamagical-markdown [options] 15 | metamagical-markdown --version 16 | metamagical-markdown --help 17 | 18 | Options: 19 | --version Displays the version number. 20 | -h, --help Displays this screen. 21 | --babel-options= A path to a '.babelrc' file (looks in CWD by default) 22 | `; 23 | 24 | // -- DEPENDENCIES ----------------------------------------------------- 25 | const docopt = require('docopt').docopt; 26 | const pkg = require('../package.json'); 27 | const fs = require('fs'); 28 | const path = require('path'); 29 | const { parse, generate } = require('./'); 30 | 31 | function show(...args) { 32 | console.log(...args); 33 | } 34 | 35 | function read(file) { 36 | return fs.readFileSync(file, 'utf-8'); 37 | } 38 | 39 | function exists(file) { 40 | try { 41 | fs.accessSync(file); 42 | return true; 43 | } catch (_e) { 44 | return false; 45 | } 46 | } 47 | 48 | function parentDirectory(dir) { 49 | const result = path.dirname(dir); 50 | if (path.resolve(result) === path.resolve(dir)) { 51 | throw new Error("No Babel configuration found."); 52 | } 53 | 54 | return result; 55 | } 56 | 57 | function findBabelConfigFrom(dir) { 58 | if (exists(path.join(dir, '.babelrc'))) { 59 | return JSON.parse(read(path.join(dir, '.babelrc'))); 60 | } else if (exists(path.join(dir, 'package.json'))) { 61 | const meta = JSON.parse(read(path.join(dir, 'package.json'))); 62 | if (meta.babel) { 63 | return meta.babel; 64 | } else { 65 | return findBabelConfigFrom(parentDirectory(dir)); 66 | } 67 | } else { 68 | return findBabelConfigFrom(parentDirectory(dir)); 69 | } 70 | } 71 | 72 | function getBabelOptions(file, babelConfig) { 73 | try { 74 | if (babelConfig) { 75 | return JSON.parse(fs.readFileSync(babelConfig, 'utf-8')); 76 | } else { 77 | return findBabelConfigFrom(path.dirname(path.resolve(file))); 78 | } 79 | } catch (_e) { 80 | return {}; 81 | } 82 | } 83 | 84 | module.exports = function Main() { 85 | const args = docopt(doc, { help: false }); 86 | 87 | ; args['--help'] ? show(doc) 88 | : args['--version'] ? show(`metamagical-markdown version ${pkg.version}`) 89 | : /* else */ show(generate(parse( 90 | read(args['']), 91 | getBabelOptions(args[''], args['--babel-options']) 92 | ))); 93 | }; 94 | -------------------------------------------------------------------------------- /experimental/static-tree/src/index.js: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------- 2 | // 3 | // This source file is part of the Meta:Magical project. 4 | // 5 | // See LICENCE for licence information. 6 | // See CONTRIBUTORS for the list of contributors to the project. 7 | // 8 | //---------------------------------------------------------------------- 9 | 10 | /*~ 11 | */ 12 | module.exports = function(meta, name, root, options = {}) { 13 | let result = Object.create(null); 14 | let references = new Map(options.references ? options.references.entries() : []); 15 | let skip = options.skip || new Set(); 16 | 17 | 18 | function isObject(value) { 19 | return Object(value) === value; 20 | } 21 | 22 | function arrayFromFilePath(path) { 23 | return path.split('/'); 24 | } 25 | 26 | function computeIndexPath(object, path) { 27 | return meta.for(object) 28 | .get(meta.fields.module) 29 | .map(m => arrayFromFilePath(m)) 30 | .getOrElse(['(unknown module)', ...path]); 31 | } 32 | 33 | function isModule(object) { 34 | return meta.for(object).get(meta.fields.isModule).getOrElse(false); 35 | } 36 | 37 | function isDocumented(object) { 38 | return meta.for(object).get(meta.fields.documentation).map(_ => true).getOrElse(false); 39 | } 40 | 41 | function allowsPopping(path) { 42 | return (path[0] === '(unknown module)' && path.length > 1) 43 | || path.length > 0; 44 | } 45 | 46 | function insertObject(name, path, object) { 47 | let parentPath = computeIndexPath(object, path); 48 | if (isModule(object) && allowsPopping(parentPath)) { 49 | name = parentPath.pop(); 50 | } 51 | 52 | references.set(object, [...parentPath, name].join('/')); 53 | 54 | let where = parentPath.reduce((container, key) => { 55 | container[key] = container[key] || Object.create(null); 56 | if (!container[key].children) { 57 | container[key].children = Object.create(null); 58 | } 59 | return container[key].children; 60 | }, result); 61 | 62 | where[name] = where[name] || Object.create(null); 63 | where[name].type = 'object'; 64 | where[name].object = object; 65 | where[name].children = where[name].children || Object.create(null); 66 | 67 | return where[name]; 68 | } 69 | 70 | function go(where, name, object, path) { 71 | if (!isObject(object) || skip.has(object)) { 72 | return; 73 | } 74 | if (!isDocumented(object) && options.skipUndocumented) { 75 | return; 76 | } 77 | if (references.has(object)) { 78 | where[name] = Object.create(null); 79 | where[name].type = 'reference'; 80 | where[name].reference = references.get(object); 81 | } else { 82 | let data = insertObject(name, path, object); 83 | meta.for(object).allProperties().forEach(({ members }) => members.forEach(p => { 84 | go(data.children, p.name, p.value, [...path, name]); 85 | })); 86 | } 87 | } 88 | 89 | go(result, name, root, []); 90 | 91 | return { 92 | references, 93 | tree: result 94 | }; 95 | }; 96 | -------------------------------------------------------------------------------- /packages/babel-plugin-metamagical-comments/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | All of the changes to the `babel-plugin-transform-metamagical-comments` project 4 | are recorded in this file. Changes are ordered from most recent to oldest 5 | versions. 6 | 7 | (This will eventually be autogenerated) 8 | 9 | 10 | ## 0.x 11 | 12 | ### [0.13.1] - 2016-11-13 13 | 14 | #### Fixes 15 | 16 | - [Properly handles getters/setters in classes](https://github.com/origamitower/metamagical/commit/6dd61214cf016391fd3a906b72ee6939de0d6172). Previously getters were being executed when attaching the documentation to the object. Documentation was also attached to the wrong object. 17 | 18 | 19 | ### [0.13.0] - 2016-11-13 20 | 21 | #### New features 22 | 23 | - [Support documentations for classes and class methods](https://github.com/origamitower/metamagical/commit/1c7e6758893c342ec049380e737bb4e5313a4696) 24 | 25 | - [Infer `deprecated` stability from `deprecated` annotations](https://github.com/origamitower/metamagical/commit/d7de0f2dbd76be043610996969b794b41fbbb57d) 26 | 27 | 28 | #### Fixes 29 | 30 | - [Allow string-valued keys in object literals to be documented](https://github.com/origamitower/metamagical/commit/ed0dfe9cc0f615d58c32746224bef93dd18e5020). Previously we only considered Identifier keys in object literals, so when you had a String key the compiler would crash. This fixes that. 31 | 32 | 33 | ### [0.12.0] - 2016-07-09 34 | 35 | 36 | #### New features 37 | 38 | - [Allow users to configure Babylon's parsing of examples](https://github.com/origamitower/metamagical/commit/2ccf1f8b7bdd26d722625bef16599c63c5fd3362) 39 | 40 | Babel doesn't expose the configured Babylon version to plugins, so this 41 | is a compromise to allow different syntactical expectations to be used 42 | in examples 43 | 44 | 45 | #### Fixes 46 | 47 | - [Don't compute module IDs if the packages don't have a name](https://github.com/origamitower/metamagical/commit/1fbab291b43c6affa9c51d2fc6cac7bb3d52bc85) ([#23](https://github.com/origamitower/metamagical/issues/23)) 48 | 49 | If the `package.json` file doesn't have a `name`, we give up inferring the 50 | module ID. This way modules outside of packages always have to provide 51 | their correct module IDs explicitly. 52 | 53 | - [Ensure ASI doesn't apply between examples.](https://github.com/origamitower/metamagical/commit/6cb2a46065e0c647821f6bb59e69bd28404679c3) 54 | 55 | Tracking these rules is hard for these cases, and failures are hard to 56 | understand. We still get things merged between example sections, but 57 | statements can't continue between one example section and the other. 58 | 59 | 60 | 61 | ### [0.11.1] - 2016-05-25 62 | 63 | The first stable release of the plugin, features: 64 | 65 | - The ability to annotate function declarations, variable definitions, assignments, methods, getters and setters; 66 | - Inferring metadata from the definition (source, signature, name, parent object); 67 | - Including metadata from package.json and filename/source position; 68 | - Inferring examples from documentation text; 69 | 70 | Known issues: 71 | 72 | - package.json's short URLs for repositories are not parsed yet; 73 | - CONTRIBUTORS/AUTHORS files are not parsed yet; 74 | - overrides are not tracked yet; 75 | -------------------------------------------------------------------------------- /packages/mocha-bridge/README.md: -------------------------------------------------------------------------------- 1 | # Meta:Magical Mocha Bridge 2 | 3 | [![Chat on Gitter](https://img.shields.io/gitter/room/origamitower/discussion.svg?style=flat-square)](https://gitter.im/origamitower/discussion) 4 | [![Build status](https://img.shields.io/travis/origamitower/metamagical/master.svg?style=flat-square)](https://travis-ci.org/origamitower/metamagical) 5 | [![NPM version](https://img.shields.io/npm/v/metamagical-mocha-bridge.svg?style=flat-square)](https://npmjs.org/package/metamagical-mocha-bridge) 6 | ![Licence](https://img.shields.io/npm/l/metamagical-mocha-bridge.svg?style=flat-square&label=licence) 7 | ![Stability: Stable](https://img.shields.io/badge/stability-experimental-orange.svg?style=flat-square) 8 | 9 | This package allows defining Mocha test cases from Meta:Magical examples. 10 | 11 | 12 | ## Example 13 | 14 | Annotate some object with Meta:Magical using the `examples` meta-data: 15 | 16 | ```js 17 | function double(n) { 18 | return n + n; 19 | } 20 | 21 | double[Symbol.for('@@meta:magical')] = { 22 | name: 'double', 23 | examples: [_ => { 24 | var assert = require('assert'); 25 | assert(double(10) === 20); 26 | assert(double(15) === 30); 27 | }] 28 | } 29 | ``` 30 | 31 | Then use this module inside a Mocha test: 32 | 33 | ```js 34 | const metamagical = require('metamagical-interface'); 35 | const defineTests = require('metamagical-mocha-bridge')(metamagical, describe, it); 36 | 37 | defineTests(double); 38 | ``` 39 | 40 | > **NOTE** 41 | > metamagical-mocha-bridge only runs tests for **named** objects. 42 | 43 | If you use the [Babel assertion comments plugin](../babel-plugin-assertion-comments), you can write this instead: 44 | 45 | ```js 46 | function double(n) { 47 | return n + n; 48 | } 49 | 50 | double[Symbol.for('@@meta:magical')] = { 51 | name: 'double', 52 | examples: [_ => { 53 | double(10); // ==> 20 54 | double(15); // ==> 30 55 | }] 56 | } 57 | ``` 58 | 59 | 60 | 61 | ## Installing 62 | 63 | The only supported way to install the transform plugin is through [npm][]. 64 | 65 | > **NOTE** 66 | > If you don't have npm yet, you'll need to install [Node.js][]. All newer 67 | > versions (4+) of Node come with npm. 68 | 69 | ```shell 70 | $ npm install metamagical-mocha-bridge 71 | ``` 72 | 73 | [npm]: https://www.npmjs.com/ 74 | [Node.js]: nodejs.org 75 | 76 | 77 | ## Support 78 | 79 | If you think you've found a bug in the project, or want to voice your 80 | frustration about using it (maybe the documentation isn't clear enough? Maybe 81 | it takes too much effort to use?), feel free to open a new issue in the 82 | [Github issue tracker](https://github.com/origamitower/metamagical/issues). 83 | 84 | Pull Requests are welcome. By submitting a Pull Request you agree with releasing 85 | your code under the MIT licence. 86 | 87 | You can join the [Gitter Channel](https://gitter.im/origamitower/discussion) for quick support. 88 | You can contact the author over [email](mailto:queen@robotlolita.me), or 89 | [Twitter](https://twitter.com/robotlolita). 90 | 91 | Note that all interactions in this project are subject to Origami Tower's 92 | [Code of Conduct](https://github.com/origamitower/metamagical/blob/master/CODE_OF_CONDUCT.md). 93 | 94 | 95 | ## Licence 96 | 97 | Meta:Magical is copyright (c) Quildreen Motta 2016, and released under the MIT licence. See the `LICENCE` file in this repository for detailed information. 98 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at contact@origamitower.com. You may 59 | also reach out to one of the following team members directly, in private: 60 | 61 | - Quildreen Motta [@robotlolita](https://github.com/robotlolita): queen at robotlolita.me 62 | 63 | All complaints will be reviewed and investigated and will result in a response 64 | that is deemed necessary and appropriate to the circumstances. The project team 65 | is obligated to maintain confidentiality with regard to the reporter of an 66 | incident. Further details of specific enforcement policies may be posted 67 | separately. 68 | 69 | Project maintainers who do not follow or enforce the Code of Conduct in good 70 | faith may face temporary or permanent repercussions as determined by other 71 | members of the project's leadership. 72 | 73 | ## Attribution 74 | 75 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 76 | available at [http://contributor-covenant.org/version/1/4][version] 77 | 78 | [homepage]: http://contributor-covenant.org 79 | [version]: http://contributor-covenant.org/version/1/4/ 80 | -------------------------------------------------------------------------------- /packages/interface/src/stability.js: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------- 2 | // 3 | // This source file is part of the Meta:Magical project. 4 | // 5 | // See LICENCE for licence information. 6 | // See CONTRIBUTORS for the list of contributors to the project. 7 | // 8 | //--------------------------------------------------------------------- 9 | 10 | 11 | // --[ Dependencies ]-------------------------------------------------- 12 | const Refinable = require('refinable'); 13 | 14 | 15 | // --[ Implementation ]------------------------------------------------ 16 | 17 | /*~ 18 | * Handles describing and normalising stability identifiers. 19 | * 20 | * --- 21 | * name : module stability 22 | * module : metamagical-interface/lib/stability 23 | * category : Metadata 24 | * platforms: 25 | * - ECMAScript 5 26 | * - ECMAScript 3 (with `es5-shim`) 27 | */ 28 | module.exports = Refinable.refine({ 29 | /*~ 30 | * Converts a textual identifier of stability to a structured 31 | * representation of the stability. 32 | * 33 | * --- 34 | * category : Constructing stability entries 35 | * stability : stable 36 | * 37 | * type: | 38 | * Stability.(String) => StabilityEntry 39 | */ 40 | fromIdentifier(id) { 41 | if (this.index.hasOwnProperty(id)) { 42 | return this.index[id]; 43 | } else { 44 | throw new Error(`No stability with id "${id}"`); 45 | } 46 | }, 47 | 48 | 49 | /*~ 50 | * An index of valid stability identifiers. 51 | * 52 | * Meta:Magical uses [Node's stability index](https://nodejs.org/dist/latest-v4.x/docs/api/documentation.html#documentation_stability_index) 53 | * 54 | * --- 55 | * stability : stable 56 | * category : Stability index 57 | */ 58 | index: { 59 | /*~ 60 | * Describes deprecated features. 61 | * 62 | * --- 63 | * stability : stable 64 | * category : Stability entry 65 | */ 66 | deprecated: Refinable.refine({ 67 | index: 0, 68 | name: 'Deprecated', 69 | description: ` 70 | This feature is known to be problematic, and will either be entirely 71 | removed from the system, or completely redesigned. You should not rely 72 | on it.` 73 | }), 74 | 75 | /*~ 76 | * Describes experimental features. 77 | * 78 | * --- 79 | * stability : stable 80 | * category : Stability entry 81 | */ 82 | experimental: Refinable.refine({ 83 | index: 1, 84 | name: 'Experimental', 85 | description: ` 86 | This feature is experimental and likely to change (or be removed) in the 87 | future.` 88 | }), 89 | 90 | /*~ 91 | * Describes stable features. 92 | * 93 | * --- 94 | * stability : stable 95 | * category : Stability entry 96 | */ 97 | stable: Refinable.refine({ 98 | index: 2, 99 | name: 'Stable', 100 | description: ` 101 | This feature is stable, and its API is unlikely to change (unless deemed 102 | necessary for security or other important reasons). You should expect 103 | backwards compatibility with the system, and a well-defined and automated 104 | (if possible) migration path if it changes.` 105 | }), 106 | 107 | /*~ 108 | * Describes locked features. 109 | * 110 | * --- 111 | * stability : stable 112 | * category : Stability entry 113 | */ 114 | locked: Refinable.refine({ 115 | index: 3, 116 | name: 'Locked', 117 | description: ` 118 | This API will not change, however security and other bug fixes will still 119 | be applied.` 120 | }) 121 | } 122 | }); 123 | -------------------------------------------------------------------------------- /experimental/static-docs/resources/html/static/prism.css: -------------------------------------------------------------------------------- 1 | /* http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript+abap+actionscript+ada+apacheconf+apl+applescript+asciidoc+aspnet+autoit+autohotkey+bash+basic+batch+c+brainfuck+bro+bison+csharp+cpp+coffeescript+ruby+css-extras+d+dart+diff+docker+eiffel+elixir+erlang+fsharp+fortran+gherkin+git+glsl+go+graphql+groovy+haml+handlebars+haskell+haxe+http+icon+inform7+ini+j+jade+java+json+julia+keyman+kotlin+latex+less+livescript+lolcode+lua+makefile+markdown+matlab+mel+mizar+monkey+nasm+nginx+nim+nix+nsis+objectivec+ocaml+oz+parigp+parser+pascal+perl+php+php-extras+powershell+processing+prolog+properties+protobuf+puppet+pure+python+q+qore+r+jsx+rest+rip+roboconf+crystal+rust+sas+sass+scss+scala+scheme+smalltalk+smarty+sql+stylus+swift+tcl+textile+twig+typescript+verilog+vhdl+vim+wiki+xojo+yaml */ 2 | /** 3 | * prism.js default theme for JavaScript, CSS and HTML 4 | * Based on dabblet (http://dabblet.com) 5 | * @author Lea Verou 6 | */ 7 | 8 | code[class*="language-"], 9 | pre[class*="language-"] { 10 | color: black; 11 | background: none; 12 | text-shadow: 0 1px white; 13 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 14 | text-align: left; 15 | white-space: pre; 16 | word-spacing: normal; 17 | word-break: normal; 18 | word-wrap: normal; 19 | line-height: 1.5; 20 | 21 | -moz-tab-size: 4; 22 | -o-tab-size: 4; 23 | tab-size: 4; 24 | 25 | -webkit-hyphens: none; 26 | -moz-hyphens: none; 27 | -ms-hyphens: none; 28 | hyphens: none; 29 | } 30 | 31 | pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, 32 | code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { 33 | text-shadow: none; 34 | background: #b3d4fc; 35 | } 36 | 37 | pre[class*="language-"]::selection, pre[class*="language-"] ::selection, 38 | code[class*="language-"]::selection, code[class*="language-"] ::selection { 39 | text-shadow: none; 40 | background: #b3d4fc; 41 | } 42 | 43 | @media print { 44 | code[class*="language-"], 45 | pre[class*="language-"] { 46 | text-shadow: none; 47 | } 48 | } 49 | 50 | /* Code blocks */ 51 | pre[class*="language-"] { 52 | padding: 1em; 53 | margin: .5em 0; 54 | overflow: auto; 55 | } 56 | 57 | :not(pre) > code[class*="language-"], 58 | pre[class*="language-"] { 59 | background: #f5f2f0; 60 | } 61 | 62 | /* Inline code */ 63 | :not(pre) > code[class*="language-"] { 64 | padding: .1em; 65 | border-radius: .3em; 66 | white-space: normal; 67 | } 68 | 69 | .token.comment, 70 | .token.prolog, 71 | .token.doctype, 72 | .token.cdata { 73 | color: slategray; 74 | } 75 | 76 | .token.punctuation { 77 | color: #999; 78 | } 79 | 80 | .namespace { 81 | opacity: .7; 82 | } 83 | 84 | .token.property, 85 | .token.tag, 86 | .token.boolean, 87 | .token.number, 88 | .token.constant, 89 | .token.symbol, 90 | .token.deleted { 91 | color: #905; 92 | } 93 | 94 | .token.selector, 95 | .token.attr-name, 96 | .token.string, 97 | .token.char, 98 | .token.builtin, 99 | .token.inserted { 100 | color: #690; 101 | } 102 | 103 | .token.operator, 104 | .token.entity, 105 | .token.url, 106 | .language-css .token.string, 107 | .style .token.string { 108 | color: #a67f59; 109 | background: hsla(0, 0%, 100%, .5); 110 | } 111 | 112 | .token.atrule, 113 | .token.attr-value, 114 | .token.keyword { 115 | color: #07a; 116 | } 117 | 118 | .token.function { 119 | color: #DD4A68; 120 | } 121 | 122 | .token.regex, 123 | .token.important, 124 | .token.variable { 125 | color: #e90; 126 | } 127 | 128 | .token.important, 129 | .token.bold { 130 | font-weight: bold; 131 | } 132 | .token.italic { 133 | font-style: italic; 134 | } 135 | 136 | .token.entity { 137 | cursor: help; 138 | } 139 | 140 | -------------------------------------------------------------------------------- /packages/interface/README.md: -------------------------------------------------------------------------------- 1 | # The Meta:Magical Interface 2 | 3 | [![Chat on Gitter](https://img.shields.io/gitter/room/origamitower/discussion.svg?style=flat-square)](https://gitter.im/origamitower/discussion) 4 | [![Build status](https://img.shields.io/travis/origamitower/metamagical/master.svg?style=flat-square)](https://travis-ci.org/origamitower/metamagical) 5 | [![NPM version](https://img.shields.io/npm/v/metamagical-interface.svg?style=flat-square)](https://npmjs.org/package/metamagical-interface) 6 | ![Licence](https://img.shields.io/npm/l/metamagical-interface.svg?style=flat-square&label=licence) 7 | ![Stability: Experimental](https://img.shields.io/badge/stability-experimental-orange.svg?style=flat-square) 8 | 9 | 10 | The Meta:Magical interface provides the basic features for annoatating live 11 | objects, and querying metadata attached to them. This project is not meant 12 | for end users, but rather for tooling developers who want to use this information 13 | in their tools. 14 | 15 | 16 | ## Installing 17 | 18 | The only supported way to install the transform plugin is through [npm][]. 19 | 20 | > **NOTE** 21 | > If you don't have npm yet, you'll need to install [Node.js][]. All newer 22 | > versions (4+) of Node come with npm. 23 | 24 | ```shell 25 | $ npm install metamagical-interface 26 | ``` 27 | 28 | [npm]: https://www.npmjs.com/ 29 | [Node.js]: nodejs.org 30 | 31 | 32 | ## Documentation 33 | 34 | To learn about the Interface, you can use the REPL browser. Before you can do 35 | that you'll need to download and build this project: 36 | 37 | ```shell 38 | git clone https://github.com/origamitower/metamagical.git 39 | cd metamagical 40 | npm install # install build tooling 41 | make all # compiles all sub projects 42 | ``` 43 | 44 | Then launch a Node REPL to browse the project: 45 | 46 | ```js 47 | node> var Interface = require('./packages/interface') 48 | node> var browser = require('./packages/repl')(Interface) 49 | node> browser.for(Interface).summary() 50 | 51 | # Interface 52 | =========== 53 | 54 | 55 | Stability: 1 - Experimental 56 | Platforms: 57 | • ECMAScript 2015 58 | 59 | The Meta:Magical interface allows one to query meta-data associated 60 | with a particular object, or attach new meta-data to any object 61 | without modifying the object. 62 | 63 | (run `.documentation()` for the full docs) 64 | 65 | 66 | ## Properties 67 | ------------- 68 | 69 | ### Additional reflective methods 70 | 71 | • allProperties() 72 | | Retrieves a categorised list of properties in the current 73 | context. 74 | 75 | • properties() 76 | | Retrieves a categorised list of properties owned by the current 77 | context. 78 | 79 | ( ... ) 80 | ``` 81 | 82 | 83 | ## Support 84 | 85 | If you think you've found a bug in the project, or want to voice your 86 | frustration about using it (maybe the documentation isn't clear enough? Maybe 87 | it takes too much effort to use?), feel free to open a new issue in the 88 | [Github issue tracker](https://github.com/origamitower/metamagical/issues). 89 | 90 | Pull Requests are welcome. By submitting a Pull Request you agree with releasing 91 | your code under the MIT licence. 92 | 93 | You can join the [Gitter Channel](https://gitter.im/origamitower/discussion) for quick support. 94 | You can contact the author over [email](mailto:queen@robotlolita.me), or 95 | [Twitter](https://twitter.com/robotlolita). 96 | 97 | Note that all interactions in this project are subject to Origami Tower's 98 | [Code of Conduct](https://github.com/origamitower/metamagical/blob/master/CODE_OF_CONDUCT.md). 99 | 100 | 101 | ## Licence 102 | 103 | Meta:Magical is copyright (c) Quildreen Motta, 2016, and released under the MIT 104 | licence. See the `LICENCE` file in this repository for detailed information. 105 | 106 | -------------------------------------------------------------------------------- /experimental/sphinx/src/rst.js: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------- 2 | // 3 | // This source file is part of the Meta:Magical project. 4 | // 5 | // See LICENCE for licence information. 6 | // See CONTRIBUTORS for the list of contributors to the project. 7 | // 8 | //---------------------------------------------------------------------- 9 | 10 | // -- DEPENDENCIES ----------------------------------------------------- 11 | const { data } = require('folktale/core/adt'); 12 | 13 | 14 | // -- ALIASES AND CONSTANTS ---------------------------------------------------------- 15 | const keys = Object.keys; 16 | const fills = [ 17 | { fill: '*', overline: true }, 18 | { fill: '=', overline: false }, 19 | { fill: '-', overline: false }, 20 | { fill: '~', overline: false }, 21 | { fill: '^', overline: false }, 22 | { fill: '\'', overline: false } 23 | ]; 24 | 25 | 26 | // -- HELPERS ---------------------------------------------------------- 27 | function repeat(text, times) { 28 | return Array.from(Array(times), () => text).join(''); 29 | } 30 | 31 | function pad(text, spaces) { 32 | return `${repeat(' ', spaces)}${text}`; 33 | } 34 | 35 | function lines(text) { 36 | return text.split(/\r\n|\n\r|\r|\n/); 37 | } 38 | 39 | function entries(object) { 40 | return keys(object).map(key => [key, object[key]]); 41 | } 42 | 43 | function compactArray(array) { 44 | return array.filter(x => x != null); 45 | } 46 | 47 | function render(node) { 48 | return node.render(); 49 | } 50 | 51 | function renderItem([key, value]) { 52 | if (value == null) { 53 | return ''; 54 | } else { 55 | return `:${key}: ${lines(value).join(' ')}`; 56 | } 57 | } 58 | 59 | function getTitle(title, level) { 60 | const { fill, overline } = fills[level]; 61 | const line = repeat(fill, title.length); 62 | 63 | if (overline) { 64 | return [Text(line), Text(title), Text(line)]; 65 | } else { 66 | return [Text(title), Text(line)]; 67 | } 68 | } 69 | 70 | // -- IMPLEMENTATION --------------------------------------------------- 71 | const RST = data('metamagical:sphinx:rst', { 72 | Text(value) { 73 | return { value }; 74 | }, 75 | 76 | Sequence(values) { 77 | return { values }; 78 | }, 79 | 80 | Block(values, indent = 0) { 81 | return { values, indent }; 82 | }, 83 | 84 | Options(values) { 85 | return { values }; 86 | }, 87 | 88 | Directive(name, arg = '', options = null, content = Text('')) { 89 | return { name, arg, options, content }; 90 | }, 91 | 92 | Title(title, level = 0) { 93 | return { title, level }; 94 | } 95 | }); 96 | 97 | const { Text, Sequence, Block, Options, Directive, Title } = RST; 98 | 99 | RST.render = function() { 100 | return this.cata({ 101 | Text: ({ value }) => 102 | value, 103 | 104 | Sequence: ({ values }) => 105 | values.map(render).join(''), 106 | 107 | Block: ({ values, indent }) => 108 | lines(values.map(render).join('\n')) 109 | .map(line => pad(line, indent)) 110 | .join('\n'), 111 | 112 | Options: ({ values }) => 113 | entries(values).map(renderItem) 114 | .filter(x => x.trim() !== '') 115 | .join('\n'), 116 | 117 | Directive: ({ name, arg, options, content }) => 118 | Block([ 119 | Sequence([ 120 | Text('.. '), Text(name), Text(':: '), Text(arg) 121 | ]), 122 | Block(compactArray([ 123 | options, 124 | Text(''), // Need a blank line between the two 125 | content, 126 | Text('') 127 | ]), 3) 128 | ]).render(), 129 | 130 | Title: ({ title, level }) => 131 | Block([ 132 | Text('') 133 | ].concat(getTitle(title, level)).concat([ 134 | Text('') 135 | ])).render() 136 | }); 137 | }; 138 | 139 | 140 | 141 | module.exports = RST; 142 | -------------------------------------------------------------------------------- /packages/interface/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | All of the changes to the `metamagical-interface` project are recorded in 4 | this file. Changes are ordered from most recent to oldest versions. 5 | 6 | (This will eventually be autogenerated) 7 | 8 | > **Why does this changelog start at 3.x?** 9 | > 10 | > Versions older than 3.x were experimental and, while public, not 11 | > announced as a release. 12 | 13 | 14 | ## 3.x 15 | 16 | The 3.x series introduces the object-oriented architecture used by 17 | the Interface module, by consolidating things such as metadata 18 | inheritance and propagation that were already used by other projects. 19 | 20 | 21 | ### [3.4.0] - 2016-07-09 22 | 23 | - Added `parents` and `hierarchy`. 24 | 25 | - Added minimal annotations for built-in hierarchy. 26 | 27 | 28 | ### [3.3.0] - 2016-05-27 29 | 30 | #### Added 31 | 32 | - Added missing documentation to all objects in the interface. 33 | 34 | - Added `isRequired` to keep track of unimplemented functionality. This is 35 | important to prevent tools from accessing getters that are there only to 36 | provide documentation on the object's layout (`Field.name` is an example). 37 | 38 | - Automatically running documentation examples so they don't get out of date. 39 | 40 | - Added a specific Makefile for Interface. 41 | 42 | - A set of primitive wrappers that allows Interface to deal with primitive 43 | objects in a limited way. This simplifies the work tooling has to do to 44 | explore an object without having things explode when encountering a 45 | primitive. 46 | 47 | #### Changed 48 | 49 | - `copyright`, `licence`, and `platforms` are not propagated anymore. 50 | Previously these were propagated because sometimes people want to know 51 | all of the licences an object has, or all of the platforms an object runs 52 | on, but there's no reasonable way of merging these fields, and it highly 53 | depends on the questions being asked. 54 | 55 | - `authors` and `maintainers` now require their arguments to be a Person object. 56 | 57 | - Fixed incorrect and outdated example code. 58 | 59 | - Sorts `(Uncategorised)` last. 60 | 61 | 62 | 63 | 64 | ### [3.2.1] - 2016-05-18 65 | 66 | #### Added 67 | 68 | - Added the `allProperties` method to `Interface`, which retrieves all 69 | properties of an object, categorised. Previously the Interface only exposed 70 | `properties`, which returned own properties categorised, but ignored 71 | inherited properties. This made tools do a lot of unnecessary work to 72 | compute the inherited properties. 73 | 74 | #### Changed 75 | 76 | - Interface now only looks at metadata that is associated directly with 77 | the object throught the `meta:magical` symbol. Previously it also returned 78 | metadata that was associated with the `[[Prototype]]` of that object, 79 | which resulted in the wrong information being shown. 80 | 81 | 82 | 83 | ### [3.1.0] - 2016-05-16 84 | 85 | #### Added 86 | 87 | - Added the `isModule` metadata for tracking whether an object is also a module. 88 | Required for tools to handle correctly how to show an object in the presence of 89 | CommonJS modules, where a function may double as a module, for example. 90 | 91 | 92 | 93 | ### [3.0.3] - 2016-05-14 94 | 95 | #### Changed 96 | 97 | - Fixed a bug with attaching new metadata to an object, which was still 98 | using the old interface. 99 | 100 | - Fixed a bug with the propagation of author metadata, where the arrays 101 | where not properly merged. 102 | 103 | 104 | 105 | ### [3.0.2] - 2016-05-14 106 | 107 | #### Changed 108 | 109 | - Fixed a bug with the inheritance of metadata in `Interface`. 110 | 111 | 112 | 113 | ### [3.0.1] - 2016-05-14 114 | 115 | ### Changed 116 | 117 | - Fixed a bug with the propagation of metadata in `Interface` and 118 | in the `stability` field, where the thunk was not being forced, 119 | and thus no metadata was inherited. 120 | 121 | 122 | 123 | ### [3.0.0] - 2016-05-14 124 | 125 | #### Added 126 | 127 | - An `Interface` object, which provides metadata for an object, 128 | and supports the concept of `Fields` for describing how to 129 | propagate and inherit metadata; 130 | - A `Stability` object describing the stability index; 131 | - A `Field` object to describe fields; 132 | - And a set of common fields used by Meta:Magical projects, in the 133 | `fields` module. -------------------------------------------------------------------------------- /packages/repl/src/terminal-display.js: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------- 2 | // 3 | // This source file is part of the Meta:Magical project. 4 | // 5 | // See LICENCE for licence information. 6 | // See CONTRIBUTORS for the list of contributors to the project. 7 | // 8 | //--------------------------------------------------------------------- 9 | 10 | // --[ Dependencies ]-------------------------------------------------- 11 | const AST = require('./ast'); 12 | const Refinable = require('refinable'); 13 | const chalk = require('chalk'); 14 | const marked = require('marked'); 15 | const MarkedTerminal = require('marked-terminal'); 16 | 17 | 18 | // ---[ Initialisation ]----------------------------------------------- 19 | marked.setOptions({ 20 | renderer: new MarkedTerminal({ 21 | reflowText: true, 22 | width: 72 23 | }) 24 | }); 25 | 26 | 27 | // --[ Helpers ]------------------------------------------------------- 28 | function lines(text) { 29 | return text.split(/\r\n|\n\r|\n|\r/); 30 | } 31 | 32 | function repeat(string, times) { 33 | return Array(times + 1).join(string); 34 | } 35 | 36 | function indent(indentation, text) { 37 | return `${repeat(' ', indentation)}${text}`; 38 | } 39 | 40 | function clamp(n, min, max) { 41 | return Math.max(min, Math.min(n, max)); 42 | } 43 | 44 | // --[ The renderer ]-------------------------------------------------- 45 | 46 | /*~ 47 | * A display for Terminal interfaces. 48 | * 49 | * --- 50 | * isModule : true 51 | * stability : experimental 52 | * category : Displays 53 | */ 54 | const TerminalDisplay = Refinable.refine({ 55 | formatting: { 56 | title: [ 57 | (title) => chalk.green.bold([title, repeat('=', title.length)].join('\n')), 58 | (title) => chalk.bold([title, repeat('-', title.length)].join('\n')), 59 | chalk.green 60 | ], 61 | 62 | subtitle: [ 63 | chalk.bold 64 | ], 65 | 66 | quote: chalk.grey.italic, 67 | code: chalk.yellow, 68 | strong: chalk.bold, 69 | emphasis: chalk.italic, 70 | good: chalk.green, 71 | error: chalk.red, 72 | warning: chalk.red.bold, 73 | detail: chalk.grey, 74 | label: chalk.bold, 75 | 76 | markdown: marked 77 | }, 78 | 79 | formattingForLevel(type, level) { 80 | const levels = this.formatting[type]; 81 | return levels[clamp(level - 1, 0, levels.length - 1)]; 82 | }, 83 | 84 | render(tree) { 85 | return tree.cata({ 86 | Nil: _ => '', 87 | Text: ({ text }) => text, 88 | 89 | Sequence: ({ items }) => 90 | items.map(x => this.render(x)).join(''), 91 | 92 | Block: ({ indentation, items }) => 93 | lines(items.map(v => this.render(v)).join('\n')) 94 | .map(line => indent(indentation, line)) 95 | .join('\n'), 96 | 97 | Title: ({ level, content }) => 98 | this.formattingForLevel('title', level)([ 99 | repeat('#', level), 100 | ' ', 101 | this.render(content) 102 | ].join('')), 103 | 104 | Subtitle: ({ level, content }) => 105 | this.formattingForLevel('subtitle', level)( 106 | this.render(content) 107 | ), 108 | 109 | Quote: ({ content }) => 110 | this.formatting.quote( 111 | lines(this.render(content)).map(line => `> ${line}`) 112 | .join('\n') 113 | ), 114 | 115 | Code: ({ language, startAt=1, source }) => 116 | this.formatting.code( 117 | lines(source.trimRight()) 118 | .map(line => indent(4, line)) 119 | .join('\n') 120 | ), 121 | 122 | List: ({ items }) => 123 | '\n' + items.map(item => { 124 | const [first, ...rest] = lines(this.render(item)); 125 | return [ 126 | ` • ${first}`, 127 | ...rest.map(line => indent(3, line)) 128 | ].join('\n') 129 | }).join('\n'), 130 | 131 | Table: ({ headings, rows }) => 132 | '(Tables are not implemented yet.)', 133 | 134 | HorizontalLine: ({ size }) => 135 | repeat('-', size), 136 | 137 | 138 | Strong: ({ content }) => 139 | this.formatting.strong(this.render(content)), 140 | 141 | Emphasis: ({ content }) => 142 | this.formatting.emphasis(this.render(content)), 143 | 144 | Good: ({ content }) => 145 | this.formatting.good(this.render(content)), 146 | 147 | Error: ({ content }) => 148 | this.formatting.error(this.render(content)), 149 | 150 | Warning: ({ content }) => 151 | this.formatting.warning(this.render(content)), 152 | 153 | Detail: ({ content }) => 154 | this.formatting.detail(this.render(content)), 155 | 156 | Label: ({ content }) => 157 | this.formatting.label(this.render(content)), 158 | 159 | Markdown: ({ text }) => 160 | this.formatting.markdown(text.trimRight()).trim() 161 | }); 162 | }, 163 | 164 | show(tree) { 165 | console.log(this.render(tree)); 166 | } 167 | }); 168 | 169 | 170 | // --[ Exports ]------------------------------------------------------- 171 | module.exports = TerminalDisplay; 172 | -------------------------------------------------------------------------------- /packages/babel-plugin-assertion-comments/README.md: -------------------------------------------------------------------------------- 1 | # Meta:Magical Babel Assertion Comments Plugin 2 | 3 | [![Chat on Gitter](https://img.shields.io/gitter/room/origamitower/discussion.svg?style=flat-square)](https://gitter.im/origamitower/discussion) 4 | [![Build status](https://img.shields.io/travis/origamitower/metamagical/master.svg?style=flat-square)](https://travis-ci.org/origamitower/metamagical) 5 | [![NPM version](https://img.shields.io/npm/v/babel-plugin-transform-assertion-comments.svg?style=flat-square)](https://npmjs.org/package/babel-plugin-transform-assertion-comments) 6 | ![Licence](https://img.shields.io/npm/l/babel-plugin-transform-assertion-comments.svg?style=flat-square&label=licence) 7 | ![Stability: Experimental](https://img.shields.io/badge/stability-experimental-orange.svg?style=flat-square) 8 | 9 | 10 | This plugin allows having assertions in your comments. 11 | 12 | 13 | ## Example 14 | 15 | ```js 16 | 1 + 2; // ==> 3 17 | [1].concat([2]) // ==> [1, ..._] 18 | { x: 1, y: 2 } // ==> { x: 1, y: 2 } 19 | ``` 20 | 21 | 22 | ## Supported assertions 23 | 24 | Assertions are defined in the form: 25 | 26 | ```js 27 | Actual Value Expected Value 28 | 29 | ; // ==> 30 | ``` 31 | 32 | Arrays are expressed using the Array literal. And may include 33 | `..._` at the end to indicate that the array may contain any 34 | other elements after that point. Arrays are otherwise compared 35 | structurally, so if the same elements, in the same position, 36 | happen in both sides, they're considered equal: 37 | 38 | ```js 39 | OK: 40 | [1, 2, 3]; // ==> [1, 2, 3] 41 | [1, 2]; // ==> [1, 2, ..._] 42 | [1, 2, 3, 4]; // ==> [1, 2, ..._] 43 | 44 | NOT OK: 45 | [1, 2, 3]; // ==> [3, 2, 1] 46 | [1, 2, 3]; // ==> [1, 3, 2] 47 | [1]; // ==> [1, 2, ..._] 48 | ``` 49 | 50 | Records are expressed using the Object literal. Records are 51 | compared structurally, and the two values are considered 52 | equal if they have the same keys, with the same values. 53 | 54 | If a record includes the `..._` spread element, then 55 | the two objects are equal if the actual value has the 56 | same keys (own or inherited) and values as the expected value: 57 | 58 | ```js 59 | OK: 60 | { __proto__: { a: 1 }, b: 2 } // ==> { a: 1, b: 2, ..._ } 61 | { __proto__: { a: 1 }, b: 2 } // ==> { a: 1, ..._ } 62 | { __proto__: { a: 1 }, b: 2 } // ==> { b: 2, ..._ } 63 | 64 | 65 | NOT OK: 66 | { __proto__: { a: 1 }, b: 2 } // ==> { a: 1, b: 1, ..._ } 67 | { __proto__: { a: 1 }, b: 2 } // ==> { a: 1, c: 2, ..._ } 68 | ``` 69 | 70 | Otherwise the two values are equal if they have the same 71 | own enumerable keys, with the same values. 72 | 73 | If the two values provide an `.equals` method, then the assertion 74 | plugin will just use that method to compare them. 75 | 76 | Otherwise the values are compared with `===`. 77 | 78 | 79 | ## Installing 80 | 81 | The only supported way to install the transform plugin is through [npm][]. 82 | 83 | > **NOTE** 84 | > If you don't have npm yet, you'll need to install [Node.js][]. All newer 85 | > versions (4+) of Node come with npm. 86 | 87 | ```shell 88 | $ npm install babel-plugin-transform-assertion-comments 89 | ``` 90 | 91 | 92 | ## Usage 93 | 94 | This package is a transform plugin for the [Babel][] compiler. You'll need to 95 | install Babel separately. 96 | 97 | The recommended way of using the transform plugin is by putting it in your 98 | `.babelrc` file. This file, which contains configuration values for Babel 99 | encoded as JSON, should be in the root of your project. 100 | 101 | To tell Babel to use the Meta:Magical plugin, add the following to the `plugins` 102 | section of your `.babelrc` (the `( ... )` marks stand for existing configuration 103 | in your file): 104 | 105 | ```js 106 | { 107 | ( ... ), 108 | "plugins": [ 109 | ( ... ), 110 | "transform-assertion-comments" 111 | ] 112 | } 113 | ``` 114 | 115 | You may provide the `module` option to define a different assertion module from 116 | Node's built-in `assert`, as long as it has the same interface: 117 | 118 | ```js 119 | { 120 | "plugins": [["transform-assertion-comments", { "module": "my-assert" }]] 121 | } 122 | ``` 123 | 124 | #### (alternative) via CLI 125 | 126 | You can tell Babel to use the transform plugin directly as a command line option: 127 | 128 | ```shell 129 | $ babel --plugins transform-assertion-comments your-script.js 130 | ``` 131 | 132 | 133 | #### (alternative) via Node API 134 | 135 | If you're using Babel programmatically, you'll want to tell Babel to use the 136 | transform plugin by providing it as an option to the `transform` method: 137 | 138 | ```js 139 | require('babel-core').transform(SOURCE_CODE, { 140 | plugins: ['transform-assertion-comments'] 141 | }) 142 | ``` 143 | 144 | 145 | ## Supported platforms 146 | 147 | The transform plugin requires Node 4+ and Babel 6+. 148 | 149 | 150 | 151 | ## Support 152 | 153 | If you think you've found a bug in the project, or want to voice your 154 | frustration about using it (maybe the documentation isn't clear enough? Maybe 155 | it takes too much effort to use?), feel free to open a new issue in the 156 | [Github issue tracker](https://github.com/origamitower/metamagical/issues). 157 | 158 | Pull Requests are welcome. By submitting a Pull Request you agree with releasing 159 | your code under the MIT licence. 160 | 161 | You can join the [Gitter Channel](https://gitter.im/origamitower/discussion) for quick support. 162 | You can contact the author over [email](mailto:queen@robotlolita.me), or 163 | [Twitter](https://twitter.com/robotlolita). 164 | 165 | Note that all interactions in this project are subject to Origami Tower's 166 | [Code of Conduct](https://github.com/origamitower/metamagical/blob/master/CODE_OF_CONDUCT.md). 167 | 168 | 169 | ## Licence 170 | 171 | Meta:Magical is copyright (c) Quildreen Motta, 2016, and released under the MIT 172 | licence. See the `LICENCE` file in this repository for detailed information. 173 | -------------------------------------------------------------------------------- /packages/interface/src/primitives.js: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------- 2 | // 3 | // This source file is part of the Meta:Magical project. 4 | // 5 | // See LICENCE for licence information. 6 | // See CONTRIBUTORS for the list of contributors to the project. 7 | // 8 | //--------------------------------------------------------------------- 9 | 10 | // --[ Helpers ]------------------------------------------------------- 11 | const VALUE = Symbol('Primitive.value'); 12 | const isPrototypeOf = Function.call.bind(Object.prototype.isPrototypeOf); 13 | 14 | function notPrimitive(value) { 15 | throw new TypeError(`Not a primitive: ${value}`); 16 | } 17 | 18 | // --[ Primitives ]---------------------------------------------------- 19 | 20 | /*~ 21 | * A Primitive provides an annotated representation for JS primitives, 22 | * which themselves can't be used as an object. 23 | * 24 | * This it much simpler to handle a lot of the complexities that come 25 | * with handling this difference at every call site. 26 | * 27 | * --- 28 | * category : Wrapper objects 29 | * stability : experimental 30 | * isModule : true 31 | */ 32 | let Primitive = Object.create(null); 33 | 34 | // ---[ Types of primitive values ]------------------------------------ 35 | 36 | /*~ 37 | * Wraps Undefined in a Primitive object. 38 | * 39 | * --- 40 | * category : Types of primitive values 41 | * stability : experimental 42 | * type: | 43 | * (Undefined) => Primitive 44 | */ 45 | Primitive.Undefined = function(value) { 46 | if (value !== undefined) { 47 | throw new TypeError('Expected undefined'); 48 | } 49 | 50 | /*~ 51 | * An Undefined value. 52 | * 53 | * This object provides an annotated representation for the underlying 54 | * Undefined JS primitive. 55 | * 56 | * --- 57 | * category : JavaScript primitives 58 | * stability : experimental 59 | * type: "Undefined" 60 | */ 61 | const Undefined = Primitive.of(value); 62 | 63 | return Undefined; 64 | }; 65 | 66 | /*~ 67 | * Wraps Null in a Primitive object. 68 | * 69 | * --- 70 | * category : Types of primitive values 71 | * stability : experimental 72 | * type: | 73 | * (Null) => Primitive 74 | */ 75 | Primitive.Null = function(value) { 76 | if (value !== null) { 77 | throw new TypeError('Expected null'); 78 | } 79 | 80 | /*~ 81 | * A null value. 82 | * 83 | * This object provides an annotated representation for the underlying 84 | * null JS primitive. 85 | * 86 | * --- 87 | * category : JavaScript primitives 88 | * stability : experimental 89 | * type: "Null" 90 | */ 91 | const Null = Primitive.of(value); 92 | 93 | return Null; 94 | }; 95 | 96 | /*~ 97 | * Wraps Boolean in a Primitive object. 98 | * 99 | * --- 100 | * category : Types of primitive values 101 | * stability : experimental 102 | * type: | 103 | * (Boolean) => Primitive 104 | */ 105 | Primitive.Boolean = function(value) { 106 | if (typeof value !== "boolean") { 107 | throw new TypeError('Expected Boolean'); 108 | } 109 | 110 | /*~ 111 | * A Boolean. 112 | * 113 | * This object provides an annotated representation for the underlying 114 | * boolean JS primitive. 115 | * 116 | * --- 117 | * category : JavaScript primitives 118 | * stability : experimental 119 | * type: "Boolean" 120 | */ 121 | const Boolean = Primitive.of(value); 122 | 123 | return Boolean; 124 | }; 125 | 126 | /*~ 127 | * Wraps Number in a Primitive object. 128 | * 129 | * --- 130 | * category : Types of primitive values 131 | * stability : experimental 132 | * type: | 133 | * (Number) => Primitive 134 | */ 135 | Primitive.Number = function(value) { 136 | if (typeof value !== "number") { 137 | throw new TypeError('Expected Number'); 138 | } 139 | 140 | /*~ 141 | * A Number. 142 | * 143 | * This object provides an annotated representation for the underlying 144 | * number JS primitive. 145 | * 146 | * --- 147 | * category : JavaScript primitives 148 | * stability : experimental 149 | * type: "Number" 150 | */ 151 | const Number = Primitive.of(value); 152 | 153 | return Number; 154 | }; 155 | 156 | /*~ 157 | * Wraps String in a Primitive object. 158 | * 159 | * --- 160 | * category : Types of primitive values 161 | * stability : experimental 162 | * type: | 163 | * (String) => Primitive 164 | */ 165 | Primitive.String = function(value) { 166 | if (typeof value !== "string") { 167 | throw new TypeError('Expected String'); 168 | } 169 | 170 | /*~ 171 | * A String. 172 | * 173 | * This object provides an annotated representation for the underlying 174 | * string JS primitive. 175 | * 176 | * --- 177 | * category : JavaScript primitives 178 | * stability : experimental 179 | * type: "String" 180 | */ 181 | const String = Primitive.of(value); 182 | 183 | return String; 184 | }; 185 | 186 | /*~ 187 | * Wraps Symbol in a Primitive object. 188 | * 189 | * --- 190 | * category : Types of primitive values 191 | * stability : experimental 192 | * type: | 193 | * (Symbol) => Primitive 194 | */ 195 | Primitive.Symbol = function(value) { 196 | if (typeof value !== "symbol") { 197 | throw new TypeError('Expected Symbol'); 198 | } 199 | 200 | /*~ 201 | * A Symbol. 202 | * 203 | * This object provides an annotated representation for the underlying 204 | * symbol JS primitive. 205 | * 206 | * --- 207 | * category : JavaScript primitives 208 | * stability : experimental 209 | * type: "Symbol" 210 | */ 211 | const Symbol = Primitive.of(value); 212 | 213 | return Symbol; 214 | }; 215 | 216 | 217 | 218 | // ---[ Creating primitives ]------------------------------------------ 219 | 220 | /*~ 221 | * Constructs a new primitive containing the given value. 222 | * 223 | * --- 224 | * category : Constructing primitives 225 | * stability : experimental 226 | * type: | 227 | * (primitive) => Primitive 228 | */ 229 | Primitive.of = function(value) { 230 | let result = Object.create(Primitive); 231 | result[VALUE] = value; 232 | return result; 233 | }; 234 | 235 | /*~ 236 | * Converts a regular primitive value to a Primitive object, or does 237 | * nothing if that's already wrapped. 238 | * 239 | * --- 240 | * category : Constructing primitives 241 | * stability : experimental 242 | * type: | 243 | * (primitive or Primitive) => Primitive 244 | */ 245 | Primitive.from = function(value) { 246 | if (isPrototypeOf(Primitive, value)) { 247 | return value; 248 | } else { 249 | return value === null ? Primitive.Null(value) 250 | : value === undefined ? Primitive.Undefined(value) 251 | : typeof value === "boolean" ? Primitive.Boolean(value) 252 | : typeof value === "number" ? Primitive.Number(value) 253 | : typeof value === "string" ? Primitive.String(value) 254 | : typeof value === "symbol" ? Primitive.Symbol(value) 255 | : /* otherwise */ notPrimitive(value); 256 | } 257 | }; 258 | 259 | 260 | // --[ Exports ]------------------------------------------------------- 261 | module.exports = Primitive; 262 | -------------------------------------------------------------------------------- /experimental/markdown/src/index.js: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------- 2 | // 3 | // This source file is part of the Meta:Magical project. 4 | // 5 | // See LICENCE for licence information. 6 | // See CONTRIBUTORS for the list of contributors to the project. 7 | // 8 | //---------------------------------------------------------------------- 9 | 10 | // -- DEPENDENCIES ----------------------------------------------------- 11 | const yaml = require('js-yaml'); 12 | const marked = require('marked'); 13 | const generateJs = require('babel-generator').default; 14 | const template = require('babel-template'); 15 | const parseJs = require('babylon').parse; 16 | const t = require('babel-types'); 17 | const babel = require('babel-core'); 18 | 19 | 20 | // -- ALIASES ---------------------------------------------------------- 21 | const extend = Object.assign; 22 | const keys = Object.keys; 23 | 24 | 25 | // -- TEMPLATES -------------------------------------------------------- 26 | const moduleTemplate = template(` 27 | module.exports = {}; 28 | module.exports[Symbol.for('@@meta:magical')] = META 29 | `); 30 | 31 | 32 | // -- HELPERS ---------------------------------------------------------- 33 | function Raw(value) { 34 | this.value = value; 35 | } 36 | 37 | function objectToExpression(object) { 38 | return t.objectExpression( 39 | entries(object).map(pairToProperty) 40 | ); 41 | } 42 | 43 | function pairToProperty([key, value]) { 44 | return t.objectProperty( 45 | t.stringLiteral(key), 46 | valueToLiteral(value) 47 | ); 48 | } 49 | 50 | function valueToLiteral(value) { 51 | return value instanceof Raw ? value.value 52 | : Array.isArray(value) ? t.arrayExpression(value.map(valueToLiteral)) 53 | : isString(value) ? t.stringLiteral(value) 54 | : isBoolean(value) ? t.booleanLiteral(value) 55 | : isNumber(value) ? t.numericLiteral(value) 56 | : isObject(value) ? objectToExpression(value) 57 | : /* otherwise */ raise(new TypeError(`Type of property not supported: ${value}`)); 58 | } 59 | 60 | function raise(error) { 61 | throw error; 62 | } 63 | 64 | function isString(value) { 65 | return typeof value === 'string'; 66 | } 67 | 68 | function isBoolean(value) { 69 | return typeof value === 'boolean'; 70 | } 71 | 72 | function isNumber(value) { 73 | return typeof value === 'number'; 74 | } 75 | 76 | function isObject(value) { 77 | return Object(value) === value; 78 | } 79 | 80 | function flatten(lists) { 81 | return lists.reduce((a, b) => a.concat(b), []); 82 | } 83 | 84 | function hasYaml(source) { 85 | return /^\s*---[\n\r]/.test(source); 86 | } 87 | 88 | function enumerate(list) { 89 | return list.map((element, index) => [element, index]); 90 | } 91 | 92 | function entries(object) { 93 | return keys(object).map(key => [key, object[key]]); 94 | } 95 | 96 | function compact(object) { 97 | let result = {}; 98 | 99 | entries(object).forEach(([key, value]) => { 100 | if (value != null) { 101 | result[key] = value; 102 | } 103 | }); 104 | 105 | return result; 106 | } 107 | 108 | function groupBy(grouping, list) { 109 | return list.reduce((result, value) => { 110 | const key = grouping(value); 111 | const values = result.get(key) || []; 112 | 113 | result.set(key, values.concat([value])); 114 | 115 | return result; 116 | }, new Map()); 117 | } 118 | 119 | function cleanDocumentation(text) { 120 | return text.replace(/^::$/gm, '').replace(/::[ \t]*$/gm, ':'); 121 | } 122 | 123 | function parseMultiDocument(source) { 124 | const lines = source.split(/\r\n|\n\r|\n|\r/); 125 | const marks = enumerate(lines).filter(([line, _]) => line === '---'); 126 | if (marks.length < 2) { 127 | throw new SyntaxError('Invalid YAML document.'); 128 | } 129 | 130 | const [[_, start], [__, end]] = marks; 131 | const yamlPart = lines.slice(start + 1, end).join('\n'); 132 | const markdownPart = lines.slice(end + 1); 133 | 134 | let meta = yaml.safeLoad(yamlPart) || {}; 135 | meta.documentation = cleanDocumentation(markdownPart.join('\n')); 136 | 137 | return [meta, markdownPart.join('\n')]; 138 | } 139 | 140 | function pairNodes(ast) { 141 | return ast.map((node, index) => [node, ast[index - 1]]); 142 | } 143 | 144 | function isLeadingExampleNode(node) { 145 | return node 146 | && (node.type === 'paragraph' || node.type === 'heading') 147 | && /::\s*$/.test(node.text); 148 | } 149 | 150 | function functionFromExamples(sources, options) { 151 | const body = flatten(sources.map(s => parseJs(s).program.body)); 152 | 153 | const { _code, _map, ast } = babel.transformFromAst(t.program(body), null, options); 154 | 155 | return new Raw(t.functionExpression( 156 | null, 157 | [], 158 | t.blockStatement(ast.program.body) 159 | )); 160 | } 161 | 162 | function inferTitle(ast) { 163 | for (let node of ast) { 164 | if (node.type === 'heading' && node.depth === 1) { 165 | return node.text; 166 | } 167 | } 168 | 169 | return null; 170 | } 171 | 172 | function parseExamples(ast) { 173 | return pairNodes(ast).reduce(({ examples, heading }, [node, previous]) => { 174 | switch (node.type) { 175 | case 'heading': 176 | return { examples, heading: node.text }; 177 | 178 | case 'code': 179 | if (isLeadingExampleNode(previous)) { 180 | return { 181 | examples: examples.concat([{ heading, code: node.text }]), 182 | heading: heading 183 | }; 184 | } else { 185 | return { examples, heading }; 186 | } 187 | 188 | default: 189 | return { examples, heading }; 190 | } 191 | }, { examples: [], heading: null }).examples; 192 | } 193 | 194 | function inferExamples(ast, options) { 195 | const groups = Array.from(groupBy(x => x.heading, parseExamples(ast)).entries()); 196 | 197 | return groups.map(([heading, examples]) => { 198 | const sources = examples.map(x => x.code); 199 | 200 | return heading == null ? functionFromExamples(sources, options) 201 | : /* otherwise */ { 202 | name: heading.replace(/::\s*$/, ''), 203 | call: functionFromExamples(sources, options) 204 | }; 205 | }); 206 | } 207 | 208 | function inferMeta(meta, markdown, options) { 209 | return extend(compact({ 210 | name: inferTitle(markdown), 211 | examples: inferExamples(markdown, options) 212 | }), meta); 213 | } 214 | 215 | 216 | // -- IMPLEMENTATION --------------------------------------------------- 217 | function parse(source, options) { 218 | if (hasYaml(source)) { 219 | const [meta, markdown] = parseMultiDocument(source); 220 | return inferMeta(meta, marked.lexer(markdown), options); 221 | } else { 222 | return inferMeta({ documentation: source }, marked.lexer(source), options); 223 | } 224 | } 225 | 226 | function generate(meta) { 227 | const ast = moduleTemplate({ META: objectToExpression(meta) }); 228 | return generateJs(t.program(ast)).code; 229 | } 230 | 231 | 232 | // -- EXPORTS ---------------------------------------------------------- 233 | module.exports = { 234 | parse, generate 235 | }; 236 | -------------------------------------------------------------------------------- /packages/babel-plugin-metamagical-comments/README.md: -------------------------------------------------------------------------------- 1 | # Meta:Magical Babel Plugin 2 | 3 | [![Chat on Gitter](https://img.shields.io/gitter/room/origamitower/discussion.svg?style=flat-square)](https://gitter.im/origamitower/discussion) 4 | [![Build status](https://img.shields.io/travis/origamitower/metamagical/master.svg?style=flat-square)](https://travis-ci.org/origamitower/metamagical) 5 | [![NPM version](https://img.shields.io/npm/v/babel-plugin-transform-metamagical-comments.svg?style=flat-square)](https://npmjs.org/package/babel-plugin-transform-metamagical-comments) 6 | ![Licence](https://img.shields.io/npm/l/babel-plugin-transform-metamagical-comments.svg?style=flat-square&label=licence) 7 | ![Stability: Experimental](https://img.shields.io/badge/stability-experimental-orange.svg?style=flat-square) 8 | 9 | 10 | This plugin allows describing Meta:Magical meta-data using doc comments, by 11 | having Babel compile the doc comments to runtime annotations. 12 | 13 | 14 | ## Example 15 | 16 | ```js 17 | /*~ 18 | * Composes two functions. 19 | * 20 | * The compose operation allows function composition. For example, if 21 | * you have two functions, `inc` and `double`, you can compose them 22 | * such that you get a new function which has the characteristics of 23 | * both. 24 | * 25 | * const inc = (x) => x + 1 26 | * const double = (x) => x * 2 27 | * const incDouble = compose(double, inc) 28 | * 29 | * incDouble(3) 30 | * // => double(inc(3)) 31 | * // => 8 32 | * 33 | * > **NOTE** 34 | * > Composition is done from right to left, rather than left to right. 35 | * 36 | * --- 37 | * category: Combinators 38 | * tags: ["Lambda Calculus"] 39 | * stability: stable 40 | * type: ('b => 'c, 'a => 'b) => 'a => 'c 41 | */ 42 | const compose = (f, g) => (value) => f(g(value)) 43 | ``` 44 | 45 | Compiles down to (with properties inferred from the source definition and 46 | package.json): 47 | 48 | ```js 49 | var compose = function compose(f, g) { 50 | return function (value) { 51 | return f(g(value)); 52 | }; 53 | }; 54 | compose[Symbol.for("@@meta:magical")] = { 55 | "name": "compose", 56 | "signature": "compose(f, g)", 57 | "type": "('b => 'c, 'a => 'b) => 'a => 'c", 58 | "category": "Combinators", 59 | "tags": ["Lambda Calculus"], 60 | "stability": "stable", 61 | "platforms": ["ECMAScript"], 62 | "licence": "MIT", 63 | "documentation": "Composes two functions.\nThe compose operation allows function composition. For example, if\nyou have two functions, `inc` and `double`, you can compose them\nsuch that you get a new function which has the characteristics of\nboth.\n const inc = (x) => x + 1\n const double = (x) => x * 2\n const incDouble = compose(double, inc)\n incDouble(3)\n // => double(inc(3))\n // => 8\n> **NOTE** \n> Composition is done from right to left, rather than left to right." 64 | }; 65 | ``` 66 | 67 | 68 | ## Installing 69 | 70 | The only supported way to install the transform plugin is through [npm][]. 71 | 72 | > **NOTE** 73 | > If you don't have npm yet, you'll need to install [Node.js][]. All newer 74 | > versions (4+) of Node come with npm. 75 | 76 | ```shell 77 | $ npm install babel-plugin-transform-metamagical-comments 78 | ``` 79 | 80 | 81 | ## Usage 82 | 83 | This package is a transform plugin for the [Babel][] compiler. You'll need to 84 | install Babel separately. 85 | 86 | The recommended way of using the transform plugin is by putting it in your 87 | `.babelrc` file. This file, which contains configuration values for Babel 88 | encoded as JSON, should be in the root of your project. 89 | 90 | To tell Babel to use the Meta:Magical plugin, add the following to the `plugins` 91 | section of your `.babelrc` (the `( ... )` marks stand for existing configuration 92 | in your file): 93 | 94 | ```js 95 | { 96 | ( ... ), 97 | "plugins": [ 98 | ( ... ), 99 | "transform-metamagical-comments" 100 | ] 101 | } 102 | ``` 103 | 104 | #### (alternative) via CLI 105 | 106 | You can tell Babel to use the transform plugin directly as a command line option: 107 | 108 | ```shell 109 | $ babel --plugins transform-metamagical-comments your-script.js 110 | ``` 111 | 112 | 113 | #### (alternative) via Node API 114 | 115 | If you're using Babel programmatically, you'll want to tell Babel to use the 116 | transform plugin by providing it as an option to the `transform` method: 117 | 118 | ```js 119 | require('babel-core').transform(SOURCE_CODE, { 120 | plugins: ['transform-metamagical-comments'] 121 | }) 122 | ``` 123 | 124 | 125 | ## Comment format 126 | 127 | This plugin only considers **leading block comments** which are marked as 128 | Meta:Magical documentation with a leading `~` symbol. As a general rule, the 129 | first line of the block comment should contain only the character `~` (with 130 | optional trailing whitespace), and the other lines should contain a leading `*` 131 | character, which the plugin will use as an starting-indentation mark. 132 | 133 | > **EXAMPLES** 134 | > 135 | > The following is considered a Meta:Magical doc comment: 136 | > 137 | > ```js 138 | > /*~ 139 | > * This is a documentation 140 | > */ 141 | > function f(...) { ... } 142 | > ``` 143 | > 144 | > The following are **not** considered a Meta:Magical doc comment: 145 | > 146 | > ```js 147 | > /* ~ 148 | > * Spaces are not allowed 149 | > */ 150 | > function f(...) { ... } 151 | > 152 | > /*~ New line is required after ~ */ 153 | > function f(...) { ... } 154 | > 155 | > function f(...) { ... } 156 | > /*~ 157 | > * Trailing block comments are not considered. 158 | > */ 159 | > ``` 160 | 161 | The doc comment is separated in two parts by a line containing at least 3 dash 162 | characters. The first part of the doc comment is the documentation field for 163 | Meta:Magical, which should be a long description of your functionality formatted 164 | as Markdown. The second part is a [YAML][] document containing any additional 165 | meta-data for Meta:Magical: 166 | 167 | ```js 168 | /*~ 169 | * This is the documentation. 170 | * 171 | * --- 172 | * this_is: a YAML document 173 | */ 174 | function f(...) { ... } 175 | ``` 176 | 177 | ## Supported platforms 178 | 179 | The transform plugin requires Node 4+ and Babel 6+. 180 | 181 | 182 | ## Support 183 | 184 | If you think you've found a bug in the project, or want to voice your 185 | frustration about using it (maybe the documentation isn't clear enough? Maybe 186 | it takes too much effort to use?), feel free to open a new issue in the 187 | [Github issue tracker](https://github.com/origamitower/metamagical/issues). 188 | 189 | Pull Requests are welcome. By submitting a Pull Request you agree with releasing 190 | your code under the MIT licence. 191 | 192 | You can join the [Gitter Channel](https://gitter.im/origamitower/discussion) for quick support. 193 | You can contact the author over [email](mailto:queen@robotlolita.me), or 194 | [Twitter](https://twitter.com/robotlolita). 195 | 196 | Note that all interactions in this project are subject to Origami Tower's 197 | [Code of Conduct](https://github.com/origamitower/metamagical/blob/master/CODE_OF_CONDUCT.md). 198 | 199 | 200 | ## Licence 201 | 202 | Meta:Magical is copyright (c) Quildreen Motta, 2016, and released under the MIT 203 | licence. See the `LICENCE` file in this repository for detailed information. 204 | -------------------------------------------------------------------------------- /experimental/static-docs/resources/html/default-theme.styl: -------------------------------------------------------------------------------- 1 | @import 'https://fonts.googleapis.com/css?family=Lora:400,400i,700,700i|PT+Sans+Caption|Raleway:100,400|Source+Sans+Pro'; 2 | 3 | // --[ Configuration ]------------------------------------------------- 4 | @import "nib" 5 | global-reset() 6 | 7 | @import "jumperskirt/includes/reset" 8 | @import "jumperskirt/includes/colours" 9 | @import "jumperskirt/includes/settings" 10 | @import "jumperskirt/grid" 11 | 12 | jsk-text-font = 'Lora', serif 13 | jsk-heading-font = 'Raleway', sans 14 | jsk-base-line-height = 1.4em 15 | jsk-base-font-size = 16px 16 | jsk-grid-width = 100% 17 | link-colour = #2980b9 18 | 19 | @import "jumperskirt/typography" 20 | 21 | 22 | 23 | // -- [ Definitions ]-------------------------------------------------- 24 | body { 25 | background: #fff 26 | } 27 | 28 | a { 29 | color: darken(link-colour, 20%) 30 | text-decoration: none 31 | } 32 | a:hover { 33 | text-decoration: underline 34 | } 35 | 36 | // ---[ Layout ] 37 | #content-wrapper { 38 | clearfix: yes 39 | jsk-grid-row: 12 40 | width: 960px 41 | margin: 2em auto 42 | 43 | #content-panel { 44 | jsk-grid-column: 9 45 | } 46 | #meta-panel { 47 | jsk-grid-column: 3 48 | } 49 | } 50 | 51 | // ---[ Text ] 52 | #content-panel { 53 | font-size: 20px 54 | line-height: 1.5em 55 | color: #5f5f5f 56 | 57 | 58 | code { 59 | font-family: monospace 60 | color: wet-asphalt 61 | background: clouds 62 | padding: 0 0.3em 63 | } 64 | 65 | .entity-title { 66 | letter-spacing: -0.02em 67 | color: #2f2f2f 68 | font-size: 36px 69 | font-weight: bold !important 70 | margin-top: 0 71 | } 72 | .highlight-summary { 73 | font-family: 'PT Sans Caption', sans-serif 74 | color: concrete 75 | margin: -0.5em 0 2em 0 76 | } 77 | 78 | .section-title { 79 | font-weight: 300 80 | font-size: 32px 81 | text-transform: uppercase 82 | margin-left: -1em 83 | border-top: 1px solid clouds 84 | padding-top: 0.5em 85 | } 86 | 87 | .definition { 88 | .signature { 89 | font-family: monospace 90 | font-size: 24px 91 | } 92 | .type-title { 93 | display: block 94 | font-family: 'Raleway', sans-serif 95 | color: concrete 96 | text-transform: uppercase 97 | font-size: 16px 98 | margin-top: 1em 99 | } 100 | pre.type { 101 | background: none 102 | padding: 0px 20px 20px 20px 103 | margin-top: 0 104 | } 105 | } 106 | 107 | .deprecation-section { 108 | @extend blockquote 109 | border-color: pomegranate 110 | margin-left: -2em 111 | 112 | .deprecation-title { 113 | color: pomegranate 114 | text-transform: uppercase 115 | font-family: 'PT Sans Caption', sans-serif 116 | border-bottom: 1px solid clouds 117 | margin-bottom: 0.5em 118 | display: block 119 | font-weight: bold 120 | } 121 | } 122 | 123 | .documentation { 124 | h2 { 125 | font-family: 'PT Sans Caption', sans-serif 126 | letter-spacing: -0.02em 127 | color: #2f2f2f 128 | font-size: 36px 129 | font-weight: bold !important 130 | line-height: 100% 131 | margin: 1.2em 0 0.6em 0 132 | } 133 | 134 | h3 { 135 | font-size: 28px 136 | letter-spacing: 0.02em 137 | border-bottom: 1px solid clouds 138 | padding-bottom: 0.3em 139 | margin-bototm: 0.3em 140 | color: #5f5f5f 141 | } 142 | 143 | h4, h5, h6 { 144 | font-size: 26px 145 | font-weight: 300 !important 146 | color: concrete 147 | margin-bottom: 0.3em 148 | letter-spacing: -0.02em 149 | text-transform: uppercase 150 | border-bottom: 1px solid clouds 151 | } 152 | } 153 | 154 | .members { 155 | .category { 156 | @extends #content-panel .documentation h2 157 | } 158 | 159 | .member { 160 | margin-bottom: 1.5em 161 | } 162 | 163 | .doc-summary { 164 | margin-left: 2em 165 | color: asbestos 166 | 167 | p { 168 | margin: 0; 169 | } 170 | } 171 | 172 | .special-tags { 173 | margin-left: 2em 174 | 175 | .tagged { 176 | font-family: 'Raleway', sans-serif 177 | font-weight: 400 178 | font-size: 12px 179 | padding: 0.3em 0.8em 180 | margin-right: 1em 181 | border: 1px solid concrete 182 | color: concrete 183 | text-transform: uppercase 184 | } 185 | 186 | .deprecated { 187 | color: pomegranate 188 | border-color: pomegranate 189 | font-weight: bold 190 | } 191 | 192 | .required { 193 | color: alizarin 194 | border-color: alizarin 195 | } 196 | 197 | .experimental { 198 | color: pumpkin 199 | border-color: pumpkin 200 | } 201 | } 202 | 203 | .member-name { 204 | font-family: monospace 205 | font-weight: bold 206 | } 207 | 208 | &.deprecated .member-name { 209 | text-decoration: line-through 210 | } 211 | } 212 | 213 | p { 214 | margin: 0 0 1.2em 0 215 | } 216 | } 217 | 218 | #meta-panel { 219 | font-family: 'Raleway', sans-serif 220 | font-size: 14px 221 | color: #5f5f5f 222 | 223 | .meta-section { 224 | margin-bottom: 2em 225 | } 226 | 227 | .meta-field { 228 | margin-bottom: 1em 229 | } 230 | 231 | .meta-field-value { 232 | ul { 233 | margin: 0 234 | list-style: none 235 | } 236 | li { 237 | margin: 0 0 0.5em 0 238 | } 239 | } 240 | 241 | .meta-field-title { 242 | letter-spacing: 0.02em 243 | font-family: 'PT Sans Caption', sans-serif 244 | color: #2f2f2f 245 | } 246 | 247 | .meta-section-title { 248 | text-transform: uppercase 249 | letter-spacing: 0.02em 250 | color: concrete 251 | font-weight: 300 252 | } 253 | 254 | .table-of-contents { 255 | margin-bottom: 2em 256 | 257 | .toc-list { 258 | list-style: none 259 | p { 260 | margin: 0 261 | } 262 | &.level-1 { 263 | margin: 0 0 0 0 264 | & > .toc-item { 265 | margin: 0.7em 0 0 0 266 | & > a, & > .no-anchor { 267 | font-weight: bold 268 | text-transform: uppercase 269 | color: #5f5f5f 270 | } 271 | } 272 | } 273 | &.level-2 { 274 | margin: 0 0 0 1em 275 | & > .toc-item { 276 | margin: 0 0 0.4em 0 277 | font-size: 13px 278 | line-height: 100% 279 | white-space: nowrap 280 | overflow: hidden 281 | text-overflow: ellipsis 282 | 283 | div, p { 284 | display: inline 285 | } 286 | } 287 | } 288 | } 289 | } 290 | } 291 | 292 | pre code[class*="language-"] { 293 | font-size: 14px 294 | line-height: 16px 295 | background: none !important 296 | padding: 0 !important 297 | } 298 | 299 | #header { 300 | height: 50px 301 | background: midnight 302 | line-height: 50px 303 | padding: 0 30px 304 | font-size: 28px 305 | font-family: 'Raleway', sans-serif 306 | 307 | a { 308 | text-decoration: none 309 | color: clouds 310 | } 311 | 312 | .version { 313 | font-size: 14px 314 | font-weight: 100 315 | margin-left: 5px 316 | font-variant: small-caps 317 | } 318 | 319 | .navigation { 320 | float: right; 321 | font-size: 80%; 322 | list-style: none; 323 | margin: 0; 324 | } 325 | 326 | .navigation-item { 327 | display: inline-block; 328 | margin: 0 0 0 0.2em; 329 | padding: 0 0.5em; 330 | border-bottom: 0px solid midnight; 331 | 332 | &:hover { 333 | border-bottom-width: 5px; 334 | transition: none; 335 | } 336 | 337 | &:hover a { 338 | color: #fff; 339 | transition: none; 340 | } 341 | } 342 | 343 | .navigation-item, a { 344 | transition: all 0.25s ease-out; 345 | } 346 | } -------------------------------------------------------------------------------- /experimental/static-docs/src/staticalise.js: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------- 2 | // 3 | // This source file is part of the Meta:Magical project. 4 | // 5 | // See LICENCE for licence information. 6 | // See CONTRIBUTORS for the list of contributors to the project. 7 | // 8 | //--------------------------------------------------------------------- 9 | 10 | // --[ Dependencies ]-------------------------------------------------- 11 | const uuid = require('node-uuid').v4; 12 | const marked = require('marked'); 13 | const Maybe = require('folktale').data.maybe; 14 | 15 | const hasOwnProperty = Object.prototype.hasOwnProperty; 16 | const prototypeOf = Object.getPrototypeOf; 17 | const ownProperties = Object.getOwnPropertyNames; 18 | const descriptor = Object.getOwnPropertyDescriptor; 19 | 20 | 21 | // --[ Helpers ]------------------------------------------------------- 22 | const isObject = (value) => Object(value) === value; 23 | 24 | const dasherise = (name) => name.replace(/\W/g, '-'); 25 | 26 | const countNonInherentPrototypeProperties = (value) => 27 | ownProperties(value.prototype).filter(k => 28 | k !== 'constructor' || descriptor(value.prototype, k).value !== value 29 | ).length; 30 | 31 | const isClass = (value) => 32 | typeof value === 'function' 33 | && isObject((descriptor(value, 'prototype') || {}).value) // we don't care about getters 34 | && countNonInherentPrototypeProperties(value) > 0; 35 | 36 | const isDocumented = (meta) => meta.get(meta.fields.documentation) 37 | .map(_ => true) 38 | .getOrElse(false); 39 | 40 | const paragraphOrHeading = (node) => node.type === 'paragraph' 41 | || node.type === 'heading'; 42 | 43 | const summary = (text) => { 44 | const maybeParagraph = marked.lexer(text).filter(paragraphOrHeading)[0]; 45 | if (maybeParagraph && maybeParagraph.type === 'paragraph') { 46 | return maybeParagraph.text; 47 | } else { 48 | return ''; 49 | } 50 | }; 51 | 52 | 53 | const compact = (object) => { 54 | let result = Object.create(null); 55 | 56 | Object.keys(object).forEach(key => { 57 | const value = object[key]; 58 | if (!value.isNothing) { 59 | result[key] = value.get(); 60 | } 61 | }); 62 | 63 | return result; 64 | }; 65 | 66 | 67 | const serialiseMeta = (meta) => { 68 | const _ = meta.fields; 69 | 70 | return compact({ 71 | copyright : meta.get(_.copyright), 72 | authors : meta.get(_.authors), 73 | maintainers : meta.get(_.maintainers), 74 | licence : meta.get(_.licence), 75 | since : meta.get(_.since), 76 | deprecated : meta.get(_.deprecated), 77 | repository : meta.get(_.repository), 78 | stability : meta.get(_.stability), 79 | homepage : meta.get(_.homepage), 80 | category : meta.get(_.category), 81 | tags : meta.get(_.tags), 82 | npmPackage : meta.get(_.npmPackage), 83 | module : meta.get(_.module), 84 | isModule : meta.get(_.isModule), 85 | isRequired : meta.get(_.isRequired), 86 | name : meta.get(_.name), 87 | location : meta.get(_.location), 88 | source : meta.get(_.source), 89 | documentation : meta.get(_.documentation), 90 | summary : meta.get(_.documentation).map(summary), 91 | signature : meta.get(_.signature), 92 | type : meta.get(_.type), 93 | throws : meta.get(_.throws), 94 | parameters : meta.get(_.parameters), 95 | returns : meta.get(_.returns), 96 | complexity : meta.get(_.complexity), 97 | platforms : meta.get(_.platforms), 98 | portable : meta.get(_.portable) 99 | }); 100 | }; 101 | 102 | 103 | const startsWith = (substring, text) => text.indexOf(substring) === 0; 104 | 105 | 106 | const propertySignature = (name, signature) => 107 | startsWith(`${name}(`, signature) ? signature 108 | : /* otherwise */ `${name}: ${signature}`; 109 | 110 | 111 | const propertyRepresentation = (meta, { name, value, kind }) => 112 | kind === 'getter' ? `get ${name}` 113 | : kind === 'setter' ? `set ${name}` 114 | : typeof value === 'function' ? meta.for(value) 115 | .get(meta.fields.signature).map(sig => propertySignature(name, sig)) 116 | .getOrElse(`${name}()`) 117 | : /* otherwise */ name; 118 | 119 | const findDefinition = (object, property) => 120 | hasOwnProperty.call(object, property) ? object 121 | : prototypeOf(object) != null ? findDefinition(prototypeOf(object), property) 122 | : /* otherwise */ null; 123 | 124 | 125 | // --[ Implementation ]------------------------------------------------ 126 | const makeStatic = (meta, root, name, options = {}) => { 127 | let references = new Map(options.references ? options.references.entries() : []); 128 | let skip = options.skip || new Set(); 129 | let skipProps = options.skipProperties || new Set(); 130 | let result = new Map(); 131 | 132 | 133 | const shouldSkip = (object) => !isObject(object) 134 | || skip.has(object) 135 | || (options.skipUndocumented && !isDocumented(meta.for(object))) 136 | || references.has(object); 137 | 138 | const shouldSkipProperty = (object, property) => 139 | skip.has(findDefinition(object, property)) 140 | || (typeof object === 'function' && ['name', 'length'].includes(property)) 141 | || (!isClass(object) && property === 'prototype'); 142 | 143 | const allowsPopping = (path) => (path[0] === '(unknown module)' && path.length > 1) 144 | || path.length > 0; 145 | 146 | 147 | const computeIndexPath = (object, path) => meta.for(object) 148 | .get(meta.fields.module) 149 | .chain(Maybe.fromNullable) 150 | .map(modulePath => modulePath.split('/')) 151 | .getOrElse(['(unknown module)', ...path]); 152 | 153 | 154 | const isModule = (object) => meta.for(object) 155 | .get(meta.fields.isModule) 156 | .getOrElse(false); 157 | 158 | 159 | const truePath = (object, name, path) => { 160 | let parentPath = computeIndexPath(object, path); 161 | if (isModule(object) && allowsPopping(parentPath)) { 162 | name = parentPath.pop(); 163 | } 164 | 165 | return [...parentPath, name].map(dasherise); 166 | }; 167 | 168 | 169 | const serialise = (object, name, path) => { 170 | const id = uuid(); 171 | references.set(object, id); 172 | return Object.assign({ 173 | id, 174 | path: truePath(object, name, path), 175 | isClass: isClass(object) 176 | }, serialiseMeta(meta.for(object))); 177 | }; 178 | 179 | const go = (object, name, path) => { 180 | if (shouldSkip(object)) return; 181 | 182 | const data = serialise(object, name, path); 183 | result.set(data.id, data); 184 | data.properties = meta.for(object).allProperties().map(group => { 185 | const members = group.members.filter(m => !shouldSkipProperty(object, m.name)); 186 | 187 | members.forEach(p => go(p.value, p.name, [...path, name])); 188 | return { 189 | category : group.category, 190 | members : members.map(member => ({ 191 | name : member.name, 192 | reference : references.get(member.value), 193 | kind : member.kind, 194 | representation : propertyRepresentation(meta, member), 195 | meta : serialiseMeta(meta.for(member.value)), 196 | isInherited : !hasOwnProperty.call(object, member.name) 197 | })) 198 | }; 199 | }).filter(group => group.members.length > 0); 200 | }; 201 | 202 | go(root, name, []); 203 | [...references.entries()].forEach(([object, id]) => { 204 | if (isClass(object)) { 205 | const entity = result.get(id); 206 | const prototype = result.get(references.get(object.prototype)); 207 | if (!prototype) { 208 | entity.isClass = false; 209 | } else { 210 | entity.prototype = prototype; 211 | } 212 | } 213 | }); 214 | 215 | return [...result.values()]; 216 | }; 217 | 218 | 219 | // --[ Exports ]------------------------------------------------------- 220 | module.exports = { 221 | makeStatic 222 | }; 223 | -------------------------------------------------------------------------------- /packages/babel-plugin-assertion-comments/src/index.js: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------- 2 | // 3 | // This source file is part of the Meta:Magical project. 4 | // 5 | // See LICENCE for licence information. 6 | // See CONTRIBUTORS for the list of contributors to the project. 7 | // 8 | //--------------------------------------------------------------------- 9 | 10 | // -- DEPENDENCIES ---------------------------------------------------- 11 | const generate = require('babel-generator').default; 12 | const template = require('babel-template'); 13 | const traverse = require('babel-traverse').default; 14 | const { parse } = require('babylon'); 15 | 16 | 17 | // -- HELPERS --------------------------------------------------------- 18 | 19 | /*~ 20 | * A template for creating an assertion AST. 21 | */ 22 | const buildAssertion = template(` 23 | __metamagical_assert_equals(require(MODULE), ACTUAL, EXPECTED, MESSAGE) 24 | `); 25 | 26 | 27 | /*~ 28 | * The deep equality function. 29 | */ 30 | function assertEquals(assert, actual, expected, message) { 31 | const assertionType = Symbol.for('@@meta:magical:assertion-type'); 32 | const rest = Symbol.for('@@meta:magical:assertion-rest'); 33 | const keys = Object.keys; 34 | 35 | function isSetoid(value) { 36 | return value 37 | && typeof value.equals === 'function'; 38 | } 39 | 40 | function isRecord(value) { 41 | return value 42 | && value[assertionType] === 'record'; 43 | } 44 | 45 | function check(predicate, ...values) { 46 | return values.every(predicate); 47 | } 48 | 49 | function getSpecialArrayLength(array) { 50 | const rests = array.map((x, i) => [x, i]).filter(pair => pair[0] === rest); 51 | if (rests.length > 1 || (rests[0] && rests[0][1] !== array.length - 1)) { 52 | assert.ok(false, `${message}. Assertions only support a single trailing rest indication for arrays currently.`); 53 | } 54 | return (rests[0] || [null, array.length])[1]; 55 | } 56 | 57 | function compareArrays(left, right) { 58 | const expectedLength = getSpecialArrayLength(right); 59 | assert.ok(Array.isArray(left), `${message}. The LHS is not an array.`); 60 | if (left.length < expectedLength) { 61 | assert.ok(false, `${message}. The LHS does not have the minimum expected length (${left.length} < ${expectedLength})`); 62 | } 63 | for (let i = 0; i < expectedLength; ++i) { 64 | compare(left[i], right[i]); 65 | } 66 | } 67 | 68 | function compareRecord(left, right) { 69 | const isPartial = right[rest]; 70 | assert.ok(Object(left) === left, message); 71 | if (!isPartial) { 72 | assert.deepStrictEqual(keys(left).sort(), keys(right).sort(), `${message}. The objects have different keys ([${keys(left).sort().join(', ')}] vs. [${keys(right).sort().join(', ')}])`); 73 | } 74 | Object.keys(right).forEach(key => { 75 | if (!(key in left)) { 76 | assert.ok(false, `${message}. The expected key ${key} is not present in the actual object.`); 77 | } 78 | compare(left[key], right[key]); 79 | }); 80 | } 81 | 82 | 83 | function compare(l, r) { 84 | return check(isSetoid, l, r) ? assert.ok(l.equals(r), `${message}. The LHS (${l}) setoid instance says the LHS and RHS (${r}) are not equal.`) 85 | : Array.isArray(r) ? compareArrays(l, r) 86 | : isRecord(r) ? compareRecord(l, r) 87 | : /* otherwise */ assert.deepStrictEqual(l, r, `${message}. The LHS (${l}) and the RHS (${r}) are not structually equal.`); 88 | } 89 | 90 | compare(actual, expected); 91 | } 92 | 93 | 94 | /*~ 95 | * Returns the first item of an array. 96 | */ 97 | function first(xs) { 98 | return xs[0]; 99 | } 100 | 101 | 102 | /*~ 103 | * Tests if a comment node in the AST is an assertion comment. 104 | */ 105 | function isAssertion(comment) { 106 | return comment.type === 'CommentLine' 107 | && /^\s*==>\s*.+$/.test(comment.value); 108 | } 109 | 110 | 111 | // -- IMPLEMENTATION -------------------------------------------------- 112 | /*~ 113 | * A Babel transformer that converts assertion comments into runtime 114 | * assertions. 115 | * 116 | * Assertion comments are usually used in examples to show readers 117 | * what a particular expression evaluates to, but they don't get 118 | * checked when one runs the code. This plugin allows people to write 119 | * assertion comments after a particular expression. It then transforms 120 | * this assertion comment into a real runtime assertion. 121 | * 122 | * 2 * 2 // ==> 4 123 | * 124 | * 125 | * ## Options 126 | * 127 | * You can configure which assertion module is used by providing the 128 | * `module` option to Babel. By default this transform uses Node's 129 | * built-in `assert` module. 130 | */ 131 | module.exports = function({ types: t }) { 132 | const assertEqualsFD = parse(assertEquals.toString()).program.body[0]; 133 | const assertEqualsAST = t.functionExpression( 134 | t.identifier('metamagical_assert_equals'), 135 | assertEqualsFD.params, 136 | assertEqualsFD.body 137 | ); 138 | 139 | function makeSymbol(name) { 140 | return t.callExpression( 141 | t.memberExpression( 142 | t.identifier('Symbol'), 143 | t.identifier('for') 144 | ), 145 | [t.stringLiteral(name)] 146 | ); 147 | } 148 | 149 | function makeRest() { 150 | return makeSymbol('@@meta:magical:assertion-rest'); 151 | } 152 | 153 | function makeAssertionType() { 154 | return makeSymbol('@@meta:magical:assertion-type'); 155 | } 156 | 157 | const astTraversal = { 158 | enter(path) { 159 | switch (path.node.type) { 160 | case 'SpreadElement': 161 | if (path.node.argument.name === "_") { 162 | path.replaceWith(makeRest()); 163 | } 164 | break; 165 | 166 | case 'ObjectExpression': 167 | path.node.properties.push( 168 | t.objectProperty(makeAssertionType(), t.stringLiteral('record'), true) 169 | ); 170 | break; 171 | 172 | case 'SpreadProperty': 173 | if (path.node.argument.name === "_") { 174 | path.replaceWith(t.objectProperty(makeRest(), t.booleanLiteral(true), true)); 175 | } 176 | break; 177 | } 178 | } 179 | }; 180 | 181 | function transformSpread(ast) { 182 | traverse(ast, astTraversal); 183 | return ast; 184 | } 185 | 186 | 187 | function parseAssertion(source) { 188 | const data = source.replace(/^\s*==>\s*/, '').trim(); 189 | 190 | try { 191 | let ast = parse(`(${data})`, { plugins: ['objectRestSpread'] }); 192 | transformSpread(ast); 193 | ast = ast.program.body; 194 | if (ast.length !== 1 && ast[0].type !== 'ExpressionStatement') { 195 | throw new TypeError(`Expected a single expression in ${data}`); 196 | } 197 | 198 | return { 199 | expression: ast[0].expression, 200 | source: data 201 | }; 202 | } catch (e) { 203 | console.log('Error parsing ' + source + ': ', e); 204 | return null; 205 | } 206 | } 207 | 208 | 209 | function getTrailingAssertion(node) { 210 | const comment = first(node.trailingComments || []); 211 | 212 | return comment && isAssertion(comment) ? parseAssertion(comment.value) 213 | : /* otherwise */ null; 214 | } 215 | 216 | 217 | return { 218 | visitor: { 219 | ExpressionStatement(path, state) { 220 | const assertModule = state.opts.module || 'assert'; 221 | const assertion = getTrailingAssertion(path.node); 222 | 223 | if (assertion) { 224 | if (!path.hub.file.scope.hasBinding('__metamagical_assert_equals')) { 225 | path.hub.file.scope.push({ 226 | id: t.identifier('__metamagical_assert_equals'), 227 | init: assertEqualsAST 228 | }); 229 | } 230 | 231 | path.node.expression = buildAssertion({ 232 | MODULE: t.stringLiteral(assertModule), 233 | ACTUAL: path.node.expression, 234 | EXPECTED: assertion.expression, 235 | MESSAGE: t.stringLiteral(`${generate(path.node.expression).code} ==> ${assertion.source}`) 236 | }); 237 | } 238 | }, 239 | 240 | CallExpression(path, state) { 241 | const assertModule = state.opts.module || 'assert'; 242 | const callee = path.node.callee; 243 | 244 | if (callee.type === "Identifier" && callee.name === "$ASSERT") { 245 | if (!path.hub.file.scope.hasBinding('__metamagical_assert_equals')) { 246 | path.hub.file.scope.push({ 247 | id: t.identifier('__metamagical_assert_equals'), 248 | init: assertEqualsAST 249 | }); 250 | } 251 | 252 | const expr = path.node.arguments[0]; 253 | if (expr.type === "BinaryExpression" && expr.operator === "==") { 254 | const leftCode = generate(expr.left).code; 255 | const rightCode = generate(expr.right).code; 256 | 257 | path.traverse(astTraversal); 258 | path.replaceWith(buildAssertion({ 259 | MODULE: t.stringLiteral(assertModule), 260 | ACTUAL: expr.left, 261 | EXPECTED: expr.right, 262 | MESSAGE: t.stringLiteral(`${leftCode} ==> ${rightCode}`) 263 | })); 264 | } else { 265 | path.replaceWith(buildAssertion({ 266 | MODULE: t.stringLiteral(assertModule), 267 | ACTUAL: expr, 268 | EXPECTED: t.booleanLiteral(true), 269 | MESSAGE: t.stringLiteral(`${generate(expr).code}`) 270 | })); 271 | } 272 | } 273 | } 274 | } 275 | }; 276 | }; 277 | -------------------------------------------------------------------------------- /experimental/static-docs/src/formatters/html.js: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------- 2 | // 3 | // This source file is part of the Meta:Magical project. 4 | // 5 | // See LICENCE for licence information. 6 | // See CONTRIBUTORS for the list of contributors to the project. 7 | // 8 | //--------------------------------------------------------------------- 9 | 10 | // --[ Dependencies ]-------------------------------------------------- 11 | const marked = require('marked'); 12 | const _ = require('hyperscript'); 13 | const path = require('path'); 14 | const fs = require('fs'); 15 | 16 | 17 | // --[ Helpers ]------------------------------------------------------- 18 | const toId = (x) => x.toLowerCase().replace(/[^\w]+/g, '-'); 19 | 20 | const filename = (entity) => `${entity.path.join('.')}.html`; 21 | 22 | const link = (from, to) => filename(to); 23 | 24 | const entries = (object) => Object.keys(object).map(key => [key, object[key]]); 25 | 26 | const read = (filename) => fs.readFileSync(filename); 27 | 28 | const compact = (object) => { 29 | let result = Object.create(null); 30 | 31 | Object.keys(object).forEach(key => { 32 | if (object[key] != null) result[key] = object[key]; 33 | }); 34 | 35 | return result; 36 | }; 37 | 38 | const markdownToHtml = (text) => { 39 | let element = _('div'); 40 | element.innerHTML = marked(text || ''); 41 | return element; 42 | }; 43 | 44 | const renderLocation = (location) => [ 45 | 'Defined in', location.filename, 46 | location.start ? `at line ${location.start.line}, column ${location.start.column}` 47 | : /* else */ null 48 | ].filter(x => x !== null).join(' '); 49 | 50 | const source = (source, location) => 51 | _('div.source-code', 52 | _('h2.section-title#source-code', 'Source Code'), 53 | _('div.source-location', 54 | location ? renderLocation(location) : ''), 55 | _('pre.source-code', _('code.language-javascript', source)) 56 | ); 57 | 58 | const renderMember = (entity, member, references) => { 59 | const page = references.get(member.reference); 60 | const meta = member.meta; 61 | 62 | return _(`div.member${meta.deprecated ? '.deprecated' : ''}`, 63 | page ? _('a.member-name', { href: link(entity, page) }, member.representation) 64 | : /* else */ _('div.member-name.no-link', member.representation), 65 | 66 | _('div.doc-summary', 67 | meta.summary ? markdownToHtml(meta.summary) : ''), 68 | 69 | _('div.special-tags', 70 | meta.deprecated ? _('span.tagged.deprecated', 'Deprecated') : '', 71 | meta.isRequired ? _('span.tagged.required', 'Abstract') : '', 72 | meta.stability === 'experimental' ? _('span.tagged.experimental', 'Experimental') : '', 73 | member.isInherited ? _('span.tagged.inherited', 'Inherited') : '' 74 | ) 75 | ); 76 | }; 77 | 78 | const renderMembers = (entity, references) => 79 | entity.isClass ? renderClassMembers(entity, references) 80 | : /* otherwise */ renderObjectMembers(entity, references); 81 | 82 | 83 | const renderClassMembers = (entity, references) => 84 | [ 85 | _('h2.section-title#properties', 'Static properties'), 86 | ...renderObjectMembers(entity, references, 4), 87 | _('h2.section-title#instance-properties', 'Instance (prototype) properties'), 88 | ...renderObjectMembers(entity.prototype, references, 4) 89 | ]; 90 | 91 | 92 | const renderObjectMembers = (entity, references, level = 3) => 93 | entity.properties.map(({ category, members }) => 94 | _('div.member-category', 95 | _(`h${level}.category`, { id: `cat-${toId(category)}` }, category), 96 | _('div.member-list', 97 | members.map(member => renderMember(entity, member, references)) 98 | ) 99 | ) 100 | ); 101 | 102 | const renderDeprecation = ({ version, reason }) => 103 | _('div.deprecation-section', 104 | _('strong.deprecation-title', `Deprecated since ${version}`), 105 | markdownToHtml(reason) 106 | ); 107 | 108 | const metaSection = (title, data) => 109 | _('div.meta-section', 110 | title ? _('strong.meta-section-title', title) : '', 111 | entries(data).map(([key, value]) => 112 | _('div.meta-field', 113 | _('strong.meta-field-title', key), 114 | _('div.meta-field-value', value) 115 | ) 116 | ) 117 | ); 118 | 119 | const documentationToc = (text) => 120 | marked.lexer(text || '') 121 | .filter(x => x.type === 'heading' && x.depth === 2) 122 | .map(x => ({ 123 | rawTitle: x.text, 124 | title: markdownToHtml(x.text), 125 | anchor: `#${toId(x.text)}` 126 | })); 127 | 128 | const memberToc = (properties) => 129 | properties.map(({ category }) => ({ 130 | title: category, 131 | anchor: `#cat-${toId(category)}` 132 | })); 133 | 134 | const tableOfContents = (list, level = 1) => 135 | _(`ul.toc-list.level-${level}`, 136 | list.map(item => 137 | _('li.toc-item', 138 | item.anchor ? _('a', { href: item.anchor, title: item.rawTitle }, item.title) 139 | : /* else */ _('span.no-anchor', item.title), 140 | 141 | item.children ? tableOfContents(item.children, level + 1) : '' 142 | ) 143 | ) 144 | ); 145 | 146 | const asList = (elements) => 147 | _('ul.meta-list', 148 | elements.map(e => _('li', e)) 149 | ); 150 | 151 | // --[ Implementation ]------------------------------------------------ 152 | const render = (entity, references, options) => { 153 | return _('div#content-wrapper', 154 | _('div#content-panel', 155 | _('h1.entity-title', entity.name || '(Anonymous)'), 156 | _('div.highlight-summary', markdownToHtml(entity.summary)), 157 | entity.signature || entity.type ? 158 | _('div.definition', 159 | _('h2#signature.section-title', 'Signature'), 160 | _('div.signature', entity.signature || ''), 161 | _('div.type-definition', 162 | entity.type ? _('strong.type-title', 'Type') : '', 163 | _('pre.type', _('code.language-haskell', (entity.type || '').trimRight())) 164 | ) 165 | ) 166 | : '', 167 | entity.deprecated ? renderDeprecation(entity.deprecated) : '', 168 | _('h2.section-title', 'Documentation'), 169 | _('div.documentation', 170 | markdownToHtml(entity.documentation) 171 | ), 172 | _('div.members', 173 | entity.isClass ? '' : _('h2.section-title#properties', 'Properties'), 174 | ...renderMembers(entity, references)), 175 | entity.source ? source(entity.source, entity.location) : '' 176 | ), 177 | _('div#meta-panel', 178 | metaSection(null, compact({ 179 | 'Stability': entity.stability, 180 | 'Since': entity.since, 181 | 'Licence': entity.licence, 182 | 'Module': entity.module, 183 | 'Platforms': entity.platforms ? asList(entity.platforms) : null 184 | })), 185 | 186 | _('div.table-of-contents', 187 | _('div.meta-section-title', 'On This Page'), 188 | tableOfContents([ 189 | entity.signature || entity.type ? { 190 | title: 'Signature', 191 | anchor: '#signature' 192 | } : null, 193 | { 194 | title: 'Documentation', 195 | children: documentationToc(entity.documentation) 196 | }, 197 | { 198 | title: 'Properties', 199 | anchor: '#properties', 200 | children: memberToc(entity.properties) 201 | }, 202 | entity.isClass ? { 203 | title: 'Instance properties', 204 | anchor: '#instance-properties', 205 | children: memberToc(entity.prototype.properties) 206 | } : null, 207 | entity.source ? { title: 'Source Code', anchor: '#source-code' } : null 208 | ].filter(Boolean)) 209 | ), 210 | 211 | metaSection('Authors', compact({ 212 | 'Copyright': entity.copyright, 213 | 'Authors': entity.authors ? asList(entity.authors) : null, 214 | 'Maintainers': entity.maintainers ? asList(entity.maintainers) : null 215 | })) 216 | ) 217 | ); 218 | }; 219 | 220 | const wrapPage = (title, content, options) => { 221 | const titleElement = _('title', title); 222 | const docTitle = _('div.doc-title', 223 | _('a', { href: options.rootPage }, options.documentationTitle), 224 | _('ul.navigation', 225 | (options.navigation || []).map(link => 226 | _('li.navigation-item', 227 | _('a', { href: link.url, title: link.altText || '' }, link.text) 228 | ) 229 | ) 230 | ) 231 | ); 232 | const cssElement = _('link', { 233 | rel: 'stylesheet', 234 | href: 'style.css' 235 | }); 236 | 237 | return ` 238 | 239 | 240 | 241 | ${titleElement.outerHTML} 242 | 243 | ${cssElement.outerHTML} 244 | 245 | 246 | 249 | ${content.outerHTML} 250 | 258 | 259 | 260 | `; 261 | }; 262 | 263 | 264 | const toHTML = (entities, options) => { 265 | const references = new Map(entities.map(x => [x.id, x])); 266 | const resources = path.join(__dirname, '../../resources/html'); 267 | const css = read(options.cssPath || path.join(resources, 'default-theme.css')); 268 | 269 | return entities.map(entity => ({ 270 | filename: filename(entity), 271 | content: wrapPage(entity.name, render(entity, references, options), options) 272 | })).concat([ 273 | { 274 | filename: 'style.css', 275 | content: css 276 | }, 277 | { 278 | filename: 'prism.css', 279 | content: read(path.join(resources, 'static/prism.css')) 280 | }, 281 | { 282 | filename: 'prism.js', 283 | content: read(path.join(resources, 'static/prism.js')) 284 | } 285 | ]); 286 | }; 287 | 288 | // --[ Exports ]------------------------------------------------------- 289 | module.exports = toHTML; 290 | -------------------------------------------------------------------------------- /experimental/mkdocs/src/index.js: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------- 2 | // 3 | // This source file is part of the Meta:Magical project. 4 | // 5 | // See LICENCE for licence information. 6 | // See CONTRIBUTORS for the list of contributors to the project. 7 | // 8 | //---------------------------------------------------------------------- 9 | 10 | // --[ Dependencies ]-------------------------------------------------- 11 | const marked = require('marked'); 12 | const path = require('path'); 13 | 14 | 15 | // --[ Aliases ]------------------------------------------------------- 16 | const keys = Object.keys; 17 | 18 | 19 | // --[ Module ]-------------------------------------------------------- 20 | module.exports = function(meta) { 21 | const f = meta.fields; 22 | 23 | // ---[ Helpers ]---------------------------------------------------- 24 | function compact(object) { 25 | let result = {}; 26 | 27 | keys(object).forEach(key => { 28 | const value = object[key]; 29 | if (!value.isNothing) { 30 | result[key] = value.get(); 31 | } 32 | }); 33 | 34 | return result; 35 | } 36 | 37 | function flatten(xss) { 38 | return xss.reduce((l, r) => l.concat(r), []); 39 | } 40 | 41 | function startsWith(substring, text) { 42 | return text.indexOf(substring) === 0; 43 | } 44 | 45 | function toId(x) { 46 | return x.replace(/\s/g, '-').replace(/[^\w\d]/g, '').toLowerCase(); 47 | } 48 | 49 | function lines(text) { 50 | return text.split(/\r\n|\n\r|\r|\n/); 51 | } 52 | 53 | function entries(object) { 54 | return keys(object).map(k => [k, object[k]]); 55 | } 56 | 57 | function values(object) { 58 | return keys(object).map(k => object[k]); 59 | } 60 | 61 | function repeat(text, times) { 62 | return Array(times + 1).join(text); 63 | } 64 | 65 | function title(text, level = 1) { 66 | return `\n${repeat('#', level)} ${text}\n`; 67 | } 68 | 69 | function para(text) { 70 | return block(lines(text)); 71 | } 72 | 73 | function quote(text) { 74 | return block(lines(text).map(x => `> ${x}`)); 75 | } 76 | 77 | function block(xs) { 78 | return `\n${xs.join('\n')}\n`; 79 | } 80 | 81 | function indent(text, spaces) { 82 | return lines(text).map((x, i) => `${i > 0? repeat(' ', spaces) : ''}${x}`) 83 | .join('\n'); 84 | } 85 | 86 | function list(xs) { 87 | return xs.map(x => ` - ${indent(x, 4)}`) 88 | .join('\n'); 89 | } 90 | 91 | function code(text, language = 'javascript') { 92 | return block([ 93 | `\`\`\`${language}`, 94 | text.trimRight(), 95 | '```' 96 | ]); 97 | } 98 | 99 | function table(object) { 100 | return list(entries(object).map(([k, v]) => `**${k}:**\n${v.isJust? v.get() : v}`)); 101 | } 102 | 103 | function link(text, url) { 104 | return `[${text}](${url})`; 105 | } 106 | 107 | function summary(text) { 108 | const maybeParagraph = marked.lexer(text)[0]; 109 | if (maybeParagraph && maybeParagraph.type === 'paragraph') { 110 | return maybeParagraph.text; 111 | } else { 112 | return ''; 113 | } 114 | } 115 | 116 | function isObject(value) { 117 | return Object(value) === value; 118 | } 119 | 120 | function typeOf(value) { 121 | return typeof value === 'string' ? 'String' 122 | : typeof value === 'number' ? 'Number' 123 | : typeof value === 'boolean' ? 'Boolean' 124 | : typeof value === 'symbol' ? 'Symbol' 125 | : value === null ? 'Null' 126 | : value === undefined ? 'Undefined' 127 | : /* otherwise */ 'Unknown'; 128 | } 129 | 130 | function intoObject(value) { 131 | if (isObject(value)) { 132 | return value; 133 | } else { 134 | return { 135 | [Symbol.for('@@meta:magical')]: { 136 | type: typeOf(value) 137 | } 138 | }; 139 | } 140 | } 141 | 142 | 143 | // ---[ Rendering helpers ]------------------------------------------ 144 | function signature(m) { 145 | return m.get(f.signature) 146 | .orElse(_ => m.get(f.name)) 147 | .getOrElse('(Anonymous)'); 148 | } 149 | 150 | function propertySignature(name, signature) { 151 | return startsWith(`${name}(`, signature) ? signature 152 | : /* otherwise */ `${name}: ${signature}`; 153 | } 154 | 155 | function propertyRepresentation({ name, value, kind }) { 156 | return kind === 'getter' ? `get ${name}` 157 | : kind === 'setter' ? `set ${name}` 158 | : typeof value === 'function' ? meta.for(value) 159 | .get(f.signature).map(sig => propertySignature(name, sig)) 160 | .getOrElse(`${name}()`) 161 | : /* otherwise */ name; 162 | } 163 | 164 | function renderDeprecation({ version, reason }) { 165 | return quote(block([ 166 | `**Deprecated since ${version}**`, 167 | reason 168 | ])); 169 | } 170 | 171 | function renderStability(id) { 172 | const { name, index, description } = meta.Stability.fromIdentifier(id); 173 | return quote(block([ 174 | `**Stability: ${index} - ${name}**`, 175 | description 176 | ])); 177 | } 178 | 179 | function renderPortability(value) { 180 | return value ? 'portable' : 'not portable'; 181 | } 182 | 183 | function renderMeta(m) { 184 | return table(compact({ 185 | 'From': m.get(f.module), 186 | 'Defined in': m.get(f.belongsTo).map(o => signature(meta.for(o()))), 187 | 'Copyright': m.get(f.copyright), 188 | 'Licence': m.get(f.licence), 189 | 'Repository': m.get(f.repository), 190 | 'Category': m.get(f.category), 191 | 'Since': m.get(f.since), 192 | 'Portability': m.get(f.portable).map(renderPortability), 193 | 'Platforms': m.get(f.platforms).map(list), 194 | 'Maintainers': m.get(f.maintainers).map(list), 195 | 'Authors': m.get(f.authors).map(list) 196 | })); 197 | } 198 | 199 | function renderFunctionMeta(m) { 200 | return table(compact({ 201 | 'Complexity': m.get(f.complexity), 202 | 'Throws': m.get(f.throws).map(table), 203 | 'Parameters': m.get(f.parameters).map(table), 204 | 'Returns': m.get(f.returns) 205 | })); 206 | } 207 | 208 | function renderSource(source) { 209 | return block([ 210 | title('Source', 2), 211 | code(source) 212 | ]); 213 | } 214 | 215 | function renderMember(property, options) { 216 | const references = options.references || new Map(); 217 | const m = meta.for(intoObject(property.value)); 218 | const doc = m.get(f.documentation).getOrElse('(No documentation)'); 219 | const skip = options.skipDetailedPage || new Set(); 220 | const prefix = options.pathPrefix || ''; 221 | const heading = `\`${propertyRepresentation(property)}\``; 222 | const ext = options.extension || ''; 223 | const hasDetails = isObject(property.value) && references.has(property.value) && !skip.has(property.value); 224 | const url = hasDetails ? path.relative(prefix, references.get(property.value) || '') + ext 225 | : /* else */ `#${toId(propertyRepresentation(property))}`; 226 | 227 | return block([ 228 | hasDetails ? title(link(heading, url), 4) 229 | : /* otherwise */ title(heading, 4), 230 | renderFunctionMeta(m), 231 | m.get(f.type).map(x => code(x, 'haskell')).getOrElse(null), 232 | summary(doc) 233 | ]); 234 | } 235 | 236 | function renderProperties(m, options, prefix = 'Properties in') { 237 | const properties = m.properties(); 238 | 239 | if (properties.length === 0) { 240 | return renderInheritedProperties(m.prototype(), options) 241 | .getOrElse(''); 242 | } else { 243 | return block([ 244 | title(`${prefix} \`${signature(m)}\``, 2), 245 | block( 246 | properties.map(({ category, members }) => block([ 247 | title(category || 'Uncategorised', 3), 248 | block(members.map(p => renderMember(p, options))) 249 | ])) 250 | ), 251 | renderInheritedProperties(m.prototype(), options).getOrElse('') 252 | ]); 253 | } 254 | } 255 | 256 | function renderInheritedProperties(maybeInterface, options) { 257 | const skip = options.excludePrototypes || new Set(); 258 | 259 | return maybeInterface.map(m => { 260 | return skip.has(m.object) ? '' 261 | : /* otherwise */ renderProperties(m, options, 'Properties inherited from'); 262 | }); 263 | } 264 | 265 | 266 | // ---[ Main rendering ]--------------------------------------------- 267 | function toMarkdown(m, options) { 268 | return block([ 269 | title(signature(m)), 270 | m.get(f.type).map(code).getOrElse(''), 271 | m.get(f.deprecated).map(renderDeprecation).getOrElse(''), 272 | '', 273 | m.get(f.stability).map(renderStability).getOrElse(''), 274 | '', 275 | renderMeta(m), 276 | renderFunctionMeta(m), 277 | '', 278 | m.get(f.documentation).getOrElse('(No documentation)'), 279 | m.get(f.source).map(renderSource).getOrElse(''), 280 | renderProperties(m, options) 281 | ]); 282 | } 283 | 284 | 285 | // ---[ Public interface ]------------------------------------------- 286 | function generate(object, options) { 287 | return toMarkdown(meta.for(object), options); 288 | } 289 | 290 | function generateTree(tree, options) { 291 | function maybeGenerate(tree, name, path) { 292 | if (tree.object) { 293 | return [{ 294 | filename: [...path, `${name || 'index'}.md`].join('/'), 295 | content: generate(tree.object, { pathPrefix: path.join('/'), ...options }) 296 | }]; 297 | } else { 298 | return []; 299 | } 300 | } 301 | 302 | function go(tree, name, path) { 303 | return maybeGenerate(tree, name, path) 304 | .concat(flatten(entries(tree.children || {}).map(([k, v]) => go(v, k, [...path, name])))); 305 | } 306 | 307 | return flatten(entries(tree).map(([k, v]) => go(v, k, []))); 308 | } 309 | 310 | return { generate, generateTree }; 311 | }; 312 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Meta:Magical [![Chat on Gitter](https://img.shields.io/gitter/room/origamitower/discussion.svg?style=flat-square)](https://gitter.im/origamitower/discussion) [![Build status](https://img.shields.io/travis/origamitower/metamagical/master.svg?style=flat-square)](https://travis-ci.org/origamitower/metamagical) ![Licence: MIT](https://img.shields.io/badge/licence-MIT-blue.svg?style=flat-square) ![Stability: Experimental](https://img.shields.io/badge/stability-experimental-orange.svg?style=flat-square) 2 | 3 | 4 | > **WARNING** 5 | > This project is still in **early stages of development**. 6 | 7 | Meta:Magical is a project to improve documentation tooling in JavaScript. At 8 | its core, Meta:Magical allows one to annotate runtime objects with arbitrary 9 | data, and query this data later. 10 | 11 | This allows for some very interesting features. For example: 12 | 13 | - A REPL may use this data to provide auto-completion features, documentation, 14 | or even show related functionality. 15 | 16 | - A tool may use example code attached to an object and run them as test cases 17 | automatically. 18 | 19 | - A compiler may output annotated objects, and enable people to inspect these 20 | annotations in ways that aren't possible with static documentation generators 21 | due to the dynamic nature of JavaScript. 22 | 23 | The current focus of development is to provide a robust framework for annotating 24 | live objects, and an interactive tool for exploring objects enriched with these 25 | annotations. 26 | 27 | 28 | ## Supported platforms 29 | 30 | Meta:Magical relies on Symbols and WeakMaps, and thus only properly works 31 | in VMs that implement ECMAScript 2015/ES6. You may run parts of it in older 32 | VMs, using Babel and polyfills, but there are no guarantees that things will 33 | work properly. 34 | 35 | 36 | ## Repository Layout 37 | 38 | This repository is organised as an aggregation of different projects that are 39 | related to Meta:Magical. The stable and active packages live in the `packages/` 40 | directory, and experiments live in the `experimental/` directory. 41 | 42 | #### Packages 43 | 44 | - [**Interface**](packages/interface) — 45 | The Meta:Magical interface provides the basis for annotating live objects, 46 | and querying this meta-data. 47 | 48 | - [**REPL Browser**](packages/repl) — 49 | The REPL Browser allows exploring annotated objects from a regular Node 50 | REPL. 51 | 52 | - [**Mocha Bridge**](packages/mocha-bridge) — 53 | The Mocha bridge allows automatically defining tests in the Mocha framework 54 | by recursively finding all examples attached to objects given a root object. 55 | 56 | - [**Babel Plugin: Meta:Magical comments**](packages/babel-plugin-metamagical-comments) — 57 | The Meta:Magical comments plugin allows annotating objects by using documentation 58 | comments, in a very similar way to things like JavaDoc, JSDoc, etc. The plugin can 59 | infer static information and automatically extract examples from documentation, which 60 | can then be automatically tested. 61 | 62 | - [**Babel Plugin: Assertion comments**](packages/babel-plugin-assertion-comments) — 63 | The assertion comments plugin isn't a Meta:Magical feature, but was created to allow 64 | examples to be defined in a more readable way. This plugin allows compiling trailing 65 | comments in the form of `2 + 2 // ==> 4` to an actual runtime assertion, and is a 66 | good fit for examples in documentation. 67 | 68 | 69 | ## How do I use this? 70 | 71 | > **NOTE** 72 | > This section is a draft and needs to be improved on a lot, but it should give 73 | > you some pointers to get started. 74 | 75 | 76 | ### I am creating a library, and want to document my objects 77 | 78 | If you're creating a library, the simplest way to use Meta:Magical to document 79 | your code is by using the 80 | [Meta:Magical Comments Babel plugin](packages/babel-plugin-metamagical-comments). 81 | This plugin allows annotating objects by just placing documentation comments 82 | before them, as you would do with something like JSDoc: 83 | 84 | ```js 85 | /*~ 86 | * This is a documentation comment. 87 | * 88 | * It may contain multiple lines, and is formatted as **Markdown**! 89 | * 90 | * --- 91 | * category : Things that do things 92 | * type: (String, Number, Boolean) => Undefined 93 | */ 94 | function doSomething(a, b, c) { 95 | It.really.does.something(); 96 | } 97 | ``` 98 | 99 | The babel plugin will provide common metadata, such as the function's signature, 100 | the module where it's defined, the position in the source, the source itself, 101 | which object it belongs to, etc. by analysing the code for you, so you only 102 | need to provide things like category, stability, and type. 103 | 104 | 105 | ### I am using a library annotated with Meta:Magical 106 | 107 | If you're using a library annotated with Meta:Magical, you can explore the 108 | objects by loading the [REPL Browser](packages/repl) package. You need to 109 | install both the `metamagical-interface` and the `metamagical-repl` packages: 110 | 111 | ```shell 112 | npm install metamagical-interface metamagical-repl 113 | ``` 114 | 115 | Once you do that, you can browse an object by feeding any object into the 116 | browser. For example, running the following in the Node REPL: 117 | 118 | ```js 119 | var Interface = require('metamagical-interface'); 120 | var browser = require('metamagical-repl')(Interface); 121 | 122 | browser.browse(Interface).summary(); 123 | ``` 124 | 125 | Would give you the following output: 126 | 127 | ```md 128 | # Interface 129 | =========== 130 | 131 | 132 | Stability: 1 - Experimental 133 | Platforms: 134 | • ECMAScript 2015 135 | 136 | The Meta:Magical interface allows one to query meta-data associated 137 | with a particular object, or attach new meta-data to any object 138 | without modifying the object. 139 | 140 | ## Properties 141 | ------------- 142 | 143 | ### Additional reflective methods 144 | 145 | • allProperties() 146 | | Retrieves a categorised list of properties in the current 147 | context. 148 | 149 | • properties() 150 | | Retrieves a categorised list of properties owned by the current 151 | context. 152 | 153 | 154 | ### Auxiliary methods for querying metadata 155 | 156 | • getInheritedMeta(name) 157 | | Retrieves metadata defined in the closest parent for the interface's 158 | context. 159 | 160 | • getOwnMeta(name) 161 | | Retrieves metadata defined directly on the current interface's context. 162 | 163 | • getPropagatedMeta(name) 164 | | Retrieves metadata defined in the children of the interface's context. 165 | 166 | ( ... ) 167 | 168 | ------------------------------------------------------------------------ 169 | 170 | From: src/index.js at line 507, column 0 171 | In package: metamagical-interface 172 | In module: metamagical-interface 173 | 174 | Copyright: (c) 2016 Quildreen Motta 175 | Licence: MIT 176 | Repository: git://github.com/origamitower/metamagical.git 177 | Web Site: https://github.com/origamitower/metamagical#readme 178 | 179 | Authors: 180 | • Quildreen Motta 181 | Maintainers: 182 | • Quildreen Motta (http://robotlolita.me/) 183 | ``` 184 | 185 | You may navigate in the browser by using the `.forProperty(name)`, 186 | `.forPrototype()`, `.forGetter(name)`, and `.forSetter(name)` methods. Each of 187 | these methods gives you a new browser for the relevant object. Better yet, you 188 | can look at the browser itself to know what you can do with it, just 189 | `browser.summary()`! 190 | 191 | 192 | ### I'd like to document someone *else*'s objects 193 | 194 | Attaching meta-data to other people's objects can be done safely with the 195 | Meta:Magical interface, which keeps an internal WeakMap associating 196 | objects with metadata. This also accounts for the cases where the object 197 | has been frozen, and thus can't be modified. 198 | 199 | If you're not writing a library to provide these annotations, you can 200 | load the `metamagical-interface` directly: 201 | 202 | ```js 203 | const Interface = require('metamagical-interface'); 204 | 205 | Interface.for(Object.prototype).update({ 206 | name: 'Object.prototype' 207 | }); 208 | ``` 209 | 210 | 211 | ### I'd like to write a tool using Meta:Magical annotations 212 | 213 | If you're writing a library, you should take the `metamagical-interface` 214 | instance provided to your library by an external user, and just specify 215 | which version of the interface you depend on in your package's `peerDependencies`. 216 | Effectively, this means that you're exposing a parametric module: 217 | 218 | ```js 219 | // your-module.js 220 | module.exports = function(Interface) { 221 | Interface.for(Object.prototype).update({ 222 | name: 'Object.prototype' 223 | }); 224 | }; 225 | ``` 226 | 227 | ```js 228 | // package.json 229 | { 230 | (...) 231 | "peerDependencies": { 232 | "metamagical-interface": "3.x" 233 | } 234 | } 235 | ``` 236 | 237 | The user of this library then provides the proper `metamagical-interface` for 238 | you to use: 239 | 240 | ```js 241 | require('your-module')(require('metamagical-interface')); 242 | ``` 243 | 244 | If you're using ES6 modules, this is a little bit more complicated since 245 | the modules are not first-class, and don't support being parameterised 246 | over other modules. A compromise is to export a function as the default 247 | binding, but you'll be losing static analysis and mutually recursive 248 | bindings by doing this: 249 | 250 | ```js 251 | // your-module.js 252 | export default function(Interface) { 253 | Interface.for(...).update(...); 254 | } 255 | ``` 256 | 257 | ```js 258 | // The client 259 | const Interface = require('metamagical-interface'); 260 | import ModuleFactory from 'your-module'; 261 | const Module = ModuleFactory(Interface); 262 | 263 | // Now you can use Module... 264 | ``` 265 | 266 | > **WHY THIS RESTRICTION?** 267 | > Since the interface needs to keep a single WeakMap containing all of the 268 | > annotations, there must be only one instance of the Interface in the entire 269 | > environment. 270 | > 271 | > JavaScript makes this hard for a number of reasons: modules are first class, 272 | > and can be instantiated multiple times; npm may install multiple versions of 273 | > a package; clients may install multiple versions of a package; code might 274 | > interact with different realms; etc. 275 | > 276 | > Parameterisation makes what's happening explicit, and leads to less 277 | > surprising behaviours. 278 | 279 | 280 | ## Support 281 | 282 | If you think you've found a bug in the project, or want to voice your 283 | frustration about using it (maybe the documentation isn't clear enough? Maybe 284 | it takes too much effort to use?), feel free to open a new issue in the 285 | [Github issue tracker](https://github.com/origamitower/metamagical/issues). 286 | 287 | Pull Requests are welcome. By submitting a Pull Request you agree with releasing 288 | your code under the MIT licence. 289 | 290 | You can join the [Gitter Channel](https://gitter.im/origamitower/discussion) for quick support. 291 | You can contact the author over [email](mailto:queen@robotlolita.me), or 292 | [Twitter](https://twitter.com/robotlolita). 293 | 294 | Note that all interactions in this project are subject to Origami Tower's 295 | [Code of Conduct](https://github.com/origamitower/metamagical/blob/master/CODE_OF_CONDUCT.md). 296 | 297 | 298 | ## Licence 299 | 300 | Meta:Magical is copyright (c) Quildreen Motta, 2016, and released under the MIT 301 | licence. See the `LICENCE` file in this repository for detailed information. 302 | 303 | -------------------------------------------------------------------------------- /experimental/sphinx/src/index.js: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------- 2 | // 3 | // This source file is part of the Meta:Magical project. 4 | // 5 | // See LICENCE for licence information. 6 | // See CONTRIBUTORS for the list of contributors to the project. 7 | // 8 | //---------------------------------------------------------------------- 9 | 10 | // -- DEPENDENCIES ----------------------------------------------------- 11 | const { exec } = require('child_process'); 12 | const { Text, Sequence, Block, Options, Directive, Title } = require('./rst'); 13 | const { Just, Nothing } = require('folktale/data/maybe'); 14 | const generateJs = require('babel-generator').default; 15 | const parseJs = require('babylon').parse; 16 | const t = require('babel-types'); 17 | const marked = require('marked'); 18 | 19 | 20 | // -- ALIASES ---------------------------------------------------------- 21 | const keys = Object.keys; 22 | const ownProperties_ = Object.getOwnPropertyNames; 23 | const getProperty = Object.getOwnPropertyDescriptor; 24 | const prototypeOf = Object.getPrototypeOf; 25 | 26 | // -- HELPERS ---------------------------------------------------------- 27 | function unique(xs) { 28 | return Array.from(new Set(xs)); 29 | } 30 | 31 | function mapObject(object, transform) { 32 | let result = {}; 33 | 34 | keys(object).forEach(key => { 35 | result[key] = transform(object[key]); 36 | }); 37 | 38 | return result; 39 | } 40 | 41 | function synopsis(doc) { 42 | const tokens = marked.lexer(doc).filter(t => t.type === 'paragraph'); 43 | if (tokens.length > 0) { 44 | return tokens[0].text; 45 | } else { 46 | return ''; 47 | } 48 | } 49 | 50 | function ownProperties(object) { 51 | const props = ownProperties_(object); 52 | 53 | if (typeof object === "function" || object === Function.prototype) { 54 | return props.filter(k => k !== "caller" && k !== "arguments"); 55 | } else { 56 | return props; 57 | } 58 | } 59 | 60 | function replaceMarkdownHeading(text) { 61 | return text.replace(/^[ \t]*#+[ \t](.+?)$/mg, '\n.. rubric:: $1\n\n'); 62 | } 63 | 64 | async function shell(originalCommand, args, options) { 65 | const argString = args.map(x => JSON.stringify(String(x))).join(' '); 66 | const command = `${originalCommand} ${argString}`; 67 | 68 | return new Promise((resolve, reject) => { 69 | const child = exec(command, (error, stdout, stderr) => { 70 | if (error) reject(error); 71 | else resolve({ output: stdout, error: stderr }); 72 | }); 73 | if (options.input) { 74 | child.stdin.end(options.input, 'utf-8'); 75 | } 76 | }); 77 | } 78 | 79 | function isFunction(value) { 80 | return typeof value === 'function'; 81 | } 82 | 83 | function compact(list) { 84 | return list.filter(x => x && !x.isNothing); 85 | } 86 | 87 | function entries(object) { 88 | return keys(object).map(key => [key, object[key]]); 89 | } 90 | 91 | function fromNullable(value) { 92 | return value == null? Nothing() : Just(value); 93 | } 94 | 95 | function flatten(lists) { 96 | return lists.reduce((l, r) => l.concat(r), []); 97 | } 98 | 99 | function compare(left, right) { 100 | return left < right ? -1 101 | : left > right ? 1 102 | : /* else */ 0; 103 | } 104 | 105 | function groupBy(array, groupingFn) { 106 | let result = new Map(); 107 | 108 | array.forEach(value => { 109 | const key = groupingFn(value); 110 | const values = result.get(key) || []; 111 | 112 | values.push(value); 113 | result.set(key, values); 114 | }); 115 | 116 | return Array.from(result.entries()); 117 | } 118 | 119 | function describeProperty(p, name) { 120 | return p.value && isFunction(p.value) ? [`${name}()`, 'function', p] 121 | : p.get && p.set ? [`get/set ${name}`, 'attribute', p] 122 | : p.get ? [`get ${name}`, 'attribute', p] 123 | : p.set ? [`set ${name}`, 'attribute', p] 124 | : /* otherwise */ [name, 'data', p]; 125 | } 126 | 127 | function groupedProperties(options, meta, object) { 128 | function category({ value }) { 129 | return isObject(value) ? meta.forObject(value).get('category').getOrElse('(Uncategorised)') 130 | : /* else */ '(Uncategorised)'; 131 | } 132 | function maybeSkipUndocumented([_, value]) { 133 | if (options.skipUndocumented === false) { 134 | return true; 135 | } else { 136 | return isObject(value) 137 | && meta.forObject(value).get('documentation').map(_ => true).getOrElse(false); 138 | } 139 | } 140 | 141 | const skipUndocumented = options.skipUndocumented === false ? false : true; 142 | 143 | const ps = ownProperties(object) 144 | .map(key => [key, object[key]]) 145 | .filter(maybeSkipUndocumented) 146 | .sort(([k1, _], [k2, __]) => compare(k1, k2)) 147 | .map(([key, value]) => { 148 | const [name, kind, property] = describeProperty(getProperty(object, key), key); 149 | return { key, name, kind, property, value } 150 | }); 151 | return groupBy(ps, category).map(([category, members]) => ({ category, members })) 152 | .sort((a, b) => compare(a.category, b.category)); 153 | } 154 | 155 | function compactObject(object) { 156 | let result = {}; 157 | 158 | entries(object).forEach(([key, value]) => { 159 | value.chain(v => result[key] = v); 160 | }); 161 | 162 | return result; 163 | } 164 | 165 | function isObject(value) { 166 | return Object(value) === value; 167 | } 168 | 169 | function link(title, doc) { 170 | return Directive( 171 | 'rst-class', 'detail-link', 172 | null, 173 | Text(`:doc:\`${title}<${doc}>\``) 174 | ); 175 | } 176 | 177 | function name(meta) { 178 | return meta.get('signature') 179 | .orElse(_ => meta.get('name')) 180 | .getOrElse('(Anonymous Object)'); 181 | } 182 | 183 | function renderDeprecation({ version, reason }) { 184 | return Directive('deprecated', version, null, Text(reason)); 185 | } 186 | 187 | function renderType(type) { 188 | return Directive('code-block', 'haskell', null, Text(type)); 189 | } 190 | 191 | function maybeMeta(meta) { 192 | return compactObject({ 193 | 'From': meta.get('module'), 194 | 'Defined in': meta.get('belongsTo').map(o => name(meta.forObect(o))), 195 | 'Copyright': meta.get('copyright'), 196 | 'Licence': meta.get('licence'), 197 | 'Repository': meta.get('repository'), 198 | 'Category': meta.get('category'), 199 | 'Since': meta.get('since'), 200 | 'Stability': fromNullable(meta.getPropagated('stability')), 201 | 'Portability': fromNullable(meta.getPropagated('portability')), 202 | 'Platforms': meta.get('platforms').map(x => x.join(', ')), 203 | 'Maintainers': meta.get('maintainers').map(x => x.join(', ')), 204 | 'Authors': fromNullable(meta.getPropagated('authors')) 205 | .map(x => unique(flatten(x)).join(', ')) 206 | }); 207 | } 208 | 209 | async function markdownToRST(markdown, headingLevel) { 210 | const { output } = await shell('pandoc', [ 211 | '--from=markdown', 212 | '--to=rst', 213 | `--base-header-level=${Number(headingLevel) || 1}` 214 | ], { input: markdown }); 215 | 216 | return output; 217 | } 218 | 219 | function inferSource(object) { 220 | return typeof object === "function" ? Just(object.toString()) 221 | : /* otherwise */ Nothing(); 222 | } 223 | 224 | function inferSourceWithoutWrapper(meta, f, babelOptions) { 225 | function getBody(node) { 226 | return Array.isArray(node) ? node 227 | : node.type === 'BlockStatement' ? node.body 228 | : /* otherwise */ (_ => { throw new Error("Invalid example.") }); 229 | } 230 | 231 | const maybeSource = meta.forObject(f).get('source'); 232 | if (maybeSource.isJust) { 233 | return maybeSource; 234 | } 235 | 236 | return inferSource(f).chain(source => { 237 | let ast; 238 | try { 239 | ast = parseJs(`(${source})`, babelOptions || {}).program.body[0]; 240 | } catch (e) { 241 | return Nothing(); 242 | } 243 | if (!ast) { 244 | return Nothing(); 245 | } else if (ast.type === 'ExpressionStatement' && ast.expression.type === 'FunctionExpression') { 246 | return Just(generateJs(t.program(getBody(ast.expression.body))).code); 247 | } else { 248 | return Nothing(); 249 | } 250 | }); 251 | } 252 | 253 | function source(meta) { 254 | return meta.get('source').orElse(_ => inferSource(meta.object())).map(code => Block([ 255 | Title('Source', 2), 256 | Directive('code-block', 'javascript', null, Text(code)) 257 | ])).getOrElse(null); 258 | } 259 | 260 | function merge(objects) { 261 | return Object.assign({}, ...objects); 262 | } 263 | 264 | function toParameters(prefix, object) { 265 | let result = {}; 266 | 267 | keys(object).forEach(key => { 268 | result[`${prefix} ${key}`] = object[key]; 269 | }); 270 | 271 | return object; 272 | } 273 | 274 | async function renderMember(options, meta, { key, name, kind, property, value }) { 275 | const doc = meta.get('documentation').getOrElse(null); 276 | const skip = options.skipDetailedPage || new Set(); 277 | const prefix = options.linkPrefix || ''; 278 | 279 | return Block([ 280 | Text('.. rst-class:: hidden-heading'), 281 | Text(''), 282 | Title(name, 4), 283 | Directive( 284 | 'py:currentmodule', 285 | meta.get('module').getOrElse('(Global)') 286 | ), 287 | Directive( 288 | `py:${kind}`, 289 | name, 290 | null, 291 | Block(compact([ 292 | kind === 'function' ? Options(merge(compact([ 293 | meta.get('throws').map(v => toParameters('throws', v)).getOrElse(null), 294 | meta.get('parameters').map(v => toParameters('param', v)).getOrElse(null), 295 | meta.get('returns').map(v => ({ 'returns': v })).getOrElse(null) 296 | ]))) 297 | : /* else */ null, 298 | meta.get('type').map(renderType).getOrElse(null), 299 | skip.has(name) ? Text(doc ? (await markdownToRST(replaceMarkdownHeading(doc), 5)) : '') 300 | : /* else */ Block([ 301 | Text(doc ? (await markdownToRST(synopsis(doc))) : ''), 302 | link('+', `${prefix}${key}`), 303 | Directive( 304 | 'toctree', 305 | '', 306 | Options({ 'hidden': '' }), 307 | Block([Text(`${prefix}${key}`)]) 308 | ) 309 | ]) 310 | ])) 311 | ) 312 | ]); 313 | } 314 | 315 | function typeOf(value) { 316 | return typeof value === 'string' ? 'String' 317 | : typeof value === 'number' ? 'Number' 318 | : typeof value === 'boolean' ? 'Boolean' 319 | : typeof value === 'symbol' ? 'Symbol' 320 | : value === null ? 'Null' 321 | : value === undefined ? 'Undefined' 322 | : /* otherwise */ 'Unknown'; 323 | } 324 | 325 | function intoObject(value) { 326 | return isObject(value) ? value 327 | : /* otherwise */ { 328 | [Symbol.for('@@meta:magical')]: { 329 | type: typeOf(value), 330 | } 331 | }; 332 | } 333 | 334 | async function properties(meta, options, prefix) { 335 | const props = groupedProperties(options, meta, meta.object()); 336 | 337 | if (props.length === 0) { 338 | return await inheritedProperties(meta, options); 339 | } else { 340 | return Block([ 341 | Title(`${prefix || 'Properties in'} ${meta.get('name').getOrElse('(Anonymous)')}`, 2), 342 | Block(await Promise.all(props.map(async ({ category, members }) => Block([ 343 | Title(category || 'Uncategorised', 3), 344 | Block(await Promise.all(members.map(m => renderMember(options, meta.forObject(intoObject(m.value)), m)))) 345 | ])))), 346 | await inheritedProperties(meta, options) 347 | ]); 348 | } 349 | } 350 | 351 | async function inheritedProperties(meta, options) { 352 | const skip = options.excludePrototypes || new Set(); 353 | const parent = prototypeOf(meta.object()); 354 | 355 | if (parent == null || skip.has(parent)) { 356 | return Block([]); 357 | } else { 358 | return await properties(meta.forObject(parent), options, 'Properties inherited from'); 359 | } 360 | } 361 | 362 | function renderCode(code) { 363 | return Directive('code-block', 'javascript', null, Text(code)); 364 | } 365 | 366 | function getProperFunction(example) { 367 | return typeof example === 'function' ? example 368 | : /* otherwise */ example.call; 369 | } 370 | 371 | function examples(meta, babelOptions) { 372 | const xs = groupBy(meta.get('examples').getOrElse([]).filter(x => !x.inferred), x => x.name); 373 | const unamed = flatten(xs.filter(([name, _]) => !name).map(([_, v]) => v)); 374 | const named = xs.filter(([name, _]) => name); 375 | 376 | if (xs.length === 0) { 377 | return null; 378 | } else { 379 | return Block([ 380 | Title('Examples', 2), 381 | Block(compact( 382 | unamed.map(f => inferSourceWithoutWrapper(meta, getProperFunction(f), babelOptions) 383 | .map(renderCode) 384 | .getOrElse(null)) 385 | )), 386 | Block(compact( 387 | named.map(([category, examples]) => { 388 | const rendered = compact(examples.map(f => inferSourceWithoutWrapper(meta, getProperFunction(f), babelOptions) 389 | .map(renderCode) 390 | .getOrElse(null))); 391 | 392 | if (rendered.length === 0) { 393 | return null; 394 | } else { 395 | return Block([ 396 | Title(category, 3), 397 | Block(rendered) 398 | ]); 399 | } 400 | }) 401 | )) 402 | ]); 403 | } 404 | } 405 | 406 | async function renderToSphinx(meta, options, babelOptions) { 407 | return Block(compact([ 408 | Title(meta.get('name').getOrElse('(Anonymous)')), 409 | meta.get('deprecated').map(renderDeprecation).getOrElse(null), 410 | Options(maybeMeta(meta)), 411 | Title(name(meta), 1), 412 | Options(compactObject({ 413 | 'Complexity': meta.get('complexity'), 414 | 'Throws': meta.get('throws').map(x => Options(x).render()) 415 | })), 416 | Text(''), 417 | meta.get('type').map(renderType).getOrElse(null), 418 | Text(await markdownToRST(meta.get('documentation').getOrElse('(No documentation)')), 2), 419 | source(meta), 420 | await properties(meta, options), 421 | examples(meta, babelOptions) 422 | ])); 423 | } 424 | 425 | 426 | // -- IMPLEMENTATION --------------------------------------------------- 427 | module.exports = function(meta, babelOptions) { 428 | 429 | async function generateOne(object, options) { 430 | const m = meta.forObject(object); 431 | return { 432 | name: m.get('name').getOrElse('(Anonymous)'), 433 | content: (await renderToSphinx(m, options, babelOptions)).render() 434 | }; 435 | } 436 | 437 | 438 | // --- EXPORTS ------------------------------------------------------- 439 | return { generateOne }; 440 | }; 441 | -------------------------------------------------------------------------------- /packages/repl/src/browser.js: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------- 2 | // 3 | // This source file is part of the Meta:Magical project. 4 | // 5 | // See LICENCE for licence information. 6 | // See CONTRIBUTORS for the list of contributors to the project. 7 | // 8 | //--------------------------------------------------------------------- 9 | 10 | // --[ Dependencies ]-------------------------------------------------- 11 | const Refinable = require('refinable'); 12 | const marked = require('marked'); 13 | const TerminalDisplay = require('./terminal-display'); 14 | const { 15 | Nil, Text, 16 | Sequence, Block, Title, Subtitle, Quote, Code, List, Table, HorizontalLine, 17 | Strong, Emphasis, Good, Error, Warning, Detail, Label, 18 | Markdown 19 | } = require('./ast'); 20 | 21 | 22 | // --[ Helpers ]------------------------------------------------------- 23 | function isNil(x) { 24 | return x && Nil.hasInstance(x); 25 | } 26 | 27 | function startsWith(prefix, text) { 28 | return text.indexOf(prefix) === 0; 29 | } 30 | 31 | function collapseNils(xs) { 32 | return xs.reduce((l, r) => { 33 | if (isNil(l[l.length - 1]) && isNil(r)) { 34 | return l; 35 | } else { 36 | return l.concat([r]); 37 | } 38 | }, []); 39 | } 40 | 41 | function ellipsis(max, text) { 42 | const x = text.trim(); 43 | 44 | return x.length >= max ? `${x.slice(0, max - 1)}…` 45 | : /* else */ x 46 | } 47 | 48 | function definitionList(object) { 49 | return List(Object.keys(object).map(key => 50 | Sequence([ 51 | Label(Text(key)), 52 | Text(': '), 53 | Text(object[key]) 54 | ]) 55 | )); 56 | } 57 | 58 | function deprecated({ since, reason }) { 59 | return Quote(Block(0, [ 60 | Error(Strong(Text(`Deprecated since ${since}`))), 61 | Nil(), 62 | Text(reason) 63 | ])); 64 | } 65 | 66 | 67 | // --[ The Browser ]--------------------------------------------------- 68 | /*~ 69 | * The Meta:Magical browser allows inspecting annotated objects and 70 | * learning how to use them. 71 | * 72 | * The Browser is parameterised on an Interface, and a Display. The 73 | * Interface tells the Browser how to access metadata, whereas the 74 | * Display tells the Browser how to show that metadata. 75 | * 76 | * See the `State & Configuration` section for more details. 77 | * 78 | * --- 79 | * isModule : true 80 | * stability : experimental 81 | * category : Browsing Metadata 82 | */ 83 | const Browser = Refinable.refine({ 84 | 85 | // ---[ State & Configuration ]-------------------------------------- 86 | /*~ 87 | * The metadata that should be displayed by this browser. 88 | * 89 | * This is an instance of `metamagical-interface`'s Interface, pointing 90 | * to the object that this browser should display. For example, in order 91 | * to see the summary of this Browser, one could refine it in the 92 | * following way: 93 | * 94 | * const Interface = require('metamagical-interface'); 95 | * Browser.refine({ metadata: Interface.for(Browser) }).summary() 96 | * 97 | * It's better to use the `.for(Interface)` method of the Browser to 98 | * refine it than refine it directly, however: 99 | * 100 | * Browser.for(Interface.for(Browser)).summary() 101 | * 102 | * --- 103 | * isRequired : true 104 | * category : State & Configuration 105 | * stability : experimental 106 | * type: | 107 | * Interface 108 | */ 109 | get metadata() { 110 | throw new Error('Unimplemented.'); 111 | }, 112 | 113 | /*~ 114 | * How the metadata should be presented to the user. 115 | * 116 | * By default this Browser uses a Terminal display, which shows the 117 | * data with some formatting using terminal escape characters. 118 | * 119 | * Any other display may be provided, however, and this can be used 120 | * for generating static documentation, for example, by providing a 121 | * display that computes HTML or Markdown, instead of outputing it 122 | * to the screen. 123 | * 124 | * A display is a type that has a single method, `show(tree)`, which 125 | * takes a tree from a Browser AST, and returns the result of showing 126 | * that tree. In essence, Displays must fulfil the following interface: 127 | * 128 | * type Display = { 129 | * show : (BrowserAST) => Any 130 | * } 131 | * 132 | * Since the Terminal display just outputs it on the terminal, it just 133 | * returns undefined. The Browser itself does nothing with the result, 134 | * it only returns it. So your Display could also return richer data 135 | * than a String, if it makes post-processing easier. 136 | * 137 | * In order to provide a Display to the browser, it has to be refined: 138 | * 139 | * const HTMLBrowser = Browser.refine({ display: HTMLDisplay }); 140 | * 141 | * --- 142 | * category : State & Configuration 143 | * stability : experimental 144 | * type: | 145 | * { show : (BrowserAST) => Any } 146 | */ 147 | get display() { 148 | return TerminalDisplay; 149 | }, 150 | 151 | 152 | // ---[ Navigating in the browser ]---------------------------------- 153 | /*~ 154 | * Changes the Interface for metadata this browser is showing. 155 | * 156 | * This should be an instane of `metamagical-interface`'s Interface 157 | * pointing to the object this browser should display. See `metadata` 158 | * for more information. 159 | * 160 | * Example: 161 | * 162 | * const Interface = require('metamagical-interface'); 163 | * const browserBrowser = Browser.for(Interface.for(Browser)); 164 | * 165 | * --- 166 | * category : Navigating in the browser 167 | * stability : experimental 168 | * type: | 169 | * Browser.(Interface) => Browser 170 | */ 171 | for(meta) { 172 | return this.refine({ metadata: meta }); 173 | }, 174 | 175 | /*~ 176 | * Browses the object at the given property of the current object. 177 | * 178 | * Example: 179 | * 180 | * const forPropertyBrowser = Browser.forProperty("forProperty"); 181 | * 182 | * --- 183 | * category : Navigating in the browser 184 | * stability : experimental 185 | * 186 | * throws: 187 | * Error: when the property doesn't exist in the object. 188 | * 189 | * type: | 190 | * Browser.(String) => Browser :: (throws Error) 191 | */ 192 | forProperty(name) { 193 | return this.metadata.property(name).map(x => this.for(x)) 194 | .orElse(_ => { throw new Error(`No property for ${name}`) }) 195 | .get(); 196 | }, 197 | 198 | /*~ 199 | * Browses the getter with the given name in the current object. 200 | * 201 | * Example: 202 | * 203 | * const newBrowser = Browser.forGetter("metadata"); 204 | * 205 | * --- 206 | * category : Navigating in the browser 207 | * stability : experimental 208 | * 209 | * throws: 210 | * Error: when the getter doesn't exist in the object. 211 | * 212 | * type: | 213 | * Browser.(String) => Browser :: (throws Error) 214 | */ 215 | forGetter(name) { 216 | return this.metadata.getter(name).map(x => this.for(x)) 217 | .orElse(_ => { throw new Error(`No getter for ${name}`) }) 218 | .get(); 219 | }, 220 | 221 | /*~ 222 | * Browses the setter with the given name in the current object. 223 | * 224 | * Example: 225 | * 226 | * let foo = { 227 | * _x: 0, 228 | * set x(value) { this._x = value } 229 | * }; 230 | * const newBrowser = Browser.for(Interface.for(foo)) 231 | * .forSetter("x"); 232 | * 233 | * --- 234 | * category : Navigating in the browser 235 | * stability : experimental 236 | * 237 | * throws: 238 | * Error: when the setter doesn't exist in the object. 239 | * 240 | * type: | 241 | * Browser.(String) => Browser :: (throws Error) 242 | */ 243 | forSetter(name) { 244 | return this.metadata.setter(name).map(x => this.for(x)) 245 | .orElse(_ => { throw new Error(`No setter for ${name}`) }) 246 | .get(); 247 | }, 248 | 249 | /*~ 250 | * Browses the prototype of the current object. 251 | * 252 | * Example: 253 | * 254 | * const newBrowser = Browser.forPrototype(); 255 | * 256 | * --- 257 | * category : Navigating in the browser 258 | * stability : experimental 259 | * 260 | * throws: 261 | * Error: when the object doesn't have a `[[Prototype]]` 262 | * 263 | * type: | 264 | * Browser.() => Browser :: (throws Error) 265 | */ 266 | forPrototype() { 267 | return this.metadata.prototype().map(x => this.for(x)) 268 | .orElse(_ => { throw new Error('No prototype') }) 269 | .get(); 270 | }, 271 | 272 | // ---[ Auxiliary operations ]--------------------------------------- 273 | getMetadata(name) { 274 | return this.metadata.get(this.metadata.fields[name]); 275 | }, 276 | 277 | getSignature() { 278 | return this.getMetadata('signature') 279 | .orElse(_ => this.getMetadata('name')) 280 | .getOrElse('(Anonymous)'); 281 | }, 282 | 283 | _section(title, content) { 284 | return Block(0, [ 285 | Title(1, title), 286 | Nil(), 287 | content 288 | ]); 289 | }, 290 | 291 | _hierarchy() { 292 | const hierarchy = this.metadata.hierarchy(); 293 | const parents = this.metadata.parents(); 294 | 295 | if (parents.length > 0) { 296 | return Sequence([ 297 | Label(Text('Hierarchy:')), 298 | Text(' '), 299 | Emphasis(Text(`(${parents.length} parent${parents.length > 0 ? 's' : ''})`)), 300 | Text(' '), 301 | Text(hierarchy.map(([name, _]) => name).join(' → ')) 302 | ]); 303 | } else { 304 | return Nil(); 305 | } 306 | }, 307 | 308 | _synopsis() { 309 | return this.getMetadata('documentation').map(doc => { 310 | const maybeParagraph = marked.lexer(doc)[0]; 311 | if (maybeParagraph && maybeParagraph.type === 'paragraph') { 312 | return maybeParagraph.text; 313 | } else { 314 | return '(No synopsis)'; 315 | } 316 | }); 317 | }, 318 | 319 | _partialDocs() { 320 | return this.getMetadata('documentation').map(doc => { 321 | const lines = doc.split(/\r\n|\n\r|\r|\n/).reduce(({ items, take }, line) => { 322 | if (!take) { 323 | return { items, take }; 324 | } else { 325 | if (/^#+\s(?!example:?:?)/i.test(line)) { 326 | return { items, take: false }; 327 | } else { 328 | return { items: [...items, line], take }; 329 | } 330 | } 331 | }, { items: [], take: true }).items; 332 | 333 | if (lines.length > 0) { 334 | return lines.join('\n'); 335 | } else { 336 | return '(No synopsis)'; 337 | } 338 | }); 339 | }, 340 | 341 | _withSignature(prefix) { 342 | return Sequence([ 343 | Text(prefix), 344 | Text(' '), 345 | Text(this.getSignature()) 346 | ]); 347 | }, 348 | 349 | _maybeDefinition(label, content) { 350 | return content.cata({ 351 | Nothing: _ => Nil(), 352 | Just: v => Sequence([ 353 | Label(Text(label)), 354 | Text(' '), 355 | v 356 | ]) 357 | }); 358 | }, 359 | 360 | _stabilityFormat: { 361 | deprecated: Error, 362 | experimental: Warning, 363 | stable: Good, 364 | locked: Good 365 | }, 366 | 367 | _location() { 368 | return this.getMetadata('location').map(loc => { 369 | return Text([ 370 | loc.filename ? loc.filename : '', 371 | loc.start ? `at line ${loc.start.line}, column ${loc.start.column}` : null 372 | ].filter(x => x !== null).join(' ')); 373 | }); 374 | }, 375 | 376 | _stabilitySummary() { 377 | return this.getMetadata('stability').map(s => { 378 | const format = this._stabilityFormat[s]; 379 | const stability = this.metadata.Stability.fromIdentifier(s); 380 | 381 | return format(Text(`${stability.index} - ${stability.name}`)); 382 | }); 383 | }, 384 | 385 | _executionMeta() { 386 | return Block(0, [ 387 | this._maybeDefinition('Stability:', this._stabilitySummary()), 388 | this._maybeDefinition('Platforms:', this.getMetadata('platforms').map(xs => List(xs.map(Text)))), 389 | this._maybeDefinition('Complexity:', this.getMetadata('complexity').map(Text)), 390 | this._maybeDefinition('Returns:', this.getMetadata('returns').map(Text)), 391 | this._maybeDefinition('Throws:', this.getMetadata('throws').map(definitionList)), 392 | this._maybeDefinition('Parameters:', this.getMetadata('parameters').map(definitionList)) 393 | ].filter(x => !isNil(x))); 394 | }, 395 | 396 | _generalMetadata() { 397 | return Block(0, [ 398 | this._maybeDefinition('Available since:', this.getMetadata('since').map(Text)), 399 | this._maybeDefinition('From:', this._location()), 400 | this._maybeDefinition('In package:', this.getMetadata('npmPackage').map(Text)), 401 | this._maybeDefinition('In module:', this.getMetadata('module').map(Text)), 402 | this._maybeDefinition('In object:', this.getMetadata('belongsTo').map(f => 403 | Text(this.for(this.metadata.for(f())).getSignature()) 404 | )), 405 | Text(''), 406 | this._maybeDefinition('Portability:', this.getMetadata('portable').map(v => 407 | Text(v ? 'portable' : 'not portable') 408 | )), 409 | this._maybeDefinition('Copyright:', this.getMetadata('copyright').map(Text)), 410 | this._maybeDefinition('Licence:', this.getMetadata('licence').map(Text)), 411 | this._maybeDefinition('Repository:', this.getMetadata('repository').map(Text)), 412 | this._maybeDefinition('Web Site:', this.getMetadata('homepage').map(Text)), 413 | Text(''), 414 | this._maybeDefinition('Authors:', this.getMetadata('authors').map(xs => List(xs.map(Text)))), 415 | this._maybeDefinition('Maintainers:', this.getMetadata('maintainers').map(xs => List(xs.map(Text)))), 416 | ].filter(x => !isNil(x))); 417 | }, 418 | 419 | _renderProperties(level, properties) { 420 | return Block(0, 421 | properties.map(({ category, members }) => { 422 | return Block(0, [ 423 | Nil(), 424 | Title(level, Text(category)), 425 | List(members.map(member => { 426 | const meta = this.metadata.for(member.value); 427 | const suffix = meta.getByName('isRequired').map(v => 428 | true ? Error(Text(' (required, but missing)')) : Nil() 429 | ).getOrElse(Nil()); 430 | const signature = meta.get(meta.fields.signature).getOrElse(member.name) 431 | .replace(/^(get|set) /, ''); 432 | const modifier = member.kind === 'getter' ? 'get ' 433 | : member.kind === 'setter' ? 'set ' 434 | : /* otherwise */ ''; 435 | const prefix = startsWith(member.name, signature) ? Nil() : Text(`${member.name}: `); 436 | const synopsis = ellipsis(120, this.for(meta)._synopsis().getOrElse('(No documentation)')); 437 | 438 | return Block(0, [ 439 | Sequence([Text(modifier), Strong(prefix), Strong(Text(signature)), suffix]), 440 | Detail(Sequence([Text(' | '), Text(synopsis)])), 441 | Nil() 442 | ]); 443 | })) 444 | ]); 445 | }) 446 | ); 447 | }, 448 | 449 | 450 | 451 | // ---[ Inspecting metadata ]---------------------------------------- 452 | 453 | /*~ 454 | * Shows the full documentation of the current object. 455 | * 456 | * --- 457 | * category : Inspecting metadata 458 | * stability : experimental 459 | * type: | 460 | * Browser.() => Any 461 | */ 462 | documentation() { 463 | return this.display.show( 464 | this._section( 465 | this._withSignature('Documentation for'), 466 | Markdown( 467 | this.getMetadata('documentation').getOrElse('(No documentation)') 468 | ) 469 | ) 470 | ); 471 | }, 472 | 473 | /*~ 474 | * Shows the source code of the current object. 475 | * 476 | * --- 477 | * category : Inspecting metadata 478 | * stability : experimental 479 | * type: | 480 | * Browser.() => Any 481 | */ 482 | source() { 483 | return this.getMetadata('source').map(source => 484 | this.display.show( 485 | this._section( 486 | this._withSignature('Source for'), 487 | Block(0, [ 488 | ...[ 489 | this._maybeDefinition('From:', this._location()), 490 | this._maybeDefinition('NPM package:', this.getMetadata('npmPackage').map(Text)), 491 | this._maybeDefinition('In module:', this.getMetadata('module').map(Text)), 492 | this._maybeDefinition('In object:', this.getMetadata('belongsTo').map(f => 493 | Text(this.for(this.metadata.for(f())).getSignature()) 494 | )) 495 | ].filter(v => !Nil.hasInstance(v)), 496 | Nil(), 497 | Code('javascript', source) 498 | ]) 499 | ) 500 | ) 501 | ).getOrElse(undefined); 502 | }, 503 | 504 | /*~ 505 | * Shows the stability of the current object. 506 | * 507 | * --- 508 | * category : Inspecting metadata 509 | * stability : experimental 510 | * type: | 511 | * Browser.() => Any 512 | */ 513 | stability() { 514 | return this.getMetadata('stability').map(id => { 515 | const stability = this.metadata.Stability.fromIdentifier(id); 516 | const format = this._stabilityFormat[id]; 517 | 518 | return this.display.show( 519 | this._section( 520 | this._withSignature('Stability for'), 521 | Block(0, [ 522 | Strong(format(Text(`${stability.index} - ${stability.name}`))), 523 | Nil(), 524 | Markdown(stability.description) 525 | ]) 526 | ) 527 | ); 528 | }).getOrElse(undefined); 529 | }, 530 | 531 | /*~ 532 | * Shows a summary of the current object, with signature, synopsis, and 533 | * properties. 534 | * 535 | * --- 536 | * category : Inspecting metadata 537 | * stability : experimental 538 | * type: | 539 | * Browser.() => Any 540 | */ 541 | summary() { 542 | return this.display.show( 543 | this._section( 544 | Text(this.getSignature()), 545 | Block(0, collapseNils([ 546 | this.getMetadata('type').map(v => Code('mli', v)).getOrElse(null), 547 | Nil(), 548 | this.getMetadata('deprecated').map(deprecated).getOrElse(null), 549 | Nil(), 550 | this._hierarchy(), 551 | this._executionMeta(), 552 | Nil(), 553 | this._partialDocs().map(Markdown).getOrElse(null), 554 | Nil(), 555 | Quote(Text('(run `.documentation()` for the full docs)')), 556 | Nil(), 557 | Block(0, [ 558 | Title(2, Text('Properties')), 559 | this._renderProperties(3, this.metadata.properties()) 560 | ]), 561 | ].filter(x => x !== null))) 562 | ) 563 | ); 564 | }, 565 | 566 | 567 | /*~ 568 | * Shows all properties that are accessible from the object. 569 | * 570 | * --- 571 | * category : Inspecting metadata 572 | * stability : experimental 573 | * type: | 574 | * Browser.() => Any 575 | */ 576 | properties() { 577 | return this.display.show( 578 | this._section( 579 | Text(`Properties accessible from ${this.getSignature()}`), 580 | this._renderProperties(2, this.metadata.allProperties()) 581 | ) 582 | ); 583 | } 584 | 585 | }); 586 | 587 | 588 | // --[ Exports ]------------------------------------------------------- 589 | module.exports = Browser; 590 | 591 | --------------------------------------------------------------------------------