This project is a...', * menu: {...} * } */ var Parser = Flatdoc.parser = {}; /** * Parses a given Markdown document. * See `Parser` for more info. */ Parser.parse = function(source, highlight) { marked = exports.marked; Parser.setMarkedOptions(highlight); var html = $("
Hello there, this is a docu...");
* Flatdoc.transformer.mangle($content);
*
* If you would like to change any of the transformations, decorate any of
* the functions in `Flatdoc.transformer`.
*/
var Transformer = Flatdoc.transformer = {};
/**
* Takes a given HTML `$content` and improves the markup of it by executing
* the transformations.
*
* > See: [Transformer](#transformer)
*/
Transformer.mangle = function($content) {
this.addIDs($content);
this.buttonize($content);
this.smartquotes($content);
};
/**
* Adds IDs to headings.
*/
Transformer.addIDs = function($content) {
var slugs = ['', '', ''];
$content.find('h1, h2, h3').each(function() {
var $el = $(this);
var num = parseInt(this.nodeName[1]);
var text = $el.text();
var slug = Flatdoc.slugify(text);
if (num > 1) slug = slugs[num - 2] + '-' + slug;
slugs.length = num - 1;
slugs = slugs.concat([slug, slug]);
$el.attr('id', slug);
});
};
/**
* Returns menu data for a given HTML.
*
* menu = Flatdoc.transformer.getMenu($content);
* menu == {
* level: 0,
* items: [{
* section: "Getting started",
* level: 1,
* items: [...]}, ...]}
*/
Transformer.getMenu = function($content) {
var root = {items: [], id: '', level: 0};
var cache = [root];
function mkdir_p(level) {
cache.length = level + 1;
var obj = cache[level];
if (!obj) {
var parent = (level > 1) ? mkdir_p(level-1) : root;
obj = { items: [], level: level };
cache = cache.concat([obj, obj]);
parent.items.push(obj);
}
return obj;
}
$content.find('h1, h2, h3').each(function() {
var $el = $(this);
var level = +(this.nodeName.substr(1));
var parent = mkdir_p(level-1);
var obj = { section: $el.text(), items: [], level: level, id: $el.attr('id') };
parent.items.push(obj);
cache[level] = obj;
});
return root;
};
/**
* Changes "button >" text to buttons.
*/
Transformer.buttonize = function($content) {
$content.find('a').each(function() {
var $a = $(this);
var m = $a.text().match(/^(.*) >$/);
if (m) $a.text(m[1]).addClass('button');
});
};
/**
* Applies smart quotes to a given element.
* It leaves `code` and `pre` blocks alone.
*/
Transformer.smartquotes = function ($content) {
var nodes = getTextNodesIn($content), len = nodes.length;
for (var i=0; i
1&&o.length>1)){e=r.slice(u+1).join("\n")+e;u=p-1}}i=s||/\n\n(?!\s*$)/.test(h);if(u!==p-1){s=h[h.length-1]==="\n";if(!i)i=s}this.tokens.push({type:i?"loose_item_start":"list_item_start"});this.token(h,false);this.tokens.push({type:"list_item_end"})}this.tokens.push({type:"list_end"});continue}if(r=this.rules.html.exec(e)){e=e.substring(r[0].length);this.tokens.push({type:this.options.sanitize?"paragraph":"html",pre:r[1]==="pre"||r[1]==="script",text:r[0]});continue}if(n&&(r=this.rules.def.exec(e))){e=e.substring(r[0].length);this.tokens.links[r[1].toLowerCase()]={href:r[2],title:r[3]};continue}if(n&&(r=this.rules.table.exec(e))){e=e.substring(r[0].length);h={type:"table",header:r[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:r[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:r[3].replace(/(?: *\| *)?\n$/,"").split("\n")};for(u=0;u "+this.inline.output(this.token.text)+" "+this.parseText()+""+r(l[2],true)+"";continue}if(l=this.rules.br.exec(t)){t=t.substring(l[0].length);e+="
";continue}if(l=this.rules.del.exec(t)){t=t.substring(l[0].length);e+=""+this.output(l[1])+"";continue}if(l=this.rules.text.exec(t)){t=t.substring(l[0].length);e+=r(l[0]);continue}if(t){throw new Error("Infinite loop on byte: "+t.charCodeAt(0))}}return e};s.prototype.outputLink=function(t,e){if(t[0][0]!=="!"){return'"+this.output(t[1])+""}else{return'"}};s.prototype.smartypants=function(t){if(!this.options.smartypants)return t;return t.replace(/--/g,"—").replace(/'([^']*)'/g,"‘$1’").replace(/"([^"]*)"/g,"“$1”").replace(/\.{3}/g,"…")};s.prototype.mangle=function(t){var e="",n=t.length,s=0,i;for(;s
\n"}case"heading":{return"
\n"}case"table":{var e="",n,s,i,l,o;e+="\n"+this.token.text+"\n";for(s=0;s \n\n";e+="\n";for(s=0;s"+n+" \n"}e+=""+l+" \n"}e+="\n"}e+="\n"+e+"
\n"}case"blockquote_start":{var e="";while(this.next().type!=="blockquote_end"){e+=this.tok()}return"\n"+e+"
\n"}case"list_start":{var h=this.token.ordered?"ol":"ul",e="";while(this.next().type!=="list_end"){e+=this.tok()}return"<"+h+">\n"+e+""+h+">\n"}case"list_item_start":{var e="";while(this.next().type!=="list_item_end"){e+=this.token.type==="text"?this.parseText():this.tok()}return"
"+r(f.message+"",true)+""}throw f}}a.options=a.setOptions=function(t){h(a.defaults,t);return a};a.defaults={gfm:true,tables:true,breaks:false,pedantic:false,sanitize:false,smartLists:false,silent:false,highlight:null,langPrefix:"lang-"};a.Parser=i;a.parser=i.parse;a.Lexer=e;a.lexer=e.lex;a.InlineLexer=s;a.inlineLexer=s.output;a.parse=a;if(typeof exports==="object"){module.exports=a}else if(typeof define==="function"&&define.amd){define(function(){return a})}else{this.marked=a}}).call(function(){return this||(typeof window!=="undefined"?window:global)}()); /*! * base64.js * http://github.com/dankogai/js-base64 */ (function(r){"use strict";if(r.Base64)return;var e="2.1.2";var t;if(typeof module!=="undefined"&&module.exports){t=require("buffer").Buffer}var n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";var a=function(r){var e={};for(var t=0,n=r.length;t
element
* directly following them:
*
* h1:has(+ p)
*
* document.querySelectorAll(
* '.content > img + pre,' +
* '.content > img + blockquote,' +
* '.content > p + pre,' +
* '.content > p + blockquote,' +
* '.content > ul + pre,' +
* '.content > ul + blockquote,' +
* '.content > ol + pre,' +
* '.content > ol + blockquote,' +
* '.content > h1 + pre,' +
* '.content > h1 + blockquote,' +
* '.content > h2 + pre,' +
* '.content > h2 + blockquote,' +
* '.content > h3 + pre,' +
* '.content > h3 + blockquote,' +
* '.content > h4 + pre,' +
* '.content > h4 + blockquote,' +
* '.content > h5 + pre,' +
* '.content > h5 + blockquote,' +
* '.content > h6 + pre,' +
* '.content > h6 + blockquote,' +
* '.content > table + pre,' +
* '.content > table + blockquote'
* ).forEach(function(e) {e.previousSibling ? (e.previousSibling.style="clear:both") : null;})
*/
continueRight
clear:left
float:left
html
overflow-x: hidden
body, td, textarea, input
font-family: $body-font
line-height: 1.6
font-size: 16px
color:$txt
@media (max-width: 480px) /* Mobile */
font-size: 12px
a
color: $accent
text-decoration: none
&:hover
color: $accent * 0.8
/* ----------------------------------------------------------------------------
* Content styling
*/
.content
img
margin: 0
box-sizing: border-box
.imageContainer
margin: $vmargin 0
p, ul, ol, h1, h2, h3, h4, h5, h6, pre, blockquote
padding: $vmargin 0
box-sizing: border-box
h1, h2, h3, h4, h5, h6
font-weight: bold
antialias()
pre
font-family: $mono-font
ul > li
list-style-type: disc
ol > li
list-style-type: decimal
ul, ol
margin-left: 20px
/* jordwalke: This margin-right is needed to allow lists to "dock" to the
* left of a right block that spans multiple left blocks. The padding-right
* is needed regardless to get the right boundary of text to be a perfect box.
* TODO: This is the source of funny body size in mobile dimensions.
*/
margin-right: -20px
ul > li
list-style-type: none
position: relative
&:before
content: ''
display: block
position: absolute
left: -17px
top: 7px
width: 5px
height: 5px
border-radius: 4px
box-sizing: border-box
background: white
border: solid 1px $grey
li > :first-child
padding-top: 0
strong, b
font-weight: bold
i, em
font-style: italic
/*color: $grey*/
p > code
background: $codebg
border-radius: 2px;
border: 1px solid $codeborder
table > code, li > code, tr > code, td > code, th > code
background: $codebg
border-radius: 2px;
border: 1px solid $codeborder
code
font-family: $mono-font
padding: 1px 3px
font-size: 0.90em
pre > code
border: none;
background: $codebg
display: block
background: transparent
font-size: 0.90em
letter-spacing: 0px
blockquote
:first-child
padding-top: 0
:last-child
padding-bottom: 0
table
margin-top: $vmargin
margin-bottom: $vmargin
padding: 0
border-collapse: collapse
/* Tables in non split mode should not have any clearing */
/* clear: both */
/* float: left */
tbody
> :nth-child(2n)
background-color: $offwhite
tr
border-top: 1px solid $line
background-color: white
margin: 0
padding: 0
th
background-color: $offwhite
text-align: auto;
font-weight: bold
border: 1px solid $line
margin: 0
padding: 6px 13px
td
text-align: auto;
border: 1px solid $line
margin: 0
padding: 6px 13px
th, td
:first-child
margin-top: 0;
:last-child
margin-bottom: 0;
/* ----------------------------------------------------------------------------
* Content
*/
.content-root
min-height: 90%
position: relative
/* Shadow around image in literate right bar */
/* Remove outline while animating open */
/* It's still not enough to hide outline while closing */
.medium-zoom--opened
.content
.imageContainer
&:after
box-shadow: none !important
@media (min-width: 1180px) /* Big desktop */
body:not(.no-literate)
.content
blockquote
.imageContainer
/* So clicks pass through to image */
pointer-events: none
overflow:hidden;
display:inline-block;
position:relative;
border-radius:0px;
&:after
content: "";
position: absolute;
width: 100%; height: 100%;
left: 0; top: 0;
box-shadow: inset 0px 0px 0px 0.5px #55555511
img
vertical-align:middle;
/* Reenable pointer events */
pointer-events: auto
.content
padding-top: $pad - $vmargin
padding-bottom: $pad
padding-left: $pad
padding-right: $pad
clearfix()
img
width: 100%
max-width: 700px
blockquote
color: $grey
h1, h2, h3
antialias()
font-family: WordFont
padding-bottom: 0
+ p, ul, ol
padding-top: 10px
h1, h2
text-transform: uppercase
letter-spacing: 1px
font-size: 1.5em
h3
font-size: 1.2em
// Lines
h1, h2, .big-heading
padding-top: $pad * 1.5
&:before
display: block
content: ''
background: linear-gradient(left, rgba($line, 1.0) 80%, rgba($line, 0.0))
box-shadow: 0 1px 0 rgba(white, 0.4)
height: 1px
position: relative
top: $pad * -1
left: $pad * -1
/* width: 100% */
@media (max-width: 768px) /* Mobile and tablet */
padding-top: $minipad * 2
&:before
background: $line
left: $pad * -1
top: $minipad * -1
width: 120%
// Small headings
h4, h5, h6, .small-heading
border-bottom: solid 1px rgba(black, 0.07)
color: $grey
padding-top: $vmargin * 2
padding-bottom: 10px
body:not(.big-h3) & h3
@extends .content .small-heading
font-size: 0.9em
body.big-h3 & h3
@extends .content .big-heading
h1:first-child
padding-top: 0
&, a, a:visited
color: $txt
&:before
display: none
@media (max-width: 768px) /* Tablet */
.content
h4, h5, h6, .small-heading, body:not(.big-h3) & h3
padding-top: $vmargin * 1.5
@media (max-width: 480px) /* Mobile */
.content
padding: $minipad
padding-top: $minipad * 2
h4, h5, h6, .small-heading, body:not(.big-h3) & h3
padding-top: $vmargin
// ----------------------------------------------------------------------------
// Code blocks
inset-box()
background: $offwhite
/* border: solid 1px $offwhite*0.95 */
/* border-top: solid 1px $offwhite*0.9 */
/* border-left: solid 1px $offwhite*0.93 */
display: block
padding: 10px
border-radius: 2px
overflow: auto
scrollbar($offwhite)
body.no-literate .content pre > code
inset-box()
@media (max-width: 1180px) /* Small desktop */
.content pre > code
inset-box()
/* Hide scrollbar but keep functionality */
pre > code
&::-webkit-scrollbar
display: none
/* Hide scrollbar for IE, Edge and Firefox */
pre > code
overflow-x:scroll
-ms-overflow-style: none /* IE and Edge */
scrollbar-width: none /* Firefox */
// ----------------------------------------------------------------------------
// Buttons
.button
antialias()
font-family: WordFont, sans-serif
letter-spacing: 0px
font-weight: bold
display: inline-block
padding: 3px 25px
border: solid 2px $accent
border-radius: 20px
margin-right: 15px
&, &:visited
background: $accent
color: white
text-shadow: none
&:hover
border-color: #111
background: #111
color: white
&.light
&, &:visited
background: transparent
color: $grey
border-color: $grey
text-shadow: none
&:hover
border-color: $grey
background: $grey
color: white
.content
.button + em
color: $grey
// ----------------------------------------------------------------------------
// Literate mode content
@media (min-width: 1180px) /* Big desktop */
body:not(.no-literate)
.content-root
background-color: $offwhite
$w = ($sidebar-width + $content-width)
/* Fix major perf issue on firefox by commenting this out: https://github.com/zachlatta/reason/commit/787b8f783288d0337a3fab5b71c54d6d5630ef6e */
box-shadow: inset $w 0 white, inset ($w + 1) 0 $line, inset ($w + 10) 0 5px -10px rgba(black, $shadow-str)
// Literate mode
@media (min-width: 1180px) /* Big desktop */
small-heading()
margin-left: $pad
width: $content-width - $pad * 2
margin-bottom: 3px
padding-left: 0
padding-right: 0
body:not(.no-literate)
.content
padding-left: 0
padding-right: 0
width: $content-width + $pre-width
max-width: none
>
.flatdoc-synced-up-left
clear: both
/*.flatdoc-synced-up-right
margin-top:$pad */
p, ul, ol, h1, h2, h3, h4, h5, h6, pre, blockquote
width: $content-width
box-sizing: border-box
padding-right: $pad
padding-left: $pad
h1, h2, h3
clear: both
/* width: 100% */
pre, blockquote
width: $pre-width
padding-left: ($pad/2)
padding-right: ($pad/2)
float: right
clear: right
+
p, ul, ol, h4, h5, h6
clear: both
p, ul, ol, h4, h5, h6
float: left
clear: left
h4, h5, h6, .small-heading, body:not(.big-h3) & h3
small-heading()
table
/* jordwalke: Moved the clearing only to here instead of the general table rule.
* Otherwise, the following layout is messed up when resizing the window to be narrow:
*
* `type Unix.error`
* : Error codes for specific `Unix` module errors.
*
* Value | Meaning
* :------------------- |:------------------------------------------
* `EACCES` | Permission denied
* `EAGAIN` | Resource temporarily unavailable; try again
* `EBADF` | Bad file descriptor
* `EBUSY` | Resource unavailable
* `EEXISTS` | File already exists
* `EISDIR` | Is a directory
* `ENOENT` | No such file or directory
* `ENOTDIR` | Not a directory
* `ENOTEMPTY` | Directory not empty
* [See all](https://caml.inria.fr/pub/docs/manual-ocaml/libref/Unix.html#TYPEerror) |
*
*
* `fun Unix.dup` : `(~cloexec:bool=?, ` `file_descr` `) => ` `file_descr`
* : Return a new file descriptor referencing the same file as the given
* descriptor. See `Unix.set_close_on_exec` for documentation on the `cloexec`
* optional argument.
* (Copied from stdlib docs)
*/
clear: left
float: left
margin-left: $pad
margin-right: $pad
max-width: $content-width - $pad*2
body:not(.no-literate):not(.big-h3)
.content > h3
small-heading()
// ----------------------------------------------------------------------------
// Header
.medium-zoom--opened > .header
/* Needs to be less than medium-zoom overlays */
z-index:50
.header
background: $headerbg
z-index:101
/* text-shadow: 0 1px 0 rgba(white, 0.5) */
border-bottom: solid 1px $line
padding: 15px 15px 15px $sidepad
position: -webkit-sticky
position: sticky
top: 0px;
clearfix()
line-height: 20px
.left
float: left
.right
text-align: right
position: absolute
right: 15px
top: 15px
iframe
display: inline-block
vertical-align: middle
h1
antialias()
font-weight: bold
font-family: WordFont, sans-serif
font-size: 1em
&, a, a:visited
color: $grey
a:hover
color: $txt
li a
font-size: 1em
color: $grey
display: block
&:hover
color: $grey*0.4
@media (min-width: 480px) /* Tablet & Desktop */
h1
float: left
ul, li
display: block
float: left
ul
margin-left: -15px
h1 + ul
border-left: solid 1px $line
margin-left: 15px
li
border-left: solid 1px rgba(white, 0.5)
border-right: solid 1px $line
&:last-child
border-right: 0
li a
padding: 0 15px
@media (max-width: 480px) /* Mobile */
// Hide extra stuff on mobile
.right
display: none
// ----------------------------------------------------------------------------
// Sidebar
.menubar
antialias()
.section
padding: $sidepad $sidepad
box-sizing: border-box
.section + .section
border-top: solid 1px $line
.section.no-line
border-top: 0
padding-top: 0
a.big.button
display: block
box-sizing: border-box
width: 100%
padding: 10px 20px
text-align: center
font-weight: bold
font-size: 1.1em
background: transparent
border: solid 3px $accent
border-radius: 30px
font-family: WordFont, sans-serif
&, &:visited
color: $accent
text-decoration: none
&:hover
background: $accent
&, &:visited
color: white
@media (max-width: 480px) /* Mobile */
.menubar
padding: $minipad
border-bottom: solid 1px $line
@media (max-width: 768px) /* Mobile and tablet */
.menubar
display: none
@media (min-width: 768px) /* Desktop */
.content-root
padding-left: $sidebar-width
.menubar
position: absolute
left: 0
top: 0
bottom: 0
width: $sidebar-width
border-right: solid 1px $line
.menubar.fixed
position: fixed
scrollbar()
overflow-y: auto
.menubar
font-size: 0.9em
// Sticky left menu:
.menu.section
position: -webkit-sticky
position: sticky
/* header height */
top: 35px
.bottom.section
position: -webkit-sticky
position: sticky
/* header height */
top: 35px
.noselect
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Safari */
-khtml-user-select: none; /* Konqueror HTML */
-moz-user-select: none; /* Old versions of Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */
user-select: none; /* Non-prefixed version, currently supported by Chrome, Edge, Opera and Firefox */
// Menu items
.menu
ul.level-1 > li + li
margin-top: 20px
a
box-sizing: border-box
position: relative
display: block
padding-top: 4px
padding-bottom: 4px
margin-right: ($sidepad * -1)
color: $txtlight
font-weight: 500!important
&:hover
color: $accent*0.8
a.level-1
font-family: WordFont, sans-serif
text-transform: uppercase
font-size: 1em
font-weight: bold
&, &:visited
color: $grey
&:hover
color: $grey*0.6
a.level-2
font-size: 1em
font-weight: normal
a.level-3
font-weight: normal
font-size: 1em
padding-left: 8px
a.active
&, &:visited, &:hover
color: $accent
// Indicator
&:after
visibility: hidden
content: ''
display: block
box-sizing: border-box
position: absolute
top: 10px
right: $sidepad
width: 9px
height: 3px
border-radius: 2px
background: $accent
// ----------------------------------------------------------------------------
// Syntax highlighting
code
.string, .number
color: #3ac
.init
color: #383
.keyword
font-weight: bold
.comment
color: $grey * 1.2
// ----------------------------------------------------------------------------
.content
.large-brief & > h1:first-child + p,
> p.brief
font-size: 1.3em
font-family: Open Sans, sans-serif
font-weight: 300
// ----------------------------------------------------------------------------
.title-area
min-height: 100px
box-sizing: border-box
antialias()
text-align: center
border-bottom: solid 1px $line
overflow: hidden
> img.bg
z-index: 0
// Start it off screen
position: absolute
left: -9999px
> div
position: relative
z-index: 1
// ----------------- Styles just for "this page" -----------------------------
.title-card
text-rendering: optimizeLegibility !important
-webkit-font-smoothing: antialiased !important
-moz-osx-font-smoothing: grayscale
.title-card
/* background: #1d3b47; */
/* background-position: center center; */
/* background-size: cover; */
color: #ccc
text-align: center
position: relative
z-index: 1
width: 100%
display: table
.title-card > .in
display: table-cell
vertical-align: middle
.title-card .headline
vertical-align: middle
display: inline-block
-webkit-box-sizing: border-box
box-sizing: border-box
/* text-shadow: 0 0 4px rgba(0, 0, 0, 0.5); */
padding-left: 40px
padding-right: 40px
max-width: 800px
line-height: 1.7
.title-card h1
font-family: 'Open Sans', sans-serif
font-size: 6em
font-weight: 600
line-height: 1.1
margin-bottom: 0.5em
color: #333
.title-card p
font-family: WordFont, sans-serif
color: #777
font-weight: bold
font-size: 1.5em
.title-card h5
margin-top: 50px
margin-bottom: 50px
font-family: WordFont, sans-serif
color: #777
letter-spacing: 2px
font-size: 1.8em
.title-card h5 span
display: inline-block
padding: 5px 20px
border: solid 3px #fff
@media (max-width: 768px) /* Tablet */
.title-card h5
margin-top: 100px
@media (max-width: 480px) /* Mobile */
.title-card
padding: 50px 0
.title-card .headline
padding-left: 10px
padding-right: 10px
min-height: none
.title-card h1
font-size: 2.2em
.title-card p
font-size: 1.1em
.title-card h5
margin-top: 40px
font-size: 0.9em
================================================
FILE: unit-tests/runner/Lib.re
================================================
module TestFramework = {
include Rely.Make({
let config =
Rely.TestFrameworkConfig.initialize({
snapshotDir: "_build/default/unit-tests",
projectDir: "",
});
});
};
open TestFramework;
open PesyEsyPesyLib;
open PesyConf;
open PesyEsyPesyErrors.Errors;
open PesyEsyPesyUtils.Utils;
describe("PesyConf.resolveRelativePath", ({test, _}) => {
test("simple case", ({expect, _}) =>
expect.string(resolveRelativePath("foo/lib")).toEqual("foo/lib")
);
test("with ../ in the path", ({expect, _}) =>
expect.string(resolveRelativePath("foo/bar/..")).toEqual("foo")
);
test("with ../../ in the path", ({expect, _}) =>
expect.string(resolveRelativePath("foo/bar/../..")).toEqual("")
);
test("with excessive ../", ({expect, _}) =>
expect.fn(() => resolveRelativePath("foo/bar/../../..")).toThrowException(
ResolveRelativePathFailure(
"Failed resolving: foo/bar/../../.. Too many `../`",
),
)
);
test("foo/bar/../baz/", ({expect, _}) =>
expect.string(resolveRelativePath("foo/bar/../baz/")).toEqual("foo/baz")
);
});
describe("PesyConf.moduleNameOf", ({test, _}) => {
test("simple case .re file", ({expect, _}) =>
expect.string(moduleNameOf("Foo.re")).toEqual("Foo")
);
test("simple case .ml file", ({expect, _}) =>
expect.string(moduleNameOf("Foo.ml")).toEqual("Foo")
);
});
describe("PesyConf.isValidBinaryFileName", ({test, _}) => {
test("For an invalid binary", ({expect, _}) =>
expect.bool(isValidBinaryFileName("Foo.re")).toBeFalse()
);
test("For a valid binary", ({expect, _}) =>
expect.bool(isValidBinaryFileName("Foo.exe")).toBeTrue()
);
});
describe("PesyConf.isValidSourceFile", ({test, _}) => {
test("For an invalid source file", ({expect, _}) =>
expect.bool(isValidSourceFile("Foo.exe")).toBeFalse()
);
test("For a valid source file", ({expect, _}) =>
expect.bool(isValidSourceFile("Foo.ml")).toBeTrue()
);
test("For a valid source file", ({expect, _}) =>
expect.bool(isValidSourceFile("Foo.re")).toBeTrue()
);
});
describe("PesyConf.isValidScopeName", ({test, _}) => {
test("For an invalid case", ({expect, _}) =>
expect.bool(isValidScopeName("myscope")).toBeFalse()
);
test("For a valid case", ({expect, _}) =>
expect.bool(isValidScopeName("@myscope")).toBeTrue()
);
});
describe("PesyConf.doubleKebabifyIfScoped", ({test, _}) => {
test("test foo-bar", ({expect, _}) =>
expect.string(doubleKebabifyIfScoped("foo-bar")).toEqual("foo-bar")
)
});
let testToPackages = (~duneVersion="1.11", jsonStr) => {
let json = JSON.ofString(jsonStr);
let pkgs = PesyConf.pkgs(json);
let pesyPackages = List.map(toPesyConf("", "", ~duneVersion), pkgs);
let dunePackages = List.map(toDunePackages("", ""), pesyPackages);
List.map(
p => {
let (_, d) = p;
d;
},
dunePackages,
);
};
describe("PesyConf.testToPackages", ({test, _}) => {
test("Sample config - 1", ({expect, _}) =>
expect.list(
testToPackages(
{|
{
"name": "foo",
"buildDirs": {
"test": {
"require": ["foo"],
"bin": { "Bar.exe": "Bar.re" }
}
}
}
|},
)
|> List.map(DuneFile.toString),
).
toEqual([
"(executables (names Bar) (modules (:standard)) (public_names Bar.exe)\n (libraries foo))\n",
])
);
test("Sample config - 2", ({expect, _}) =>
expect.list(
testToPackages(
{|
{
"name": "foo",
"buildDirs": {
"testlib": {
"require": ["foo"],
"namespace": "Foo",
"name": "bar.lib",
"modes": ["best"]
}
}
}
|},
)
|> List.map(DuneFile.toString),
).
toEqual([
"(library (name Foo) (modules (:standard)) (libraries foo) (modes best))\n",
])
);
test("Sample config - 3", ({expect, _}) =>
expect.list(
testToPackages(
{|
{
"name": "foo",
"buildDirs": {
"testlib": {
"require": ["foo"],
"namespace": "Foo",
"name": "bar.lib",
"cNames": ["stubs"]
}
}
}
|},
)
|> List.map(DuneFile.toString),
).
toEqual([
"(library (name Foo) (modules (:standard)) (libraries foo) (c_names stubs))\n",
])
);
test("Sample config - 4", ({expect, _}) =>
expect.list(
testToPackages(
{|
{
"name": "foo",
"buildDirs": {
"testlib": {
"namespace": "Foo",
"name": "bar.lib",
"implements": ["foo"]
}
}
}
|},
)
|> List.map(DuneFile.toString),
).
toEqual([
"(library (name Foo) (modules (:standard)) (implements foo))\n",
])
);
test("Sample config - 5", ({expect, _}) =>
expect.list(
testToPackages(
{|
{
"name": "foo",
"buildDirs": {
"testlib": {
"namespace": "Foo",
"name": "bar.lib",
"wrapped": false
}
}
}
|},
)
|> List.map(DuneFile.toString),
).
toEqual([
"(library (name Foo) (modules (:standard)) (wrapped false))\n",
])
);
test("Sample config - 6", ({expect, _}) =>
expect.list(
testToPackages(
{|
{
"name": "foo",
"buildDirs": {
"testlib": {
"bin": { "bar.exe": "Foo.re" },
"modes": ["best", "c"]
}
}
}
|},
)
|> List.map(DuneFile.toString),
).
toEqual([
"(executables (names Foo) (modules (:standard)) (public_names bar.exe)\n (modes (best c)))\n",
])
);
test("Sample config - 7", ({expect, _}) =>
expect.list(
testToPackages(
{|
{
"name": "foo",
"buildDirs": {
"testlib": {
"require": ["foo"],
"namespace": "Foo",
"name": "bar.lib",
"flags": ["-w", "-33+9"]
}
}
}
|},
)
|> List.map(DuneFile.toString),
).
toEqual([
"(library (name Foo) (modules (:standard)) (libraries foo) (flags -w -33+9))\n",
])
);
test("Sample config - 8", ({expect, _}) =>
expect.list(
testToPackages(
{|
{
"name": "foo",
"buildDirs": {
"testlib": {
"namespace": "Foo",
"name": "bar.lib",
"ocamlcFlags": ["-annot", "-c"]
}
}
}
|},
)
|> List.map(DuneFile.toString),
).
toEqual([
"(library (name Foo) (modules (:standard)) (ocamlc_flags -annot -c))\n",
])
);
test("Sample config - 9", ({expect, _}) =>
expect.list(
testToPackages(
{|
{
"name": "foo",
"buildDirs": {
"testlib": {
"namespace": "Foo",
"name": "bar.lib",
"ocamloptFlags": ["-rectypes", "-nostdlib"]
}
}
}
|},
)
|> List.map(DuneFile.toString),
).
toEqual([
"(library (name Foo) (modules (:standard))\n (ocamlopt_flags -rectypes -nostdlib))\n",
])
);
test("Sample config - 10", ({expect, _}) =>
expect.list(
testToPackages(
{|
{
"name": "foo",
"buildDirs": {
"testlib": {
"namespace": "Foo",
"name": "bar.lib",
"jsooFlags": ["-pretty", "-no-inline"]
}
}
}
|},
)
|> List.map(DuneFile.toString),
).
toEqual([
"(library (name Foo) (modules (:standard)) (js_of_ocaml -pretty -no-inline))\n",
])
);
test("Sample config - 11", ({expect, _}) =>
expect.list(
testToPackages(
{|
{
"name": "foo",
"buildDirs": {
"testlib": {
"namespace": "Foo",
"name": "bar.lib",
"preprocess": [ "pps", "lwt_ppx" ]
}
}
}
|},
)
|> List.map(DuneFile.toString),
).
toEqual([
"(library (name Foo) (modules (:standard)) (preprocess (pps lwt_ppx)))\n",
])
);
test("Sample config - 12", ({expect, _}) =>
expect.list(
testToPackages(
{|
{
"name": "foo",
"buildDirs": {
"testlib": {
"namespace": "Foo",
"name": "bar.lib",
"includeSubdirs": "unqualified"
}
}
}
|},
)
|> List.map(DuneFile.toString),
).
toEqual([
"(library (name Foo) (modules (:standard)))\n(include_subdirs unqualified)\n",
])
);
test("Sample config - 12", ({expect, _}) =>
expect.list(
testToPackages(
{|
{
"name": "foo",
"buildDirs": {
"testlib": {
"namespace": "Foo",
"name": "bar.lib",
"includeSubdirs": "unqualified"
}
}
}
|},
)
|> List.map(DuneFile.toString),
).
toEqual([
"(library (name Foo) (modules (:standard)))\n(include_subdirs unqualified)\n",
])
);
test("Sample config - 14", ({expect, _}) =>
expect.list(
testToPackages(
{|
{
"name": "foo",
"buildDirs": {
"testexe": {
"bin": { "Foo.exe": "Foo.re" },
"rawBuildConfigFooter": [
"(install (section share_root) (files (asset.txt as asset.txt)))"
]
}
}
}
|},
)
|> List.map(DuneFile.toString),
).
toEqual([
"(executables (names Foo) (modules (:standard)) (public_names Foo.exe))\n(install (section share_root) (files (asset.txt as asset.txt)))\n",
])
);
test("Sample config - 14", ({expect, _}) =>
expect.list(
testToPackages(
{|
{
"name": "foo",
"buildDirs": {
"testexe": {
"bin": { "Foo.exe": "Foo.re" }
}
}
} |},
)
|> List.map(DuneFile.toString),
).
toEqual([
"(executables (names Foo) (modules (:standard)) (public_names Foo.exe))\n",
])
);
test("Sample config - 15", ({expect, _}) =>
expect.list(
testToPackages(
{|
{
"name": "foo",
"buildDirs": {
"testlib": {
"namespace": "Foo",
"name": "bar.lib",
"rawBuildConfigFooter": [
"(install (section share_root) (files (asset.txt as asset.txt)))"
]
}
}
}
|},
)
|> List.map(DuneFile.toString),
).
toEqual([
"(library (name Foo) (modules (:standard)))\n(install (section share_root) (files (asset.txt as asset.txt)))\n",
])
);
test("Sample config - 16", ({expect, _}) =>
expect.list(
testToPackages(
{|
{
"name": "foo",
"buildDirs": {
"testlib": {
"namespace": "Foo",
"name": "bar.lib",
"rawBuildConfig": [
"(libraries lwt lwt.unix raw.lib)",
"(preprocess (pps lwt_ppx))"
]
}
}
}
|},
)
|> List.map(DuneFile.toString),
).
toEqual([
"(library (name Foo) (modules (:standard)) (libraries lwt lwt.unix raw.lib)\n (preprocess (pps lwt_ppx)))\n",
])
);
test("Sample config - 17", ({expect, _}) =>
expect.list(
testToPackages(
{|
{
"name": "foo",
"buildDirs": {
"testlib": {
"require": ["foo"],
"namespace": "Foo",
"name": "bar.lib",
"flags": "-verbose"
}
}
}
|},
)
|> List.map(DuneFile.toString),
).
toEqual([
"(library (name Foo) (modules (:standard)) (libraries foo) (flags -verbose))\n",
])
);
test("Sample config - 18", ({expect, _}) =>
expect.list(
testToPackages(
{|
{
"name": "foo",
"buildDirs": {
"testlib": {
"namespace": "Foo",
"name": "bar.lib",
"ocamlcFlags": "-annot -c"
}
}
}
|},
)
|> List.map(DuneFile.toString),
).
toEqual([
"(library (name Foo) (modules (:standard)) (ocamlc_flags -annot -c))\n",
])
);
test("Sample config - 19", ({expect, _}) =>
expect.list(
testToPackages(
{|
{
"name": "foo",
"buildDirs": {
"testlib": {
"namespace": "Foo",
"name": "bar.lib",
"ocamloptFlags": "-rectypes -nostdlib"
}
}
}
|},
)
|> List.map(DuneFile.toString),
).
toEqual([
"(library (name Foo) (modules (:standard))\n (ocamlopt_flags -rectypes -nostdlib))\n",
])
);
test("Sample config - 20", ({expect, _}) =>
expect.list(
testToPackages(
{|
{
"name": "foo",
"buildDirs": {
"testlib": {
"namespace": "Foo",
"name": "bar.lib",
"jsooFlags": "-pretty -no-inline"
}
}
}
|},
)
|> List.map(DuneFile.toString),
).
toEqual([
"(library (name Foo) (modules (:standard)) (js_of_ocaml -pretty -no-inline))\n",
])
);
test("Sample config - 21", ({expect, _}) =>
expect.list(
testToPackages(
{|
{
"name": "foo",
"buildDirs": {
"testlib": {
"bin": { "bar.bc.js": "Foo.re" },
"modes": "js"
}
}
}
|},
)
|> List.map(DuneFile.toString),
).
toEqual([
"(executables (names Foo) (modules (:standard)) (public_names bar.bc.js)\n (modes js))\n",
])
);
test("Sample config - 22", ({expect, _}) =>
expect.list(
testToPackages(
{|
{
"name": "foo",
"buildDirs": {
"testlib": {
"bin": { "bar.exe": "Foo.re" },
"modes": "exe object shared_object"
}
}
}
|},
)
|> List.map(DuneFile.toString),
).
toEqual([
"(executables (names Foo) (modules (:standard)) (public_names bar.exe)\n (modes exe object shared_object))\n",
])
);
test("Sample config - 23", ({expect, _}) =>
expect.list(
testToPackages(
{|
{
"name": "foo",
"buildDirs": {
"testlib": {
"bin": { "bar.exe": "Foo.re" },
"modes": ["js", "plugin"]
}
}
}
|},
)
|> List.map(DuneFile.toString),
).
toEqual([
"(executables (names Foo) (modules (:standard)) (public_names bar.exe)\n (modes js plugin))\n",
])
);
test("Sample config - 24", ({expect, _}) =>
expect.list(
testToPackages(
{|
{
"name": "foo",
"buildDirs": {
"testlib": {
"bin": { "bar.exe": "Foo.re" },
"modes": [["byte", "exe"], ["byte", "js"]]
}
}
}
|},
)
|> List.map(DuneFile.toString),
).
toEqual([
"(executables (names Foo) (modules (:standard)) (public_names bar.exe)\n (modes (byte exe) (byte js)))\n",
])
);
test("Sample config - 25", ({expect, _}) =>
expect.list(
testToPackages(
{|
{
"name": "foo",
"buildDirs": {
"testlib": {
"bin": { "bar.exe": "Foo.re" },
"modes": ["byte", "exe"]
}
}
}
|},
)
|> List.map(DuneFile.toString),
).
toEqual([
"(executables (names Foo) (modules (:standard)) (public_names bar.exe)\n (modes (byte exe)))\n",
])
);
test("Sample config - 26", ({expect, _}) =>
expect.list(
testToPackages(
{|
{
"name": "foo",
"buildDirs": {
"testlib": {
"bin": { "bar.exe": "Foo.re" },
"modes": ["exe"]
}
}
}
|},
)
|> List.map(DuneFile.toString),
).
toEqual([
"(executables (names Foo) (modules (:standard)) (public_names bar.exe)\n (modes exe))\n",
])
);
test("Sample config - 27", ({expect, _}) =>
expect.list(
testToPackages(
{|
{
"name": "foo",
"buildDirs": {
"testlib": {
"require": ["foo"],
"namespace": "Foo",
"name": "bar.lib",
"foreignStubs": [
{
"language": "c"
}
]
}
}
}
|},
~duneVersion="2.0",
)
|> List.map(DuneFile.toString),
).
toEqual([
"(library (name Foo) (modules (:standard)) (libraries foo)\n (foreign_stubs (language c) (names :standard) (flags :standard)))\n",
])
);
test("Sample config - 28", ({expect, _}) =>
expect.list(
testToPackages(
{|
{
"name": "foo",
"buildDirs": {
"testlib": {
"require": ["foo"],
"namespace": "Foo",
"name": "bar.lib",
"foreignStubs": [
{
"language": "c",
"names": ["src1", "src2"],
"flags": []
}
]
}
}
}
|},
~duneVersion="2.0",
)
|> List.map(DuneFile.toString),
).
toEqual([
"(library (name Foo) (modules (:standard)) (libraries foo)\n (foreign_stubs (language c) (names src1 src2) (flags :standard)))\n",
])
);
test("Sample config - 29", ({expect, _}) =>
expect.list(
testToPackages(
{|
{
"name": "foo",
"buildDirs": {
"testlib": {
"require": ["foo"],
"namespace": "Foo",
"name": "bar.lib",
"foreignStubs": [
{
"language": "c",
"names": ["src1", "src2"]
},
{
"language": "cxx",
"names": ["src3"],
"flags": ["-02"]
}
]
}
}
}
|},
~duneVersion="2.0",
)
|> List.map(DuneFile.toString),
).
toEqual([
"(library (name Foo) (modules (:standard)) (libraries foo)\n (foreign_stubs (language c) (names src1 src2) (flags :standard))\n (foreign_stubs (language cxx) (names src3) (flags -02)))\n",
])
);
test("Sample config - 30", ({expect, _}) =>
expect.list(
testToPackages(
{|
{
"name": "foo",
"buildDirs": {
"testlib": {
"require": ["foo"],
"namespace": "Foo",
"name": "bar.lib",
"cNames": ["stubs"],
"foreignStubs": [
{
"language": "c",
"names": ["src1", "src2"]
},
{
"language": "cxx",
"names": ["src3"],
"flags": ["-02"]
}
]
}
}
}
|},
~duneVersion="2.0",
)
|> List.map(DuneFile.toString),
).
toEqual([
"(library (name Foo) (modules (:standard)) (libraries foo)\n (foreign_stubs (language c) (names src1 src2) (flags :standard))\n (foreign_stubs (language cxx) (names src3) (flags -02)))\n",
])
);
test("Sample config - 31", ({expect, _}) =>
expect.list(
testToPackages(
{|
{
"name": "foo",
"buildDirs": {
"testlib": {
"require": ["foo"],
"namespace": "Foo",
"name": "bar.lib",
"cNames": ["stub1", "stub2"],
"foreignStubs": [
{
"language": "c",
"names": ["src1", "src2"]
},
{
"language": "cxx",
"names": ["src3"],
"flags": ["-02"]
}
]
}
}
}
|},
~duneVersion="1.11",
)
|> List.map(DuneFile.toString),
).
toEqual([
"(library (name Foo) (modules (:standard)) (libraries foo)\n (c_names stub1 stub2))\n",
])
);
});
================================================
FILE: unit-tests/runner/RunUnitTests.re
================================================
module UtilsTests = Utils;
module LibTests = Lib;
LibTests.TestFramework.cli();
UtilsTests.TestFramework.cli();
================================================
FILE: unit-tests/runner/Utils.re
================================================
module TestFramework = {
include Rely.Make({
let config =
Rely.TestFrameworkConfig.initialize({
snapshotDir: "_build/default/unit-tests",
projectDir: "",
});
});
};
open PesyEsyPesyUtils;
open TestFramework;
open Utils;
describe("Utils.filterNone", ({test, _}) => {
test("simple case", ({expect, _}) =>
expect.list(filterNone([Some(1), None, None, Some(2), None, Some(3)])).
toEqual([
1,
2,
3,
])
)
});
================================================
FILE: unit-tests/runner/dune
================================================
(test
(name RunUnitTests)
(libraries pesy--esy-pesy.lib pesy--esy-pesy.utils rely.lib))
================================================
FILE: utils/FieldTypes.re
================================================
open Printf;
type t =
| Bool(bool)
| String(string)
| List(list(t));
exception ConversionException(string);
let toBool =
fun
| Bool(b) => b
| String(s) =>
raise(
ConversionException(sprintf("Expected string. Actual string (%s)", s)),
)
| List(_l) => raise(ConversionException("Expected string. Actual list"));
let toString =
fun
| String(s) => s
| Bool(b) =>
raise(
ConversionException(
sprintf("Expected string. Actual bool (%s)", string_of_bool(b)),
),
)
| List(_) => raise(ConversionException("Expected string. Actual list"));
let toList =
fun
| List(l) => l
| Bool(b) =>
raise(
ConversionException(
sprintf("Expected list. Actual bool (%s)", string_of_bool(b)),
),
)
| String(_) => raise(ConversionException("Expected list. Actual string"));
================================================
FILE: utils/JSON.re
================================================
/* TODO: Making parsing more lenient? Eg. allow string where single element list is valid */
include Yojson.Basic;
open Printf;
open Yojson.Basic;
type t = Yojson.Basic.t;
/* private */
exception MissingJSONMember(string);
/* public */
exception InvalidJSONValue(string);
exception NullJSONValue(unit);
let ofString = jstr => from_string(jstr);
let fromFile = path => from_file(path);
let member = (j, m) =>
try(Util.member(m, j)) {
| _ =>
raise(
MissingJSONMember(Printf.sprintf("%s was missing in the json", m)),
)
};
let toOption = v => v === `Null ? None : Some(v);
let toKeyValuePairs = (json: Yojson.Basic.t) =>
switch (json) {
| `Assoc(jsonKeyValuePairs) => jsonKeyValuePairs
| `Null => raise(NullJSONValue())
| _ => raise(InvalidJSONValue("Expected key value pairs"))
};
let toListKVPairs = (json: Yojson.Basic.t) =>
switch (json) {
| `List(kvs) => List.map(toKeyValuePairs, kvs)
| `Null => raise(NullJSONValue())
| _ => raise(InvalidJSONValue("Expected list of key value pairs"))
};
let rec toValue = (json: Yojson.Basic.t) =>
switch (json) {
| `Bool(b) => FieldTypes.Bool(b)
| `String(s) => FieldTypes.String(s)
| `List(jl) => FieldTypes.List(List.map(j => toValue(j), jl))
| `Null => raise(NullJSONValue())
| _ =>
raise(
InvalidJSONValue(
sprintf(
"Value must be either string, bool or list of string. Found %s",
to_string(json),
),
),
)
};
let debug = t => to_string(t);
================================================
FILE: utils/JSON.rei
================================================
include (module type of Yojson.Basic);
type t = Yojson.Basic.t;
exception NullJSONValue(unit);
exception InvalidJSONValue(string);
let ofString: string => t;
let fromFile: string => t;
let member: (t, string) => t;
let toOption: t => option(t);
let toKeyValuePairs: t => list((string, t));
let toListKVPairs: t => list(list((string, t)));
let toValue: t => FieldTypes.t;
let debug: t => string;
================================================
FILE: utils/Utils.re
================================================
open Printf;
let prompt = q => {
print_endline(q);
read_line();
};
let kebab = str => {
let charStrings = Str.split(Str.regexp_string(""), str);
let k =
String.concat(
"",
List.map(
c =>
if (Char.code(c.[0]) >= 65 && Char.code(c.[0]) <= 90) {
"-" ++ String.lowercase_ascii(c);
} else {
c;
},
charStrings,
),
);
if (k.[0] == '-') {
String.sub(k, 1, String.length(k) - 1);
} else {
k;
};
};
let getCurrentDirName = () => Filename.basename(Sys.getenv("PWD"));
let upperCamelCasify = kebab => {
let parts = Str.split(Str.regexp_string("-"), kebab);
let k = String.concat("", List.map(String.capitalize_ascii, parts));
if (String.length(k) > 0 && k.[0] == '-') {
String.sub(k, 1, String.length(k) - 1);
} else {
k;
};
};
let removeScope = kebab =>
Str.global_replace(Str.regexp("[^\\/]*/"), "", kebab);
module NoLwt = {
open Printf;
let write = (file, str) => {
let oc = open_out(file);
fprintf(oc, "%s", str);
close_out(oc);
};
};
let readFile = file => {
let buf = ref("");
let breakOut = ref(false);
let ic = open_in(file);
while (! breakOut^) {
let line =
try(input_line(ic)) {
| End_of_file =>
breakOut := true;
"";
};
buf := buf^ ++ "\n" ++ line;
};
buf^;
};
module Path = {
let (/) = Filename.concat;
};
let parent = Filename.dirname;
let loadTemplate = name =>
readFile(
Path.(
(Sys.executable_name |> parent |> parent)
/ "share"
/ "template-repo"
/ name
),
);
let buffer_size = 8192;
let buffer = Bytes.create(buffer_size);
let copyFile = (input_name, output_name) => {
open Unix;
let fd_in = openfile(input_name, [O_RDONLY], 0);
let fd_out = openfile(output_name, [O_WRONLY, O_CREAT, O_TRUNC], 438);
let rec copy_loop = () =>
switch (read(fd_in, buffer, 0, buffer_size)) {
| 0 => ()
| r =>
ignore(write(fd_out, buffer, 0, r));
copy_loop();
};
copy_loop();
close(fd_in);
close(fd_out);
};
let copyTemplate = (input_name, output_name) => {
copyFile(
Path.(
(Sys.executable_name |> parent |> parent)
/ "share"
/ "template-repo"
/ input_name
),
output_name,
);
};
let r = Str.regexp;
let exists = Sys.file_exists;
let mkdir = (~perms=?, p) =>
switch (perms) {
| Some(x) => Unix.mkdir(p, x)
| None => Unix.mkdir(p, 0o755)
};
let rec mkdirp = p => {
let directory_created =
try(Sys.is_directory(p)) {
| Sys_error(_) => false
};
if (!directory_created) {
mkdirp(Filename.dirname(p));
mkdir(p);
};
};
let spf = Printf.sprintf;
let renderAsciiTree = (dir, name, namespace, require, isLast) =>
if (isLast) {
spf({js|│
└─ %s
%s
%s
|js}, dir, name, namespace);
} else {
Printf.sprintf(
{js|│
├─ %s
│ %s
│ %s
|js},
dir,
name,
namespace,
)
++ (require == "" ? "" : (isLast ? " " : "│ ") ++ require);
};
/* Capable of rendering a tree of depth 1 */
exception RenderAsciiTreeChildrenError(string);
let renderAscTreeChildren =
fun
| [] =>
raise(RenderAsciiTreeChildrenError("Tree cannot have zero children"))
| [firstChild, ...restChildren] => {
let firstChildLog = sprintf("├─ %s", firstChild);
let restChildrenLog =
List.map(c => sprintf("│ %s", c), restChildren);
String.concat("\n", ["│", firstChildLog, ...restChildrenLog]);
};
let renderAscLastTree =
fun
| [] =>
raise(RenderAsciiTreeChildrenError("Tree cannot have zero children"))
| [firstChild, ...restChildren] => {
let firstChildLog = sprintf("└─ %s", firstChild);
let restChildrenLog = List.map(c => sprintf(" %s", c), restChildren);
String.concat("\n", ["│", firstChildLog, ...restChildrenLog]);
};
let rec renderAscTree =
fun
| [] => ()
| [t] => print_endline(renderAscLastTree(t))
| [t, ...rest] => {
print_endline(renderAscTreeChildren(t));
renderAscTree(rest);
};
let readFileOpt = f =>
if (exists(f)) {
Some(readFile(f));
} else {
None;
};
/* module Cache = { */
/* module CacheInternal = { */
/* type t = {path: string}; */
/* let init = path => {path: path}; */
/* }; */
/* let init = () => { */
/* /\* let cacheStoragePath = *\/ */
/* /\* Path.((Sys.executable_name |> parent |> parent) / "share" / "cache"); *\/ */
/* }; */
/* let get = (cache: CacheInternal.t, key: string) => {}; */
/* }; */
let commandOutput = (command, args) => {
open Unix;
let cmd =
String.concat(" ", Array.to_list(Array.append([|command|], args)));
let (cout, cin, cerr) = open_process_full(cmd, Unix.environment());
let repeat = ref(true);
let stdout = ref("");
let stderr = ref("");
while (repeat^) {
ignore(
try(stdout := stdout^ ++ input_line(cout)) {
| End_of_file => repeat := false
},
);
};
while (repeat^) {
ignore(
try(stderr := stderr^ ++ input_line(cerr)) {
| End_of_file => repeat := false
},
);
};
switch (close_process_full((cout, cin, cerr))) {
| WEXITED(_c) => (stdout^, stderr^)
| WSIGNALED(_c) => (stdout^, stderr^)
| WSTOPPED(_c) => (stdout^, stderr^)
};
};
let runCommandWithEnv = (command, args) => {
open Unix; /* let attach = */
/* Unix.create_process_env( */
/* command, */
/* Array.append([|command|], args), */
/* Unix.environment(), */
/* ); */
/* let pid = attach(Unix.stdin, Unix.stdout, Unix.stderr); */
/* switch (Unix.waitpid([], pid)) { */
/* | (_, WEXITED(c)) => c */
/* | (_, WSIGNALED(c)) => c */
/* | (_, WSTOPPED(c)) => c */
/* }; */
let cmd =
String.concat(" ", Array.to_list(Array.append([|command|], args)));
let (cout, cin, cerr) = open_process_full(cmd, Unix.environment());
let repeat = ref(true);
while (repeat^) {
ignore(
try({
let line = input_line(cerr);
print_endline(