1
1
mirror of https://github.com/tweag/nickel.git synced 2024-09-19 23:47:54 +03:00

initial website

This commit is contained in:
Rok Garbas 2021-06-29 11:24:35 +02:00
parent 14bbb7e850
commit a75ca912f6
No known key found for this signature in database
GPG Key ID: A0E01EF44C27BF00
38 changed files with 54974 additions and 0 deletions

40
website/.github/settings.yml vendored Normal file
View 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
View File

@ -0,0 +1,4 @@
node_modules/
.cache/
public
.idea

21
website/LICENSE Normal file
View 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
View File

@ -0,0 +1,4 @@
# Nickel-lang.org website
This repository contains the content of the Nickel language website. This is
WIP.

View 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
View 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: { '}': '&#123;' }
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
View 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

File diff suppressed because it is too large Load Diff

47
website/package.json Normal file
View 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"
}
}

View 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;
});

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View 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};

View 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

View 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

View 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>
)}
/>
)
}

View 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}

View 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};

Binary file not shown.

Binary file not shown.

BIN
website/src/images/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View 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

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View 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
View 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 couldnt 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

View 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

View 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>&quot;</code>. <code>m#&quot;</code> and <code>&quot;#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&#39;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&#39;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 &quot;0.1.2&quot; 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
View 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

View 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

View 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_]*)?))/,
};

View 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;
}
}

View 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;
}