initial website
40
website/.github/settings.yml
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
repository:
|
||||
has_wiki: false
|
||||
|
||||
labels:
|
||||
- name: "duplicate"
|
||||
color: cfd3d7
|
||||
- name: "good first issue"
|
||||
color: 7057ff
|
||||
- name: "invalid"
|
||||
color: cfd3d7
|
||||
- name: "more data needed"
|
||||
color: bfdadc
|
||||
- name: "P0"
|
||||
color: b60205
|
||||
description: "blocker: fix immediately!"
|
||||
- name: "P1"
|
||||
color: d93f0b
|
||||
description: "critical: next release"
|
||||
- name: "P2"
|
||||
color: e99695
|
||||
description: "major: an upcoming release"
|
||||
- name: "P3"
|
||||
color: fbca04
|
||||
description: "minor: not priorized"
|
||||
- name: "P4"
|
||||
color: fef2c0
|
||||
description: "unimportant: consider wontfix or other priority"
|
||||
- name: "question"
|
||||
color: d876e3
|
||||
- name: "type: bug"
|
||||
color: 0052cc
|
||||
- name: "type: documentation"
|
||||
color: 0052cc
|
||||
- name: "type: feature request"
|
||||
color: 0052cc
|
||||
- name: "wontfix"
|
||||
color: ffffff
|
||||
- name: "merge-queue"
|
||||
color: 0e8a16
|
||||
description: "merge on green CI"
|
4
website/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
node_modules/
|
||||
.cache/
|
||||
public
|
||||
.idea
|
21
website/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) Tweag Holding and its affiliates.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
4
website/README.md
Normal file
@ -0,0 +1,4 @@
|
||||
# Nickel-lang.org website
|
||||
|
||||
This repository contains the content of the Nickel language website. This is
|
||||
WIP.
|
5
website/gatsby-browser.js
Normal file
@ -0,0 +1,5 @@
|
||||
import "./src/styles/custom.scss";
|
||||
import "jquery/dist/jquery.min.js";
|
||||
import "@popperjs/core/dist/umd/popper.min.js";
|
||||
import "bootstrap/dist/js/bootstrap.min.js";
|
||||
import "./src/styles/global.css";
|
89
website/gatsby-config.js
Normal file
@ -0,0 +1,89 @@
|
||||
module.exports = {
|
||||
siteMetadata: {
|
||||
title: "Nickel",
|
||||
menuLinks: [
|
||||
{
|
||||
name: 'Getting started',
|
||||
link: '/getting-started'
|
||||
},
|
||||
// Disabling the documentation page for now.
|
||||
// There is just not enough interesting content to show.
|
||||
/*{
|
||||
name: 'Documentation',
|
||||
link: '/documentation'
|
||||
},*/
|
||||
{
|
||||
name: 'Playground',
|
||||
link: '/playground'
|
||||
},
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
'gatsby-plugin-react-helmet',
|
||||
'gatsby-plugin-sharp',
|
||||
'gatsby-transformer-sharp',
|
||||
'gatsby-plugin-image',
|
||||
{
|
||||
resolve: `gatsby-source-filesystem`,
|
||||
options: {
|
||||
name: `markdown-pages`,
|
||||
path: `${__dirname}/src/markdown-pages`,
|
||||
},
|
||||
},
|
||||
`gatsby-plugin-sass`,
|
||||
{
|
||||
resolve: 'gatsby-transformer-remark',
|
||||
options: {
|
||||
plugins: [
|
||||
{
|
||||
resolve: `gatsby-remark-prismjs`,
|
||||
options: {
|
||||
// Class prefix for <pre> tags containing syntax highlighting;
|
||||
// defaults to 'language-' (e.g. <pre class="language-js">).
|
||||
// If your site loads Prism into the browser at runtime,
|
||||
// (e.g. for use with libraries like react-live),
|
||||
// you may use this to prevent Prism from re-processing syntax.
|
||||
// This is an uncommon use-case though;
|
||||
// If you're unsure, it's best to use the default value.
|
||||
classPrefix: "language-",
|
||||
// This is used to allow setting a language for inline code
|
||||
// (i.e. single backticks) by creating a separator.
|
||||
// This separator is a string and will do no white-space
|
||||
// stripping.
|
||||
// A suggested value for English speakers is the non-ascii
|
||||
// character '›'.
|
||||
inlineCodeMarker: null,
|
||||
// Customize the prompt used in shell output
|
||||
// Values below are default
|
||||
prompt: {
|
||||
user: "devops",
|
||||
host: "nickel-lang",
|
||||
global: false,
|
||||
},
|
||||
// By default the HTML entities <>&'" are escaped.
|
||||
// Add additional HTML escapes by providing a mapping
|
||||
// of HTML entities and their escape value IE: { '}': '{' }
|
||||
escapeEntities: {},
|
||||
languageExtensions: [
|
||||
{
|
||||
language: "nickel",
|
||||
definition: {
|
||||
comment: /\/\/.+/,
|
||||
let: /let/,
|
||||
in: /in/,
|
||||
fun: /fun/,
|
||||
switch: /switch/,
|
||||
numLiteral: /[0-9]*\.?[0-9]+/,
|
||||
baseTypes: /((?:Num)|(?:Bool)|(?:Str)|(?:List:(?:[a-zA-Z0-9_]*)?))/,
|
||||
stringSimple: /".+"/,
|
||||
stringMulti: /m(#)+".+"\1m/,
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
8
website/gatsby-node.js
Normal file
@ -0,0 +1,8 @@
|
||||
exports.onCreateWebpackConfig = ({ stage, actions }) => {
|
||||
actions.setWebpackConfig({
|
||||
experiments: {
|
||||
// This was necessary to have the Nickel WASM REPL work with Webpack
|
||||
syncWebAssembly: true,
|
||||
},
|
||||
})
|
||||
};
|
49305
website/package-lock.json
generated
Normal file
47
website/package.json
Normal file
@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "nickel-lang.org",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "nickel-lang.org",
|
||||
"author": "Yann Hamdaoui",
|
||||
"keywords": [
|
||||
"gatsby"
|
||||
],
|
||||
"scripts": {
|
||||
"develop": "gatsby develop",
|
||||
"start": "gatsby develop",
|
||||
"build": "gatsby build",
|
||||
"serve": "gatsby serve",
|
||||
"clean": "gatsby clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.35",
|
||||
"@fortawesome/free-brands-svg-icons": "^5.15.3",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.15.3",
|
||||
"@fortawesome/react-fontawesome": "^0.1.14",
|
||||
"@popperjs/core": "^2.9.2",
|
||||
"ace-builds": "^1.4.12",
|
||||
"ansi-to-react": "^6.1.5",
|
||||
"asciinema-player": "^2.6.1",
|
||||
"bootstrap": "^4.6.0",
|
||||
"create-react-class": "^15.7.0",
|
||||
"gatsby": "^3.2.1",
|
||||
"gatsby-plugin-fontawesome-css": "^1.1.0",
|
||||
"gatsby-plugin-image": "^1.3.0",
|
||||
"gatsby-plugin-react-helmet": "^4.3.0",
|
||||
"gatsby-plugin-sass": "^4.8.0",
|
||||
"gatsby-plugin-sharp": "^3.3.0",
|
||||
"gatsby-remark-prismjs": "^5.0.0",
|
||||
"gatsby-source-filesystem": "^3.3.0",
|
||||
"gatsby-transformer-remark": "^4.0.0",
|
||||
"gatsby-transformer-sharp": "^3.3.0",
|
||||
"jquery": "^3.6.0",
|
||||
"prismjs": "^1.23.0",
|
||||
"react": "^17.0.1",
|
||||
"react-ace": "^9.4.0",
|
||||
"react-bootstrap": "^1.5.2",
|
||||
"react-bootstrap-icons": "^1.5.0",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-helmet": "^6.1.0"
|
||||
}
|
||||
}
|
159
website/src/ace-nickel-mode/ace-nickel-mode.js
Normal file
@ -0,0 +1,159 @@
|
||||
// import * as ace from 'ace-builds/src-min-noconflict/ace.js'
|
||||
|
||||
import ace from "ace-builds/src-noconflict/ace";
|
||||
|
||||
ace.define('ace/mode/nickel_highlight_rules', ['require', 'exports', 'ace/lib/oop', 'ace/mode/text_highlight_rules'], function (_require, exports, _module) {
|
||||
const oop = ace.require("ace/lib/oop");
|
||||
const TextHighlightRules = ace.require("ace/mode/text_highlight_rules").TextHighlightRules;
|
||||
|
||||
const NickelHighlightRules = function () {
|
||||
const constantLanguage = "true|false|null";
|
||||
const keywordControl = "switch|import|if|else|then";
|
||||
const keywordDeclaration = "let|in";
|
||||
const keywordMetavalue = "doc|default";
|
||||
|
||||
const keywordMapper = this.createKeywordMapper({
|
||||
"constant.language.nickel": constantLanguage,
|
||||
"keyword.control.nickel": keywordControl,
|
||||
"keyword.declaration.nickel": keywordDeclaration,
|
||||
'keyword.metavalue.nickel': keywordMetavalue,
|
||||
}, "identifier");
|
||||
|
||||
// Although Ace supports modal lexing (the next, push and pop rules allow to
|
||||
// maintain a state and a stack), we can't encode nickel
|
||||
// constiable-length delimiter directly with one nice generic rule.
|
||||
//
|
||||
// We thus generate a rule for lengths 1, 2 and 3 (m#", m##", and m###")
|
||||
// plus write a generic rule for size n. The generic rule is wrong for
|
||||
// length 5 and above, but this is highly unlikely to be used in
|
||||
// practice.
|
||||
|
||||
// Generate the starting rule of a string with constiable-length
|
||||
// delimiters
|
||||
let genQqdoc = length => ({
|
||||
token: "string",
|
||||
regex: `m${'#'.repeat(length)}\"`,
|
||||
next: `qqdoc${length}`,
|
||||
});
|
||||
|
||||
// Generate the escape and end rules of a string with constiable-length delimiters
|
||||
let genQqdocState = length => ({
|
||||
[`qqdoc${length}`]: [
|
||||
{
|
||||
token: "constant.language.escape",
|
||||
regex: `${'#'.repeat(length)}{`,
|
||||
push: "start",
|
||||
}, {
|
||||
token: "string",
|
||||
regex: `"${'#'.repeat(length)}m`,
|
||||
next: "pop",
|
||||
}, {
|
||||
defaultToken: "string"
|
||||
}]
|
||||
});
|
||||
|
||||
this.$rules = {
|
||||
"start": [{
|
||||
token: "comment",
|
||||
regex: /\/\/.*$/
|
||||
}, {
|
||||
regex: "(==|!=|<=?|>=?)",
|
||||
token: ["keyword.operator.comparison.nickel"]
|
||||
}, {
|
||||
regex: "(\\+\\+|@)",
|
||||
token: ["keyword.operator.combinator.nickel"]
|
||||
}, {
|
||||
regex: "(#|->|:)",
|
||||
token: ["keyword.operator.type.nickel"]
|
||||
}, {
|
||||
regex: "=",
|
||||
token: "keyword.operator.assignment.nickel"
|
||||
},
|
||||
{
|
||||
token: "string",
|
||||
regex: "\"",
|
||||
next: "qqstring",
|
||||
},
|
||||
{
|
||||
token: "string",
|
||||
regex: "m(#{4,})\"",
|
||||
next: "qqdocn"
|
||||
},
|
||||
genQqdoc(1),
|
||||
genQqdoc(2),
|
||||
genQqdoc(3), {
|
||||
token: "constant.numeric", // hex
|
||||
regex: "0[xX][0-9a-fA-F]+\\b"
|
||||
}, {
|
||||
token: "constant.numeric", // float
|
||||
regex: "[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b"
|
||||
}, {
|
||||
token: keywordMapper,
|
||||
regex: "[a-zA-Z_$][a-zA-Z0-9_$]*\\b"
|
||||
}, {
|
||||
regex: "}",
|
||||
token: function (_val, _start, stack) {
|
||||
return stack[1] && stack[1].charAt(0) === "q" ? "constant.language.escape" : "text";
|
||||
},
|
||||
next: "pop"
|
||||
}],
|
||||
"qqdocn": [
|
||||
{
|
||||
token: "constant.language.escape",
|
||||
regex: "#{4,}{",
|
||||
push: "start"
|
||||
}, {
|
||||
token: "string",
|
||||
regex: "\"(#{4,})m",
|
||||
next: "pop"
|
||||
}, {
|
||||
defaultToken: "string"
|
||||
}],
|
||||
...genQqdocState(1),
|
||||
...genQqdocState(2),
|
||||
...genQqdocState(3),
|
||||
"qqstring": [
|
||||
{
|
||||
token: "constant.language.escape",
|
||||
regex: "#{",
|
||||
push: "start"
|
||||
}, {
|
||||
token: "string",
|
||||
regex: '"',
|
||||
next: "pop"
|
||||
}, {
|
||||
defaultToken: "string"
|
||||
}],
|
||||
};
|
||||
|
||||
this.normalizeRules();
|
||||
};
|
||||
|
||||
oop.inherits(NickelHighlightRules, TextHighlightRules);
|
||||
|
||||
exports.NickelHighlightRules = NickelHighlightRules;
|
||||
});
|
||||
|
||||
ace.define("ace/mode/nickel",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/nickel_highlight_rules","ace/mode/folding/cstyle"], function (_require, exports, _module) {
|
||||
const oop = ace.require("ace/lib/oop");
|
||||
const TextMode = ace.require("ace/mode/text").Mode;
|
||||
const NimHighlightRules = ace.require("ace/mode/nickel_highlight_rules").NickelHighlightRules;
|
||||
// const CStyleFoldMode = ace.require("ace/mode/folding/cstyle").FoldMode;
|
||||
|
||||
const Mode = function () {
|
||||
TextMode.call(this);
|
||||
this.HighlightRules = NimHighlightRules;
|
||||
// this.foldingRules = new CStyleFoldMode();
|
||||
this.$behaviour = this.$defaultBehaviour;
|
||||
};
|
||||
|
||||
oop.inherits(Mode, TextMode);
|
||||
|
||||
(function () {
|
||||
this.lineCommentStart = "//";
|
||||
this.$id = "ace/mode/nickel";
|
||||
}).call(Mode.prototype);
|
||||
|
||||
exports.Mode = Mode;
|
||||
|
||||
});
|
2563
website/src/asciinema-player/asciinema-player.css
Normal file
1213
website/src/asciinema-player/asciinema-player.js
Normal file
164
website/src/components/editor.js
Normal file
@ -0,0 +1,164 @@
|
||||
import * as React from 'react';
|
||||
import AceEditor from 'react-ace';
|
||||
import {nickelCodes, REPL_RUN_EVENT} from './repl'
|
||||
import {PLAYGROUND_SEND_EVENT} from "./playground";
|
||||
|
||||
import "ace-builds/src-noconflict/theme-solarized_dark";
|
||||
import "../ace-nickel-mode/ace-nickel-mode";
|
||||
import ReactDOMServer from "react-dom/server";
|
||||
|
||||
const EDITOR_SEND_EVENT = 'nickel-repl:send';
|
||||
|
||||
/**
|
||||
* Nickel code editor component, based on the Ace editor.
|
||||
*/
|
||||
export default class Editor extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const value = this.props.value ? this.props.value : `let data = {value = "Hello," ++ " world!"} in data.value`;
|
||||
|
||||
this.state = {
|
||||
value,
|
||||
placeholder: 'Write your code. Press Ctrl+Enter (Cmd+Enter) to run it',
|
||||
theme: "solarized_dark",
|
||||
mode: "nickel",
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
enableBasicAutocompletion: false,
|
||||
enableLiveAutocompletion: false,
|
||||
fontSize: 14,
|
||||
showGutter: true,
|
||||
showPrintMargin: true,
|
||||
highlightActiveLine: true,
|
||||
enableSnippets: false,
|
||||
showLineNumbers: true,
|
||||
annotations: [],
|
||||
};
|
||||
|
||||
if(this.props.fit && this.props.fit === 'code') {
|
||||
const lines = value.split(/\r?\n/g).length;
|
||||
this.state.maxLines = lines;
|
||||
this.state.minLines = lines;
|
||||
}
|
||||
else if(this.props.fit && this.props.fit === 'lines' && this.props.lines) {
|
||||
this.state.maxLines = this.props.lines;
|
||||
this.state.minLines = this.props.lines;
|
||||
}
|
||||
|
||||
this.onChange = this.onChange.bind(this);
|
||||
this.onREPLRun = this.onREPLRun.bind(this);
|
||||
this.send = this.send.bind(this);
|
||||
this.aceEditorRef = React.createRef();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// Listen to the REPL's execution events, in order to update potential error messages.
|
||||
document.addEventListener(REPL_RUN_EVENT, this.onREPLRun);
|
||||
document.addEventListener(PLAYGROUND_SEND_EVENT, this.send);
|
||||
}
|
||||
|
||||
onChange(newValue) {
|
||||
this.setState({
|
||||
value: newValue
|
||||
});
|
||||
}
|
||||
|
||||
getHeight() {
|
||||
return this.aceEditorRef.current.editor.getSession().getScreenLength() * this.aceEditorRef.current.editor.renderer.lineHeight + this.aceEditorRef.current.editor.renderer.scrollBarH.height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Static component displaying a Nickel diagnostic error.
|
||||
* @param diagnostic
|
||||
* @param label
|
||||
* @returns {*}
|
||||
*/
|
||||
annotationWidget(diagnostic, label) {
|
||||
const labelClass = label.style === nickelCodes.error.label.PRIMARY ? 'ansi-red-fg' : 'ansi-blue-fg';
|
||||
return (<div>
|
||||
<span className={"ansi-bright-red-fg"}>{diagnostic.msg}</span><br/>
|
||||
<span className={labelClass}>{label.msg}</span><br/>
|
||||
<ul>
|
||||
{diagnostic.notes.map(note => <li>{note}</li>)}
|
||||
</ul>
|
||||
</div>)
|
||||
}
|
||||
|
||||
/**
|
||||
* Once the REPL has run, update the error messages.
|
||||
* @param result
|
||||
*/
|
||||
onREPLRun({detail: result}) {
|
||||
if (result.tag === nickelCodes.result.ERROR) {
|
||||
const annotations = result.errors.filter(diagnostic => diagnostic.severity >= nickelCodes.error.severity.WARNING)
|
||||
.map(diagnostic => (
|
||||
diagnostic.labels.map(label => ({
|
||||
row: label.line_start,
|
||||
column: label.col_start,
|
||||
html: ReactDOMServer.renderToStaticMarkup(this.annotationWidget(diagnostic, label)),
|
||||
type: label.style === nickelCodes.error.label.PRIMARY ? 'error' : 'warning',
|
||||
}))
|
||||
)).flat();
|
||||
|
||||
// In some obscure circumstances (annotation on the last line, and then insertion of a new line), annotations disappear, even if the user send the same input again.
|
||||
// To avoid this and make annotations reappear at least when sending an input, we clear the old one first, to triggers reactive updates.
|
||||
this.setState({annotations: []}, () => this.setState({annotations}));
|
||||
} else {
|
||||
this.setState({annotations: []});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch an EDITOR_SEND_EVENT with the current content as a payload.
|
||||
*/
|
||||
send() {
|
||||
// Dispatch the result as an event, so that the editor or other components can react to the outcome of the last input
|
||||
const event = new CustomEvent(EDITOR_SEND_EVENT, {detail: this.state.value});
|
||||
document.dispatchEvent(event);
|
||||
}
|
||||
|
||||
render() {
|
||||
return <AceEditor
|
||||
ref={this.aceEditorRef}
|
||||
placeholder={this.state.placeholder}
|
||||
mode={this.state.mode}
|
||||
theme={this.state.theme}
|
||||
name={"nickel-repl-input"}
|
||||
height={this.state.height}
|
||||
width={this.state.width}
|
||||
minLines={this.state.minLines}
|
||||
maxLines={this.state.maxLines}
|
||||
onChange={this.onChange}
|
||||
onSelectionChange={this.onSelectionChange}
|
||||
onCursorChange={this.onCursorChange}
|
||||
onValidate={this.onValidate}
|
||||
value={this.state.value}
|
||||
annotations={this.state.annotations}
|
||||
fontSize={this.state.fontSize}
|
||||
showPrintMargin={this.state.showPrintMargin}
|
||||
showGutter={this.state.showGutter}
|
||||
highlightActiveLine={this.state.highlightActiveLine}
|
||||
commands={[
|
||||
{
|
||||
name: 'send-repl',
|
||||
bindKey: {
|
||||
win: 'Ctrl-enter',
|
||||
mac: 'Cmd-enter',
|
||||
},
|
||||
exec: this.send,
|
||||
},
|
||||
]}
|
||||
setOptions={{
|
||||
useWorker: false,
|
||||
enableBasicAutocompletion: this.state.enableBasicAutocompletion,
|
||||
enableLiveAutocompletion: this.state.enableLiveAutocompletion,
|
||||
enableSnippets: this.state.enableSnippets,
|
||||
showLineNumbers: this.state.showLineNumbers,
|
||||
tabSize: 2
|
||||
}}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
export {Editor, EDITOR_SEND_EVENT};
|
39
website/src/components/footer.js
Normal file
@ -0,0 +1,39 @@
|
||||
import React from "react";
|
||||
import { Link } from "gatsby";
|
||||
import PropTypes from "prop-types";
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import {
|
||||
faGithub,
|
||||
faTwitter,
|
||||
faDiscourse,
|
||||
} from "@fortawesome/free-brands-svg-icons";
|
||||
import {
|
||||
faArrowUp
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
const Footer = () => (
|
||||
<footer className="bg-secondary text-center text-white">
|
||||
<div className="container pt-3">
|
||||
<section>
|
||||
<Link className="btn btn-outline-light btn-floating m-1" to="https://twitter.com/nickel_lang" role="button"
|
||||
><FontAwesomeIcon icon={faTwitter}/></Link>
|
||||
|
||||
<Link className="btn btn-outline-light btn-floating m-1" to="https://github.com/tweag/nickel/" role="button"
|
||||
><FontAwesomeIcon icon={faGithub}/></Link>
|
||||
|
||||
<Link className="btn btn-outline-light btn-floating m-1" to="#" role="button"
|
||||
><FontAwesomeIcon icon={faDiscourse}/></Link>
|
||||
|
||||
<Link className="btn btn-outline-light btn-floating m-1 ml-4" to="#" role="button"
|
||||
><FontAwesomeIcon icon={faArrowUp}/></Link>
|
||||
</section>
|
||||
</div>
|
||||
<hr/>
|
||||
<div className="text-center pb-3">
|
||||
© 2021 Copyright:
|
||||
Nickel contributors
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
|
||||
export default Footer
|
38
website/src/components/header.js
Normal file
@ -0,0 +1,38 @@
|
||||
import React from "react"
|
||||
import { Link } from "gatsby"
|
||||
import PropTypes from "prop-types"
|
||||
import {StaticImage} from "gatsby-plugin-image";
|
||||
|
||||
const Header = ({ siteTitle, menuLinks }) => (
|
||||
<header>
|
||||
<nav className="navbar navbar-expand-lg navbar-light bg-primary">
|
||||
<div className="container-fluid">
|
||||
<Link className="navbar-brand" to="/">
|
||||
<StaticImage className={"logo-navbar"} src="../images/nickel-logo-2.svg" alt="logo"/><span className="nickel">Nickel</span>
|
||||
</Link>
|
||||
<button className="navbar-toggler" type="button" data-bs-toggle="collapse"
|
||||
data-bs-target="#navbarNavAltMarkup"
|
||||
aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span className="navbar-toggler-icon"/>
|
||||
</button>
|
||||
<div className="collapse navbar-collapse" id="navbarNavAltMarkup">
|
||||
<div className="navbar-nav mx-auto">
|
||||
{menuLinks.map(link => (
|
||||
<Link key={link.name} className="nav-link" activeClassName="active" to={link.link}>{link.name}</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
);
|
||||
|
||||
Header.propTypes = {
|
||||
siteTitle: PropTypes.string,
|
||||
};
|
||||
|
||||
Header.defaultProps = {
|
||||
siteTitle: `Nickel`,
|
||||
};
|
||||
|
||||
export default Header
|
42
website/src/components/layout.js
Normal file
@ -0,0 +1,42 @@
|
||||
import React from "react"
|
||||
import {graphql, StaticQuery} from "gatsby"
|
||||
import {Helmet} from "react-helmet"
|
||||
import Header from "./header"
|
||||
import Footer from "./footer"
|
||||
|
||||
export default function Layout({children}) {
|
||||
return (
|
||||
<StaticQuery
|
||||
query={graphql`
|
||||
query SiteTitleQuery {
|
||||
site {
|
||||
siteMetadata {
|
||||
title
|
||||
menuLinks {
|
||||
name
|
||||
link
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`}
|
||||
render={data => (
|
||||
<React.Fragment>
|
||||
<Helmet
|
||||
title={'Nickel'}
|
||||
meta={[
|
||||
{name: 'description', content: 'Sample'},
|
||||
{name: 'keywords', content: 'sample, something'},
|
||||
]}
|
||||
>
|
||||
</Helmet>
|
||||
<Header siteTitle={data.site.siteMetadata.title} menuLinks={data.site.siteMetadata.menuLinks}/>
|
||||
<div>
|
||||
{children}
|
||||
</div>
|
||||
<Footer/>
|
||||
</React.Fragment>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
93
website/src/components/playground.js
Normal file
@ -0,0 +1,93 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import Editor from "./editor";
|
||||
import {modes, Repl} from "./repl";
|
||||
import {Command} from 'react-bootstrap-icons';
|
||||
|
||||
const PLAYGROUND_SEND_EVENT = 'playground:send';
|
||||
|
||||
/**
|
||||
* Playground component, composed of both a code editor and a REPL component.
|
||||
*/
|
||||
export default class Playground extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {mode: this.props.mode};
|
||||
this.setMode = this.setMode.bind(this);
|
||||
this.dispatchSendEvent = this.dispatchSendEvent.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// In fit-to-code mode, fix the height of the output (terminal) element to the height of the current code
|
||||
if(this.props.fit === 'code' || this.props.fit === 'lines') {
|
||||
this.terminalContainer.style.height = this.editor.getHeight() + "px";
|
||||
}
|
||||
|
||||
// If a program was provided initially, run it.
|
||||
if(this.props.value) {
|
||||
this.editor.send();
|
||||
}
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
mode: modes.REPL,
|
||||
fit: 'page',
|
||||
};
|
||||
|
||||
setTerminalContainer = element => this.terminalContainer = element;
|
||||
setEditor = editor => this.editor = editor;
|
||||
|
||||
replTabStyle = (mode) => ('nav-link link-secondary' + (this.state.mode === mode ? ' active' : ''));
|
||||
|
||||
setMode = (mode) => {
|
||||
this.setState({mode});
|
||||
};
|
||||
|
||||
dispatchSendEvent = () => {
|
||||
const event = new CustomEvent(PLAYGROUND_SEND_EVENT);
|
||||
document.dispatchEvent(event);
|
||||
};
|
||||
|
||||
render() {
|
||||
return <React.Fragment>
|
||||
<div className={"row"}>
|
||||
<div className={"col-6 playground-tab d-flex align-items-center"}>
|
||||
<div>
|
||||
<button className={"btn btn-primary"} onClick={() => this.dispatchSendEvent()}>Run</button>
|
||||
<kbd className={"ml-4"}>Ctrl</kbd>+<kbd>Enter</kbd> (or <kbd>Cmd <Command/>
|
||||
</kbd>+<kbd>Enter</kbd>)
|
||||
</div>
|
||||
</div>
|
||||
<ul className={"col-6 nav nav-pills playground-tab"}>
|
||||
<li className="nav-item">
|
||||
<a className={this.replTabStyle(modes.REPL)} onClick={() => this.setMode(modes.REPL)}>REPL</a>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<a className={this.replTabStyle(modes.JSON)} onClick={() => this.setMode(modes.JSON)}>JSON</a>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<a className={this.replTabStyle(modes.YAML)} onClick={() => this.setMode(modes.YAML)}>YAML</a>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<a className={this.replTabStyle(modes.TOML)} onClick={() => this.setMode(modes.TOML)}>TOML</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<section className={'row playground-container overflow-hidden flex-grow-1'}>
|
||||
<div className={'col-6'}>
|
||||
<Editor ref={this.setEditor} fit={this.props.fit} lines={this.props.lines} value={this.props.value}/>
|
||||
</div>
|
||||
<div id={"playground-terminal-container"}
|
||||
ref={this.setTerminalContainer}
|
||||
className={'col-6 ansi-monokai playground-terminal-container'}>
|
||||
<Repl containerId={"playground-terminal-container"} className={'playground-terminal'}
|
||||
mode={this.state.mode}/>
|
||||
</div>
|
||||
</section>
|
||||
</React.Fragment>
|
||||
}
|
||||
}
|
||||
|
||||
export {Playground, PLAYGROUND_SEND_EVENT}
|
227
website/src/components/repl.js
Normal file
@ -0,0 +1,227 @@
|
||||
import * as React from 'react';
|
||||
import {repl_init, repl_input} from "nickel-repl";
|
||||
import Ansi from "ansi-to-react";
|
||||
import {EDITOR_SEND_EVENT} from "./editor";
|
||||
|
||||
const REPL_RUN_EVENT = 'nickel-repl:run';
|
||||
|
||||
/**
|
||||
* Codes returned by the Nickel WASM evaluator.
|
||||
* @type {{result: {BLANK: number, SUCCESS: number, PARTIAL: number, ERROR: number}, error: {severity: {HELP: number, BUG: number, NOTE: number, ERROR: number, WARNING: number}, label: {SECONDARY: number, PRIMARY: number}}}}
|
||||
*/
|
||||
const nickelCodes = {
|
||||
result: {
|
||||
SUCCESS: 0,
|
||||
BLANK: 1,
|
||||
PARTIAL: 2,
|
||||
ERROR: 3,
|
||||
},
|
||||
error: {
|
||||
severity: {
|
||||
HELP: 1,
|
||||
NOTE: 2,
|
||||
WARNING: 3,
|
||||
ERROR: 4,
|
||||
BUG: 5,
|
||||
},
|
||||
label: {
|
||||
PRIMARY: 0,
|
||||
SECONDARY: 1,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const modes = {
|
||||
REPL: 'repl',
|
||||
JSON: 'json',
|
||||
TOML: 'toml',
|
||||
YAML: 'yaml',
|
||||
};
|
||||
|
||||
/**
|
||||
* An REPL. This component can run Nickel programs or REPL commands and display a stylized output.
|
||||
*/
|
||||
export default class Repl extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.state = {
|
||||
lastInput: '',
|
||||
// Output displayed in REPL mode. It is appended to at each run.
|
||||
output_repl: "Nickel Online REPL | Welcome to the Nickel online REPL.\n"
|
||||
+ "See the output of your snippets here.\n\n",
|
||||
// Output displayed in serialize mode. Cleared at each new run.
|
||||
output_serialize: '',
|
||||
};
|
||||
this.endRef = React.createRef();
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
mode: modes.REPL
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the current REPL output as an array of lines.
|
||||
* @returns {string[]}
|
||||
*/
|
||||
lines() {
|
||||
return this.state.output_repl.split(/\r?\n/g);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const result = repl_init();
|
||||
|
||||
if (result.tag === nickelCodes.result.ERROR) {
|
||||
this.write(`Initialization error: ${result.msg}\n`);
|
||||
} else {
|
||||
// /!\ WARNING: result is moved by the Rust code when calling to the repl() method. Do not use or copy result after this call to repl().
|
||||
this.repl = result.repl();
|
||||
this.prompt();
|
||||
}
|
||||
document.addEventListener(EDITOR_SEND_EVENT, this.onSend.bind(this))
|
||||
}
|
||||
|
||||
/**
|
||||
* Write text. Newlines and ANSI escape codes are converted to HTML before rendering.
|
||||
* In serialize mode, the new output erase the old content. In REPL mode, the new output is appended to.
|
||||
* Because state updates are asynchronous, this returns a Promise that resolves when everything is up to date.
|
||||
* @param data String
|
||||
* @returns {Promise<unknown>}
|
||||
*/
|
||||
write(data) {
|
||||
return new Promise(resolve => {
|
||||
if (this.props.mode === modes.REPL) {
|
||||
this.setState({output_repl: this.state.output_repl + data}, resolve);
|
||||
} else {
|
||||
this.setState({output_serialize: data}, resolve);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the output.
|
||||
* @returns {Promise<unknown>}
|
||||
*/
|
||||
clear() {
|
||||
return new Promise(resolve => {
|
||||
if (this.props.mode === modes.REPL) {
|
||||
this.setState({output_repl: ''}, resolve);
|
||||
} else {
|
||||
this.setState({output_serialize: ''}, resolve);
|
||||
}
|
||||
}
|
||||
).then(() => this.prompt());
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a new line followed by a prompt, if in REPL mode. Do nothing otherwise.
|
||||
* @returns {Promise<unknown>}
|
||||
*/
|
||||
prompt = () => {
|
||||
if (this.props.mode === modes.REPL) {
|
||||
return this.write('\n\u001b[32mnickel>\u001b[0m ');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Run it. In REPL mode, the input is also appended to the output.
|
||||
* @param input String
|
||||
*/
|
||||
onSend = ({detail: input}) => {
|
||||
if (this.props.mode === modes.REPL) {
|
||||
return this.write(input).then(() => this.run(input));
|
||||
} else {
|
||||
return this.run(input);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Unescape serialized output_repl. To serialize an input, the REPL component wraps the program in a call to `builtins.serialize`.
|
||||
* The returned result is a Nickel string with escaped characters. This function unescapes them.
|
||||
* @param result String
|
||||
* @returns String
|
||||
*/
|
||||
unescape = (result) => {
|
||||
if (result.charAt(0) === '"' && result.slice(result.length - 2, result.length) === '"\n') {
|
||||
return result.slice(1, result.length - 2).replaceAll('\\"', '"').replaceAll('\\\\', '\\')
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Run an input and write the result in the output.
|
||||
* @param input String
|
||||
* @returns {Promise<number>} A promise resolving to the return code of the execution of the Nickel REPL, or -1 if the REPL wasn't loaded.
|
||||
*/
|
||||
run = (input) => {
|
||||
if (this.repl === null) {
|
||||
console.error("Terminal: REPL is not loaded (this.repl === null)");
|
||||
return new Promise(resolve => resolve(-1));
|
||||
}
|
||||
|
||||
this.setState({lastInput: input});
|
||||
|
||||
if (this.props.mode !== modes.REPL) {
|
||||
const format = this.props.mode.charAt(0).toUpperCase() + this.props.mode.slice(1);
|
||||
input = `builtins.serialize \`${format} (${input})`;
|
||||
}
|
||||
|
||||
let result = repl_input(this.repl, input);
|
||||
let task;
|
||||
|
||||
if (this.props.mode === modes.REPL) {
|
||||
task = this.write("\n" + result.msg).then(() => this.prompt());
|
||||
} else {
|
||||
//If there's an error, we run the original snippet in order to have a better error message.
|
||||
if (result.tag === nickelCodes.result.ERROR) {
|
||||
const resultAlone = repl_input(this.repl, this.state.lastInput);
|
||||
|
||||
// If there's no error for the original snippet alone, this may be a NonSerializable error. In this case,
|
||||
// we keep the first error message.
|
||||
if (resultAlone.tag === nickelCodes.result.ERROR) {
|
||||
result = resultAlone;
|
||||
}
|
||||
|
||||
task = this.write(result.msg);
|
||||
} else {
|
||||
task = this.write(this.unescape(result.msg));
|
||||
}
|
||||
}
|
||||
|
||||
// Dispatch the result as an event, so that the editor or other components can react to the outcome of the last input
|
||||
const event = new CustomEvent(REPL_RUN_EVENT, {detail: result});
|
||||
document.dispatchEvent(event);
|
||||
|
||||
return task.then(() => result.tag);
|
||||
};
|
||||
|
||||
componentDidUpdate = (prevProps) => {
|
||||
// If we switched mode to a serialization mode and there is a last input, we re-run the last input
|
||||
if (this.props.mode !== prevProps.mode && this.props.mode !== modes.REPL && this.state.lastInput !== '') {
|
||||
this.run(this.state.lastInput);
|
||||
}
|
||||
|
||||
// Scroll to the last message
|
||||
const terminalContainer = document.getElementById(this.props.containerId);
|
||||
terminalContainer.scrollTop = terminalContainer.scrollHeight;
|
||||
};
|
||||
|
||||
render() {
|
||||
let content;
|
||||
|
||||
if (this.props.mode === modes.REPL) {
|
||||
content = this.lines().map((line, index) => <div key={index}><Ansi useClasses>{line.toString()}</Ansi><br/>
|
||||
</div>);
|
||||
} else {
|
||||
content = <Ansi useClasses>{this.state.output_serialize}</Ansi>;
|
||||
}
|
||||
|
||||
return <div style={{whiteSpace: 'pre'}}>
|
||||
{content}
|
||||
<div ref={this.endRef}/>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
export {Repl, REPL_RUN_EVENT, nickelCodes, modes};
|
BIN
website/src/fonts/bureau-grotesque-55-regular.woff
Normal file
BIN
website/src/fonts/century-gothic.woff
Normal file
BIN
website/src/images/icon.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
website/src/images/merge-2.png
Normal file
After Width: | Height: | Size: 9.7 KiB |
BIN
website/src/images/merge.png
Normal file
After Width: | Height: | Size: 10 KiB |
1
website/src/images/nickel-logo-1.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg id="Calque_1" data-name="Calque 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 299.83 339.82"><defs><style>.cls-1{fill:#e0c3fc;}.cls-2{fill:#fff;}</style></defs><path class="cls-1" d="M1110.37,614.62V465.38a20.67,20.67,0,0,0-10.34-17.9L970.79,372.86a20.68,20.68,0,0,0-20.67,0L820.88,447.48a20.67,20.67,0,0,0-10.34,17.9V614.62a20.67,20.67,0,0,0,10.34,17.9l129.24,74.62a20.68,20.68,0,0,0,20.67,0L1100,632.52A20.67,20.67,0,0,0,1110.37,614.62Z" transform="translate(-810.54 -370.09)"/><path class="cls-2" d="M1023.14,509.09V511q0,34.5,0,69a2.31,2.31,0,0,1-1.57,2.53q-25.8,11.37-51.54,22.85c-.44.2-.89.37-1.5.62,0-.49-.1-.88-.1-1.28q0-35,0-70a1.77,1.77,0,0,1,1.2-2q26.14-11.55,52.27-23.18C1022.24,509.45,1022.59,509.32,1023.14,509.09Z" transform="translate(-810.54 -370.09)"/><path class="cls-2" d="M898.14,509.06l8.94,3.94q22,9.79,44.11,19.56a2.38,2.38,0,0,1,1.67,2.64q-.08,34.41,0,68.83v2c-.62-.24-1.11-.41-1.59-.62q-25.83-11.48-51.68-22.92a2.09,2.09,0,0,1-1.45-2.28q.06-34.66,0-69.33Z" transform="translate(-810.54 -370.09)"/><path class="cls-2" d="M1011.61,497.07l-23.47,10.42c-8.76,3.89-17.52,7.8-26.3,11.65a3.1,3.1,0,0,1-2.24.06q-24.45-10.77-48.86-21.66c-.25-.1-.48-.25-.91-.47a11.69,11.69,0,0,1,1.11-.67q24.27-10.78,48.56-21.54a3,3,0,0,1,2.1-.07q24.52,10.83,49,21.74C1010.84,496.63,1011.06,496.77,1011.61,497.07Z" transform="translate(-810.54 -370.09)"/></svg>
|
After Width: | Height: | Size: 1.3 KiB |
1
website/src/images/nickel-logo-2.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg id="Calque_1" data-name="Calque 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 299.09 338.96"><defs><style>.cls-1{fill:#3a3a3c;}.cls-2{fill:#e0c3fc;}</style></defs><path class="cls-1" d="M1110,614.41V465.59a20.68,20.68,0,0,0-10.34-17.9l-128.87-74.4a20.63,20.63,0,0,0-20.67,0l-128.88,74.4a20.69,20.69,0,0,0-10.33,17.9V614.41a20.69,20.69,0,0,0,10.33,17.9l128.88,74.4a20.63,20.63,0,0,0,20.67,0l128.87-74.4A20.68,20.68,0,0,0,1110,614.41Z" transform="translate(-810.91 -370.52)"/><path class="cls-2" d="M1023,509.16v1.9q0,34.41,0,68.83a2.31,2.31,0,0,1-1.56,2.53Q995.71,593.76,970,605.21c-.44.2-.89.37-1.5.62,0-.49-.1-.88-.1-1.28q0-34.9,0-69.8a1.77,1.77,0,0,1,1.19-2q26.1-11.52,52.15-23.12Z" transform="translate(-810.91 -370.52)"/><path class="cls-2" d="M898.29,509.14c3.12,1.37,6,2.64,8.92,3.93,14.67,6.51,29.32,13,44,19.51a2.39,2.39,0,0,1,1.66,2.63q-.07,34.34,0,68.67v2c-.62-.24-1.11-.41-1.58-.62q-25.77-11.44-51.56-22.87a2.07,2.07,0,0,1-1.44-2.27q0-34.58,0-69.15Z" transform="translate(-810.91 -370.52)"/><path class="cls-2" d="M1011.49,497.17l-23.42,10.4c-8.74,3.88-17.47,7.79-26.23,11.62a3.15,3.15,0,0,1-2.24.07q-24.39-10.75-48.74-21.61c-.25-.11-.48-.25-.9-.48.44-.26.75-.5,1.1-.66q24.21-10.77,48.45-21.49a3,3,0,0,1,2.09-.07q24.46,10.8,48.89,21.69C1010.72,496.74,1010.94,496.88,1011.49,497.17Z" transform="translate(-810.91 -370.52)"/></svg>
|
After Width: | Height: | Size: 1.3 KiB |
BIN
website/src/images/reuse-2.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
website/src/images/reuse.png
Normal file
After Width: | Height: | Size: 7.9 KiB |
BIN
website/src/images/validate-2.png
Normal file
After Width: | Height: | Size: 8.2 KiB |
BIN
website/src/images/validate.png
Normal file
After Width: | Height: | Size: 5.6 KiB |
174
website/src/markdown-pages/getting-started.md
Normal file
@ -0,0 +1,174 @@
|
||||
---
|
||||
page: "getting-started"
|
||||
---
|
||||
|
||||
# Getting started
|
||||
|
||||
Nickel is quite new and not yet distributed using the standard channels
|
||||
(binaries, nix package, Rust crate, and so on). We are sorry if the installation
|
||||
process is not yet optimal, but this should change soon, so stay tuned.
|
||||
|
||||
## Build from source using Nix
|
||||
|
||||
Using [Nix]("https://nixos.org/") is the easiest way to get a Nickel executable
|
||||
running:
|
||||
|
||||
1. Clone the [Nickel repository](https://github.io/tweag/nickel)
|
||||
locally and set it as the current directory:
|
||||
|
||||
```shell-session
|
||||
$ git clone git@github.com:tweag/nickel.git
|
||||
Cloning in 'nickel'...
|
||||
[..]
|
||||
$ cd nickel
|
||||
devops@nickel-lang:~/nickel$
|
||||
```
|
||||
|
||||
1. Invoke nix build:
|
||||
|
||||
```shell-session
|
||||
devops@nickel-lang:~/nickel$ nix build
|
||||
[1 built, 0.0 MiB DL]
|
||||
devops@nickel-lang:~/nickel$
|
||||
```
|
||||
|
||||
1. If everything went right, a binary is now available in the
|
||||
result directory:
|
||||
|
||||
```shell-session
|
||||
devops@nickel-lang:~/nickel$ ./result/bin/nickel -V
|
||||
nickel 0.1.0
|
||||
devops@nickel-lang:~/nickel$
|
||||
```
|
||||
|
||||
## Build from source without Nix
|
||||
|
||||
You will find alternative ways to build Nickel from source by cloning the
|
||||
[repository](href="https://github.io/tweag/nickel) and following the
|
||||
instructions of the
|
||||
[README](href="https://github.com/tweag/nickel/#getting-started").
|
||||
|
||||
## Write your first configuration
|
||||
|
||||
Nickel has a ton of cool features, like gradual typing, contracts and a merge
|
||||
system. However, you'll only have to deal with them once you need them. Writing
|
||||
basic configuration is almost as writing JSON or YAML. Let us start with a
|
||||
basic fictional app configuration:
|
||||
|
||||
```nickel
|
||||
{
|
||||
name = "example",
|
||||
description = m#"
|
||||
This is an awesome software I'm developing.
|
||||
Please use it!
|
||||
"#m,
|
||||
version = "0.1.1",
|
||||
main = "index.js",
|
||||
keywords = ["example", "config"],
|
||||
scripts = {
|
||||
test = m#"test.sh --option --install example --version "0.1.1""#m,
|
||||
do_stuff = "do_stuff.sh subcommand",
|
||||
},
|
||||
contributors = [{
|
||||
name = "John Doe",
|
||||
email = "johndoe@example.com"
|
||||
}, {
|
||||
name = "Ivy Lane",
|
||||
url = "https=//example.com/ivylane"
|
||||
}],
|
||||
dependencies = {
|
||||
dep1 = "^1.0.0",
|
||||
dep3 = "6.7"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This program describe a record delimited by `{` and `}`, consisting in a list of
|
||||
key-value pairs, akin to JSON's objects. Nickel basic datatypes include strings
|
||||
delimited by `"` and lists, by `[` and `]`.
|
||||
|
||||
The m#" and "#m delimits multiline strings. In such strings, the common
|
||||
indentation prefix is stripped, and special characters (excepted
|
||||
interpolation #{}) loose their meaning. It is useful for two purpose
|
||||
illustrated here:
|
||||
|
||||
- Writing strings spanning multiple lines while keeping the same
|
||||
indentation as code.
|
||||
- Writing strings with special characters in it, without having to
|
||||
escape them (", \, and so on).
|
||||
|
||||
## Export
|
||||
|
||||
Now, save the content in "example.ncl" and run nickel export (or
|
||||
./result/bin/nickel export if you haven't made a symbolic link):
|
||||
|
||||
```shell-session
|
||||
devops@nickel-lang:~/nickel$ nickel -f example.ncl export --format yaml
|
||||
---
|
||||
contributors:
|
||||
- email: johndoe@example.com
|
||||
name: John Doe
|
||||
- name: Ivy Lane
|
||||
url: https=//example.com/ivylane
|
||||
dependencies:
|
||||
dep1: ^1.0.0
|
||||
dep3: "6.7"
|
||||
description: "This is an awesome software I'm developing.\nPlease use it!"
|
||||
keywords:
|
||||
- example
|
||||
- config
|
||||
main: index.js
|
||||
name: example
|
||||
scripts:
|
||||
do_stuff: do_stuff.sh subcommand
|
||||
test: "test.sh --option --install example --version \"0.1.1\""
|
||||
version: 0.1.1
|
||||
```
|
||||
|
||||
Currently supported formats are yaml, toml, json, and raw. json is the
|
||||
default, while raw expect a string result that it output directly, useful to
|
||||
generate e.g. shell scripts or other custom data.
|
||||
|
||||
## Reuse
|
||||
|
||||
Nickel is a programming language. This allows you not only to describe, but to
|
||||
generate data. There's some repetition in our previous example (reproducing only
|
||||
the interesting part):
|
||||
|
||||
```nickel
|
||||
name = "example",
|
||||
version = "0.1.1",
|
||||
scripts = {
|
||||
test = m#"test.sh --option --install example --version "0.1.1""#m,
|
||||
```
|
||||
|
||||
Apart from aesthetics, a more serious issue is inconsistency. If you bump the
|
||||
version number in version, you may forget to do so in the test scripts as well,
|
||||
leading to an incorrect configuration. To remedy this problem, let us have a
|
||||
single source of truth by reusing the value of name and version in test, using
|
||||
the interpolation syntax `#{expr}`:
|
||||
|
||||
```nickel
|
||||
name = "example",
|
||||
version = "0.1.1",
|
||||
scripts = {
|
||||
test = m#"test.sh --option --install #{name} --version "#{version}""#m
|
||||
```
|
||||
|
||||
Now, if we change version to "0.1.2" and export the result, the test script
|
||||
invocation is updated as well:
|
||||
|
||||
```yaml
|
||||
# [...]
|
||||
scripts:
|
||||
do_stuff: do_stuff.sh subcommand
|
||||
test: "test.sh --option --install example --version \"0.1.2\""
|
||||
version: 0.1.2
|
||||
```
|
||||
|
||||
## Going further
|
||||
|
||||
This was a short introduction that should get you started. But Nickel is a
|
||||
full-fledged programming language, featuring higher-order functions, gradual
|
||||
typing, contracts, and much more. You'll find more resources on the
|
||||
[Documentation](/documentation) page.
|
54
website/src/pages/404.js
Normal file
@ -0,0 +1,54 @@
|
||||
import * as React from "react"
|
||||
import { Link } from "gatsby"
|
||||
|
||||
// styles
|
||||
const pageStyles = {
|
||||
color: "#232129",
|
||||
padding: "96px",
|
||||
fontFamily: "-apple-system, Roboto, sans-serif, serif",
|
||||
}
|
||||
const headingStyles = {
|
||||
marginTop: 0,
|
||||
marginBottom: 64,
|
||||
maxWidth: 320,
|
||||
}
|
||||
|
||||
const paragraphStyles = {
|
||||
marginBottom: 48,
|
||||
}
|
||||
const codeStyles = {
|
||||
color: "#8A6534",
|
||||
padding: 4,
|
||||
backgroundColor: "#FFF4DB",
|
||||
fontSize: "1.25rem",
|
||||
borderRadius: 4,
|
||||
}
|
||||
|
||||
// markup
|
||||
const NotFoundPage = () => {
|
||||
return (
|
||||
<main style={pageStyles}>
|
||||
<title>Not found</title>
|
||||
<h1 style={headingStyles}>Page not found</h1>
|
||||
<p style={paragraphStyles}>
|
||||
Sorry{" "}
|
||||
<span role="img" aria-label="Pensive emoji">
|
||||
😔
|
||||
</span>{" "}
|
||||
we couldn’t find what you were looking for.
|
||||
<br />
|
||||
{process.env.NODE_ENV === "development" ? (
|
||||
<>
|
||||
<br />
|
||||
Try creating a page in <code style={codeStyles}>src/pages/</code>.
|
||||
<br />
|
||||
</>
|
||||
) : null}
|
||||
<br />
|
||||
<Link to="/">Go home</Link>.
|
||||
</p>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
export default NotFoundPage
|
30
website/src/pages/documentation.js
Normal file
@ -0,0 +1,30 @@
|
||||
import * as React from "react"
|
||||
import Layout from "../components/layout"
|
||||
import { Link } from "gatsby"
|
||||
|
||||
const IndexPage = () => {
|
||||
return (
|
||||
<Layout>
|
||||
<main className="container main-container">
|
||||
<div className="row">
|
||||
<h1 className="main-title col-12">Documentation</h1>
|
||||
|
||||
<div className="mt-4 col-12">
|
||||
<div className="list-group">
|
||||
<Link to="https://github.com/tweag/nickel/#readme" className="list-group-item list-group-item-action">The
|
||||
Nickel README</Link>
|
||||
<Link to="https://github.com/tweag/nickel/blob/master/RATIONALE.md"
|
||||
className="list-group-item list-group-item-action">Design rationale</Link>
|
||||
<Link to="#" className="list-group-item list-group-item-action
|
||||
disabled">Tutorials<span className={'text-primary'}> - coming soon</span></Link>
|
||||
<Link to="#" className="list-group-item list-group-item-action disabled">The Nickel Manual <span className={'text-primary'}> - coming soon</span></Link>
|
||||
<Link to="#" className="list-group-item list-group-item-action disabled">Language specification<span className={'text-primary'}> - coming soon</span></Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</Layout>
|
||||
)
|
||||
};
|
||||
|
||||
export default IndexPage
|
208
website/src/pages/getting-started.js
Normal file
@ -0,0 +1,208 @@
|
||||
import * as React from "react"
|
||||
import {useEffect} from "react"
|
||||
import Layout from "../components/layout"
|
||||
|
||||
import Prism from "prismjs";
|
||||
import "prismjs/components/prism-bash";
|
||||
import "prismjs/components/prism-yaml";
|
||||
import "prismjs/themes/prism-tomorrow.css";
|
||||
import "prismjs/plugins/command-line/prism-command-line";
|
||||
import "prismjs/plugins/command-line/prism-command-line.css";
|
||||
import Playground from "../components/playground";
|
||||
import {modes} from "../components/repl";
|
||||
import nickelLanguageDefinition from "../prism/nickel";
|
||||
|
||||
// Escaping curly braces and other stuff in JSX is tiring, so we define all code examples here
|
||||
const codeExamples = {
|
||||
withNix: {
|
||||
clone: `git clone git@github.com:tweag/nickel.git
|
||||
Cloning in 'nickel'...
|
||||
[..]
|
||||
cd nickel`,
|
||||
build: `nix build
|
||||
[1 built, 0.0 MiB DL]`,
|
||||
run: `./result/bin/nickel -V
|
||||
nickel 0.1.0`,
|
||||
},
|
||||
firstConfig: `{
|
||||
name = "example",
|
||||
description = m#"
|
||||
This is an awesome software I'm developing.
|
||||
Please use it!
|
||||
"#m,
|
||||
version = "0.1.1",
|
||||
main = "index.js",
|
||||
keywords = ["example", "config"],
|
||||
scripts = {
|
||||
test = m#"test.sh --option --install example --version "0.1.1""#m,
|
||||
do_stuff = "do_stuff.sh subcommand",
|
||||
},
|
||||
contributors = [{
|
||||
name = "John Doe",
|
||||
email = "johndoe@example.com"
|
||||
}, {
|
||||
name = "Ivy Lane",
|
||||
url = "https://example.com/ivylane"
|
||||
}],
|
||||
dependencies = {
|
||||
dep1 = "^1.0.0",
|
||||
dep3 = "6.7"
|
||||
}
|
||||
}`,
|
||||
export: `nickel -f example.ncl export --format yaml
|
||||
---
|
||||
contributors:
|
||||
- email: johndoe@example.com
|
||||
name: John Doe
|
||||
- name: Ivy Lane
|
||||
url: https=//example.com/ivylane
|
||||
dependencies:
|
||||
dep1: ^1.0.0
|
||||
dep3: "6.7"
|
||||
description: "This is an awesome software I'm developing.\\nPlease use it!"
|
||||
keywords:
|
||||
- example
|
||||
- config
|
||||
main: index.js
|
||||
name: example
|
||||
scripts:
|
||||
do_stuff: do_stuff.sh subcommand
|
||||
test: "test.sh --option --install example --version \\"0.1.1\\""
|
||||
version: 0.1.1`,
|
||||
reuse: {
|
||||
problem: `name = "example",
|
||||
version = "0.1.1",
|
||||
scripts = {
|
||||
test = m#"test.sh --option --install example --version "0.1.1""#m,`,
|
||||
diff: `name = "example",
|
||||
version = "0.1.1",
|
||||
scripts = {
|
||||
test = m#"test.sh --option --install #{name} --version "#{version}""#m`,
|
||||
result: `# [...]
|
||||
scripts:
|
||||
do_stuff: do_stuff.sh subcommand
|
||||
test: "test.sh --option --install example --version \\"0.1.2\\""
|
||||
version: 0.1.2`,
|
||||
},
|
||||
};
|
||||
|
||||
const IndexPage = () => {
|
||||
useEffect(() => {
|
||||
Prism.languages.nickel = nickelLanguageDefinition;
|
||||
Prism.highlightAll();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<main className="container content-main-container content">
|
||||
<h1 id="getting-started" className={'main-title'}>Getting started</h1>
|
||||
|
||||
<p>Nickel is still young and the installation process is not yet optimal. Sorry about that! We are focused on improving the
|
||||
experience, so keep updated. </p>
|
||||
|
||||
<h2 id="build-from-source-using-nix">Build from source using Nix</h2>
|
||||
|
||||
<p>Using <a className={"link-primary"} href="https://nixos.org/">Nix</a> is the easiest way
|
||||
to get a Nickel executable
|
||||
running.</p>
|
||||
|
||||
<ol>
|
||||
<li><p>Clone the <a className={"link-primary"} href="https://github.com/tweag/nickel">Nickel
|
||||
repository</a> and set it as the current directory:</p>
|
||||
|
||||
<pre className={'command-line language-bash'} data-user="devops" data-host="nickel"
|
||||
data-output="2-3:"><code>{codeExamples.withNix.clone}</code></pre>
|
||||
</li>
|
||||
<li><p>Invoke <code>nix build</code>:</p>
|
||||
<pre className={'command-line language-bash'} data-user="devops" data-host="nickel:~/nickel"
|
||||
data-output="2:"><code>{codeExamples.withNix.build}</code></pre>
|
||||
</li>
|
||||
<li><p>If everything went right, a binary is now available in the <code>result</code> directory:</p>
|
||||
<pre className={'command-line language-bash'} data-user="devops" data-host="nickel:~/nickel"
|
||||
data-output="2:"><code>{codeExamples.withNix.run}</code></pre>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="build-from-source-without-nix">Build from source without Nix</h2>
|
||||
|
||||
<p>Please refer to the <a
|
||||
className={"link-primary"}
|
||||
href="https://github.com/tweag/nickel/#getting-started">README</a> of the <a
|
||||
className={"link-primary"}
|
||||
href="https://github.com/tweag/nickel">Nickel repository</a> for alternative ways of building Nickel.</p>
|
||||
|
||||
<h2 id="write-your-first-configuration">Write your first configuration</h2>
|
||||
|
||||
<p> Nickel has advanced features to help you handle and organize complex configurations (gradual typing, contracts, a merge system, and so on).
|
||||
But you'll only have to deal with any of this once you need to.
|
||||
Writing a basic configuration is as simple as writing JSON or YAML. Let us write a manifest of a fictional app:</p>
|
||||
|
||||
<Playground fit={'code'} mode={modes.YAML} value={codeExamples.firstConfig}/>
|
||||
|
||||
<p/>This program is composed of <i>record</i>. A record is the same thing as an object in JSON. It is a list of
|
||||
key-value pairs delimited
|
||||
by <code>{'{'}</code> and <code>{'}'}</code>. In general, the values of Nickel map directly to a
|
||||
corresponding value in JSON (excluding functions). Thus, the basic datatypes of Nickel are the same as in JSON:
|
||||
<ul>
|
||||
<li>Records (objects) delimited by <code>{'{'}</code> and <code>{'}'}</code>.</li>
|
||||
<li>Strings, delimiter by <code>"</code>. <code>m#"</code> and <code>"#m</code> delimit multiline strings.
|
||||
</li>
|
||||
<li>Numbers (floats)</li>
|
||||
<li>Lists, delimited by <code>[</code> and <code>]</code> and separated by <code>,</code>.</li>
|
||||
</ul>
|
||||
|
||||
<p/>Multiline strings are an alternative way of defining strings. Line 11 is an example of such a string. Without diving into the details, multiline strings are
|
||||
useful for:
|
||||
<ul>
|
||||
<li>Write strings spanning several lines, as their name suggests. Multiline strings can be indented at the same
|
||||
level as the surrounding code while still producing the expected result (the common indentation prefix is stripped).
|
||||
</li>
|
||||
<li>Write strings with special characters without having to escape them.</li>
|
||||
</ul>
|
||||
|
||||
In our example, using a multiline string saves us from escaping the double quotes <code>"</code>.
|
||||
<h2 id="export">Export</h2>
|
||||
<p>The ultimate goal of a Nickel program is to produce a static configuration. To do so, save the content of our example above in <code>example.ncl</code> and run <code>nickel export</code> (or
|
||||
<code>./result/bin/nickel export</code> if you haven't made a symbolic link):</p>
|
||||
<pre className={'command-line language-bash'} data-user="devops" data-host="nickel:~/nickel"
|
||||
data-output="2-21:"><code>{codeExamples.export}</code></pre>
|
||||
|
||||
<p>Nickel currently supports exporting to and importing from YAML, TOML and JSON.</p>
|
||||
|
||||
<h2 id="reuse">Reuse</h2>
|
||||
|
||||
<p>Nickel is a programming language. This allows you not only to describe, but to
|
||||
generate data. There's repetition in our previous example:</p>
|
||||
<pre><code className={'language-nickel'}>{codeExamples.reuse.problem}</code></pre>
|
||||
|
||||
<p>The version <code>0.1.1</code> appears both in <code>version</code> and <code>scripts.test</code>.
|
||||
The name <code>example</code> appears both in <code>name</code> and <code>scripts.test</code> as well.
|
||||
Pure aesthetics aside, a more serious issue is inconsistency. If you bump the
|
||||
version number in <code>version</code>, you may forget to do so in the <code>scripts.test</code> as well,
|
||||
ending up wih incompatible version numbers in the same configuration. To remedy the problem, let's have a
|
||||
single source of truth by reusing the value of <code>name</code> and <code>version</code> in <code>scripts.test</code>, using
|
||||
the string interpolation syntax <code>#{'{expr}'}</code>:</p>
|
||||
<pre><code className={'language-nickel'}>{codeExamples.reuse.diff}</code></pre>
|
||||
|
||||
<p>Now, if we change version to "0.1.2" and export the result, the test script
|
||||
invocation is updated as well:</p>
|
||||
|
||||
<pre><code className={'language-yaml'}>{codeExamples.reuse.result}</code></pre>
|
||||
|
||||
<h2 id="going-further">Going further</h2>
|
||||
|
||||
<p>This short introduction should get you started. Nickel is a
|
||||
full-fledged programming language, featuring higher-order functions, gradual
|
||||
typing, contracts, and more! Additional resources are to come on this website, so stay tuned. In the meantime, you can find <a
|
||||
className={"link-primary"}
|
||||
href="https://github.com/tweag/nickel/tree/master/examples">examples in the repository</a>. You will also find more details on the language and its design in the <a
|
||||
className={"link-primary"}
|
||||
href="https://github.com/tweag/nickel/#nickel">README</a> or in the <a
|
||||
className={"link-primary"}
|
||||
href="https://github.com/tweag/nickel/blob/master/RATIONALE.md">design rationale</a>.</p>
|
||||
</main>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default IndexPage
|
101
website/src/pages/index.js
Normal file
@ -0,0 +1,101 @@
|
||||
import * as React from "react"
|
||||
import Layout from "../components/layout"
|
||||
import {StaticImage} from "gatsby-plugin-image";
|
||||
import mergeImage from '../images/merge-2.png';
|
||||
import validateImage from '../images/validate-2.png';
|
||||
import reuseImage from '../images/reuse-2.png';
|
||||
import PlaygroundComponent from "../components/playground";
|
||||
import {modes} from "../components/repl";
|
||||
import {Command} from "react-bootstrap-icons";
|
||||
|
||||
const codeExample = `let conf = {
|
||||
name = "NiCl",
|
||||
version = "0.0.1$",
|
||||
description = "My cool app!"
|
||||
} in
|
||||
|
||||
let SemanticVersion = fun label value =>
|
||||
let pattern = "^\\\\d{1,2}\\\\.\\\\d{1,2}(\\\\.\\\\d{1,2})?$" in
|
||||
if strings.isMatch value pattern then
|
||||
value
|
||||
else
|
||||
let msg = "invalid version number" in
|
||||
contracts.blame (contracts.tag msg label)
|
||||
in
|
||||
|
||||
let AppSchema = {
|
||||
name | Str,
|
||||
version | #SemanticVersion,
|
||||
description | Str,
|
||||
} in
|
||||
|
||||
conf | #AppSchema`
|
||||
|
||||
const IndexPage = () => {
|
||||
return (
|
||||
<Layout>
|
||||
<main className="container main-container">
|
||||
<section className="row first-section-block">
|
||||
<div className="col-12 text-center">
|
||||
<h1 className="main-title mb-4"><StaticImage className={"logo"} src="../images/nickel-logo-2.svg" alt="logo"/><span className="nickel">Nickel</span></h1>
|
||||
<div className="main-subtitle mt-4 mb-4 title-font">Better configuration
|
||||
for less
|
||||
</div>
|
||||
|
||||
<div className="mt-4 mb-4 main-text">
|
||||
Write complex configurations. Modular, correct and boilerplate-free.
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr className={'horizontal-sep'}/>
|
||||
|
||||
<section className={'row section-block'}>
|
||||
<div className="col-12 text-center">
|
||||
<h2 className="mb-4">Try it out. Find the error!</h2>
|
||||
<div className="mt-4 mb-4 main-text">
|
||||
This configuration contains an error. Fix it and type <kbd>Ctrl</kbd>+<kbd>Enter</kbd> (or <kbd>Cmd <Command/>
|
||||
</kbd>+<kbd>Enter</kbd>) or click <span className={'btn btn-primary disabled'}>Run</span> to try your solution.
|
||||
</div>
|
||||
<div className={'text-left landingpage-playground-wrapper'}>
|
||||
<PlaygroundComponent value={codeExample} fit={'code'} mode={modes.JSON}/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr className={'horizontal-sep'}/>
|
||||
|
||||
<section className="row last-section-block">
|
||||
<div className="col-4 main-text text-center landingpage-column">
|
||||
<img src={mergeImage} className="abstract-illustration" alt={""}/>
|
||||
<h3 className="mb-4 mt-4">Merge</h3>
|
||||
<div className="text-left mt-4">
|
||||
Write simple, modular blocks. Merge them into a complex configuration.
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-4 main-text text-center landingpage-column">
|
||||
<img src={validateImage} className="abstract-illustration" alt={""}/>
|
||||
<h3 className="mb-4 mt-4">Verify & Validate</h3>
|
||||
<div className="text-left mt-4">
|
||||
<p>Use (opt-in) static typing to verify functions, if you need to. Let
|
||||
type inference do the boring work.</p>
|
||||
|
||||
<p>Use contracts
|
||||
to validate your data and ensure they conform to a given schema.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-4 main-text text-center landingpage-column">
|
||||
<img src={reuseImage} className="abstract-illustration" alt={""}/>
|
||||
<h3 className="mb-4 mt-4">Reuse</h3>
|
||||
<div className="text-left lt-4">
|
||||
Don't hack, don't reinvent the wheel: Nickel is a
|
||||
programming language. Factorize. Reuse the generic parts. Import external libraries.
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</Layout>
|
||||
)
|
||||
};
|
||||
|
||||
export default IndexPage
|
22
website/src/pages/playground.js
Normal file
@ -0,0 +1,22 @@
|
||||
import * as React from "react"
|
||||
import Layout from "../components/layout"
|
||||
import {Helmet} from "react-helmet";
|
||||
import PlaygroundComponent from "../components/playground";
|
||||
|
||||
const PlaygroundPage = () => {
|
||||
return (
|
||||
<Layout>
|
||||
<div className={"container-fluid playground-main-container d-flex flex-column"}>
|
||||
<section className={"row"}>
|
||||
<div className={"col-12 text-center"}>
|
||||
<h1 className="main-title">Playground</h1>
|
||||
Experiment with the Nickel REPL online!
|
||||
</div>
|
||||
</section>
|
||||
<PlaygroundComponent/>
|
||||
</div>
|
||||
</Layout>
|
||||
)
|
||||
};
|
||||
|
||||
export default PlaygroundPage
|
22
website/src/prism/nickel.js
Normal file
@ -0,0 +1,22 @@
|
||||
export default {
|
||||
comment: /\/\/.+/,
|
||||
string: [
|
||||
/m(#)+".*"\1m/,
|
||||
/".+"/,
|
||||
],
|
||||
operator: [
|
||||
/>/, />=/, /</, /<=/, /&/, /==/, /&&/, /\|\|/, /!/, /\+/, /@/, /-/, /\+\+/,
|
||||
],
|
||||
keyword: [
|
||||
/let/,
|
||||
/in/,
|
||||
/fun/,
|
||||
/switch/,
|
||||
/forall/,
|
||||
],
|
||||
punctuation: [
|
||||
/:/, /,/, /;/, /\{/, /}/, /\(/, /\)/, /=/, /\|/, /#/,
|
||||
],
|
||||
number: /[0-9]*\.?[0-9]+/,
|
||||
builtin: /((?:Dyn)|(?:Num)|(?:Bool)|(?:Str)|(?:List:(?:[a-zA-Z0-9_]*)?))/,
|
||||
};
|
48
website/src/styles/custom.scss
Normal file
@ -0,0 +1,48 @@
|
||||
@use "sass:map";
|
||||
@use "sass:color";
|
||||
|
||||
$theme-colors: (
|
||||
"primary": #e0c3fc,
|
||||
"secondary": #8ec5fc
|
||||
);
|
||||
|
||||
@import '~bootstrap/scss/bootstrap.scss';
|
||||
|
||||
@mixin link($color) {
|
||||
$selected: map.get($theme-colors, $color);
|
||||
color: color.adjust($selected, $lightness: -10%);
|
||||
|
||||
&:hover, &:focus {
|
||||
color: color.adjust($selected, $lightness: -40%);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: $selected !important;
|
||||
}
|
||||
}
|
||||
|
||||
a:hover { text-decoration: none; }
|
||||
|
||||
.link-primary {
|
||||
@include link("primary");
|
||||
}
|
||||
|
||||
.link-secondary {
|
||||
@include link("secondary");
|
||||
}
|
||||
|
||||
.horizontal-sep {
|
||||
height: 5px;
|
||||
background: map.get($theme-colors, "primary");
|
||||
}
|
||||
|
||||
.content {
|
||||
h1, h2, h3, h4, h5 {
|
||||
margin-bottom: 0.5em;
|
||||
margin-top: 2.5em;
|
||||
}
|
||||
|
||||
h1:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
252
website/src/styles/global.css
Normal file
@ -0,0 +1,252 @@
|
||||
.main-title {
|
||||
font-size: 3em;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.main-subtitle {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.main-text {
|
||||
font-size: 1.5em;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.secondary-text {
|
||||
font-size: 1.3em;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.main-container {
|
||||
padding-top: 5em;
|
||||
padding-bottom: 5em;
|
||||
}
|
||||
|
||||
.content-main-container {
|
||||
margin-top: 5em;
|
||||
padding-bottom: 5em;
|
||||
}
|
||||
|
||||
.playground-main-container {
|
||||
margin-top: 5em;
|
||||
margin-bottom: 5em;
|
||||
height: 80vh;
|
||||
}
|
||||
|
||||
.playground-terminal {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.playground-tab {
|
||||
margin-top: 2.5em;
|
||||
margin-bottom: 0.5em;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.playground-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.playground-terminal-container {
|
||||
max-height: 100%;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
#playground-input {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.section-block {
|
||||
padding-top: 12.5em;
|
||||
padding-bottom: 12.5em;
|
||||
}
|
||||
|
||||
.first-section-block {
|
||||
padding-top: 7.5em;
|
||||
padding-bottom: 12.5em;
|
||||
}
|
||||
|
||||
.last-section-block {
|
||||
padding-top: 12.5em;
|
||||
}
|
||||
|
||||
.landingpage-column {
|
||||
padding-left: 2em;
|
||||
padding-right: 2em;
|
||||
}
|
||||
|
||||
.landingpage-playground-wrapper {
|
||||
margin-top: 5em;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: auto;
|
||||
width: 100px;
|
||||
vertical-align: middle;
|
||||
margin-right: 0.2em;
|
||||
}
|
||||
|
||||
.logo-navbar {
|
||||
height: auto;
|
||||
width: 50px;
|
||||
vertical-align: middle;
|
||||
margin-right: 0.1em;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Century Gothic";
|
||||
src: url('../fonts/century-gothic.woff')
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Bureau Grotesque";
|
||||
src: url('../fonts/bureau-grotesque-55-regular.woff')
|
||||
}
|
||||
|
||||
.nickel {
|
||||
font-family: "Century Gothic";
|
||||
}
|
||||
|
||||
.title-font, h1, h2, h3 {
|
||||
font-family: "Bureau Grotesque";
|
||||
}
|
||||
|
||||
.list-elt {
|
||||
padding-top: 0.2em;
|
||||
padding-bottom: 0.2em;
|
||||
}
|
||||
|
||||
.abstract-illustration {
|
||||
height: 6em;
|
||||
}
|
||||
|
||||
.code {
|
||||
word-wrap: normal;
|
||||
background-color: #f8f9fa;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.code pre {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.code pre > code {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
word-break: normal;
|
||||
white-space: pre;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.full-page {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.overflow-scroll {
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
.asciinema-iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.ansi-monokai {
|
||||
background: #2F3129;
|
||||
color: #8F908A;
|
||||
}
|
||||
|
||||
.ansi-monokai code {
|
||||
color: #8F908A;
|
||||
}
|
||||
|
||||
.ansi-black-bg {
|
||||
color: #002b36;
|
||||
}
|
||||
|
||||
.ansi-black-fg .ansi-bright-black-fg {
|
||||
color: #073642;
|
||||
}
|
||||
|
||||
.ansi-green-bg {
|
||||
color: #586e75;
|
||||
}
|
||||
|
||||
.ansi-yellow-bg {
|
||||
color: #657b83;
|
||||
}
|
||||
|
||||
.ansi-blue-bg {
|
||||
color: #839496;
|
||||
}
|
||||
|
||||
.ansi-cyan-bg {
|
||||
color: #93a1a1;
|
||||
}
|
||||
|
||||
.ansi-white-fg .ansi-bright-white-fg {
|
||||
color: #eee8d5;
|
||||
}
|
||||
|
||||
.ansi-white-bg {
|
||||
color: #fdf6e3;
|
||||
}
|
||||
|
||||
.ansi-yellow-fg .ansi-bright-yellow-fg {
|
||||
color: #b58900;
|
||||
}
|
||||
|
||||
.ansi-red-bg {
|
||||
color: #cb4b16;
|
||||
}
|
||||
|
||||
.ansi-red-fg, .ansi-bright-red-fg {
|
||||
color: #dc322f;
|
||||
}
|
||||
|
||||
.ansi-magenta-fg, .ansi-bright-magenta-fg {
|
||||
color: #d33682;
|
||||
}
|
||||
|
||||
.ansi-magenta-fb {
|
||||
color: #6c71c4;
|
||||
}
|
||||
|
||||
.ansi-blue-fg, .ansi-bright-blue-fg {
|
||||
color: #268bd2;
|
||||
}
|
||||
|
||||
.ansi-cyan-fg, .ansi-bright-cyan-fg {
|
||||
color: #2aa198;
|
||||
}
|
||||
|
||||
.ansi-green-fg, .ansi-bright-green-fg {
|
||||
color: #859900;
|
||||
}
|
||||
|
||||
.ansi-bright-black-fg, .ansi-bright-red-fg, .ansi-bright-green-fg, .ansi-bright-yellow-fg,
|
||||
.ansi-bright-blue-fg, .ansi-bright-magenta-fg, .ansi-bright-cyan-fg, .ansi-bright-white-fg {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.nickel-primary {
|
||||
color: #e0c3fc;
|
||||
}
|
||||
|
||||
.nickel-bg-primary {
|
||||
background-color: #e0c3fc;
|
||||
}
|
||||
|
||||
.nickel-secondary {
|
||||
color: #8ec5fc;
|
||||
}
|
||||
|
||||
.nickel-bg-secondary {
|
||||
background-color: #8ec5fc;
|
||||
}
|