diff --git a/.gitignore b/.gitignore index 5f489845d..9d99b5961 100644 --- a/.gitignore +++ b/.gitignore @@ -4,8 +4,6 @@ out *.swp *.swo -*.bs.js - # Generated via update-index script public/blog/feed.xml @@ -24,3 +22,6 @@ lib/ .vscode/ .vercel + +# for IDE tooling tests +bin.exe diff --git a/_blogposts/2020-08-28-new-rescript-logo.mdx b/_blogposts/2020-08-28-new-rescript-logo.mdx index 07bd5fdfb..efdd71d31 100644 --- a/_blogposts/2020-08-28-new-rescript-logo.mdx +++ b/_blogposts/2020-08-28-new-rescript-logo.mdx @@ -8,7 +8,7 @@ description: | Today, our resident designer Bettina is unveiling to us the fresh new ReScript branding we've been long waiting for. We hope you're as excited about the result as us! --- -import Image from "components/Image.bs"; +import Image from "components/Image"; ## Why the Rebranding? diff --git a/_blogposts/archive/state-of-reasonml-org-2020-q2-pt1.mdx b/_blogposts/archive/state-of-reasonml-org-2020-q2-pt1.mdx index f3bfd3810..17c8a6b5f 100644 --- a/_blogposts/archive/state-of-reasonml-org-2020-q2-pt1.mdx +++ b/_blogposts/archive/state-of-reasonml-org-2020-q2-pt1.mdx @@ -10,7 +10,7 @@ description: | to expect in the future. The first part is an introduction to our project. --- -import Image from "components/Image.bs"; +import Image from "components/Image"; ## Hello World! diff --git a/_blogposts/archive/state-of-reasonml-org-2020-q2-pt3.mdx b/_blogposts/archive/state-of-reasonml-org-2020-q2-pt3.mdx index 71a494def..4fa82c786 100644 --- a/_blogposts/archive/state-of-reasonml-org-2020-q2-pt3.mdx +++ b/_blogposts/archive/state-of-reasonml-org-2020-q2-pt3.mdx @@ -9,7 +9,7 @@ description: | talk about upcoming tools and features. --- -import Image from "components/Image.bs"; +import Image from "components/Image"; ## Future Tools for the Community diff --git a/_blogposts/archive/state-of-reasonml-org-2020-q2-pt4.mdx b/_blogposts/archive/state-of-reasonml-org-2020-q2-pt4.mdx index 4d4c275b0..c42715f6e 100644 --- a/_blogposts/archive/state-of-reasonml-org-2020-q2-pt4.mdx +++ b/_blogposts/archive/state-of-reasonml-org-2020-q2-pt4.mdx @@ -10,7 +10,7 @@ description: | This is the final part of the series about our vision of a more accessible Reason platform. --- -import Image from "components/Image.bs"; +import Image from "components/Image"; ## It's all Opinions diff --git a/bindings/IntlDateTimeFormat.js b/bindings/IntlDateTimeFormat.js new file mode 100644 index 000000000..98386503d --- /dev/null +++ b/bindings/IntlDateTimeFormat.js @@ -0,0 +1,143 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as Js_mapperRt from "bs-platform/lib/es6/js_mapperRt.js"; + +var _map = {"CH":"zh","RUS":"ru-RU","SWE":"sv-SE","US":"en-US"}; + +var _revMap = {"zh":"CH","ru-RU":"RUS","sv-SE":"SWE","en-US":"US"}; + +function localeToJs(param) { + return _map[param]; +} + +function localeFromJs(param) { + return Js_mapperRt.raiseWhenNotFound(_revMap[param]); +} + +var _map$1 = {"long":"long","short":"short","narrow":"narrow"}; + +function tToJs(param) { + return param; +} + +function tFromJs(param) { + return Js_mapperRt.raiseWhenNotFound(_map$1[param]); +} + +function make(value) { + return value; +} + +var Weekday = { + tToJs: tToJs, + tFromJs: tFromJs, + make: make +}; + +var _map$2 = {"long":"long","short":"short","narrow":"narrow"}; + +function tToJs$1(param) { + return param; +} + +function tFromJs$1(param) { + return Js_mapperRt.raiseWhenNotFound(_map$2[param]); +} + +function make$1(value) { + return value; +} + +var Era = { + tToJs: tToJs$1, + tFromJs: tFromJs$1, + make: make$1 +}; + +var _map$3 = {"numeric":"numeric","twoDigit":"2-digit"}; + +var _revMap$1 = {"numeric":"numeric","2-digit":"twoDigit"}; + +function tToJs$2(param) { + return _map$3[param]; +} + +function tFromJs$2(param) { + return Js_mapperRt.raiseWhenNotFound(_revMap$1[param]); +} + +function make$2(value) { + return _map$3[value]; +} + +var Year = { + tToJs: tToJs$2, + tFromJs: tFromJs$2, + make: make$2 +}; + +var _map$4 = {"numeric":"numeric","twoDigit":"2-digit"}; + +var _revMap$2 = {"numeric":"numeric","2-digit":"twoDigit"}; + +function tToJs$3(param) { + return _map$4[param]; +} + +function tFromJs$3(param) { + return Js_mapperRt.raiseWhenNotFound(_revMap$2[param]); +} + +function make$3(value) { + return _map$4[value]; +} + +var Day = { + tToJs: tToJs$3, + tFromJs: tFromJs$3, + make: make$3 +}; + +var _map$5 = {"long":"long","short":"short","narrow":"narrow","numeric":"numeric","twoDigit":"2-digit"}; + +var _revMap$3 = {"long":"long","short":"short","narrow":"narrow","numeric":"numeric","2-digit":"twoDigit"}; + +function tToJs$4(param) { + return _map$5[param]; +} + +function tFromJs$4(param) { + return Js_mapperRt.raiseWhenNotFound(_revMap$3[param]); +} + +function make$4(value) { + return _map$5[value]; +} + +var Month = { + tToJs: tToJs$4, + tFromJs: tFromJs$4, + make: make$4 +}; + +function make$5(localeOpt, options, date) { + var locale = localeOpt !== undefined ? localeOpt : "US"; + return new (Intl.DateTimeFormat)(localeToJs(locale), options).format(date); +} + +var $$Date = { + Weekday: Weekday, + Era: Era, + Year: Year, + Day: Day, + Month: Month, + make: make$5 +}; + +export { + localeToJs , + localeFromJs , + $$Date , + +} +/* No side effect */ diff --git a/bindings/IntlDateTimeFormat.re b/bindings/IntlDateTimeFormat.re deleted file mode 100644 index bb766d5ed..000000000 --- a/bindings/IntlDateTimeFormat.re +++ /dev/null @@ -1,83 +0,0 @@ -type intl; - -/* Supported locales */ -[@bs.deriving {jsConverter: newType}] -type locale = [ - | [@bs.as "zh"] `CH - | [@bs.as "ru-RU"] `RUS - | [@bs.as "sv-SE"] `SWE - | [@bs.as "en-US"] `US -]; - -module Date = { - module Weekday = { - [@bs.deriving {jsConverter: newType}] - type t = [ | `long | `short | `narrow]; - - let make = value => tToJs(value); - }; - - module Era = { - [@bs.deriving {jsConverter: newType}] - type t = [ | `long | `short | `narrow]; - - let make = value => tToJs(value); - }; - - module Year = { - [@bs.deriving {jsConverter: newType}] - type t = [ | `numeric | [@bs.as "2-digit"] `twoDigit]; - - let make = value => tToJs(value); - }; - - module Day = { - [@bs.deriving {jsConverter: newType}] - type t = [ | `numeric | [@bs.as "2-digit"] `twoDigit]; - - let make = value => tToJs(value); - }; - - module Month = { - /* Helper for month option */ - [@bs.deriving {jsConverter: newType}] - type t = [ - | `long - | `short - | `narrow - | `numeric - | [@bs.as "2-digit"] `twoDigit - ]; - - let make = value => tToJs(value); - }; - - /* Options for Intl.DateTimeFormat */ - [@bs.deriving abstract] - type options = { - [@bs.optional] - weekday: Weekday.abs_t, - [@bs.optional] - era: Era.abs_t, - [@bs.optional] - year: Year.abs_t, - [@bs.optional] - day: Day.abs_t, - [@bs.optional] - month: Month.abs_t, - }; - - /* - Intl.DateTimeFormat - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat - */ - [@bs.new] [@bs.scope "Intl"] - external dateTimeFormat: (abs_locale, option(options)) => intl = - "DateTimeFormat"; - - /* Intl.DateTimeFormat.prototype.format() */ - [@bs.send] external format: (intl, Js.Date.t) => string = "format"; - - let make = (~locale=`US, ~options=?, date) => - dateTimeFormat(localeToJs(locale), options)->format(date); -}; diff --git a/bindings/IntlDateTimeFormat.res b/bindings/IntlDateTimeFormat.res new file mode 100644 index 000000000..6048132fa --- /dev/null +++ b/bindings/IntlDateTimeFormat.res @@ -0,0 +1,71 @@ +type intl + +/* Supported locales */ +@bs.deriving({jsConverter: newType}) +type locale = [@bs.as("zh") #CH | @bs.as("ru-RU") #RUS | @bs.as("sv-SE") #SWE | @bs.as("en-US") #US] + +module Date = { + module Weekday = { + @bs.deriving({jsConverter: newType}) + type t = [#long | #short | #narrow] + + let make = value => tToJs(value) + } + + module Era = { + @bs.deriving({jsConverter: newType}) + type t = [#long | #short | #narrow] + + let make = value => tToJs(value) + } + + module Year = { + @bs.deriving({jsConverter: newType}) + type t = [#numeric | @bs.as("2-digit") #twoDigit] + + let make = value => tToJs(value) + } + + module Day = { + @bs.deriving({jsConverter: newType}) + type t = [#numeric | @bs.as("2-digit") #twoDigit] + + let make = value => tToJs(value) + } + + module Month = { + /* Helper for month option */ + @bs.deriving({jsConverter: newType}) + type t = [#long | #short | #narrow | #numeric | @bs.as("2-digit") #twoDigit] + + let make = value => tToJs(value) + } + + /* Options for Intl.DateTimeFormat */ + @bs.deriving(abstract) + type options = { + @bs.optional + weekday: Weekday.abs_t, + @bs.optional + era: Era.abs_t, + @bs.optional + year: Year.abs_t, + @bs.optional + day: Day.abs_t, + @bs.optional + month: Month.abs_t, + } + + /* + Intl.DateTimeFormat + https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat + */ + @bs.new @bs.scope("Intl") + external dateTimeFormat: (abs_locale, option) => intl = "DateTimeFormat" + + /* Intl.DateTimeFormat.prototype.format() */ + @bs.send external format: (intl, Js.Date.t) => string = "format" + + let make = (~locale=#US, ~options=?, date) => + dateTimeFormat(localeToJs(locale), options)->format(date) +} diff --git a/bindings/Next.js b/bindings/Next.js new file mode 100644 index 000000000..68f77eb54 --- /dev/null +++ b/bindings/Next.js @@ -0,0 +1,42 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + + +var Req = {}; + +var Res = {}; + +var GetServerSideProps = { + Req: Req, + Res: Res +}; + +var GetStaticProps = {}; + +var GetStaticPaths = {}; + +var Link = {}; + +var Events = {}; + +var Router = { + Events: Events +}; + +var Head = {}; + +var $$Error = {}; + +var Dynamic = {}; + +export { + GetServerSideProps , + GetStaticProps , + GetStaticPaths , + Link , + Router , + Head , + $$Error , + Dynamic , + +} +/* No side effect */ diff --git a/bindings/Next.re b/bindings/Next.re deleted file mode 100644 index 1d86fa7c1..000000000 --- a/bindings/Next.re +++ /dev/null @@ -1,139 +0,0 @@ -module GetServerSideProps = { - module Req = { - type t; - }; - - module Res = { - type t; - - [@bs.send] external setHeader: (t, string, string) => unit = "setHeader"; - [@bs.send] external write: (t, string) => unit = "write"; - [@bs.send] external end_: t => unit = "end"; - }; - - // See: https://github.com/zeit/next.js/blob/canary/packages/next/types/index.d.ts - type context('props, 'params) = { - params: Js.t('params), - query: Js.Dict.t(string), - req: Req.t, - res: Res.t, - }; - - type t('props, 'params) = - context('props, 'params) => Js.Promise.t({. "props": 'props}); -}; - -module GetStaticProps = { - // See: https://github.com/zeit/next.js/blob/canary/packages/next/types/index.d.ts - type context('props, 'params) = { - params: 'params, - query: Js.Dict.t(string), - req: Js.Nullable.t(Js.t('props)), - }; - - type t('props, 'params) = - context('props, 'params) => Promise.t({. "props": 'props}); -}; - -module GetStaticPaths = { - // 'params: dynamic route params used in dynamic routing paths - // Example: pages/[id].js would result in a 'params = { id: string } - type path('params) = {params: 'params}; - - type return('params) = { - paths: array(path('params)), - fallback: bool, - }; - - type t('params) = unit => Promise.t(return('params)); -}; - -module Link = { - [@bs.module "next/link"] [@react.component] - external make: - ( - ~href: string=?, - ~_as: string=?, - ~prefetch: bool=?, - ~replace: option(bool)=?, - ~shallow: option(bool)=?, - ~passHref: option(bool)=?, - ~children: React.element - ) => - React.element = - "default"; -}; - -module Router = { - /* - Make sure to only register events via a useEffect hook! - */ - module Events = { - type t; - - [@bs.send] - external on: - ( - t, - [@bs.string] [ - | `routeChangeStart(string => unit) - | `routeChangeComplete(string => unit) - | `hashChangeComplete(string => unit) - ] - ) => - unit = - "on"; - - [@bs.send] - external off: - ( - t, - [@bs.string] [ - | `routeChangeStart(string => unit) - | `routeChangeComplete(string => unit) - | `hashChangeComplete(string => unit) - ] - ) => - unit = - "off"; - }; - - type router = { - route: string, - asPath: string, - events: Events.t, - pathname: string, - query: Js.Dict.t(string), - }; - - [@bs.send] external push: (router, string) => unit = "push"; - - [@bs.module "next/router"] external useRouter: unit => router = "useRouter"; - [@bs.send] external replace: (router, string) => unit = "replace"; -}; - -module Head = { - [@bs.module "next/head"] [@react.component] - external make: (~children: React.element) => React.element = "default"; -}; - -module Error = { - [@bs.module "next/error"] [@react.component] - external make: (~statusCode: int, ~children: React.element) => React.element = - "default"; -}; - -module Dynamic = { - [@bs.deriving abstract] - type options = { - [@bs.optional] - ssr: bool, - [@bs.optional] - loading: unit => React.element, - }; - - [@bs.module "next/dynamic"] - external dynamic: (unit => Js.Promise.t('a), options) => 'a = "default"; - - [@bs.val] external import: string => Js.Promise.t('a) = "import"; -}; \ No newline at end of file diff --git a/bindings/Next.res b/bindings/Next.res new file mode 100644 index 000000000..d79263c3c --- /dev/null +++ b/bindings/Next.res @@ -0,0 +1,129 @@ +module GetServerSideProps = { + module Req = { + type t + } + + module Res = { + type t + + @bs.send external setHeader: (t, string, string) => unit = "setHeader" + @bs.send external write: (t, string) => unit = "write" + @bs.send external end_: t => unit = "end" + } + + // See: https://github.com/zeit/next.js/blob/canary/packages/next/types/index.d.ts + type context<'props, 'params> = { + params: Js.t<'params>, + query: Js.Dict.t, + req: Req.t, + res: Res.t, + } + + type t<'props, 'params> = context<'props, 'params> => Js.Promise.t<{"props": 'props}> +} + +module GetStaticProps = { + // See: https://github.com/zeit/next.js/blob/canary/packages/next/types/index.d.ts + type context<'props, 'params> = { + params: 'params, + query: Js.Dict.t, + req: Js.Nullable.t>, + } + + type t<'props, 'params> = context<'props, 'params> => Promise.t<{"props": 'props}> +} + +module GetStaticPaths = { + // 'params: dynamic route params used in dynamic routing paths + // Example: pages/[id].js would result in a 'params = { id: string } + type path<'params> = {params: 'params} + + type return<'params> = { + paths: array>, + fallback: bool, + } + + type t<'params> = unit => Promise.t> +} + +module Link = { + @bs.module("next/link") @react.component + external make: ( + ~href: string=?, + ~_as: string=?, + ~prefetch: bool=?, + ~replace: option=?, + ~shallow: option=?, + ~passHref: option=?, + ~children: React.element, + ) => React.element = "default" +} + +module Router = { + /* + Make sure to only register events via a useEffect hook! + */ + module Events = { + type t + + @bs.send + external on: ( + t, + @bs.string + [ + | #routeChangeStart(string => unit) + | #routeChangeComplete(string => unit) + | #hashChangeComplete(string => unit) + ], + ) => unit = "on" + + @bs.send + external off: ( + t, + @bs.string + [ + | #routeChangeStart(string => unit) + | #routeChangeComplete(string => unit) + | #hashChangeComplete(string => unit) + ], + ) => unit = "off" + } + + type router = { + route: string, + asPath: string, + events: Events.t, + pathname: string, + query: Js.Dict.t, + } + + @bs.send external push: (router, string) => unit = "push" + + @bs.module("next/router") external useRouter: unit => router = "useRouter" + @bs.send external replace: (router, string) => unit = "replace" +} + +module Head = { + @bs.module("next/head") @react.component + external make: (~children: React.element) => React.element = "default" +} + +module Error = { + @bs.module("next/error") @react.component + external make: (~statusCode: int, ~children: React.element) => React.element = "default" +} + +module Dynamic = { + @bs.deriving(abstract) + type options = { + @bs.optional + ssr: bool, + @bs.optional + loading: unit => React.element, + } + + @bs.module("next/dynamic") + external dynamic: (unit => Js.Promise.t<'a>, options) => 'a = "default" + + @bs.val external \"import": string => Js.Promise.t<'a> = "import" +} diff --git a/bindings/NodeGlob.js b/bindings/NodeGlob.js new file mode 100644 index 000000000..d856702bf --- /dev/null +++ b/bindings/NodeGlob.js @@ -0,0 +1,2 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE +/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ diff --git a/bindings/NodeGlob.re b/bindings/NodeGlob.re deleted file mode 100644 index 98219db17..000000000 --- a/bindings/NodeGlob.re +++ /dev/null @@ -1 +0,0 @@ -[@bs.module "glob"] external sync: string => array(string) = "sync"; diff --git a/bindings/NodeGlob.res b/bindings/NodeGlob.res new file mode 100644 index 000000000..a24d83e78 --- /dev/null +++ b/bindings/NodeGlob.res @@ -0,0 +1 @@ +@bs.module("glob") external sync: string => array = "sync" diff --git a/bindings/RescriptCompilerApi.js b/bindings/RescriptCompilerApi.js new file mode 100644 index 000000000..ac6d65d61 --- /dev/null +++ b/bindings/RescriptCompilerApi.js @@ -0,0 +1,479 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as Js_exn from "bs-platform/lib/es6/js_exn.js"; +import * as Js_dict from "bs-platform/lib/es6/js_dict.js"; +import * as Belt_Int from "bs-platform/lib/es6/belt_Int.js"; +import * as Belt_List from "bs-platform/lib/es6/belt_List.js"; +import * as Belt_Option from "bs-platform/lib/es6/belt_Option.js"; +import * as Json_decode from "@glennsl/bs-json/src/Json_decode.js"; +import * as Caml_js_exceptions from "bs-platform/lib/es6/caml_js_exceptions.js"; + +function toString(t) { + switch (t) { + case /* Reason */0 : + return "Reason"; + case /* OCaml */1 : + return "OCaml"; + case /* Res */2 : + return "ReScript"; + + } +} + +function toExt(t) { + switch (t) { + case /* Reason */0 : + return "re"; + case /* OCaml */1 : + return "ml"; + case /* Res */2 : + return "res"; + + } +} + +function decode(json) { + var other = Json_decode.string(json); + switch (other) { + case "ml" : + return /* OCaml */1; + case "re" : + return /* Reason */0; + case "res" : + return /* Res */2; + default: + throw { + RE_EXN_ID: Json_decode.DecodeError, + _1: "Unknown language \"" + other + "\"", + Error: new Error() + }; + } +} + +var Lang = { + toString: toString, + toExt: toExt, + decode: decode +}; + +function fromString(apiVersion) { + var match = Belt_List.fromArray(apiVersion.split(".")); + if (!match) { + return { + _0: apiVersion, + [Symbol.for("name")]: "UnknownVersion" + }; + } + var match$1 = match.tl; + if (!match$1) { + return { + _0: apiVersion, + [Symbol.for("name")]: "UnknownVersion" + }; + } + var maj = Belt_Int.fromString(match.hd); + Belt_Int.fromString(match$1.hd); + if (maj !== undefined && maj >= 1) { + return /* V1 */0; + } else { + return { + _0: apiVersion, + [Symbol.for("name")]: "UnknownVersion" + }; + } +} + +function defaultTargetLang(t) { + if (t) { + return /* Reason */0; + } else { + return /* Res */2; + } +} + +function availableLanguages(t) { + if (t) { + return [/* Res */2]; + } else { + return [ + /* Reason */0, + /* Res */2 + ]; + } +} + +var Version = { + fromString: fromString, + defaultTargetLang: defaultTargetLang, + availableLanguages: availableLanguages +}; + +function decode$1(json) { + return { + fullMsg: Json_decode.field("fullMsg", Json_decode.string, json), + shortMsg: Json_decode.field("shortMsg", Json_decode.string, json), + row: Json_decode.field("row", Json_decode.$$int, json), + column: Json_decode.field("column", Json_decode.$$int, json), + endRow: Json_decode.field("endRow", Json_decode.$$int, json), + endColumn: Json_decode.field("endColumn", Json_decode.$$int, json) + }; +} + +function toCompactErrorLine(prefix, locMsg) { + var prefix$1 = prefix === "W" ? "W" : "E"; + return "[" + prefix$1 + "] Line " + locMsg.row + ", " + locMsg.column + ": " + locMsg.shortMsg; +} + +function makeId(t) { + return String(t.row) + ("-" + (String(t.endRow) + ("-" + (String(t.column) + ("-" + String(t.endColumn)))))); +} + +function dedupe(arr) { + var result = {}; + for(var i = 0 ,i_finish = arr.length; i < i_finish; ++i){ + var locMsg = arr[i]; + var id = makeId(locMsg); + result[id] = locMsg; + } + return Js_dict.values(result); +} + +var LocMsg = { + decode: decode$1, + toCompactErrorLine: toCompactErrorLine, + makeId: makeId, + dedupe: dedupe +}; + +function decode$2(json) { + var warnNumber = Json_decode.field("warnNumber", Json_decode.$$int, json); + var details = decode$1(json); + if (Json_decode.field("isError", Json_decode.bool, json)) { + return { + TAG: 1, + warnNumber: warnNumber, + details: details, + [Symbol.for("name")]: "WarnErr" + }; + } else { + return { + TAG: 0, + warnNumber: warnNumber, + details: details, + [Symbol.for("name")]: "Warn" + }; + } +} + +function toCompactErrorLine$1(t) { + var prefix; + prefix = t.TAG ? "E" : "W"; + var details = t.details; + var msg = "(Warning number " + t.warnNumber + ") " + details.shortMsg; + return "[" + prefix + "] Line " + details.row + ", " + details.column + ": " + msg; +} + +var Warning = { + decode: decode$2, + toCompactErrorLine: toCompactErrorLine$1 +}; + +function decode$3(json) { + return { + msg: Json_decode.field("msg", Json_decode.string, json), + warn_flags: Json_decode.field("warn_flags", Json_decode.string, json), + warn_error_flags: Json_decode.field("warn_error_flags", Json_decode.string, json) + }; +} + +var WarningFlag = { + decode: decode$3 +}; + +function decode$4(time, json) { + return { + js_code: Json_decode.field("js_code", Json_decode.string, json), + warnings: Json_decode.field("warnings", (function (param) { + return Json_decode.array(decode$2, param); + }), json), + time: time + }; +} + +var CompileSuccess = { + decode: decode$4 +}; + +function decode$5(json) { + return { + code: Json_decode.field("code", Json_decode.string, json), + fromLang: Json_decode.field("fromLang", decode, json), + toLang: Json_decode.field("toLang", decode, json) + }; +} + +var ConvertSuccess = { + decode: decode$5 +}; + +function decode$6(json) { + var other = Json_decode.field("type", Json_decode.string, json); + switch (other) { + case "other_error" : + var locMsgs = Json_decode.field("errors", (function (param) { + return Json_decode.array(decode$1, param); + }), json); + return { + TAG: 4, + _0: locMsgs, + [Symbol.for("name")]: "OtherErr" + }; + case "syntax_error" : + var locMsgs$1 = Json_decode.field("errors", (function (param) { + return Json_decode.array(decode$1, param); + }), json); + return { + TAG: 0, + _0: dedupe(locMsgs$1), + [Symbol.for("name")]: "SyntaxErr" + }; + case "type_error" : + var locMsgs$2 = Json_decode.field("errors", (function (param) { + return Json_decode.array(decode$1, param); + }), json); + return { + TAG: 1, + _0: locMsgs$2, + [Symbol.for("name")]: "TypecheckErr" + }; + case "warning_error" : + var warnings = Json_decode.field("errors", (function (param) { + return Json_decode.array(decode$2, param); + }), json); + return { + TAG: 2, + _0: warnings, + [Symbol.for("name")]: "WarningErr" + }; + case "warning_flag_error" : + var warningFlag = decode$3(json); + return { + TAG: 3, + _0: warningFlag, + [Symbol.for("name")]: "WarningFlagErr" + }; + default: + throw { + RE_EXN_ID: Json_decode.DecodeError, + _1: "Unknown type \"" + other + "\" in CompileFail result", + Error: new Error() + }; + } +} + +var CompileFail = { + decode: decode$6 +}; + +function decode$7(time, json) { + try { + var match = Json_decode.field("type", Json_decode.string, json); + switch (match) { + case "success" : + return { + TAG: 1, + _0: decode$4(time, json), + [Symbol.for("name")]: "Success" + }; + case "unexpected_error" : + return { + TAG: 2, + _0: Json_decode.field("msg", Json_decode.string, json), + [Symbol.for("name")]: "UnexpectedError" + }; + default: + return { + TAG: 0, + _0: decode$6(json), + [Symbol.for("name")]: "Fail" + }; + } + } + catch (raw_errMsg){ + var errMsg = Caml_js_exceptions.internalToOCamlException(raw_errMsg); + if (errMsg.RE_EXN_ID === Json_decode.DecodeError) { + return { + TAG: 3, + _0: errMsg._1, + _1: json, + [Symbol.for("name")]: "Unknown" + }; + } + throw errMsg; + } +} + +var CompilationResult = { + decode: decode$7 +}; + +function decode$8(fromLang, toLang, json) { + try { + var other = Json_decode.field("type", Json_decode.string, json); + switch (other) { + case "success" : + return { + TAG: 0, + _0: decode$5(json), + [Symbol.for("name")]: "Success" + }; + case "syntax_error" : + var locMsgs = Json_decode.field("errors", (function (param) { + return Json_decode.array(decode$1, param); + }), json); + return { + TAG: 1, + fromLang: fromLang, + toLang: toLang, + details: locMsgs, + [Symbol.for("name")]: "Fail" + }; + case "unexpected_error" : + return { + TAG: 2, + _0: Json_decode.field("msg", Json_decode.string, json), + [Symbol.for("name")]: "UnexpectedError" + }; + default: + return { + TAG: 3, + _0: "Unknown conversion result type \"" + other + "\"", + _1: json, + [Symbol.for("name")]: "Unknown" + }; + } + } + catch (raw_errMsg){ + var errMsg = Caml_js_exceptions.internalToOCamlException(raw_errMsg); + if (errMsg.RE_EXN_ID === Json_decode.DecodeError) { + return { + TAG: 3, + _0: errMsg._1, + _1: json, + [Symbol.for("name")]: "Unknown" + }; + } + throw errMsg; + } +} + +var ConversionResult = { + decode: decode$8 +}; + +var Config = {}; + +function resCompile(t, code) { + var startTime = performance.now(); + var json = t.rescript.compile(code); + var stopTime = performance.now(); + return decode$7(stopTime - startTime, json); +} + +function resFormat(t, code) { + var json = t.rescript.format(code); + return decode$8(/* Res */2, /* Res */2, json); +} + +function reasonCompile(t, code) { + var startTime = performance.now(); + var json = t.reason.compile(code); + var stopTime = performance.now(); + return decode$7(stopTime - startTime, json); +} + +function reasonFormat(t, code) { + var json = t.reason.format(code); + return decode$8(/* Reason */0, /* Reason */0, json); +} + +function ocamlCompile(t, code) { + var startTime = performance.now(); + var json = t.ocaml.compile(code); + var stopTime = performance.now(); + return decode$7(stopTime - startTime, json); +} + +function setConfig(t, config) { + var match = config.module_system; + var moduleSystem; + switch (match) { + case "es6" : + moduleSystem = "es6"; + break; + case "nodejs" : + moduleSystem = "nodejs"; + break; + default: + moduleSystem = undefined; + } + Belt_Option.forEach(moduleSystem, (function (moduleSystem) { + t.setModuleSystem(moduleSystem); + + })); + t.setWarnFlags(config.warn_flags); + +} + +function convertSyntax(t, fromLang, toLang, code) { + try { + return decode$8(fromLang, toLang, t.convertSyntax(toExt(fromLang), toExt(toLang), code)); + } + catch (raw_obj){ + var obj = Caml_js_exceptions.internalToOCamlException(raw_obj); + if (obj.RE_EXN_ID === Js_exn.$$Error) { + var m = obj._1.message; + if (m !== undefined) { + return { + TAG: 2, + _0: m, + [Symbol.for("name")]: "UnexpectedError" + }; + } else { + return { + TAG: 2, + _0: "", + [Symbol.for("name")]: "UnexpectedError" + }; + } + } + throw obj; + } +} + +var Compiler = { + resCompile: resCompile, + resFormat: resFormat, + reasonCompile: reasonCompile, + reasonFormat: reasonFormat, + ocamlCompile: ocamlCompile, + setConfig: setConfig, + convertSyntax: convertSyntax +}; + +export { + Lang , + Version , + LocMsg , + Warning , + WarningFlag , + CompileSuccess , + ConvertSuccess , + CompileFail , + CompilationResult , + ConversionResult , + Config , + Compiler , + +} +/* No side effect */ diff --git a/bindings/RescriptCompilerApi.re b/bindings/RescriptCompilerApi.re deleted file mode 100644 index 7ac089920..000000000 --- a/bindings/RescriptCompilerApi.re +++ /dev/null @@ -1,452 +0,0 @@ -// This module establishes the communication to the -// loaded bucklescript API exposed by the bs-platform-js -// bundle - -// It is only safe to call these functions when a bundle -// has been loaded, so we'd prefer to protect this -// API with the playground compiler manager state - -[@bs.val] [@bs.scope "performance"] external now: unit => float = "now"; - -module Lang = { - type t = - | Reason - | OCaml - | Res; - - let toString = t => - switch (t) { - | Res => "ReScript" - | Reason => "Reason" - | OCaml => "OCaml" - }; - - let toExt = t => - switch (t) { - | Res => "res" - | Reason => "re" - | OCaml => "ml" - }; - - let decode = (json): t => { - open! Json.Decode; - switch (string(json)) { - | "ml" => OCaml - | "re" => Reason - | "res" => Res - | other => raise(DecodeError({j|Unknown language "$other"|j})) - }; - }; -}; - -module Version = { - type t = - | V1 - | UnknownVersion(string); - - // Helps finding the right API version - let fromString = (apiVersion: string): t => - switch (Js.String2.split(apiVersion, ".")->Belt.List.fromArray) { - | [maj, min, ..._] => - let maj = Belt.Int.fromString(maj); - let min = Belt.Int.fromString(min); - - switch (maj, min) { - | (Some(maj), Some(_)) - | (Some(maj), None) => - if (maj >= 1) { - V1; - } else { - UnknownVersion(apiVersion); - } - | _ => UnknownVersion(apiVersion) - }; - | _ => UnknownVersion(apiVersion) - }; - - let defaultTargetLang = t => - switch (t) { - | V1 => Lang.Res - | _ => Reason - }; - - let availableLanguages = t => - switch (t) { - | V1 => [|Lang.Reason, Res|] - | UnknownVersion(_) => [|Res|] - }; -}; - -module LocMsg = { - type t = { - fullMsg: string, - shortMsg: string, - row: int, - column: int, - endRow: int, - endColumn: int, - }; - - let decode = (json): t => { - Json.Decode.{ - fullMsg: json->field("fullMsg", string, _), - shortMsg: json->field("shortMsg", string, _), - row: json->field("row", int, _), - column: json->field("column", int, _), - endRow: json->field("endRow", int, _), - endColumn: json->field("endColumn", int, _), - }; - }; - - type prefix = [ | `W | `E]; - - // Useful for showing errors in a more compact format - let toCompactErrorLine = (~prefix: prefix, locMsg: t) => { - let {row, column, shortMsg} = locMsg; - let prefix = - switch (prefix) { - | `W => "W" - | `E => "E" - }; - - {j|[$prefix] Line $row, $column: $shortMsg|j}; - }; - - // Creates a somewhat unique id based on the rows / cols of the locMsg - let makeId = t => { - Belt.Int.( - toString(t.row) - ++ "-" - ++ toString(t.endRow) - ++ "-" - ++ toString(t.column) - ++ "-" - ++ toString(t.endColumn) - ); - }; - - let dedupe = (arr: array(t)) => { - let result = Js.Dict.empty(); - - for (i in 0 to Js.Array.length(arr) - 1) { - let locMsg = Js.Array2.unsafe_get(arr, i); - let id = makeId(locMsg); - - // The last element with the same id wins - result->Js.Dict.set(id, locMsg); - }; - Js.Dict.values(result); - }; -}; - -module Warning = { - type t = - | Warn({ - warnNumber: int, - details: LocMsg.t, - }) - | WarnErr({ - warnNumber: int, - details: LocMsg.t, - }); // Describes an erronous warning - - let decode = (json): t => { - open! Json.Decode; - - let warnNumber = field("warnNumber", int, json); - let details = LocMsg.decode(json); - - field("isError", bool, json) - ? WarnErr({warnNumber, details}) : Warn({warnNumber, details}); - }; - - // Useful for showing errors in a more compact format - let toCompactErrorLine = (t: t) => { - let prefix = - switch (t) { - | Warn(_) => "W" - | WarnErr(_) => "E" - }; - - let (row, column, msg) = - switch (t) { - | Warn({warnNumber, details}) - | WarnErr({warnNumber, details}) => - let {LocMsg.row, column, shortMsg} = details; - let msg = {j|(Warning number $warnNumber) $shortMsg|j}; - (row, column, msg); - }; - - {j|[$prefix] Line $row, $column: $msg|j}; - }; -}; - -module WarningFlag = { - type t = { - msg: string, - warn_flags: string, - warn_error_flags: string, - }; - - let decode = (json): t => { - Json.Decode.{ - msg: field("msg", string, json), - warn_flags: field("warn_flags", string, json), - warn_error_flags: field("warn_error_flags", string, json), - }; - }; -}; - -module CompileSuccess = { - type t = { - js_code: string, - warnings: array(Warning.t), - time: float // total compilation time - }; - - let decode = (~time: float, json): t => { - Json.Decode.{ - js_code: field("js_code", string, json), - warnings: field("warnings", array(Warning.decode), json), - time, - }; - }; -}; - -module ConvertSuccess = { - type t = { - code: string, - fromLang: Lang.t, - toLang: Lang.t, - }; - - let decode = (json): t => { - Json.Decode.{ - code: field("code", string, json), - fromLang: field("fromLang", Lang.decode, json), - toLang: field("toLang", Lang.decode, json), - }; - }; -}; - -module CompileFail = { - type t = - | SyntaxErr(array(LocMsg.t)) - | TypecheckErr(array(LocMsg.t)) - | WarningErr(array(Warning.t)) - | WarningFlagErr(WarningFlag.t) - | OtherErr(array(LocMsg.t)); - - let decode = (json): t => { - open! Json.Decode; - - switch (field("type", string, json)) { - | "syntax_error" => - let locMsgs = field("errors", array(LocMsg.decode), json); - // TODO: There seems to be a bug in the ReScript bundle that reports - // back multiple LocMsgs of the same value - locMsgs->LocMsg.dedupe->SyntaxErr; - | "type_error" => - let locMsgs = field("errors", array(LocMsg.decode), json); - TypecheckErr(locMsgs); - | "warning_error" => - let warnings = field("errors", array(Warning.decode), json); - WarningErr(warnings); - | "other_error" => - let locMsgs = field("errors", array(LocMsg.decode), json); - OtherErr(locMsgs); - - | "warning_flag_error" => - let warningFlag = WarningFlag.decode(json); - WarningFlagErr(warningFlag); - | other => - raise(DecodeError({j|Unknown type "$other" in CompileFail result|j})) - }; - }; -}; - -module CompilationResult = { - type t = - | Fail(CompileFail.t) // When a compilation failed with some error result - | Success(CompileSuccess.t) - | UnexpectedError(string) // Errors that slip through as uncaught exceptions of the compiler bundle - | Unknown(string, Js.Json.t); - - // TODO: We might change this specific api completely before launching - let decode = (~time: float, json: Js.Json.t): t => { - open! Json.Decode; - - try( - switch (field("type", string, json)) { - | "success" => Success(CompileSuccess.decode(~time, json)) - | "unexpected_error" => UnexpectedError(field("msg", string, json)) - | _ => Fail(CompileFail.decode(json)) - } - ) { - | DecodeError(errMsg) => Unknown(errMsg, json) - }; - }; -}; - -module ConversionResult = { - type t = - | Success(ConvertSuccess.t) - | Fail({ - fromLang: Lang.t, - toLang: Lang.t, - details: array(LocMsg.t), - }) // When a compilation failed with some error result - | UnexpectedError(string) // Errors that slip through as uncaught exceptions within the playground - | Unknown(string, Js.Json.t); - - let decode = (~fromLang: Lang.t, ~toLang: Lang.t, json): t => { - open! Json.Decode; - try( - switch (field("type", string, json)) { - | "success" => Success(ConvertSuccess.decode(json)) - | "unexpected_error" => UnexpectedError(field("msg", string, json)) - | "syntax_error" => - let locMsgs = field("errors", array(LocMsg.decode), json); - Fail({fromLang, toLang, details: locMsgs}); - | other => Unknown({j|Unknown conversion result type "$other"|j}, json) - } - ) { - | DecodeError(errMsg) => Unknown(errMsg, json) - }; - }; -}; - -module Config = { - type t = { - module_system: string, - warn_flags: string, - }; -}; - -module Compiler = { - type t; - - // Factory - [@bs.val] [@bs.scope "rescript_compiler"] external make: unit => t = "make"; - - [@bs.get] external version: t => string = "version"; - - /* - Res compiler actions - */ - [@bs.get] [@bs.scope "rescript"] external resVersion: t => string = "version"; - - [@bs.send] [@bs.scope "rescript"] - external resCompile: (t, string) => Js.Json.t = "compile"; - - let resCompile = (t, code): CompilationResult.t => { - let startTime = now(); - let json = resCompile(t, code); - let stopTime = now(); - - CompilationResult.decode(~time=stopTime -. startTime, json); - }; - - [@bs.send] [@bs.scope "rescript"] - external resFormat: (t, string) => Js.Json.t = "format"; - - let resFormat = (t, code): ConversionResult.t => { - let json = resFormat(t, code); - ConversionResult.decode(~fromLang=Res, ~toLang=Res, json); - }; - - /* - Reason compiler actions - */ - [@bs.get] [@bs.scope "reason"] - external reasonVersion: t => string = "version"; - - [@bs.send] [@bs.scope "reason"] - external reasonCompile: (t, string) => Js.Json.t = "compile"; - let reasonCompile = (t, code): CompilationResult.t => { - let startTime = now(); - let json = reasonCompile(t, code); - let stopTime = now(); - - CompilationResult.decode(~time=stopTime -. startTime, json); - }; - - [@bs.send] [@bs.scope "reason"] - external reasonFormat: (t, string) => Js.Json.t = "format"; - - let reasonFormat = (t, code): ConversionResult.t => { - let json = reasonFormat(t, code); - ConversionResult.decode(~fromLang=Reason, ~toLang=Reason, json); - }; - - /* - OCaml compiler actions (Note: no pretty print available for OCaml) - */ - [@bs.get] [@bs.scope "ocaml"] external ocamlVersion: t => string = "version"; - - [@bs.send] [@bs.scope "ocaml"] - external ocamlCompile: (t, string) => Js.Json.t = "compile"; - - let ocamlCompile = (t, code): CompilationResult.t => { - let startTime = now(); - let json = ocamlCompile(t, code); - let stopTime = now(); - - CompilationResult.decode(~time=stopTime -. startTime, json); - }; - - /* - Config setter / getters - */ - [@bs.send] external getConfig: t => Config.t = "getConfig"; - - [@bs.send] external setFilename: (t, string) => bool = "setFilename"; - - [@bs.send] - external setModuleSystem: (t, [@bs.string] [ | `es6 | `nodejs]) => bool = - "setModuleSystem"; - - [@bs.send] external setWarnFlags: (t, string) => bool = "setWarnFlags"; - - let setConfig = (t: t, config: Config.t): unit => { - let moduleSystem = - switch (config.module_system) { - | "nodejs" => `nodejs->Some - | "es6" => `es6->Some - | _ => None - }; - - Belt.Option.forEach(moduleSystem, moduleSystem => - t->setModuleSystem(moduleSystem)->ignore - ); - - t->setWarnFlags(config.warn_flags)->ignore; - }; - - [@bs.send] - external convertSyntax: (t, string, string, string) => Js.Json.t = - "convertSyntax"; - - // General format function - let convertSyntax = - (t, ~fromLang: Lang.t, ~toLang: Lang.t, ~code: string) - : ConversionResult.t => - - // TODO: There is an issue where trying to convert an empty Reason code - // to ReScript code would throw an unhandled JSOO exception - // we'd either need to special case the empty Reason code parsing, - // or handle the error on the JSOO bundle side more gracefully - try( - convertSyntax(t, Lang.toExt(fromLang), Lang.toExt(toLang), code) - ->ConversionResult.decode(~fromLang, ~toLang) - ) { - | Js.Exn.Error(obj) => - switch (Js.Exn.message(obj)) { - | Some(m) => ConversionResult.UnexpectedError(m) - | None => UnexpectedError("") - } - }; -}; - -[@bs.val] [@bs.scope "rescript_compiler"] -external apiVersion: string = "api_version"; diff --git a/bindings/RescriptCompilerApi.res b/bindings/RescriptCompilerApi.res new file mode 100644 index 000000000..2ae776248 --- /dev/null +++ b/bindings/RescriptCompilerApi.res @@ -0,0 +1,426 @@ +// This module establishes the communication to the +// loaded bucklescript API exposed by the bs-platform-js +// bundle + +// It is only safe to call these functions when a bundle +// has been loaded, so we'd prefer to protect this +// API with the playground compiler manager state + +@bs.val @bs.scope("performance") external now: unit => float = "now" + +module Lang = { + type t = + | Reason + | OCaml + | Res + + let toString = t => + switch t { + | Res => "ReScript" + | Reason => "Reason" + | OCaml => "OCaml" + } + + let toExt = t => + switch t { + | Res => "res" + | Reason => "re" + | OCaml => "ml" + } + + let decode = (json): t => { + open! Json.Decode + switch string(json) { + | "ml" => OCaml + | "re" => Reason + | "res" => Res + | other => raise(DecodeError(j`Unknown language "$other"`)) + } + } +} + +module Version = { + type t = + | V1 + | UnknownVersion(string) + + // Helps finding the right API version + let fromString = (apiVersion: string): t => + switch Js.String2.split(apiVersion, ".")->Belt.List.fromArray { + | list{maj, min, ..._} => + let maj = Belt.Int.fromString(maj) + let min = Belt.Int.fromString(min) + + switch (maj, min) { + | (Some(maj), Some(_)) + | (Some(maj), None) => + if maj >= 1 { + V1 + } else { + UnknownVersion(apiVersion) + } + | _ => UnknownVersion(apiVersion) + } + | _ => UnknownVersion(apiVersion) + } + + let defaultTargetLang = t => + switch t { + | V1 => Lang.Res + | _ => Reason + } + + let availableLanguages = t => + switch t { + | V1 => [Lang.Reason, Res] + | UnknownVersion(_) => [Res] + } +} + +module LocMsg = { + type t = { + fullMsg: string, + shortMsg: string, + row: int, + column: int, + endRow: int, + endColumn: int, + } + + let decode = (json): t => { + open Json.Decode + { + fullMsg: json->field("fullMsg", string, _), + shortMsg: json->field("shortMsg", string, _), + row: json->field("row", int, _), + column: json->field("column", int, _), + endRow: json->field("endRow", int, _), + endColumn: json->field("endColumn", int, _), + } + } + + type prefix = [#W | #E] + + // Useful for showing errors in a more compact format + let toCompactErrorLine = (~prefix: prefix, locMsg: t) => { + let {row, column, shortMsg} = locMsg + let prefix = switch prefix { + | #W => "W" + | #E => "E" + } + + j`[$prefix] Line $row, $column: $shortMsg` + } + + // Creates a somewhat unique id based on the rows / cols of the locMsg + let makeId = t => { + open Belt.Int + toString(t.row) ++ + ("-" ++ + (toString(t.endRow) ++ ("-" ++ (toString(t.column) ++ ("-" ++ toString(t.endColumn)))))) + } + + let dedupe = (arr: array) => { + let result = Js.Dict.empty() + + for i in 0 to Js.Array.length(arr) - 1 { + let locMsg = Js.Array2.unsafe_get(arr, i) + let id = makeId(locMsg) + + // The last element with the same id wins + result->Js.Dict.set(id, locMsg) + } + Js.Dict.values(result) + } +} + +module Warning = { + type t = + | Warn({warnNumber: int, details: LocMsg.t}) + | WarnErr({warnNumber: int, details: LocMsg.t}) // Describes an erronous warning + + let decode = (json): t => { + open! Json.Decode + + let warnNumber = field("warnNumber", int, json) + let details = LocMsg.decode(json) + + field("isError", bool, json) + ? WarnErr({warnNumber: warnNumber, details: details}) + : Warn({warnNumber: warnNumber, details: details}) + } + + // Useful for showing errors in a more compact format + let toCompactErrorLine = (t: t) => { + let prefix = switch t { + | Warn(_) => "W" + | WarnErr(_) => "E" + } + + let (row, column, msg) = switch t { + | Warn({warnNumber, details}) + | WarnErr({warnNumber, details}) => + let {LocMsg.row: row, column, shortMsg} = details + let msg = j`(Warning number $warnNumber) $shortMsg` + (row, column, msg) + } + + j`[$prefix] Line $row, $column: $msg` + } +} + +module WarningFlag = { + type t = { + msg: string, + warn_flags: string, + warn_error_flags: string, + } + + let decode = (json): t => { + open Json.Decode + { + msg: field("msg", string, json), + warn_flags: field("warn_flags", string, json), + warn_error_flags: field("warn_error_flags", string, json), + } + } +} + +module CompileSuccess = { + type t = { + js_code: string, + warnings: array, + time: float, // total compilation time + } + + let decode = (~time: float, json): t => { + open Json.Decode + { + js_code: field("js_code", string, json), + warnings: field("warnings", array(Warning.decode), json), + time: time, + } + } +} + +module ConvertSuccess = { + type t = { + code: string, + fromLang: Lang.t, + toLang: Lang.t, + } + + let decode = (json): t => { + open Json.Decode + { + code: field("code", string, json), + fromLang: field("fromLang", Lang.decode, json), + toLang: field("toLang", Lang.decode, json), + } + } +} + +module CompileFail = { + type t = + | SyntaxErr(array) + | TypecheckErr(array) + | WarningErr(array) + | WarningFlagErr(WarningFlag.t) + | OtherErr(array) + + let decode = (json): t => { + open! Json.Decode + + switch field("type", string, json) { + | "syntax_error" => + let locMsgs = field("errors", array(LocMsg.decode), json) + // TODO: There seems to be a bug in the ReScript bundle that reports + // back multiple LocMsgs of the same value + locMsgs->LocMsg.dedupe->SyntaxErr + | "type_error" => + let locMsgs = field("errors", array(LocMsg.decode), json) + TypecheckErr(locMsgs) + | "warning_error" => + let warnings = field("errors", array(Warning.decode), json) + WarningErr(warnings) + | "other_error" => + let locMsgs = field("errors", array(LocMsg.decode), json) + OtherErr(locMsgs) + + | "warning_flag_error" => + let warningFlag = WarningFlag.decode(json) + WarningFlagErr(warningFlag) + | other => raise(DecodeError(j`Unknown type "$other" in CompileFail result`)) + } + } +} + +module CompilationResult = { + type t = + | Fail(CompileFail.t) // When a compilation failed with some error result + | Success(CompileSuccess.t) + | UnexpectedError(string) // Errors that slip through as uncaught exceptions of the compiler bundle + | Unknown(string, Js.Json.t) + + // TODO: We might change this specific api completely before launching + let decode = (~time: float, json: Js.Json.t): t => { + open! Json.Decode + + try switch field("type", string, json) { + | "success" => Success(CompileSuccess.decode(~time, json)) + | "unexpected_error" => UnexpectedError(field("msg", string, json)) + | _ => Fail(CompileFail.decode(json)) + } catch { + | DecodeError(errMsg) => Unknown(errMsg, json) + } + } +} + +module ConversionResult = { + type t = + | Success(ConvertSuccess.t) + | Fail({fromLang: Lang.t, toLang: Lang.t, details: array}) // When a compilation failed with some error result + | UnexpectedError(string) // Errors that slip through as uncaught exceptions within the playground + | Unknown(string, Js.Json.t) + + let decode = (~fromLang: Lang.t, ~toLang: Lang.t, json): t => { + open! Json.Decode + try switch field("type", string, json) { + | "success" => Success(ConvertSuccess.decode(json)) + | "unexpected_error" => UnexpectedError(field("msg", string, json)) + | "syntax_error" => + let locMsgs = field("errors", array(LocMsg.decode), json) + Fail({fromLang: fromLang, toLang: toLang, details: locMsgs}) + | other => Unknown(j`Unknown conversion result type "$other"`, json) + } catch { + | DecodeError(errMsg) => Unknown(errMsg, json) + } + } +} + +module Config = { + type t = { + module_system: string, + warn_flags: string, + } +} + +module Compiler = { + type t + + // Factory + @bs.val @bs.scope("rescript_compiler") external make: unit => t = "make" + + @bs.get external version: t => string = "version" + + /* + Res compiler actions + */ + @bs.get @bs.scope("rescript") external resVersion: t => string = "version" + + @bs.send @bs.scope("rescript") + external resCompile: (t, string) => Js.Json.t = "compile" + + let resCompile = (t, code): CompilationResult.t => { + let startTime = now() + let json = resCompile(t, code) + let stopTime = now() + + CompilationResult.decode(~time=stopTime -. startTime, json) + } + + @bs.send @bs.scope("rescript") + external resFormat: (t, string) => Js.Json.t = "format" + + let resFormat = (t, code): ConversionResult.t => { + let json = resFormat(t, code) + ConversionResult.decode(~fromLang=Res, ~toLang=Res, json) + } + + /* + Reason compiler actions + */ + @bs.get @bs.scope("reason") + external reasonVersion: t => string = "version" + + @bs.send @bs.scope("reason") + external reasonCompile: (t, string) => Js.Json.t = "compile" + let reasonCompile = (t, code): CompilationResult.t => { + let startTime = now() + let json = reasonCompile(t, code) + let stopTime = now() + + CompilationResult.decode(~time=stopTime -. startTime, json) + } + + @bs.send @bs.scope("reason") + external reasonFormat: (t, string) => Js.Json.t = "format" + + let reasonFormat = (t, code): ConversionResult.t => { + let json = reasonFormat(t, code) + ConversionResult.decode(~fromLang=Reason, ~toLang=Reason, json) + } + + /* + OCaml compiler actions (Note: no pretty print available for OCaml) + */ + @bs.get @bs.scope("ocaml") external ocamlVersion: t => string = "version" + + @bs.send @bs.scope("ocaml") + external ocamlCompile: (t, string) => Js.Json.t = "compile" + + let ocamlCompile = (t, code): CompilationResult.t => { + let startTime = now() + let json = ocamlCompile(t, code) + let stopTime = now() + + CompilationResult.decode(~time=stopTime -. startTime, json) + } + + /* + Config setter / getters + */ + @bs.send external getConfig: t => Config.t = "getConfig" + + @bs.send external setFilename: (t, string) => bool = "setFilename" + + @bs.send + external setModuleSystem: (t, @bs.string [#es6 | #nodejs]) => bool = "setModuleSystem" + + @bs.send external setWarnFlags: (t, string) => bool = "setWarnFlags" + + let setConfig = (t: t, config: Config.t): unit => { + let moduleSystem = switch config.module_system { + | "nodejs" => #nodejs->Some + | "es6" => #es6->Some + | _ => None + } + + Belt.Option.forEach(moduleSystem, moduleSystem => t->setModuleSystem(moduleSystem)->ignore) + + t->setWarnFlags(config.warn_flags)->ignore + } + + @bs.send + external convertSyntax: (t, string, string, string) => Js.Json.t = "convertSyntax" + + // General format function + let convertSyntax = (t, ~fromLang: Lang.t, ~toLang: Lang.t, ~code: string): ConversionResult.t => + // TODO: There is an issue where trying to convert an empty Reason code + // to ReScript code would throw an unhandled JSOO exception + // we'd either need to special case the empty Reason code parsing, + // or handle the error on the JSOO bundle side more gracefully + try convertSyntax(t, Lang.toExt(fromLang), Lang.toExt(toLang), code)->ConversionResult.decode( + ~fromLang, + ~toLang, + ) catch { + | Js.Exn.Error(obj) => + switch Js.Exn.message(obj) { + | Some(m) => ConversionResult.UnexpectedError(m) + | None => UnexpectedError("") + } + } +} + +@bs.val @bs.scope("rescript_compiler") +external apiVersion: string = "api_version" diff --git a/bindings/Worker.js b/bindings/Worker.js new file mode 100644 index 000000000..535cc58cf --- /dev/null +++ b/bindings/Worker.js @@ -0,0 +1,18 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + + +function Make(Config) { + var App = {}; + var $$Worker = {}; + return { + make: Config.make, + App: App, + $$Worker: $$Worker + }; +} + +export { + Make , + +} +/* No side effect */ diff --git a/bindings/Worker.re b/bindings/Worker.re deleted file mode 100644 index 0757b2fc5..000000000 --- a/bindings/Worker.re +++ /dev/null @@ -1,33 +0,0 @@ -type worker; - -[@bs.new] external make: string => worker = "Worker"; - -module type Config = { - type fromApp; - type fromWorker; - let make: unit => worker; -}; - -module Make = (Config: Config) => { - include Config; - - module App = { - [@bs.send] external postMessage: (worker, fromApp) => unit = "postMessage"; - [@bs.set] - external onMessage: (worker, {. "data": fromWorker} => unit) => unit = - "onmessage"; - [@bs.set] - external onError: (worker, Js.t('a) => unit) => unit = "onerror"; - [@bs.send] external terminate: worker => unit = "terminate"; - }; - - module Worker = { - type self; - [@bs.val] external postMessage: fromWorker => unit = "postMessage"; - [@bs.set] - external onMessage: (self, {. "data": fromApp} => unit) => unit = - "onmessage"; - [@bs.val] external self: self = "self"; - [@bs.val] external importScripts: string => unit; - }; -}; diff --git a/bindings/Worker.res b/bindings/Worker.res new file mode 100644 index 000000000..6cda4c250 --- /dev/null +++ b/bindings/Worker.res @@ -0,0 +1,31 @@ +type worker + +@bs.new external make: string => worker = "Worker" + +module type Config = { + type fromApp + type fromWorker + let make: unit => worker +} + +module Make = (Config: Config) => { + include Config + + module App = { + @bs.send external postMessage: (worker, fromApp) => unit = "postMessage" + @bs.set + external onMessage: (worker, {"data": fromWorker} => unit) => unit = "onmessage" + @bs.set + external onError: (worker, Js.t<'a> => unit) => unit = "onerror" + @bs.send external terminate: worker => unit = "terminate" + } + + module Worker = { + type self + @bs.val external postMessage: fromWorker => unit = "postMessage" + @bs.set + external onMessage: (self, {"data": fromApp} => unit) => unit = "onmessage" + @bs.val external self: self = "self" + @bs.val external importScripts: string => unit = "" + } +} diff --git a/bsconfig.json b/bsconfig.json index 1c0440430..f428f1797 100644 --- a/bsconfig.json +++ b/bsconfig.json @@ -30,7 +30,8 @@ }, { "dir": "scripts", - "subdirs": true + "subdirs": true, + "type": "dev" }, { "dir": "components", @@ -45,9 +46,8 @@ "warnings": { "error": "+8" }, - "suffix": ".bs.js", + "suffix": ".js", "bsc-flags": [ - "-bs-no-version-header", "-bs-super-errors", "-bs-g" ], diff --git a/common/Ansi.js b/common/Ansi.js new file mode 100644 index 000000000..2f274f4b9 --- /dev/null +++ b/common/Ansi.js @@ -0,0 +1,549 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as Belt_Array from "bs-platform/lib/es6/belt_Array.js"; +import * as Caml_array from "bs-platform/lib/es6/caml_array.js"; +import * as Belt_Option from "bs-platform/lib/es6/belt_Option.js"; +import * as Caml_option from "bs-platform/lib/es6/caml_option.js"; + +function toString(c) { + switch (c) { + case /* Black */0 : + return "black"; + case /* Red */1 : + return "red"; + case /* Green */2 : + return "green"; + case /* Yellow */3 : + return "yellow"; + case /* Blue */4 : + return "blue"; + case /* Magenta */5 : + return "magenta"; + case /* Cyan */6 : + return "cyan"; + case /* White */7 : + return "white"; + + } +} + +var Color = { + toString: toString +}; + +function paramToString(s) { + if (typeof s === "number") { + return "bold"; + } + switch (s.TAG | 0) { + case /* Fg */0 : + return "Fg(" + (toString(s._0) + ")"); + case /* Bg */1 : + return "Bg(" + (toString(s._0) + ")"); + case /* Unknown */2 : + return "Unknown: " + s._0; + + } +} + +var Sgr = { + paramToString: paramToString +}; + +var esc = "\u001B"; + +function isAscii(c) { + return /[\x40-\x7F]/.test(c); +} + +function fromString(input) { + return { + input: input, + pos: -1 + }; +} + +function isDone(p) { + return p.pos >= p.input.length; +} + +function next(p) { + if (isDone(p)) { + return p.input[p.pos]; + } + var c = p.input[p.pos + 1 | 0]; + p.pos = p.pos + 1 | 0; + return c; +} + +function untilNextEsc(p) { + var ret; + while(!isDone(p) && ret === undefined) { + var c = next(p); + if (c === esc) { + ret = Caml_option.some(undefined); + } + + }; + return ret; +} + +function look(p, num) { + var length = p.input.length; + var pos = (p.pos + num | 0) >= length ? length - 1 | 0 : p.pos + num | 0; + return p.input[pos]; +} + +var $$Location = { + fromString: fromString, + isDone: isDone, + next: next, + untilNextEsc: untilNextEsc, + look: look +}; + +function lex(_accOpt, _stateOpt, p) { + while(true) { + var accOpt = _accOpt; + var stateOpt = _stateOpt; + var acc = accOpt !== undefined ? accOpt : []; + var state = stateOpt !== undefined ? stateOpt : /* Scan */0; + if (isDone(p)) { + return acc; + } + if (typeof state === "number") { + var c = next(p); + var state$1 = c === esc ? ({ + TAG: 0, + startPos: p.pos, + content: "", + [Symbol.for("name")]: "ReadSgr" + }) : ({ + TAG: 1, + startPos: p.pos, + content: c, + [Symbol.for("name")]: "ReadText" + }); + _stateOpt = state$1; + _accOpt = acc; + continue ; + } + if (state.TAG) { + var content = state.content; + var startPos = state.startPos; + var c$1 = next(p); + var endPos = p.pos - 1 | 0; + if (c$1 === esc) { + var token_0 = { + startPos: startPos, + endPos: endPos + }; + var token = { + TAG: 0, + loc: token_0, + content: content, + [Symbol.for("name")]: "Text" + }; + acc.push(token); + _stateOpt = { + TAG: 0, + startPos: p.pos, + content: c$1, + [Symbol.for("name")]: "ReadSgr" + }; + _accOpt = acc; + continue ; + } + if (isDone(p)) { + var token_0$1 = { + startPos: startPos, + endPos: endPos + }; + var token$1 = { + TAG: 0, + loc: token_0$1, + content: content, + [Symbol.for("name")]: "Text" + }; + acc.push(token$1); + return acc; + } + var content$1 = content + c$1; + _stateOpt = { + TAG: 1, + startPos: startPos, + content: content$1, + [Symbol.for("name")]: "ReadText" + }; + _accOpt = acc; + continue ; + } + var content$2 = state.content; + var startPos$1 = state.startPos; + var c$2 = next(p); + if (c$2 !== "[" && isAscii(c$2)) { + var raw = content$2 + c$2; + var loc_endPos = (startPos$1 + raw.length | 0) - 1 | 0; + var loc = { + startPos: startPos$1, + endPos: loc_endPos + }; + var x = /\[([0-9;]+)([\x40-\x7F])/.exec(raw); + var token$2; + if (x !== null) { + var str = Caml_array.get(x, 1); + if (str == null) { + token$2 = { + TAG: 1, + loc: loc, + raw: raw, + params: [], + [Symbol.for("name")]: "Sgr" + }; + } else { + var other = ( + (str == null) ? undefined : Caml_option.some(str) + ).split(";"); + var exit = 0; + if (other.length !== 1) { + exit = 1; + } else { + var match = other[0]; + if (match === "0") { + token$2 = { + TAG: 2, + loc: loc, + raw: raw, + [Symbol.for("name")]: "ClearSgr" + }; + } else { + exit = 1; + } + } + if (exit === 1) { + var params = Belt_Array.map(other, (function (s) { + switch (s) { + case "1" : + return /* Bold */0; + case "30" : + return { + TAG: 0, + _0: /* Black */0, + [Symbol.for("name")]: "Fg" + }; + case "31" : + return { + TAG: 0, + _0: /* Red */1, + [Symbol.for("name")]: "Fg" + }; + case "32" : + return { + TAG: 0, + _0: /* Green */2, + [Symbol.for("name")]: "Fg" + }; + case "33" : + return { + TAG: 0, + _0: /* Yellow */3, + [Symbol.for("name")]: "Fg" + }; + case "34" : + return { + TAG: 0, + _0: /* Blue */4, + [Symbol.for("name")]: "Fg" + }; + case "35" : + return { + TAG: 0, + _0: /* Magenta */5, + [Symbol.for("name")]: "Fg" + }; + case "36" : + return { + TAG: 0, + _0: /* Cyan */6, + [Symbol.for("name")]: "Fg" + }; + case "37" : + return { + TAG: 0, + _0: /* White */7, + [Symbol.for("name")]: "Fg" + }; + case "40" : + return { + TAG: 1, + _0: /* Black */0, + [Symbol.for("name")]: "Bg" + }; + case "41" : + return { + TAG: 1, + _0: /* Red */1, + [Symbol.for("name")]: "Bg" + }; + case "42" : + return { + TAG: 1, + _0: /* Green */2, + [Symbol.for("name")]: "Bg" + }; + case "43" : + return { + TAG: 1, + _0: /* Yellow */3, + [Symbol.for("name")]: "Bg" + }; + case "44" : + return { + TAG: 1, + _0: /* Blue */4, + [Symbol.for("name")]: "Bg" + }; + case "45" : + return { + TAG: 1, + _0: /* Magenta */5, + [Symbol.for("name")]: "Bg" + }; + case "46" : + return { + TAG: 1, + _0: /* Cyan */6, + [Symbol.for("name")]: "Bg" + }; + case "47" : + return { + TAG: 1, + _0: /* White */7, + [Symbol.for("name")]: "Bg" + }; + default: + return { + TAG: 2, + _0: s, + [Symbol.for("name")]: "Unknown" + }; + } + })); + token$2 = { + TAG: 1, + loc: loc, + raw: raw, + params: params, + [Symbol.for("name")]: "Sgr" + }; + } + + } + } else { + token$2 = { + TAG: 1, + loc: loc, + raw: raw, + params: [], + [Symbol.for("name")]: "Sgr" + }; + } + acc.push(token$2); + _stateOpt = /* Scan */0; + _accOpt = acc; + continue ; + } + _stateOpt = { + TAG: 0, + startPos: startPos$1, + content: content$2 + c$2, + [Symbol.for("name")]: "ReadSgr" + }; + _accOpt = acc; + continue ; + }; +} + +function lex$1(p) { + return lex(undefined, undefined, p); +} + +var Lexer = { + lex: lex$1 +}; + +function parse(input) { + return lex(undefined, undefined, { + input: input, + pos: -1 + }); +} + +function onlyText(tokens) { + return Belt_Array.keep(tokens, (function (x) { + switch (x.TAG | 0) { + case /* Text */0 : + return true; + case /* Sgr */1 : + case /* ClearSgr */2 : + return false; + + } + })); +} + +function fromTokens(tokens) { + var ret = []; + var params = []; + var content = ""; + var length = tokens.length; + for(var i = 0; i < length; ++i){ + var token = Belt_Array.getExn(tokens, i); + var isLast = i === (length - 1 | 0); + switch (token.TAG | 0) { + case /* Text */0 : + content = content + token.content; + if (isLast && content !== "") { + var element = { + content: content, + params: params + }; + ret.push(element); + } + break; + case /* Sgr */1 : + var match = Belt_Array.reduce(Belt_Array.concat(params, token.params), [ + undefined, + undefined, + [] + ], (function (acc, next) { + var other = acc[2]; + var bg = acc[1]; + var fg = acc[0]; + if (typeof next !== "number") { + switch (next.TAG | 0) { + case /* Fg */0 : + return [ + next, + bg, + other + ]; + case /* Bg */1 : + return [ + fg, + next, + other + ]; + case /* Unknown */2 : + break; + + } + } + if (Caml_option.undefined_to_opt(other.find(function (o2) { + return next === o2; + })) === undefined) { + other.push(next); + } + return [ + fg, + bg, + other + ]; + })); + if (content !== "") { + var element$1 = { + content: content, + params: params + }; + ret.push(element$1); + content = ""; + } + params = Belt_Array.concatMany([ + Belt_Option.mapWithDefault(match[0], [], (function (v) { + return [v]; + })), + Belt_Option.mapWithDefault(match[1], [], (function (v) { + return [v]; + })), + match[2] + ]); + break; + case /* ClearSgr */2 : + if (content !== "") { + var element$2 = { + content: content, + params: params + }; + ret.push(element$2); + params = []; + content = ""; + } + break; + + } + } + return ret; +} + +function toString$1(e) { + var content = e.content.replace(/\n/g, "\\n").replace(esc, ""); + var params = Belt_Array.map(e.params, paramToString).join(", "); + return "SgrString params: " + params + " | content: " + content; +} + +var SgrString = { + fromTokens: fromTokens, + toString: toString$1 +}; + +function tokenString(t) { + switch (t.TAG | 0) { + case /* Text */0 : + var match = t.loc; + var content = t.content.replace(/\n/g, "\\n").replace(esc, ""); + return "Text \"" + content + "\" (" + match.startPos + " to " + match.endPos + ")"; + case /* Sgr */1 : + var match$1 = t.loc; + var raw = t.raw.replace(esc, ""); + var params = Belt_Array.map(t.params, paramToString).join(", "); + return "Sgr \"" + raw + "\" -> " + params + " (" + match$1.startPos + " to " + match$1.endPos + ")"; + case /* ClearSgr */2 : + var match$2 = t.loc; + var raw$1 = t.raw.replace(esc, ""); + return "Clear Sgr \"" + raw$1 + "\" (" + match$2.startPos + " to " + match$2.endPos + ")"; + + } +} + +function plainString(tokens) { + return Belt_Array.map(tokens, (function (x) { + switch (x.TAG | 0) { + case /* Text */0 : + return x.content; + case /* Sgr */1 : + case /* ClearSgr */2 : + return ""; + + } + })).join(""); +} + +var Printer = { + tokenString: tokenString, + plainString: plainString +}; + +export { + Color , + Sgr , + esc , + isAscii , + $$Location , + Lexer , + parse , + onlyText , + SgrString , + Printer , + +} +/* No side effect */ diff --git a/common/Ansi.re b/common/Ansi.re deleted file mode 100644 index 400f951de..000000000 --- a/common/Ansi.re +++ /dev/null @@ -1,393 +0,0 @@ -/* - This module parses ANSI encoded string into structured data. - It is mostly build for parsing BuckleScript terminal output and - is not implementing the whole Ansi functionality. - - See: https://en.wikipedia.org/wiki/ANSI_escape_code#Escape_sequences - - Other relevant resources: - ---- - OCaml compiler Misc.Color encoding colors in the output printers: - https://github.com/rescript-lang/ocaml/blob/0a3ec12fef5dbee795778a47b18024563b5e49f2/utils/misc.ml - */ - -module Color = { - type t = - | Black // fg=30 bg=40 - | Red // fg=31 bg=41 - | Green // fg=32 bg=42 - | Yellow // fg=33 bg=43 - | Blue // fg=34 bg=44 - | Magenta // fg=35 bg=45 - | Cyan // fg=36 bg=46 - | White; // fg=37 bg=47 - - let toString = (c: t) => - switch (c) { - | Black => "black" - | Red => "red" - | Green => "green" - | Yellow => "yellow" - | Blue => "blue" - | Magenta => "magenta" - | Cyan => "cyan" - | White => "white" - }; -}; - -module Sgr = { - // We don't encode Clear (0) here since it's a special case - type param = - | Bold // 1 - | Fg(Color.t) // 30 - 37 - | Bg(Color.t) // 40 - 47 - | Unknown(string); - - let paramToString = s => { - switch (s) { - | Bold => "bold" - | Fg(c) => "Fg(" ++ Color.toString(c) ++ ")" - | Bg(c) => "Bg(" ++ Color.toString(c) ++ ")" - | Unknown(s) => "Unknown: " ++ s - }; - }; -}; - -let esc = {j|\u001B|j}; - -let isAscii = (c: string) => { - Js.Re.test_([%re {|/[\x40-\x7F]/|}], c); -}; - -module Location = { - type t = { - input: string, - mutable pos: int, - }; - - type loc = { - startPos: int, - endPos: int, - }; - - let fromString = input => {input, pos: (-1)}; - - let isDone = p => p.pos >= Js.String.length(p.input); - - let next = p => - if (!isDone(p)) { - let c = Js.String2.get(p.input, p.pos + 1); - p.pos = p.pos + 1; - c; - } else { - Js.String2.get(p.input, p.pos); - }; - - let untilNextEsc = p => { - let ret = ref(None); - while (!isDone(p) && ret^ == None) { - let c = next(p); - - if (c === esc) { - ret := Some(); - }; - }; - ret^; - }; - - // Look is useful to look ahead without reading the character - // from the stream - let look = (p, num) => { - let length = Js.String.length(p.input); - - let pos = - if (p.pos + num >= length) { - length - 1; - } else { - p.pos + num; - }; - - Js.String2.get(p.input, pos); - }; -}; - -module Lexer = { - open Location; - open Sgr; - - type token = - | Text({ - loc: Location.loc, - content: string, - }) - | Sgr({ - loc: Location.loc, - raw: string, - params: array(Sgr.param), - }) - | ClearSgr({ - loc: Location.loc, - raw: string, - }); - - type state = - | Scan - | ReadSgr({ - startPos: int, - content: string, - }) // contains collected data - | ReadText({ - startPos: int, - content: string, - }); - - // Note: the acc array will be mutated in the process - let rec lex = - (~acc: array(token)=[||], ~state=Scan, p: Location.t) - : array(token) => - if (isDone(p)) { - acc; - } else { - switch (state) { - | Scan => - // Checks for the next character and does a state - // transition according to the entity we are reading (SGR / Text) - let c = next(p); - - let state = - if (c === esc) { - ReadSgr({startPos: p.pos, content: ""}); - } else { - ReadText({startPos: p.pos, content: c}); - }; - lex(~acc, ~state, p); - | ReadText({startPos, content}) => - let c = next(p); - - let endPos = p.pos - 1; - - // Determine if we are keep reading text, or start read SGRs - if (c === esc) { - let token = Text({ - loc: { - startPos, - endPos, - }, - content, - }); - Js.Array2.push(acc, token)->ignore; - lex(~acc, ~state=ReadSgr({startPos: p.pos, content: c}), p); - } else if (isDone(p)) { - let token = Text({ - loc: { - startPos, - endPos, - }, - content, - }); - Js.Array2.push(acc, token)->ignore; - acc; - } else { - let content = content ++ c; - lex(~acc, ~state=ReadText({startPos, content}), p); - }; - | ReadSgr({startPos, content}) => - let c = next(p); - - // on termination - if (c !== "[" && isAscii(c)) { - let raw = content ++ c; - - let loc = {startPos, endPos: startPos + Js.String.length(raw) - 1}; - - let token = - Js.Re.exec_([%re {|/\[([0-9;]+)([\x40-\x7F])/|}], raw) - ->( - fun - | Some(result) => { - let groups = Js.Re.captures(result); - switch (Js.Nullable.toOption(groups[1])) { - | Some(str) => - switch (Js.String2.split(str, ";")) { - | [|"0"|] => ClearSgr({loc, raw}) - | other => - let params = - Belt.Array.map(other, s => { - switch (s) { - | "1" => Bold - | "30" => Fg(Black) - | "31" => Fg(Red) - | "32" => Fg(Green) - | "33" => Fg(Yellow) - | "34" => Fg(Blue) - | "35" => Fg(Magenta) - | "36" => Fg(Cyan) - | "37" => Fg(White) - | "40" => Bg(Black) - | "41" => Bg(Red) - | "42" => Bg(Green) - | "43" => Bg(Yellow) - | "44" => Bg(Blue) - | "45" => Bg(Magenta) - | "46" => Bg(Cyan) - | "47" => Bg(White) - | o => Unknown(o) - } - }); - Sgr({loc, raw, params}); - } - - | None => Sgr({loc, raw, params: [||]}) - }; - } - | None => Sgr({loc, raw, params: [||]}) - ); - Js.Array2.push(acc, token)->ignore; - lex(~acc, ~state=Scan, p); - } else { - lex(~acc, ~state=ReadSgr({startPos, content: content ++ c}), p); - }; - }; - }; - - // We hide the original implementation to prevent users - // to pass in their own `acc` array (this array is getting mutated and - // this could cause side-effects for the consumer otherwise) - let lex = (p: Location.t) => { - lex(p); - }; -}; - -let parse = (input: string) => { - let p = Location.fromString(input); - Lexer.lex(p); -}; - -let onlyText = (tokens: array(Lexer.token)) => { - Lexer.( - Belt.Array.keep( - tokens, - fun - | Text(_) => true - | _ => false, - ) - ); -}; - -module SgrString = { - // A sgr encoded element - open Lexer; - - type t = { - content: string, - params: array(Sgr.param), - }; - - let fromTokens = (tokens: array(token)): array(t) => { - let ret = [||]; - let params = ref([||]); - let content = ref(""); - - let length = Js.Array.length(tokens); - for (i in 0 to length - 1) { - let token = Belt.Array.getExn(tokens, i); - - let isLast = i === length - 1; - - switch (token) { - | Text(data) => - content := content^ ++ data.content; - if (isLast && content^ !== "") { - let element = {content: content^, params: params^}; - Js.Array2.push(ret, element)->ignore; - }; - | Sgr(data) => - // merge together specific sgr params - let (fg, bg, rest) = - Belt.Array.concat(params^, data.params) - ->Belt.Array.reduce( - (None, None, [||]), - (acc, next) => { - let (fg, bg, other) = acc; - switch (next) { - | Fg(_) => (Some(next), bg, other) - | Bg(_) => (fg, Some(next), other) - | o => - if (Js.Array2.find(other, o2 => o === o2) === None) { - Js.Array2.push(other, next)->ignore; - }; - (fg, bg, other); - }; - }, - ); - - if (content^ !== "") { - let element = {content: content^, params: params^}; - Js.Array2.push(ret, element)->ignore; - content := ""; - }; - - params := - Belt.Array.concatMany([| - Belt.Option.mapWithDefault(fg, [||], v => [|v|]), - Belt.Option.mapWithDefault(bg, [||], v => [|v|]), - rest, - |]); - | ClearSgr(_) => - if (content^ !== "") { - let element = {content: content^, params: params^}; - Js.Array2.push(ret, element)->ignore; - - params := [||]; - content := ""; - } - }; - }; - - ret; - }; - - let toString = (e: t): string => { - let content = - Js.String2.( - replaceByRe(e.content, [%re "/\\n/g"], "\\n")->replace(esc, "") - ); - let params = - Belt.Array.map(e.params, Sgr.paramToString)->Js.Array2.joinWith(", "); - - {j|SgrString params: $params | content: $content|j}; - }; -}; - -module Printer = { - open Lexer; - - let tokenString = (t: token): string => { - switch (t) { - | Text({content, loc: {startPos, endPos}}) => - let content = - Js.String2.( - replaceByRe(content, [%re "/\\n/g"], "\\n")->replace(esc, "") - ); - {j|Text "$content" ($startPos to $endPos)|j}; - | Sgr({params, raw, loc: {startPos, endPos}}) => - let raw = Js.String2.replace(raw, esc, ""); - let params = - Belt.Array.map(params, Sgr.paramToString)->Js.Array2.joinWith(", "); - {j|Sgr "$raw" -> $params ($startPos to $endPos)|j}; - | ClearSgr({loc: {startPos, endPos}, raw}) => - let raw = Js.String2.replace(raw, esc, ""); - {j|Clear Sgr "$raw" ($startPos to $endPos)|j}; - }; - }; - - let plainString = (tokens: array(token)): string => { - Belt.Array.map( - tokens, - fun - | Lexer.Text({content}) => content - | _ => "", - ) - ->Js.Array2.joinWith(""); - }; -}; diff --git a/common/Ansi.res b/common/Ansi.res new file mode 100644 index 000000000..e89ec8827 --- /dev/null +++ b/common/Ansi.res @@ -0,0 +1,358 @@ +/* + This module parses ANSI encoded string into structured data. + It is mostly build for parsing BuckleScript terminal output and + is not implementing the whole Ansi functionality. + + See: https://en.wikipedia.org/wiki/ANSI_escape_code#Escape_sequences + + Other relevant resources: + ---- + OCaml compiler Misc.Color encoding colors in the output printers: + https://github.com/rescript-lang/ocaml/blob/0a3ec12fef5dbee795778a47b18024563b5e49f2/utils/misc.ml + */ + +module Color = { + type t = + | Black // fg=30 bg=40 + | Red // fg=31 bg=41 + | Green // fg=32 bg=42 + | Yellow // fg=33 bg=43 + | Blue // fg=34 bg=44 + | Magenta // fg=35 bg=45 + | Cyan // fg=36 bg=46 + | White // fg=37 bg=47 + + let toString = (c: t) => + switch c { + | Black => "black" + | Red => "red" + | Green => "green" + | Yellow => "yellow" + | Blue => "blue" + | Magenta => "magenta" + | Cyan => "cyan" + | White => "white" + } +} + +module Sgr = { + // We don't encode Clear (0) here since it's a special case + type param = + | Bold // 1 + | Fg(Color.t) // 30 - 37 + | Bg(Color.t) // 40 - 47 + | Unknown(string) + + let paramToString = s => + switch s { + | Bold => "bold" + | Fg(c) => "Fg(" ++ (Color.toString(c) ++ ")") + | Bg(c) => "Bg(" ++ (Color.toString(c) ++ ")") + | Unknown(s) => "Unknown: " ++ s + } +} + +let esc = j`\\u001B` + +let isAscii = (c: string) => Js.Re.test_(%re(`/[\\x40-\\x7F]/`), c) + +module Location = { + type t = { + input: string, + mutable pos: int, + } + + type loc = { + startPos: int, + endPos: int, + } + + let fromString = input => {input: input, pos: -1} + + let isDone = p => p.pos >= Js.String.length(p.input) + + let next = p => + if !isDone(p) { + let c = Js.String2.get(p.input, p.pos + 1) + p.pos = p.pos + 1 + c + } else { + Js.String2.get(p.input, p.pos) + } + + let untilNextEsc = p => { + let ret = ref(None) + while !isDone(p) && ret.contents == None { + let c = next(p) + + if c === esc { + ret := Some() + } + } + ret.contents + } + + // Look is useful to look ahead without reading the character + // from the stream + let look = (p, num) => { + let length = Js.String.length(p.input) + + let pos = if p.pos + num >= length { + length - 1 + } else { + p.pos + num + } + + Js.String2.get(p.input, pos) + } +} + +module Lexer = { + open Location + open Sgr + + type token = + | Text({loc: Location.loc, content: string}) + | Sgr({loc: Location.loc, raw: string, params: array}) + | ClearSgr({loc: Location.loc, raw: string}) + + type state = + | Scan + | ReadSgr({startPos: int, content: string}) // contains collected data + | ReadText({startPos: int, content: string}) + + // Note: the acc array will be mutated in the process + let rec lex = (~acc: array=[], ~state=Scan, p: Location.t): array => + if isDone(p) { + acc + } else { + switch state { + | Scan => + // Checks for the next character and does a state + // transition according to the entity we are reading (SGR / Text) + let c = next(p) + + let state = if c === esc { + ReadSgr({startPos: p.pos, content: ""}) + } else { + ReadText({startPos: p.pos, content: c}) + } + lex(~acc, ~state, p) + | ReadText({startPos, content}) => + let c = next(p) + + let endPos = p.pos - 1 + + // Determine if we are keep reading text, or start read SGRs + if c === esc { + let token = Text({ + loc: { + startPos: startPos, + endPos: endPos, + }, + content: content, + }) + Js.Array2.push(acc, token)->ignore + lex(~acc, ~state=ReadSgr({startPos: p.pos, content: c}), p) + } else if isDone(p) { + let token = Text({ + loc: { + startPos: startPos, + endPos: endPos, + }, + content: content, + }) + Js.Array2.push(acc, token)->ignore + acc + } else { + let content = content ++ c + lex(~acc, ~state=ReadText({startPos: startPos, content: content}), p) + } + | ReadSgr({startPos, content}) => + let c = next(p) + + // on termination + if c !== "[" && isAscii(c) { + let raw = content ++ c + + let loc = {startPos: startPos, endPos: startPos + Js.String.length(raw) - 1} + + let token = Js.Re.exec_(%re(`/\\[([0-9;]+)([\\x40-\\x7F])/`), raw)->( + x => + switch x { + | Some(result) => + let groups = Js.Re.captures(result) + switch Js.Nullable.toOption(groups[1]) { + | Some(str) => + switch Js.String2.split(str, ";") { + | ["0"] => ClearSgr({loc: loc, raw: raw}) + | other => + let params = Belt.Array.map(other, s => + switch s { + | "1" => Bold + | "30" => Fg(Black) + | "31" => Fg(Red) + | "32" => Fg(Green) + | "33" => Fg(Yellow) + | "34" => Fg(Blue) + | "35" => Fg(Magenta) + | "36" => Fg(Cyan) + | "37" => Fg(White) + | "40" => Bg(Black) + | "41" => Bg(Red) + | "42" => Bg(Green) + | "43" => Bg(Yellow) + | "44" => Bg(Blue) + | "45" => Bg(Magenta) + | "46" => Bg(Cyan) + | "47" => Bg(White) + | o => Unknown(o) + } + ) + Sgr({loc: loc, raw: raw, params: params}) + } + + | None => Sgr({loc: loc, raw: raw, params: []}) + } + | None => Sgr({loc: loc, raw: raw, params: []}) + } + ) + Js.Array2.push(acc, token)->ignore + lex(~acc, ~state=Scan, p) + } else { + lex(~acc, ~state=ReadSgr({startPos: startPos, content: content ++ c}), p) + } + } + } + + // We hide the original implementation to prevent users + // to pass in their own `acc` array (this array is getting mutated and + // this could cause side-effects for the consumer otherwise) + let lex = (p: Location.t) => lex(p) +} + +let parse = (input: string) => { + let p = Location.fromString(input) + Lexer.lex(p) +} + +let onlyText = (tokens: array) => { + open Lexer + Belt.Array.keep(tokens, x => + switch x { + | Text(_) => true + | _ => false + } + ) +} + +module SgrString = { + // A sgr encoded element + open Lexer + + type t = { + content: string, + params: array, + } + + let fromTokens = (tokens: array): array => { + let ret = [] + let params = ref([]) + let content = ref("") + + let length = Js.Array.length(tokens) + for i in 0 to length - 1 { + let token = Belt.Array.getExn(tokens, i) + + let isLast = i === length - 1 + + switch token { + | Text(data) => + content := content.contents ++ data.content + if isLast && content.contents !== "" { + let element = {content: content.contents, params: params.contents} + Js.Array2.push(ret, element)->ignore + } + | Sgr(data) => + // merge together specific sgr params + let (fg, bg, rest) = + Belt.Array.concat(params.contents, data.params)->Belt.Array.reduce( + (None, None, []), + (acc, next) => { + let (fg, bg, other) = acc + switch next { + | Fg(_) => (Some(next), bg, other) + | Bg(_) => (fg, Some(next), other) + | o => + if Js.Array2.find(other, o2 => o === o2) === None { + Js.Array2.push(other, next)->ignore + } + (fg, bg, other) + } + }, + ) + + if content.contents !== "" { + let element = {content: content.contents, params: params.contents} + Js.Array2.push(ret, element)->ignore + content := "" + } + + params := + Belt.Array.concatMany([ + Belt.Option.mapWithDefault(fg, [], v => [v]), + Belt.Option.mapWithDefault(bg, [], v => [v]), + rest, + ]) + | ClearSgr(_) => + if content.contents !== "" { + let element = {content: content.contents, params: params.contents} + Js.Array2.push(ret, element)->ignore + + params := [] + content := "" + } + } + } + + ret + } + + let toString = (e: t): string => { + let content = { + open Js.String2 + replaceByRe(e.content, %re("/\\n/g"), "\\n")->replace(esc, "") + } + let params = Belt.Array.map(e.params, Sgr.paramToString)->Js.Array2.joinWith(", ") + + j`SgrString params: $params | content: $content` + } +} + +module Printer = { + open Lexer + + let tokenString = (t: token): string => + switch t { + | Text({content, loc: {startPos, endPos}}) => + let content = { + open Js.String2 + replaceByRe(content, %re("/\\n/g"), "\\n")->replace(esc, "") + } + j`Text "$content" ($startPos to $endPos)` + | Sgr({params, raw, loc: {startPos, endPos}}) => + let raw = Js.String2.replace(raw, esc, "") + let params = Belt.Array.map(params, Sgr.paramToString)->Js.Array2.joinWith(", ") + j`Sgr "$raw" -> $params ($startPos to $endPos)` + | ClearSgr({loc: {startPos, endPos}, raw}) => + let raw = Js.String2.replace(raw, esc, "") + j`Clear Sgr "$raw" ($startPos to $endPos)` + } + + let plainString = (tokens: array): string => Belt.Array.map(tokens, x => + switch x { + | Lexer.Text({content}) => content + | _ => "" + } + )->Js.Array2.joinWith("") +} diff --git a/common/App.js b/common/App.js new file mode 100644 index 000000000..652ae491a --- /dev/null +++ b/common/App.js @@ -0,0 +1,290 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as Url from "./Url.js"; +import * as Meta from "../components/Meta.js"; +import * as React from "react"; +import * as Belt_List from "bs-platform/lib/es6/belt_List.js"; +import * as Belt_Array from "bs-platform/lib/es6/belt_Array.js"; +import * as MainLayout from "../layouts/MainLayout.js"; +import * as Caml_option from "bs-platform/lib/es6/caml_option.js"; +import * as Router from "next/router"; +import * as JsDocsLayout from "../layouts/JsDocsLayout.js"; +import * as DomDocsLayout from "../layouts/DomDocsLayout.js"; +import * as BeltDocsLayout from "../layouts/BeltDocsLayout.js"; +import * as CommunityLayout from "../layouts/CommunityLayout.js"; +import * as ManualDocsLayout from "../layouts/ManualDocsLayout.js"; +import * as ApiOverviewLayout from "../layouts/ApiOverviewLayout.js"; +import * as GenTypeDocsLayout from "../layouts/GenTypeDocsLayout.js"; +import * as JsDocsLayout8_0_0 from "../layouts/JsDocsLayout8_0_0.js"; +import * as DomDocsLayout8_0_0 from "../layouts/DomDocsLayout8_0_0.js"; +import * as BeltDocsLayout8_0_0 from "../layouts/BeltDocsLayout8_0_0.js"; +import * as ManualDocsLayout8_0_0 from "../layouts/ManualDocsLayout8_0_0.js"; +import * as ApiOverviewLayout8_0_0 from "../layouts/ApiOverviewLayout8_0_0.js"; +import * as ReasonCompilerDocsLayout from "../layouts/ReasonCompilerDocsLayout.js"; + +require('../styles/main.css') +; + +let hljs = require('highlight.js/lib/highlight'); + let js = require('highlight.js/lib/languages/javascript'); + let ocaml = require('highlight.js/lib/languages/ocaml'); + let reason = require('../plugins/reason-highlightjs'); + let res = require('../plugins/res-syntax-highlightjs'); + let bash = require('highlight.js/lib/languages/bash'); + let json = require('highlight.js/lib/languages/json'); + let html = require('highlight.js/lib/languages/xml'); + let ts = require('highlight.js/lib/languages/typescript'); + let text = require('highlight.js/lib/languages/plaintext'); + let diff = require('highlight.js/lib/languages/diff'); + + hljs.registerLanguage('reason', reason); + hljs.registerLanguage('res', res); + hljs.registerLanguage('javascript', js); + hljs.registerLanguage('ts', ts); + hljs.registerLanguage('ocaml', ocaml); + hljs.registerLanguage('sh', bash); + hljs.registerLanguage('json', json); + hljs.registerLanguage('text', text); + hljs.registerLanguage('html', html); + hljs.registerLanguage('diff', diff); +; + +function $$default(props) { + var component = props.Component; + var pageProps = props.pageProps; + var router = Router.useRouter(); + var content = React.createElement(component, pageProps); + var url = Url.parse(router.route); + var base = url.base; + var exit = 0; + if (base.length !== 2) { + exit = 1; + } else { + var match = base[0]; + if (match === "docs") { + var match$1 = base[1]; + switch (match$1) { + case "gentype" : + var match$2 = url.version; + if (typeof match$2 === "number") { + if (match$2 === 0) { + return React.createElement(GenTypeDocsLayout.make, { + children: content + }); + } + exit = 1; + } else { + exit = 1; + } + break; + case "manual" : + var pagepath = url.pagepath; + var version = url.version; + var match$3 = Belt_Array.get(pagepath, 0); + var exit$1 = 0; + if (match$3 === "api") { + if (typeof version === "number") { + if (version !== 0) { + return content; + } + var match$4 = pagepath.length; + var match$5 = Belt_Array.get(pagepath, 1); + var exit$2 = 0; + if (match$4 === 1) { + return React.createElement(ApiOverviewLayout.Docs.make, { + children: content + }); + } + if (match$4 !== 2) { + exit$2 = 3; + } else { + if (match$5 === undefined) { + return null; + } + switch (match$5) { + case "belt" : + return React.createElement(BeltDocsLayout.Prose.make, { + children: content + }); + case "js" : + return React.createElement(JsDocsLayout.Prose.make, { + children: content + }); + default: + exit$2 = 3; + } + } + if (exit$2 === 3) { + if (match$5 === undefined) { + return null; + } + switch (match$5) { + case "belt" : + return React.createElement(BeltDocsLayout.Docs.make, { + children: content + }); + case "dom" : + return React.createElement(DomDocsLayout.Docs.make, { + children: content + }); + case "js" : + return React.createElement(JsDocsLayout.Docs.make, { + children: content + }); + default: + return null; + } + } + + } else { + if (version._0 !== "v8.0.0") { + return content; + } + var match$6 = pagepath.length; + var match$7 = Belt_Array.get(pagepath, 1); + var exit$3 = 0; + if (match$6 === 1) { + return React.createElement(ApiOverviewLayout8_0_0.Docs.make, { + children: content + }); + } + if (match$6 !== 2) { + exit$3 = 3; + } else { + if (match$7 === undefined) { + return null; + } + switch (match$7) { + case "belt" : + return React.createElement(BeltDocsLayout8_0_0.Prose.make, { + children: content + }); + case "js" : + return React.createElement(JsDocsLayout8_0_0.Prose.make, { + children: content + }); + default: + exit$3 = 3; + } + } + if (exit$3 === 3) { + if (match$7 === undefined) { + return null; + } + switch (match$7) { + case "belt" : + return React.createElement(BeltDocsLayout8_0_0.Docs.make, { + children: content + }); + case "dom" : + return React.createElement(DomDocsLayout8_0_0.Docs.make, { + children: content + }); + case "js" : + return React.createElement(JsDocsLayout8_0_0.Docs.make, { + children: content + }); + default: + return null; + } + } + + } + } else { + exit$1 = 2; + } + if (exit$1 === 2) { + if (typeof version === "number") { + if (version !== 0) { + return null; + } else { + return React.createElement(ManualDocsLayout.Prose.make, { + frontmatter: component.frontmatter, + children: content + }); + } + } else if (version._0 === "v8.0.0") { + return React.createElement(ManualDocsLayout8_0_0.Prose.make, { + frontmatter: component.frontmatter, + children: content + }); + } else { + return null; + } + } + break; + case "reason-compiler" : + var match$8 = url.version; + if (typeof match$8 === "number") { + if (match$8 === 0) { + return React.createElement(ReasonCompilerDocsLayout.make, { + children: content + }); + } + exit = 1; + } else { + exit = 1; + } + break; + default: + exit = 1; + } + } else { + exit = 1; + } + } + if (exit === 1) { + var match$9 = Belt_List.fromArray(base); + var exit$4 = 0; + if (match$9) { + switch (match$9.hd) { + case "blog" : + return content; + case "community" : + return React.createElement(CommunityLayout.make, { + children: content + }); + case "try" : + if (!match$9.tl) { + return content; + } + exit$4 = 2; + break; + default: + exit$4 = 2; + } + } else { + exit$4 = 2; + } + if (exit$4 === 2) { + var match$10 = url.base; + var title; + if (match$10.length !== 1) { + title = undefined; + } else { + var match$11 = match$10[0]; + title = match$11 === "docs" ? "Overview | ReScript Documentation" : undefined; + } + var tmp = {}; + if (title !== undefined) { + tmp.title = Caml_option.valFromOption(title); + } + return React.createElement(MainLayout.make, { + children: null + }, React.createElement(Meta.make, tmp), React.createElement("div", { + className: "flex justify-center" + }, React.createElement("div", { + className: "max-w-705 w-full" + }, content))); + } + + } + +} + +export { + $$default , + $$default as default, + +} +/* Not a pure module */ diff --git a/common/App.rei b/common/App.rei deleted file mode 100644 index df861d873..000000000 --- a/common/App.rei +++ /dev/null @@ -1,5 +0,0 @@ - -type props; - -let default: (props) => React.element; - diff --git a/common/App.re b/common/App.res similarity index 53% rename from common/App.re rename to common/App.res index c1297c56c..7e9359e52 100644 --- a/common/App.re +++ b/common/App.res @@ -5,12 +5,11 @@ // Really good article on state persistence within layouts: // https://adamwathan.me/2019/10/17/persistent-layout-patterns-in-nextjs/ -%raw -"require('../styles/main.css')"; +%%raw("require('../styles/main.css')") // Register all the highlightjs stuff for the whole application -%raw -{| +%%raw( + ` let hljs = require('highlight.js/lib/highlight'); let js = require('highlight.js/lib/languages/javascript'); let ocaml = require('highlight.js/lib/languages/ocaml'); @@ -33,72 +32,58 @@ hljs.registerLanguage('text', text); hljs.registerLanguage('html', html); hljs.registerLanguage('diff', diff); -|}; +` +) -type pageComponent = React.component(Js.t({.})); -type pageProps = Js.t({.}); +type pageComponent = React.component<{.}> +type pageProps = {.} -type props = { - . - "Component": pageComponent, - "pageProps": pageProps, -}; +type props = {"Component": pageComponent, "pageProps": pageProps} -[@bs.get] -external frontmatter: React.component(Js.t({.})) => Js.Json.t = - "frontmatter"; +@bs.get +external frontmatter: React.component<{.}> => Js.Json.t = "frontmatter" let default = (props: props): React.element => { - let component = props##"Component"; - let pageProps = props##pageProps; + let component = props["Component"] + let pageProps = props["pageProps"] - let router = Next.Router.useRouter(); + let router = Next.Router.useRouter() - let content = React.createElement(component, pageProps); + let content = React.createElement(component, pageProps) - let url = router.route->Url.parse; + let url = router.route->Url.parse - switch (url) { + switch url { // docs routes - | {base: [|"docs", "manual"|], pagepath, version} => + | {base: ["docs", "manual"], pagepath, version} => // check if it's an api route - switch (Belt.Array.get(pagepath, 0)) { + switch Belt.Array.get(pagepath, 0) { | Some("api") => - switch (version) { + switch version { | Latest => switch (Belt.Array.length(pagepath), Belt.Array.get(pagepath, 1)) { | (1, _) => content - | (2, Some("js")) => - content - | (2, Some("belt")) => - content + | (2, Some("js")) => content + | (2, Some("belt")) => content | (_, Some("js")) => content - | (_, Some("belt")) => - content - | (_, Some("dom")) => - content + | (_, Some("belt")) => content + | (_, Some("dom")) => content | _ => React.null } | Version("v8.0.0") => switch (Belt.Array.length(pagepath), Belt.Array.get(pagepath, 1)) { - | (1, _) => - content - | (2, Some("js")) => - content - | (2, Some("belt")) => - content - | (_, Some("js")) => - content - | (_, Some("belt")) => - content - | (_, Some("dom")) => - content + | (1, _) => content + | (2, Some("js")) => content + | (2, Some("belt")) => content + | (_, Some("js")) => content + | (_, Some("belt")) => content + | (_, Some("dom")) => content | _ => React.null } | _ => content } | _ => - switch (version) { + switch version { | Latest => frontmatter}> content @@ -110,32 +95,29 @@ let default = (props: props): React.element => { | _ => React.null } } - | {base: [|"docs", "reason-compiler"|], version: Latest} => + | {base: ["docs", "reason-compiler"], version: Latest} => content - | {base: [|"docs", "gentype"|], version: Latest} => - content + | {base: ["docs", "gentype"], version: Latest} => content // common routes | {base} => - switch (Belt.List.fromArray(base)) { - | ["community", ..._rest] => content - | ["try"] => content - | ["blog"] => content // Blog implements its own layout as well - | ["blog", ..._rest] => - // Here, the layout will be handled by the Blog_Article component + switch Belt.List.fromArray(base) { + | list{"community", ..._rest} => content + | list{"try"} => content + | list{"blog"} => content // Blog implements its own layout as well + | list{"blog", ..._rest} => // Here, the layout will be handled by the Blog_Article component // to keep the frontmatter parsing etc in one place content | _ => - let title = - switch (url) { - | {base: [|"docs"|]} => Some("Overview | ReScript Documentation") - | _ => None - }; + let title = switch url { + | {base: ["docs"]} => Some("Overview | ReScript Documentation") + | _ => None + }
content
-
; + } - }; -}; + } +} diff --git a/common/App.resi b/common/App.resi new file mode 100644 index 000000000..f73bdd36c --- /dev/null +++ b/common/App.resi @@ -0,0 +1,3 @@ +type props + +let default: props => React.element diff --git a/common/BlogApi.js b/common/BlogApi.js new file mode 100644 index 000000000..ceceeb44e --- /dev/null +++ b/common/BlogApi.js @@ -0,0 +1,127 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as Fs from "fs"; +import * as Path from "path"; +import * as DateStr from "./DateStr.js"; +import * as Js_dict from "bs-platform/lib/es6/js_dict.js"; +import * as Process from "process"; +import * as Belt_Array from "bs-platform/lib/es6/belt_Array.js"; +import * as Belt_Option from "bs-platform/lib/es6/belt_Option.js"; +import * as Caml_option from "bs-platform/lib/es6/caml_option.js"; +import GrayMatter from "gray-matter"; +import * as BlogFrontmatter from "./BlogFrontmatter.js"; + +var index = (require('../index_data/blog_posts.json')); + +var GrayMatter$1 = {}; + +var postsDirectory = Path.join(Process.cwd(), "./_blogposts"); + +function getFullSlug(slug) { + return Belt_Option.map(Js_dict.get(index, slug), (function (relPath) { + return relPath.replace(/\.mdx?/, ""); + })); +} + +function getPostBySlug(slug) { + var relPath = index[slug]; + var fullslug = relPath.replace(/\.mdx?/, ""); + var fullPath = Path.join(postsDirectory, fullslug + ".mdx"); + if (!Fs.existsSync(fullPath)) { + return ; + } + var fileContents = Fs.readFileSync(fullPath, "utf8"); + var match = GrayMatter(fileContents); + var archived = fullPath.includes("/archive/"); + return { + slug: slug, + content: match.content, + fullslug: fullslug, + archived: archived, + frontmatter: match.data + }; +} + +function getAllPosts(param) { + return Belt_Array.reduce(Object.keys(index), [], (function (acc, slug) { + var post = getPostBySlug(slug); + if (post !== undefined) { + acc.push(post); + } + return acc; + })); +} + +function dateToUTCString(date) { + date.setHours(15.0); + return date.toUTCString(); +} + +function getLatest(maxOpt, baseUrlOpt, param) { + var max = maxOpt !== undefined ? maxOpt : 10; + var baseUrl = baseUrlOpt !== undefined ? baseUrlOpt : "https://rescript-lang.org"; + var authors = BlogFrontmatter.Author.getAllAuthors(undefined); + return Belt_Array.reduce(getAllPosts(undefined), [], (function (acc, next) { + var fm = BlogFrontmatter.decode(authors, next.frontmatter); + if (fm.TAG) { + return acc; + } + var fm$1 = fm._0; + var description = Belt_Option.getWithDefault(Caml_option.null_to_opt(fm$1.description), ""); + var item_title = fm$1.title; + var item_href = baseUrl + ("/blog/" + next.slug); + var item_pubDate = DateStr.toDate(fm$1.date); + var item = { + title: item_title, + href: item_href, + description: description, + pubDate: item_pubDate + }; + return Belt_Array.concat(acc, [item]); + })).sort(function (item1, item2) { + var v1 = item1.pubDate.valueOf(); + var v2 = item2.pubDate.valueOf(); + if (v1 === v2) { + return 0; + } else if (v1 > v2) { + return -1; + } else { + return 1; + } + }).slice(0, max); +} + +function toXmlString(siteTitleOpt, siteDescriptionOpt, items) { + var siteTitle = siteTitleOpt !== undefined ? siteTitleOpt : "ReScript Blog"; + var siteDescription = siteDescriptionOpt !== undefined ? siteDescriptionOpt : ""; + var latestPubDateElement = Belt_Option.getWithDefault(Belt_Option.map(Belt_Array.get(items, 0), (function (item) { + var latestPubDateStr = dateToUTCString(item.pubDate); + return "" + latestPubDateStr + ""; + })), ""); + var itemsStr = Belt_Array.reduce(items, "", (function (acc, item) { + var description = item.description; + var href = item.href; + var descriptionElement = description === "" ? "" : "\n \n \n "; + var dateStr = dateToUTCString(item.pubDate); + return acc + ("\n \n <![CDATA[" + item.title + "]]>\n " + href + " \n " + href + " \n " + descriptionElement + "\n\n " + dateStr + "\n\n "); + })); + return "\n \n \n " + siteTitle + "\n https://rescript-lang.org\n " + siteDescription + "\n en\n " + latestPubDateElement + "\n " + itemsStr + "\n\n \n "; +} + +var RssFeed = { + dateToUTCString: dateToUTCString, + getLatest: getLatest, + toXmlString: toXmlString +}; + +export { + index , + GrayMatter$1 as GrayMatter, + postsDirectory , + getFullSlug , + getPostBySlug , + getAllPosts , + RssFeed , + +} +/* index Not a pure module */ diff --git a/common/BlogApi.re b/common/BlogApi.res similarity index 50% rename from common/BlogApi.re rename to common/BlogApi.res index 53994bf12..99a1fd4de 100644 --- a/common/BlogApi.re +++ b/common/BlogApi.res @@ -28,20 +28,18 @@ // Manual mapping between slugs and actual file // { [slug: string] : [filepath: string] } // The filepath is relative within _blogposts -let index: Js.Dict.t(string) = [%raw - "require('../index_data/blog_posts.json')" -]; +let index: Js.Dict.t = %raw("require('../index_data/blog_posts.json')") module GrayMatter = { type output = { data: Js.Json.t, content: string, - }; + } - [@bs.module "gray-matter"] external matter: string => output = "default"; -}; + @bs.module("gray-matter") external matter: string => output = "default" +} -let postsDirectory = Node.Path.join2(Node.Process.cwd(), "./_blogposts"); +let postsDirectory = Node.Path.join2(Node.Process.cwd(), "./_blogposts") type postData = { slug: string, @@ -49,49 +47,42 @@ type postData = { fullslug: string, archived: bool, frontmatter: Js.Json.t, -}; +} -let getFullSlug = (slug: string) => { - Belt.Option.map(index->Js.Dict.get(slug), relPath => { - Js.String2.replaceByRe(relPath, [%re "/\\.mdx?/"], "") - }); -}; +let getFullSlug = (slug: string) => + Belt.Option.map(index->Js.Dict.get(slug), relPath => + Js.String2.replaceByRe(relPath, %re("/\\.mdx?/"), "") + ) let getPostBySlug = (slug: string) => { - let relPath = index->Js.Dict.unsafeGet(slug); + let relPath = index->Js.Dict.unsafeGet(slug) - let fullslug = Js.String2.replaceByRe(relPath, [%re "/\\.mdx?/"], ""); + let fullslug = Js.String2.replaceByRe(relPath, %re("/\\.mdx?/"), "") - let fullPath = Node.Path.join2(postsDirectory, fullslug ++ ".mdx"); + let fullPath = Node.Path.join2(postsDirectory, fullslug ++ ".mdx") // TODO: We need to handle handle archived files differently later on - if (Node.Fs.existsSync(fullPath)) { - let fileContents = Node.Fs.readFileSync(fullPath, `utf8); - let {GrayMatter.data, content} = GrayMatter.matter(fileContents); + if Node.Fs.existsSync(fullPath) { + let fileContents = Node.Fs.readFileSync(fullPath, #utf8) + let {GrayMatter.data: data, content} = GrayMatter.matter(fileContents) // We currently derive the archived state from the directory hierarchy - let archived = Js.String2.includes(fullPath, "/archive/"); + let archived = Js.String2.includes(fullPath, "/archive/") - Some({slug, fullslug, content, frontmatter: data, archived}); + Some({slug: slug, fullslug: fullslug, content: content, frontmatter: data, archived: archived}) } else { - None; - }; -}; - -let getAllPosts = () => { - Js.Dict.keys(index) - ->Belt.Array.reduce( - [||], - (acc, slug) => { - switch (getPostBySlug(slug)) { - | Some(post) => Js.Array2.push(acc, post)->ignore - | None => () - }; - acc; - }, - ); -}; + None + } +} + +let getAllPosts = () => Js.Dict.keys(index)->Belt.Array.reduce([], (acc, slug) => { + switch getPostBySlug(slug) { + | Some(post) => Js.Array2.push(acc, post)->ignore + | None => () + } + acc + }) module RssFeed = { // Module inspired by @@ -110,85 +101,68 @@ module RssFeed = { href: string, description: string, pubDate: Js.Date.t, - }; + } // TODO: This is yet again a dirty approach to prevent UTC to substract too many // hours of my local timezone so it does end up on another day, so we set the hours // to 15 o clock. We need to reconsider the way we parse blog article dates, // since the dates should always be parsed from a single timezone perspective let dateToUTCString = date => { - date->Js.Date.setHours(15.0)->ignore; - date->Js.Date.toUTCString; - }; + date->Js.Date.setHours(15.0)->ignore + date->Js.Date.toUTCString + } // Retrieves the most recent [max] blog post feed items - let getLatest = - (~max=10, ~baseUrl="https://rescript-lang.org", ()): array(item) => { - let authors = BlogFrontmatter.Author.getAllAuthors(); - let items = - getAllPosts() - ->Belt.Array.reduce([||], (acc, next) => { - switch (BlogFrontmatter.decode(~authors, next.frontmatter)) { - | Ok(fm) => - let description = - Js.Null.toOption(fm.description) - ->Belt.Option.getWithDefault(""); - let item = { - title: fm.title, - href: baseUrl ++ "/blog/" ++ next.slug, - description, - pubDate: DateStr.toDate(fm.date), - }; - - Belt.Array.concat(acc, [|item|]); - | Error(_) => acc - } - }) - ->Js.Array2.sortInPlaceWith((item1, item2) => { - let v1 = item1.pubDate->Js.Date.valueOf; - let v2 = item2.pubDate->Js.Date.valueOf; - if (v1 === v2) { - 0; - } else if (v1 > v2) { - (-1); - } else { - 1; - }; - }) - ->Js.Array2.slice(~start=0, ~end_=max); - items; - }; - - let toXmlString = - (~siteTitle="ReScript Blog", ~siteDescription="", items: array(item)) => { - let latestPubDateElement = - Belt.Array.get(items, 0) - ->Belt.Option.map(item => { - let latestPubDateStr = item.pubDate->dateToUTCString; - {j|$latestPubDateStr|j}; - }) - ->Belt.Option.getWithDefault(""); - - let itemsStr = - Belt.Array.reduce( - items, - "", - (acc, item) => { - let {title, pubDate, description, href} = item; - - let descriptionElement = - switch (description) { - | "" => "" - | desc => {j| + let getLatest = (~max=10, ~baseUrl="https://rescript-lang.org", ()): array => { + let authors = BlogFrontmatter.Author.getAllAuthors() + let items = getAllPosts()->Belt.Array.reduce([], (acc, next) => + switch BlogFrontmatter.decode(~authors, next.frontmatter) { + | Ok(fm) => + let description = Js.Null.toOption(fm.description)->Belt.Option.getWithDefault("") + let item = { + title: fm.title, + href: baseUrl ++ ("/blog/" ++ next.slug), + description: description, + pubDate: DateStr.toDate(fm.date), + } + + Belt.Array.concat(acc, [item]) + | Error(_) => acc + } + )->Js.Array2.sortInPlaceWith((item1, item2) => { + let v1 = item1.pubDate->Js.Date.valueOf + let v2 = item2.pubDate->Js.Date.valueOf + if v1 === v2 { + 0 + } else if v1 > v2 { + -1 + } else { + 1 + } + })->Js.Array2.slice(~start=0, ~end_=max) + items + } + + let toXmlString = (~siteTitle="ReScript Blog", ~siteDescription="", items: array) => { + let latestPubDateElement = Belt.Array.get(items, 0)->Belt.Option.map(item => { + let latestPubDateStr = item.pubDate->dateToUTCString + j`$latestPubDateStr` + })->Belt.Option.getWithDefault("") + + let itemsStr = Belt.Array.reduce(items, "", (acc, item) => { + let {title, pubDate, description, href} = item + + let descriptionElement = switch description { + | "" => "" + | desc => j` - |j} - }; + ` + } - // TODO: convert pubdate to string - let dateStr = pubDate->dateToUTCString; - acc - ++ {j| + // TODO: convert pubdate to string + let dateStr = pubDate->dateToUTCString + j`${acc} <![CDATA[$title]]> $href @@ -197,11 +171,10 @@ module RssFeed = { $dateStr - |j}; - }, - ); + ` + }) - let ret = {j| + let ret = j` $siteTitle @@ -212,8 +185,8 @@ module RssFeed = { $itemsStr - |j}; + ` //rescript-lang.org - ret; - }; -}; + ret + } +} diff --git a/common/BlogFrontmatter.js b/common/BlogFrontmatter.js new file mode 100644 index 000000000..f0becaa10 --- /dev/null +++ b/common/BlogFrontmatter.js @@ -0,0 +1,225 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as Js_null from "bs-platform/lib/es6/js_null.js"; +import * as Belt_Array from "bs-platform/lib/es6/belt_Array.js"; +import * as Belt_Option from "bs-platform/lib/es6/belt_Option.js"; +import * as Json_decode from "@glennsl/bs-json/src/Json_decode.js"; +import * as Caml_exceptions from "bs-platform/lib/es6/caml_exceptions.js"; +import * as Caml_js_exceptions from "bs-platform/lib/es6/caml_js_exceptions.js"; + +var rawAuthors = (require('../index_data/blog_authors.json')); + +function getDisplayName(author) { + var fullname = author.fullname; + if (fullname !== null) { + return fullname; + } else { + return "@" + author.username; + } +} + +function decode(json) { + return { + username: Json_decode.field("username", Json_decode.string, json), + fullname: Js_null.fromOption(Json_decode.optional((function (param) { + return Json_decode.field("fullname", Json_decode.string, param); + }), json)), + role: Json_decode.field("role", Json_decode.string, json), + imgUrl: Js_null.fromOption(Json_decode.optional((function (param) { + return Json_decode.field("img_url", Json_decode.string, param); + }), json)), + twitter: Js_null.fromOption(Json_decode.optional((function (param) { + return Json_decode.field("twitter", Json_decode.string, param); + }), json)) + }; +} + +function getAllAuthors(param) { + return Json_decode.array(decode, rawAuthors); +} + +var Author = { + rawAuthors: rawAuthors, + getDisplayName: getDisplayName, + decode: decode, + getAllAuthors: getAllAuthors +}; + +function toString(c) { + switch (c) { + case /* Compiler */0 : + return "Compiler"; + case /* Syntax */1 : + return "Syntax"; + case /* Ecosystem */2 : + return "Ecosystem"; + case /* Docs */3 : + return "Docs"; + case /* Community */4 : + return "Community"; + + } +} + +var Category = { + toString: toString +}; + +function toString$1(c) { + switch (c) { + case /* Release */0 : + return "Release"; + case /* Testing */1 : + return "Testing"; + case /* Preview */2 : + return "Preview"; + case /* Roadmap */3 : + return "Roadmap"; + + } +} + +var Badge = { + toString: toString$1 +}; + +function decodeCategory(str) { + var str$1 = str.toLowerCase(); + switch (str$1) { + case "community" : + return /* Community */4; + case "compiler" : + return /* Compiler */0; + case "docs" : + return /* Docs */3; + case "ecosystem" : + return /* Ecosystem */2; + case "syntax" : + return /* Syntax */1; + default: + throw { + RE_EXN_ID: Json_decode.DecodeError, + _1: "Unknown category \"" + str$1 + "\"", + Error: new Error() + }; + } +} + +function decodeBadge(str) { + var str$1 = str.toLowerCase(); + switch (str$1) { + case "preview" : + return /* Preview */2; + case "release" : + return /* Release */0; + case "roadmap" : + return /* Roadmap */3; + case "testing" : + return /* Testing */1; + default: + throw { + RE_EXN_ID: Json_decode.DecodeError, + _1: "Unknown category \"" + str$1 + "\"", + Error: new Error() + }; + } +} + +var AuthorNotFound = Caml_exceptions.create("BlogFrontmatter.AuthorNotFound"); + +function decodeAuthor(fieldName, authors, username) { + var author = authors.find(function (a) { + return a.username === username; + }); + if (author !== undefined) { + return author; + } + throw { + RE_EXN_ID: AuthorNotFound, + _1: "Couldn\'t find author \"" + username + "\" in field " + fieldName, + Error: new Error() + }; +} + +function authorDecoder(fieldName, authors, json) { + var multiple = function (j) { + return Belt_Array.map(Json_decode.array(Json_decode.string, j), (function (param) { + return decodeAuthor(fieldName, authors, param); + })); + }; + var single = function (j) { + return [decodeAuthor(fieldName, authors, Json_decode.string(j))]; + }; + return Json_decode.either(single, multiple)(json); +} + +function decode$1(authors, json) { + var fm; + try { + fm = { + author: decodeAuthor("author", authors, Json_decode.field("author", Json_decode.string, json)), + co_authors: Belt_Option.getWithDefault(Json_decode.optional((function (param) { + return Json_decode.field("co-authors", (function (param) { + return authorDecoder("co-authors", authors, param); + }), param); + }), json), []), + date: Json_decode.field("date", Json_decode.string, json), + previewImg: Js_null.fromOption(Json_decode.optional((function (param) { + return Json_decode.field("previewImg", Json_decode.string, param); + }), json)), + articleImg: Js_null.fromOption(Json_decode.optional((function (param) { + return Json_decode.field("articleImg", Json_decode.string, param); + }), json)), + title: Json_decode.field("title", Json_decode.string, json), + category: Js_null.fromOption(Json_decode.optional((function (j) { + return decodeCategory(Json_decode.field("category", Json_decode.string, j)); + }), json)), + badge: Js_null.fromOption(Json_decode.optional((function (j) { + return decodeBadge(Json_decode.field("badge", Json_decode.string, j)); + }), json)), + description: Json_decode.nullable((function (param) { + return Json_decode.field("description", Json_decode.string, param); + }), json), + canonical: Js_null.fromOption(Json_decode.optional((function (param) { + return Json_decode.field("canonical", Json_decode.string, param); + }), json)) + }; + } + catch (raw_str){ + var str = Caml_js_exceptions.internalToOCamlException(raw_str); + if (str.RE_EXN_ID === Json_decode.DecodeError) { + return { + TAG: 1, + _0: str._1, + [Symbol.for("name")]: "Error" + }; + } + if (str.RE_EXN_ID === AuthorNotFound) { + return { + TAG: 1, + _0: str._1, + [Symbol.for("name")]: "Error" + }; + } + throw str; + } + return { + TAG: 0, + _0: fm, + [Symbol.for("name")]: "Ok" + }; +} + +export { + Author , + Category , + Badge , + decodeCategory , + decodeBadge , + AuthorNotFound , + decodeAuthor , + authorDecoder , + decode$1 as decode, + +} +/* rawAuthors Not a pure module */ diff --git a/common/BlogFrontmatter.re b/common/BlogFrontmatter.re deleted file mode 100644 index c7cba1808..000000000 --- a/common/BlogFrontmatter.re +++ /dev/null @@ -1,187 +0,0 @@ -// Note: Every optional value in here must be encoded -// as Js.Null.t, since it will be used for JSON serialization -// within Next's static generation - -module Author = { - type t = { - username: string, - fullname: Js.null(string), - role: string, - imgUrl: Js.null(string), - twitter: Js.null(string), - }; - - // We could *theoratically* query most of the - // author data from different sources, like twitter - // or github, but decided against it for simplicity - // reasons for now - let rawAuthors: Js.Json.t = [%raw - "require('../index_data/blog_authors.json')" - ]; - - let getDisplayName = (author: t): string => { - switch (author.fullname->Js.Null.toOption) { - | Some(fullname) => fullname - | None => "@" ++ author.username - }; - }; - - let decode = (json: Js.Json.t) => { - Json.Decode.{ - username: json->field("username", string, _), - fullname: - json->optional(field("fullname", string), _)->Js.Null.fromOption, - role: json->field("role", string, _), - imgUrl: - json->optional(field("img_url", string), _)->Js.Null.fromOption, - twitter: - json->optional(field("twitter", string), _)->Js.Null.fromOption, - }; - }; - - let getAllAuthors = (): array(t) => { - rawAuthors->Json.Decode.array(decode, _); - }; -}; - -module Category = { - type t = - | Compiler - | Syntax - | Ecosystem - | Docs - | Community; - - let toString = (c: t): string => { - switch (c) { - | Compiler => "Compiler" - | Syntax => "Syntax" - | Ecosystem => "Ecosystem" - | Docs => "Docs" - | Community => "Community" - }; - }; -}; - -module Badge = { - type t = - | Release - | Testing - | Preview - | Roadmap; - - let toString = (c: t): string => { - switch (c) { - | Release => "Release" - | Testing => "Testing" - | Preview => "Preview" - | Roadmap => "Roadmap" - }; - }; -}; - -type t = { - author: Author.t, - co_authors: array(Author.t), - date: DateStr.t, - previewImg: Js.null(string), - articleImg: Js.null(string), - title: string, - category: Js.null(Category.t), - badge: Js.null(Badge.t), - description: Js.null(string), - canonical: Js.null(string), -}; - -let decodeCategory = (str: string): Category.t => { - switch (Js.String2.toLowerCase(str)) { - | "compiler" => Compiler - | "syntax" => Syntax - | "ecosystem" => Ecosystem - | "docs" => Docs - | "community" => Community - | str => raise(Json.Decode.DecodeError({j|Unknown category "$str"|j})) - }; -}; - -let decodeBadge = (str: string): Badge.t => { - switch (Js.String2.toLowerCase(str)) { - | "release" => Release - | "testing" => Testing - | "preview" => Preview - | "roadmap" => Roadmap - | str => raise(Json.Decode.DecodeError({j|Unknown category "$str"|j})) - }; -}; - -exception AuthorNotFound(string); - -let decodeAuthor = (~fieldName: string, ~authors: array(Author.t), username) => { - switch (Js.Array2.find(authors, a => a.username === username)) { - | Some(author) => author - | None => - raise( - AuthorNotFound( - {j|Couldn't find author "$username" in field $fieldName|j}, - ), - ) - }; -}; - -let authorDecoder = (~fieldName: string, ~authors, json) => { - open Json.Decode; - - let multiple = j => { - array(string, j)->Belt.Array.map(decodeAuthor(~fieldName, ~authors)); - }; - - let single = j => { - [|string(j)->decodeAuthor(~fieldName, ~authors)|]; - }; - - either(single, multiple, json); -}; - -let decode = (~authors: array(Author.t), json: Js.Json.t): result(t, string) => { - Json.Decode.( - switch ( - { - author: - json - ->field("author", string, _) - ->decodeAuthor(~fieldName="author", ~authors), - co_authors: - json - ->optional( - field( - "co-authors", - authorDecoder(~fieldName="co-authors", ~authors), - ), - _, - ) - ->Belt.Option.getWithDefault([||]), - date: json->field("date", string, _)->DateStr.fromString, - category: - json - ->optional(j => {field("category", string, j)->decodeCategory}, _) - ->Js.Null.fromOption, - badge: - json - ->optional(j => {field("badge", string, j)->decodeBadge}, _) - ->Js.Null.fromOption, - previewImg: - json->optional(field("previewImg", string), _)->Js.Null.fromOption, - articleImg: - json->optional(field("articleImg", string), _)->Js.Null.fromOption, - title: json->field("title", string, _), - description: json->nullable(field("description", string), _), - canonical: - json->optional(field("canonical", string), _)->Js.Null.fromOption, - } - ) { - | fm => Ok(fm) - | exception (DecodeError(str)) => Error(str) - | exception (AuthorNotFound(str)) => Error(str) - } - ); -}; diff --git a/common/BlogFrontmatter.res b/common/BlogFrontmatter.res new file mode 100644 index 000000000..3f8cc8f3e --- /dev/null +++ b/common/BlogFrontmatter.res @@ -0,0 +1,146 @@ +// Note: Every optional value in here must be encoded +// as Js.Null.t, since it will be used for JSON serialization +// within Next's static generation + +module Author = { + type t = { + username: string, + fullname: Js.null, + role: string, + imgUrl: Js.null, + twitter: Js.null, + } + + // We could *theoratically* query most of the + // author data from different sources, like twitter + // or github, but decided against it for simplicity + // reasons for now + let rawAuthors: Js.Json.t = %raw("require('../index_data/blog_authors.json')") + + let getDisplayName = (author: t): string => + switch author.fullname->Js.Null.toOption { + | Some(fullname) => fullname + | None => "@" ++ author.username + } + + let decode = (json: Js.Json.t) => { + open Json.Decode + { + username: json->field("username", string, _), + fullname: json->optional(field("fullname", string), _)->Js.Null.fromOption, + role: json->field("role", string, _), + imgUrl: json->optional(field("img_url", string), _)->Js.Null.fromOption, + twitter: json->optional(field("twitter", string), _)->Js.Null.fromOption, + } + } + + let getAllAuthors = (): array => rawAuthors->Json.Decode.array(decode, _) +} + +module Category = { + type t = + | Compiler + | Syntax + | Ecosystem + | Docs + | Community + + let toString = (c: t): string => + switch c { + | Compiler => "Compiler" + | Syntax => "Syntax" + | Ecosystem => "Ecosystem" + | Docs => "Docs" + | Community => "Community" + } +} + +module Badge = { + type t = + | Release + | Testing + | Preview + | Roadmap + + let toString = (c: t): string => + switch c { + | Release => "Release" + | Testing => "Testing" + | Preview => "Preview" + | Roadmap => "Roadmap" + } +} + +type t = { + author: Author.t, + co_authors: array, + date: DateStr.t, + previewImg: Js.null, + articleImg: Js.null, + title: string, + category: Js.null, + badge: Js.null, + description: Js.null, + canonical: Js.null, +} + +let decodeCategory = (str: string): Category.t => + switch Js.String2.toLowerCase(str) { + | "compiler" => Compiler + | "syntax" => Syntax + | "ecosystem" => Ecosystem + | "docs" => Docs + | "community" => Community + | str => raise(Json.Decode.DecodeError(j`Unknown category "$str"`)) + } + +let decodeBadge = (str: string): Badge.t => + switch Js.String2.toLowerCase(str) { + | "release" => Release + | "testing" => Testing + | "preview" => Preview + | "roadmap" => Roadmap + | str => raise(Json.Decode.DecodeError(j`Unknown category "$str"`)) + } + +exception AuthorNotFound(string) + +let decodeAuthor = (~fieldName: string, ~authors: array, username) => + switch Js.Array2.find(authors, a => a.username === username) { + | Some(author) => author + | None => raise(AuthorNotFound(j`Couldn't find author "$username" in field $fieldName`)) + } + +let authorDecoder = (~fieldName: string, ~authors, json) => { + open Json.Decode + + let multiple = j => array(string, j)->Belt.Array.map(decodeAuthor(~fieldName, ~authors)) + + let single = j => [string(j)->decodeAuthor(~fieldName, ~authors)] + + either(single, multiple, json) +} + +let decode = (~authors: array, json: Js.Json.t): result => { + open Json.Decode + switch { + author: json->field("author", string, _)->decodeAuthor(~fieldName="author", ~authors), + co_authors: json + ->optional(field("co-authors", authorDecoder(~fieldName="co-authors", ~authors)), _) + ->Belt.Option.getWithDefault([]), + date: json->field("date", string, _)->DateStr.fromString, + category: json + ->optional(j => field("category", string, j)->decodeCategory, _) + ->Js.Null.fromOption, + badge: json->optional(j => field("badge", string, j)->decodeBadge, _)->Js.Null.fromOption, + previewImg: json->optional(field("previewImg", string), _)->Js.Null.fromOption, + articleImg: json->optional(field("articleImg", string), _)->Js.Null.fromOption, + title: json->field("title", string, _), + description: json->nullable(field("description", string), _), + canonical: json->optional(field("canonical", string), _)->Js.Null.fromOption, + } { + | fm => Ok(fm) + | exception DecodeError(str) => Error(str) + | exception AuthorNotFound(str) => Error(str) + } +} diff --git a/common/ColorTheme.js b/common/ColorTheme.js new file mode 100644 index 000000000..2b3fe6e77 --- /dev/null +++ b/common/ColorTheme.js @@ -0,0 +1,24 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + + +var _map = {"Reason":"theme-reason","Js":"theme-js"}; + +var _revMap = {"theme-reason":"Reason","theme-js":"Js"}; + +function tToJs(param) { + return _map[param]; +} + +function tFromJs(param) { + return _revMap[param]; +} + +var toCN = tToJs; + +export { + tToJs , + tFromJs , + toCN , + +} +/* No side effect */ diff --git a/common/ColorTheme.re b/common/ColorTheme.re deleted file mode 100644 index e70fb13a0..000000000 --- a/common/ColorTheme.re +++ /dev/null @@ -1,9 +0,0 @@ -// Equivalent to styles/_theme.css - -[@bs.deriving jsConverter] -type t = [ - | [@bs.as "theme-reason"] `Reason - | [@bs.as "theme-js"] `Js -]; - -let toCN = value => tToJs(value); diff --git a/common/ColorTheme.res b/common/ColorTheme.res new file mode 100644 index 000000000..874194e42 --- /dev/null +++ b/common/ColorTheme.res @@ -0,0 +1,6 @@ +// Equivalent to styles/_theme.css + +@bs.deriving(jsConverter) +type t = [@bs.as("theme-reason") #Reason | @bs.as("theme-js") #Js] + +let toCN = value => tToJs(value) diff --git a/common/CompilerManagerHook.js b/common/CompilerManagerHook.js new file mode 100644 index 000000000..a871260a6 --- /dev/null +++ b/common/CompilerManagerHook.js @@ -0,0 +1,556 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as Curry from "bs-platform/lib/es6/curry.js"; +import * as React from "react"; +import * as $$Promise from "reason-promise/src/js/promise.js"; +import * as Belt_Array from "bs-platform/lib/es6/belt_Array.js"; +import * as Caml_array from "bs-platform/lib/es6/caml_array.js"; +import * as Belt_Option from "bs-platform/lib/es6/belt_Option.js"; +import * as Caml_option from "bs-platform/lib/es6/caml_option.js"; +import * as SimpleRequest from "./SimpleRequest.js"; +import * as LoadScript from "../ffi/loadScript"; +import LoadScript$1 from "../ffi/loadScript"; +import * as RescriptCompilerApi from "../bindings/RescriptCompilerApi.js"; + +function loadScript(prim, prim$1, prim$2) { + return LoadScript$1(prim, prim$1, prim$2); +} + +function removeScript(prim) { + LoadScript.removeScript(prim); + +} + +function loadScriptPromise(url) { + var match = $$Promise.pending(undefined); + var resolve = match[1]; + LoadScript$1(url, (function (param) { + return Curry._1(resolve, { + TAG: 0, + _0: undefined, + [Symbol.for("name")]: "Ok" + }); + }), (function (_err) { + return Curry._1(resolve, { + TAG: 1, + _0: "Could not load script: " + url, + [Symbol.for("name")]: "Error" + }); + })); + return match[0]; +} + +var LoadScript$2 = { + loadScript: loadScript, + removeScript: removeScript, + loadScriptPromise: loadScriptPromise +}; + +function parseVersions(versions) { + return versions.split("\n").filter(function (v) { + return v !== ""; + }); +} + +function getCompilerUrl(version) { + return "https://cdn.jsdelivr.net/gh/ryyppy/bs-platform-js-releases@master/" + version + "/compiler.js"; +} + +function getLibraryCmijUrl(version, libraryName) { + return "https://cdn.jsdelivr.net/gh/ryyppy/bs-platform-js-releases@master/" + version + "/" + libraryName + "/cmij.js"; +} + +var CdnMeta = { + parseVersions: parseVersions, + getCompilerUrl: getCompilerUrl, + getLibraryCmijUrl: getLibraryCmijUrl +}; + +var FinalResult = {}; + +function attachCompilerAndLibraries(version, libraries, param) { + var compilerUrl = getCompilerUrl(version); + return $$Promise.map($$Promise.flatMap($$Promise.map($$Promise.mapError(loadScriptPromise(compilerUrl), (function (_msg) { + return "Could not load compiler from url " + compilerUrl; + })), (function (r) { + if (r.TAG) { + return [$$Promise.resolved({ + TAG: 1, + _0: r._0, + [Symbol.for("name")]: "Error" + })]; + } else { + return Belt_Array.map(libraries, (function (lib) { + var cmijUrl = getLibraryCmijUrl(version, lib); + return $$Promise.mapError(loadScriptPromise(cmijUrl), (function (_msg) { + return "Could not load cmij from url " + cmijUrl; + })); + })); + } + })), $$Promise.allArray), (function (all) { + var errors = Belt_Array.reduce(all, [], (function (acc, r) { + if (r.TAG) { + return acc.concat([r._0]); + } else { + return acc; + } + })); + if (errors.length !== 0) { + return { + TAG: 1, + _0: errors, + [Symbol.for("name")]: "Error" + }; + } else { + return { + TAG: 0, + _0: undefined, + [Symbol.for("name")]: "Ok" + }; + } + })); +} + +function useCompilerManager(initialLangOpt, onAction, param) { + var initialLang = initialLangOpt !== undefined ? initialLangOpt : /* Res */2; + var match = React.useState(function () { + return /* Init */0; + }); + var setState = match[1]; + var state = match[0]; + var dispatch = function (action) { + Belt_Option.forEach(onAction, (function (cb) { + return Curry._1(cb, action); + })); + switch (action.TAG | 0) { + case /* SwitchToCompiler */0 : + var libraries = action.libraries; + var id = action.id; + if (typeof state === "number") { + return ; + } + if (state.TAG !== /* Ready */2) { + return ; + } + var ready = state._0; + if (ready.selected.id !== id) { + return Curry._1(setState, (function (param) { + return { + TAG: 1, + _0: ready, + _1: id, + _2: libraries, + [Symbol.for("name")]: "SwitchingCompiler" + }; + })); + } else { + return ; + } + case /* SwitchLanguage */1 : + var code = action.code; + var lang = action.lang; + if (typeof state === "number") { + return ; + } + if (state.TAG !== /* Ready */2) { + return ; + } + var ready$1 = state._0; + var instance = ready$1.selected.instance; + var availableTargetLangs = RescriptCompilerApi.Version.availableLanguages(ready$1.selected.apiVersion); + var currentLang = ready$1.targetLang; + return Belt_Option.forEach(Caml_option.undefined_to_opt(availableTargetLangs.find(function (l) { + return l === lang; + })), (function (lang) { + var match = ready$1.selected.apiVersion; + var match$1; + if (match) { + match$1 = [ + /* Nothing */0, + lang + ]; + } else { + var convResult; + switch (currentLang) { + case /* Reason */0 : + convResult = lang >= 2 ? RescriptCompilerApi.Compiler.convertSyntax(instance, /* Reason */0, /* Res */2, code) : undefined; + break; + case /* OCaml */1 : + convResult = undefined; + break; + case /* Res */2 : + convResult = lang !== 0 ? undefined : RescriptCompilerApi.Compiler.convertSyntax(instance, /* Res */2, /* Reason */0, code); + break; + + } + if (convResult !== undefined) { + if (convResult.TAG) { + var secondTry = RescriptCompilerApi.Compiler.convertSyntax(instance, lang, lang, code); + match$1 = [ + { + TAG: 0, + _0: secondTry, + [Symbol.for("name")]: "Conv" + }, + lang + ]; + } else { + match$1 = [ + { + TAG: 0, + _0: convResult, + [Symbol.for("name")]: "Conv" + }, + lang + ]; + } + } else { + match$1 = [ + /* Nothing */0, + lang + ]; + } + } + var targetLang = match$1[1]; + var result = match$1[0]; + return Curry._1(setState, (function (param) { + return { + TAG: 2, + _0: { + versions: ready$1.versions, + selected: ready$1.selected, + targetLang: targetLang, + errors: [], + result: result + }, + [Symbol.for("name")]: "Ready" + }; + })); + })); + case /* Format */2 : + var code$1 = action._0; + if (typeof state === "number") { + return ; + } + if (state.TAG !== /* Ready */2) { + return ; + } + var ready$2 = state._0; + var instance$1 = ready$2.selected.instance; + var match = ready$2.targetLang; + var convResult; + switch (match) { + case /* Reason */0 : + convResult = RescriptCompilerApi.Compiler.reasonFormat(instance$1, code$1); + break; + case /* OCaml */1 : + convResult = undefined; + break; + case /* Res */2 : + convResult = RescriptCompilerApi.Compiler.resFormat(instance$1, code$1); + break; + + } + var result = convResult !== undefined && (convResult.TAG || code$1 !== convResult._0.code) ? ({ + TAG: 0, + _0: convResult, + [Symbol.for("name")]: "Conv" + }) : ready$2.result; + return Curry._1(setState, (function (param) { + return { + TAG: 2, + _0: { + versions: ready$2.versions, + selected: ready$2.selected, + targetLang: ready$2.targetLang, + errors: [], + result: result + }, + [Symbol.for("name")]: "Ready" + }; + })); + case /* CompileCode */3 : + var code$2 = action._1; + var lang$1 = action._0; + if (typeof state === "number") { + return ; + } + if (state.TAG !== /* Ready */2) { + return ; + } + var ready$3 = state._0; + return Curry._1(setState, (function (param) { + return { + TAG: 3, + _0: ready$3, + _1: [ + lang$1, + code$2 + ], + [Symbol.for("name")]: "Compiling" + }; + })); + case /* UpdateConfig */4 : + var config = action._0; + if (typeof state === "number") { + return ; + } + if (state.TAG !== /* Ready */2) { + return ; + } + var ready$4 = state._0; + RescriptCompilerApi.Compiler.setConfig(ready$4.selected.instance, config); + return Curry._1(setState, (function (param) { + var init = ready$4.selected; + var selected_id = init.id; + var selected_apiVersion = init.apiVersion; + var selected_compilerVersion = init.compilerVersion; + var selected_ocamlVersion = init.ocamlVersion; + var selected_reasonVersion = init.reasonVersion; + var selected_libraries = init.libraries; + var selected_instance = init.instance; + var selected = { + id: selected_id, + apiVersion: selected_apiVersion, + compilerVersion: selected_compilerVersion, + ocamlVersion: selected_ocamlVersion, + reasonVersion: selected_reasonVersion, + libraries: selected_libraries, + config: config, + instance: selected_instance + }; + return { + TAG: 2, + _0: { + versions: ready$4.versions, + selected: selected, + targetLang: ready$4.targetLang, + errors: ready$4.errors, + result: ready$4.result + }, + [Symbol.for("name")]: "Ready" + }; + })); + + } + }; + var dispatchError = function (err) { + return Curry._1(setState, (function (prev) { + var msg = err._0; + if (typeof prev === "number") { + return { + TAG: 0, + _0: msg, + [Symbol.for("name")]: "SetupFailed" + }; + } + if (prev.TAG !== /* Ready */2) { + return { + TAG: 0, + _0: msg, + [Symbol.for("name")]: "SetupFailed" + }; + } + var ready = prev._0; + return { + TAG: 2, + _0: { + versions: ready.versions, + selected: ready.selected, + targetLang: ready.targetLang, + errors: ready.errors.concat([msg]), + result: ready.result + }, + [Symbol.for("name")]: "Ready" + }; + })); + }; + React.useEffect((function () { + if (typeof state === "number") { + var libraries = ["reason-react"]; + var completed = function (res) { + if (res.TAG) { + var match = res._0; + dispatchError({ + TAG: 0, + _0: "Error occurred: " + match.text + " (status-code: " + match.status + ")", + [Symbol.for("name")]: "SetupError" + }); + } else { + var versions = parseVersions(res._0.text); + if (versions.length !== 0) { + var latest = Caml_array.get(versions, 0); + $$Promise.get(attachCompilerAndLibraries(latest, libraries, undefined), (function (result) { + if (result.TAG) { + var msg = result._0.join("; "); + return dispatchError({ + TAG: 1, + _0: msg, + [Symbol.for("name")]: "CompilerLoadingError" + }); + } + var instance = rescript_compiler.make(); + var apiVersion = RescriptCompilerApi.Version.fromString(rescript_compiler.api_version); + var config = instance.getConfig(); + var selected_compilerVersion = instance.version; + var selected_ocamlVersion = instance.ocaml.version; + var selected_reasonVersion = instance.reason.version; + var selected = { + id: latest, + apiVersion: apiVersion, + compilerVersion: selected_compilerVersion, + ocamlVersion: selected_ocamlVersion, + reasonVersion: selected_reasonVersion, + libraries: libraries, + config: config, + instance: instance + }; + var targetLang = Belt_Option.getWithDefault(Caml_option.undefined_to_opt(RescriptCompilerApi.Version.availableLanguages(apiVersion).find(function (l) { + return l === initialLang; + })), RescriptCompilerApi.Version.defaultTargetLang(apiVersion)); + return Curry._1(setState, (function (param) { + return { + TAG: 2, + _0: { + versions: versions, + selected: selected, + targetLang: targetLang, + errors: [], + result: /* Nothing */0 + }, + [Symbol.for("name")]: "Ready" + }; + })); + })); + } else { + dispatchError({ + TAG: 0, + _0: "No compiler versions found", + [Symbol.for("name")]: "SetupError" + }); + } + } + + }; + SimpleRequest.send(SimpleRequest.make(completed, undefined, /* Plain */1, "https://cdn.jsdelivr.net/gh/ryyppy/bs-platform-js-releases@latest/VERSIONS")); + } else { + switch (state.TAG | 0) { + case /* SwitchingCompiler */1 : + var libraries$1 = state._2; + var version = state._1; + var ready = state._0; + $$Promise.get(attachCompilerAndLibraries(version, libraries$1, undefined), (function (result) { + if (result.TAG) { + var msg = result._0.join("; "); + return dispatchError({ + TAG: 1, + _0: msg, + [Symbol.for("name")]: "CompilerLoadingError" + }); + } + var prim = getCompilerUrl(ready.selected.id); + LoadScript.removeScript(prim); + Belt_Array.forEach(ready.selected.libraries, (function (lib) { + var prim = getLibraryCmijUrl(ready.selected.id, lib); + LoadScript.removeScript(prim); + + })); + var instance = rescript_compiler.make(); + var apiVersion = RescriptCompilerApi.Version.fromString(rescript_compiler.api_version); + var config = instance.getConfig(); + var selected_compilerVersion = instance.version; + var selected_ocamlVersion = instance.ocaml.version; + var selected_reasonVersion = instance.reason.version; + var selected = { + id: version, + apiVersion: apiVersion, + compilerVersion: selected_compilerVersion, + ocamlVersion: selected_ocamlVersion, + reasonVersion: selected_reasonVersion, + libraries: libraries$1, + config: config, + instance: instance + }; + return Curry._1(setState, (function (param) { + return { + TAG: 2, + _0: { + versions: ready.versions, + selected: selected, + targetLang: RescriptCompilerApi.Version.defaultTargetLang(apiVersion), + errors: [], + result: /* Nothing */0 + }, + [Symbol.for("name")]: "Ready" + }; + })); + })); + break; + case /* SetupFailed */0 : + case /* Ready */2 : + break; + case /* Compiling */3 : + var match = state._1; + var code = match[1]; + var ready$1 = state._0; + var apiVersion = ready$1.selected.apiVersion; + var instance = ready$1.selected.instance; + var compResult; + if (apiVersion) { + compResult = { + TAG: 2, + _0: "Can\'t handle result of compiler API version \"" + apiVersion._0 + "\"", + [Symbol.for("name")]: "UnexpectedError" + }; + } else { + switch (match[0]) { + case /* Reason */0 : + compResult = RescriptCompilerApi.Compiler.reasonCompile(instance, code); + break; + case /* OCaml */1 : + compResult = RescriptCompilerApi.Compiler.ocamlCompile(instance, code); + break; + case /* Res */2 : + compResult = RescriptCompilerApi.Compiler.resCompile(instance, code); + break; + + } + } + Curry._1(setState, (function (param) { + return { + TAG: 2, + _0: { + versions: ready$1.versions, + selected: ready$1.selected, + targetLang: ready$1.targetLang, + errors: ready$1.errors, + result: { + TAG: 1, + _0: compResult, + [Symbol.for("name")]: "Comp" + } + }, + [Symbol.for("name")]: "Ready" + }; + })); + break; + + } + } + + }), [state]); + return [ + state, + dispatch + ]; +} + +export { + LoadScript$2 as LoadScript, + CdnMeta , + FinalResult , + attachCompilerAndLibraries , + useCompilerManager , + +} +/* react Not a pure module */ diff --git a/common/CompilerManagerHook.re b/common/CompilerManagerHook.re deleted file mode 100644 index de1058183..000000000 --- a/common/CompilerManagerHook.re +++ /dev/null @@ -1,489 +0,0 @@ -/* - This module is intended to manage following things: - - Loading available versions of bs-platform-js releases - - Loading actual bs-platform-js bundles on demand - - Loading third-party libraries together with the compiler bundle - - Sending data back and forth between consumer and compiler - - The interface is defined with a finite state and action dispatcher. - */ - -open RescriptCompilerApi; - -module LoadScript = { - type err; - - [@bs.module "../ffi/loadScript"] - external loadScript: - (~src: string, ~onSuccess: unit => unit, ~onError: err => unit) => - (. unit) => unit = - "default"; - - [@bs.module "../ffi/loadScript"] - external removeScript: (~src: string) => unit = "removeScript"; - - let loadScriptPromise = (url: string): Promise.t(result(unit, string)) => { - let (p, resolve) = Promise.pending(); - loadScript( - ~src=url, - ~onSuccess=() => {resolve(Ok())}, - ~onError=_err => {resolve(Error({j|Could not load script: $url|j}))}, - ) - ->ignore; - p; - }; -}; - -module CdnMeta = { - // Splits and sanitizes the content of the VERSIONS file - let parseVersions = (versions: string) => { - Js.String2.split(versions, "\n")->Js.Array2.filter(v => v !== ""); - }; - - let getCompilerUrl = (version: string): string => { - {j|https://cdn.jsdelivr.net/gh/ryyppy/bs-platform-js-releases@master/$version/compiler.js|j}; - }; - - let getLibraryCmijUrl = (version: string, libraryName: string): string => { - {j|https://cdn.jsdelivr.net/gh/ryyppy/bs-platform-js-releases@master/$version/$libraryName/cmij.js|j}; - }; -}; - -module FinalResult = { - /* A final result is the last operation the compiler has done, right now this includes... */ - type t = - | Conv(ConversionResult.t) - | Comp(CompilationResult.t) - | Nothing; -}; - -/* - This function loads the compiler, plus a defined set of libraries that are available - on our bs-platform-js-releases channel. - - Due to JSOO specifics, even if we already loaded a compiler before, we need to make sure - to load the compiler bundle first, and then load the library cmij files right after that. - - If you don't respect the loading order, then the loaded cmij files will not hook into the - jsoo filesystem and the compiler won't be able to find the cmij content. - - We coupled the compiler / library loading to prevent ppl to try loading compiler / cmij files - separately and cause all kinds of race conditions. - */ -let attachCompilerAndLibraries = - (~version: string, ~libraries: array(string), ()) - : Promise.t(result(unit, array(string))) => { - let compilerUrl = CdnMeta.getCompilerUrl(version); - - // Useful for debugging our local build - /*let compilerUrl = "/static/linked-bs-bundle.js";*/ - - LoadScript.loadScriptPromise(compilerUrl) - ->Promise.mapError(_msg => - {j|Could not load compiler from url $compilerUrl|j} - ) - ->Promise.map(r => { - switch (r) { - | Ok () => - Belt.Array.map( - libraries, - lib => { - let cmijUrl = CdnMeta.getLibraryCmijUrl(version, lib); - LoadScript.loadScriptPromise(cmijUrl) - ->Promise.mapError(_msg => - {j|Could not load cmij from url $cmijUrl|j} - ); - }, - ) - | Error(msg) => [|Promise.resolved(Error(msg))|] - } - }) - ->Promise.flatMap(Promise.allArray) - ->Promise.map(all => { - // all: array(Promise.result(unit, string)) - let errors = - Belt.Array.reduce(all, [||], (acc, r) => - switch (r) { - | Error(msg) => Js.Array2.concat(acc, [|msg|]) - | _ => acc - } - ); - - switch (errors) { - | [||] => Ok() - | errs => Error(errs) - }; - }); -}; - -type error = - | SetupError(string) - | CompilerLoadingError(string); - -type selected = { - id: string, // The id used for loading the compiler bundle (ideally should be the same as compilerVersion) - apiVersion: Version.t, // The playground API version in use - compilerVersion: string, - ocamlVersion: string, - reasonVersion: string, - libraries: array(string), - config: Config.t, - instance: Compiler.t, -}; - -type ready = { - versions: array(string), - selected, - targetLang: Lang.t, - errors: array(string), // For major errors like bundle loading - result: FinalResult.t, -}; - -type state = - | Init - | SetupFailed(string) - | SwitchingCompiler(ready, string, array(string)) // (ready, targetId, libraries) - | Ready(ready) - | Compiling(ready, (Lang.t, string)); - -type action = - | SwitchToCompiler({ - id: string, - libraries: array(string), - }) - | SwitchLanguage({ - lang: Lang.t, - code: string, - }) - | Format(string) - | CompileCode(Lang.t, string) - | UpdateConfig(Config.t); - -// ~initialLang: -// The target language the compiler should be set to during -// playground initialization. If the compiler doesn't support the language, it -// will default to ReScript syntax -// -// ~onAction: -// This function is especially useful if you want to maintain state that -// depends on any action happening in the compiler, no matter if a state -// transition happened, or not. We need that for a ActivityIndicator -// component to give feedback to the user that an action happened (useful in -// cases where the output didn't visually change) -let useCompilerManager = - (~initialLang: Lang.t=Res, ~onAction: option(action => unit)=?, ()) => { - let (state, setState) = React.useState(_ => Init); - - // Dispatch method for the public interface - let dispatch = (action: action): unit => { - Belt.Option.forEach(onAction, cb => cb(action)); - switch (action) { - | SwitchToCompiler({id, libraries}) => - switch (state) { - | Ready(ready) => - // TODO: Check if libraries have changed as well - if (ready.selected.id !== id) { - setState(_ => SwitchingCompiler(ready, id, libraries)); - } else { - (); - } - | _ => () - } - | UpdateConfig(config) => - switch (state) { - | Ready(ready) => - ready.selected.instance->Compiler.setConfig(config); - setState(_ => { - let selected = {...ready.selected, config}; - Ready({...ready, selected}); - }); - | _ => () - } - | CompileCode(lang, code) => - switch (state) { - | Ready(ready) => setState(_ => Compiling(ready, (lang, code))) - | _ => () - } - | SwitchLanguage({lang, code}) => - switch (state) { - | Ready(ready) => - let instance = ready.selected.instance; - let availableTargetLangs = - Version.availableLanguages(ready.selected.apiVersion); - - let currentLang = ready.targetLang; - - Js.Array2.find(availableTargetLangs, l => l === lang) - ->Belt.Option.forEach(lang => { - // Try to automatically transform code - let (result, targetLang) = - switch (ready.selected.apiVersion) { - | V1 => - let convResult = - switch (currentLang, lang) { - | (Reason, Res) => - instance - ->Compiler.convertSyntax( - ~fromLang=Reason, - ~toLang=Res, - ~code, - ) - ->Some - | (Res, Reason) => - instance - ->Compiler.convertSyntax( - ~fromLang=Res, - ~toLang=Reason, - ~code, - ) - ->Some - | _ => None - }; - - /* - Syntax convertion works the following way: - If currentLang -> otherLang is not valid, try to pretty print the code - with the otherLang, in case we e.g. want to copy paste or otherLang code - in the editor and quickly switch to it - */ - switch (convResult) { - | Some(result) => - switch (result) { - | ConversionResult.Fail(_) - | Unknown(_, _) - | UnexpectedError(_) => - let secondTry = - instance->Compiler.convertSyntax( - ~fromLang=lang, - ~toLang=lang, - ~code, - ); - switch (secondTry) { - | ConversionResult.Fail(_) - | Unknown(_, _) - | UnexpectedError(_) => ( - FinalResult.Conv(secondTry), - lang, - ) - | Success(_) => (Conv(secondTry), lang) - }; - | ConversionResult.Success(_) => (Conv(result), lang) - } - | None => (Nothing, lang) - }; - | _ => (Nothing, lang) - }; - - setState(_ => - Ready({...ready, result, errors: [||], targetLang}) - ); - }); - | _ => () - } - | Format(code) => - switch (state) { - | Ready(ready) => - let instance = ready.selected.instance; - let convResult = - switch (ready.targetLang) { - | Res => instance->Compiler.resFormat(code)->Some - | Reason => instance->Compiler.reasonFormat(code)->Some - | _ => None - }; - - let result = - switch (convResult) { - | Some(result) => - switch (result) { - | ConversionResult.Success(success) => - // We will only change the result to a ConversionResult - // in case the reformatting has actually changed code - // otherwise we'd loose previous compilationResults, although - // the result should be the same anyways - if (code !== success.code) { - FinalResult.Conv(result); - } else { - ready.result; - } - | ConversionResult.Fail(_) - | Unknown(_, _) - | UnexpectedError(_) => FinalResult.Conv(result) - } - | None => ready.result - }; - - setState(_ => Ready({...ready, result, errors: [||]})); - | _ => () - } - }; - }; - - let dispatchError = (err: error) => { - setState(prev => { - let msg = - switch (err) { - | SetupError(msg) => msg - | CompilerLoadingError(msg) => msg - }; - switch (prev) { - | Ready(ready) => - Ready({...ready, errors: Js.Array2.concat(ready.errors, [|msg|])}) - | _ => SetupFailed(msg) - }; - }); - }; - - React.useEffect1( - () => { - switch (state) { - | Init => - let libraries = [|"reason-react"|]; - - let completed = res => { - open SimpleRequest; - switch (res) { - | Ok({text}) => - switch (CdnMeta.parseVersions(text)) { - | [||] => - dispatchError(SetupError({j|No compiler versions found|j})) - | versions => - // Fetching the initial compiler is different, since we - // don't have any running version downloaded yet - - let latest = versions[0]; - - attachCompilerAndLibraries(~version=latest, ~libraries, ()) - ->Promise.get(result => { - switch (result) { - | Ok () => - let instance = Compiler.make(); - let apiVersion = apiVersion->Version.fromString; - let config = instance->Compiler.getConfig; - - let selected = { - id: latest, - apiVersion, - compilerVersion: instance->Compiler.version, - ocamlVersion: instance->Compiler.ocamlVersion, - reasonVersion: instance->Compiler.reasonVersion, - config, - libraries, - instance, - }; - - let targetLang = - Version.availableLanguages(apiVersion) - ->Js.Array2.find(l => {l === initialLang}) - ->Belt.Option.getWithDefault( - Version.defaultTargetLang(apiVersion), - ); - - setState(_ => { - Ready({ - selected, - targetLang, - versions, - errors: [||], - result: FinalResult.Nothing, - }) - }); - | Error(errs) => - let msg = Js.Array2.joinWith(errs, "; "); - - dispatchError(CompilerLoadingError(msg)); - } - }); - } - | Error({text, status}) => - dispatchError( - SetupError({j|Error occurred: $text (status-code: $status)|j}), - ) - }; - (); - }; - - SimpleRequest.( - make( - ~contentType=Plain, - ~completed, - "https://cdn.jsdelivr.net/gh/ryyppy/bs-platform-js-releases@latest/VERSIONS", - ) - ->send - ); - | SwitchingCompiler(ready, version, libraries) => - attachCompilerAndLibraries(~version, ~libraries, ()) - ->Promise.get(result => { - switch (result) { - | Ok () => - // Make sure to remove the previous script from the DOM as well - LoadScript.removeScript( - ~src=CdnMeta.getCompilerUrl(ready.selected.id), - ); - - Belt.Array.forEach(ready.selected.libraries, lib => { - LoadScript.removeScript( - ~src=CdnMeta.getLibraryCmijUrl(ready.selected.id, lib), - ) - }); - - let instance = Compiler.make(); - let apiVersion = apiVersion->Version.fromString; - let config = instance->Compiler.getConfig; - - let selected = { - id: version, - apiVersion, - compilerVersion: instance->Compiler.version, - ocamlVersion: instance->Compiler.ocamlVersion, - reasonVersion: instance->Compiler.reasonVersion, - config, - libraries, - instance, - }; - - setState(_ => { - Ready({ - selected, - targetLang: Version.defaultTargetLang(apiVersion), - versions: ready.versions, - errors: [||], - result: FinalResult.Nothing, - }) - }); - | Error(errs) => - let msg = Js.Array2.joinWith(errs, "; "); - - dispatchError(CompilerLoadingError(msg)); - } - }) - | Compiling(ready, (lang, code)) => - let apiVersion = ready.selected.apiVersion; - let instance = ready.selected.instance; - - let compResult = - switch (apiVersion) { - | Version.V1 => - switch (lang) { - | Lang.OCaml => instance->Compiler.ocamlCompile(code) - | Lang.Reason => instance->Compiler.reasonCompile(code) - | Lang.Res => instance->Compiler.resCompile(code) - } - | UnknownVersion(apiVersion) => - CompilationResult.UnexpectedError( - {j|Can't handle result of compiler API version "$apiVersion"|j}, - ) - }; - - setState(_ => { - Ready({...ready, result: FinalResult.Comp(compResult)}) - }); - | SetupFailed(_) - | Ready(_) => () - }; - None; - }, - [|state|], - ); - - (state, dispatch); -}; diff --git a/common/CompilerManagerHook.res b/common/CompilerManagerHook.res new file mode 100644 index 000000000..c1788094a --- /dev/null +++ b/common/CompilerManagerHook.res @@ -0,0 +1,419 @@ +/* + This module is intended to manage following things: + - Loading available versions of bs-platform-js releases + - Loading actual bs-platform-js bundles on demand + - Loading third-party libraries together with the compiler bundle + - Sending data back and forth between consumer and compiler + + The interface is defined with a finite state and action dispatcher. + */ + +open RescriptCompilerApi + +module LoadScript = { + type err + + @bs.module("../ffi/loadScript") + external loadScript: ( + ~src: string, + ~onSuccess: unit => unit, + ~onError: err => unit, + . unit, + ) => unit = "default" + + @bs.module("../ffi/loadScript") + external removeScript: (~src: string) => unit = "removeScript" + + let loadScriptPromise = (url: string): Promise.t> => { + let (p, resolve) = Promise.pending() + loadScript( + ~src=url, + ~onSuccess=() => resolve(Ok()), + ~onError=_err => resolve(Error(j`Could not load script: $url`)), + )->ignore + p + } +} + +module CdnMeta = { + // Splits and sanitizes the content of the VERSIONS file + let parseVersions = (versions: string) => + Js.String2.split(versions, "\n")->Js.Array2.filter(v => v !== "") + + let getCompilerUrl = (version: string): string => + j`https://cdn.jsdelivr.net/gh/ryyppy/bs-platform-js-releases@master/$version/compiler.js` //cdn.jsdelivr.net/gh/ryyppy/bs-platform-js-releases@master/$version/compiler.js|j}; + + let getLibraryCmijUrl = (version: string, libraryName: string): string => + j`https://cdn.jsdelivr.net/gh/ryyppy/bs-platform-js-releases@master/$version/$libraryName/cmij.js` //cdn.jsdelivr.net/gh/ryyppy/bs-platform-js-releases@master/$version/$libraryName/cmij.js|j}; +} + +module FinalResult = { + /* A final result is the last operation the compiler has done, right now this includes... */ + type t = + | Conv(ConversionResult.t) + | Comp(CompilationResult.t) + | Nothing +} + +/* + This function loads the compiler, plus a defined set of libraries that are available + on our bs-platform-js-releases channel. + + Due to JSOO specifics, even if we already loaded a compiler before, we need to make sure + to load the compiler bundle first, and then load the library cmij files right after that. + + If you don't respect the loading order, then the loaded cmij files will not hook into the + jsoo filesystem and the compiler won't be able to find the cmij content. + + We coupled the compiler / library loading to prevent ppl to try loading compiler / cmij files + separately and cause all kinds of race conditions. + */ +let attachCompilerAndLibraries = (~version: string, ~libraries: array, ()): Promise.t< + result>, +> => { + let compilerUrl = CdnMeta.getCompilerUrl(version) + + // Useful for debugging our local build + /* let compilerUrl = "/static/linked-bs-bundle.js"; */ + + LoadScript.loadScriptPromise(compilerUrl) + ->Promise.mapError(_msg => j`Could not load compiler from url $compilerUrl`) + ->Promise.map(r => + switch r { + | Ok() => Belt.Array.map(libraries, lib => { + let cmijUrl = CdnMeta.getLibraryCmijUrl(version, lib) + LoadScript.loadScriptPromise(cmijUrl)->Promise.mapError(_msg => + j`Could not load cmij from url $cmijUrl` + ) + }) + | Error(msg) => [Promise.resolved(Error(msg))] + } + ) + ->Promise.flatMap(Promise.allArray) + ->Promise.map(all => { + // all: array(Promise.result(unit, string)) + let errors = Belt.Array.reduce(all, [], (acc, r) => + switch r { + | Error(msg) => Js.Array2.concat(acc, [msg]) + | _ => acc + } + ) + + switch errors { + | [] => Ok() + | errs => Error(errs) + } + }) +} + +type error = + | SetupError(string) + | CompilerLoadingError(string) + +type selected = { + id: string, // The id used for loading the compiler bundle (ideally should be the same as compilerVersion) + apiVersion: Version.t, // The playground API version in use + compilerVersion: string, + ocamlVersion: string, + reasonVersion: string, + libraries: array, + config: Config.t, + instance: Compiler.t, +} + +type ready = { + versions: array, + selected: selected, + targetLang: Lang.t, + errors: array, // For major errors like bundle loading + result: FinalResult.t, +} + +type state = + | Init + | SetupFailed(string) + | SwitchingCompiler(ready, string, array) // (ready, targetId, libraries) + | Ready(ready) + | Compiling(ready, (Lang.t, string)) + +type action = + | SwitchToCompiler({id: string, libraries: array}) + | SwitchLanguage({lang: Lang.t, code: string}) + | Format(string) + | CompileCode(Lang.t, string) + | UpdateConfig(Config.t) + +// ~initialLang: +// The target language the compiler should be set to during +// playground initialization. If the compiler doesn't support the language, it +// will default to ReScript syntax + +// ~onAction: +// This function is especially useful if you want to maintain state that +// depends on any action happening in the compiler, no matter if a state +// transition happened, or not. We need that for a ActivityIndicator +// component to give feedback to the user that an action happened (useful in +// cases where the output didn't visually change) +let useCompilerManager = (~initialLang: Lang.t=Res, ~onAction: option unit>=?, ()) => { + let (state, setState) = React.useState(_ => Init) + + // Dispatch method for the public interface + let dispatch = (action: action): unit => { + Belt.Option.forEach(onAction, cb => cb(action)) + switch action { + | SwitchToCompiler({id, libraries}) => + switch state { + | Ready(ready) => + // TODO: Check if libraries have changed as well + if ready.selected.id !== id { + setState(_ => SwitchingCompiler(ready, id, libraries)) + } else { + () + } + | _ => () + } + | UpdateConfig(config) => + switch state { + | Ready(ready) => + ready.selected.instance->Compiler.setConfig(config) + setState(_ => { + let selected = {...ready.selected, config: config} + Ready({...ready, selected: selected}) + }) + | _ => () + } + | CompileCode(lang, code) => + switch state { + | Ready(ready) => setState(_ => Compiling(ready, (lang, code))) + | _ => () + } + | SwitchLanguage({lang, code}) => + switch state { + | Ready(ready) => + let instance = ready.selected.instance + let availableTargetLangs = Version.availableLanguages(ready.selected.apiVersion) + + let currentLang = ready.targetLang + + Js.Array2.find(availableTargetLangs, l => l === lang)->Belt.Option.forEach(lang => { + // Try to automatically transform code + let (result, targetLang) = switch ready.selected.apiVersion { + | V1 => + let convResult = switch (currentLang, lang) { + | (Reason, Res) => + instance->Compiler.convertSyntax(~fromLang=Reason, ~toLang=Res, ~code)->Some + | (Res, Reason) => + instance->Compiler.convertSyntax(~fromLang=Res, ~toLang=Reason, ~code)->Some + | _ => None + } + + /* + Syntax convertion works the following way: + If currentLang -> otherLang is not valid, try to pretty print the code + with the otherLang, in case we e.g. want to copy paste or otherLang code + in the editor and quickly switch to it + */ + switch convResult { + | Some(result) => + switch result { + | ConversionResult.Fail(_) + | Unknown(_, _) + | UnexpectedError(_) => + let secondTry = + instance->Compiler.convertSyntax(~fromLang=lang, ~toLang=lang, ~code) + switch secondTry { + | ConversionResult.Fail(_) + | Unknown(_, _) + | UnexpectedError(_) => (FinalResult.Conv(secondTry), lang) + | Success(_) => (Conv(secondTry), lang) + } + | ConversionResult.Success(_) => (Conv(result), lang) + } + | None => (Nothing, lang) + } + | _ => (Nothing, lang) + } + + setState(_ => Ready({...ready, result: result, errors: [], targetLang: targetLang})) + }) + | _ => () + } + | Format(code) => + switch state { + | Ready(ready) => + let instance = ready.selected.instance + let convResult = switch ready.targetLang { + | Res => instance->Compiler.resFormat(code)->Some + | Reason => instance->Compiler.reasonFormat(code)->Some + | _ => None + } + + let result = switch convResult { + | Some(result) => + switch result { + | ConversionResult.Success(success) => + // We will only change the result to a ConversionResult + // in case the reformatting has actually changed code + // otherwise we'd loose previous compilationResults, although + // the result should be the same anyways + if code !== success.code { + FinalResult.Conv(result) + } else { + ready.result + } + | ConversionResult.Fail(_) + | Unknown(_, _) + | UnexpectedError(_) => + FinalResult.Conv(result) + } + | None => ready.result + } + + setState(_ => Ready({...ready, result: result, errors: []})) + | _ => () + } + } + } + + let dispatchError = (err: error) => setState(prev => { + let msg = switch err { + | SetupError(msg) => msg + | CompilerLoadingError(msg) => msg + } + switch prev { + | Ready(ready) => Ready({...ready, errors: Js.Array2.concat(ready.errors, [msg])}) + | _ => SetupFailed(msg) + } + }) + + React.useEffect1(() => { + switch state { + | Init => + let libraries = ["reason-react"] + + let completed = res => { + open SimpleRequest + switch res { + | Ok({text}) => + switch CdnMeta.parseVersions(text) { + | [] => dispatchError(SetupError(j`No compiler versions found`)) + | versions => + // Fetching the initial compiler is different, since we + // don't have any running version downloaded yet + + let latest = versions[0] + + attachCompilerAndLibraries(~version=latest, ~libraries, ())->Promise.get(result => + switch result { + | Ok() => + let instance = Compiler.make() + let apiVersion = apiVersion->Version.fromString + let config = instance->Compiler.getConfig + + let selected = { + id: latest, + apiVersion: apiVersion, + compilerVersion: instance->Compiler.version, + ocamlVersion: instance->Compiler.ocamlVersion, + reasonVersion: instance->Compiler.reasonVersion, + config: config, + libraries: libraries, + instance: instance, + } + + let targetLang = + Version.availableLanguages(apiVersion) + ->Js.Array2.find(l => l === initialLang) + ->Belt.Option.getWithDefault(Version.defaultTargetLang(apiVersion)) + + setState(_ => Ready({ + selected: selected, + targetLang: targetLang, + versions: versions, + errors: [], + result: FinalResult.Nothing, + })) + | Error(errs) => + let msg = Js.Array2.joinWith(errs, "; ") + + dispatchError(CompilerLoadingError(msg)) + } + ) + } + | Error({text, status}) => + dispatchError(SetupError(j`Error occurred: $text (status-code: $status)`)) + } + () + } + + open SimpleRequest + make( + ~contentType=Plain, + ~completed, + "https://cdn.jsdelivr.net/gh/ryyppy/bs-platform-js-releases@latest/VERSIONS", + )->send + | SwitchingCompiler(ready, version, libraries) => + attachCompilerAndLibraries(~version, ~libraries, ())->Promise.get(result => + switch result { + | Ok() => + // Make sure to remove the previous script from the DOM as well + LoadScript.removeScript(~src=CdnMeta.getCompilerUrl(ready.selected.id)) + + Belt.Array.forEach(ready.selected.libraries, lib => + LoadScript.removeScript(~src=CdnMeta.getLibraryCmijUrl(ready.selected.id, lib)) + ) + + let instance = Compiler.make() + let apiVersion = apiVersion->Version.fromString + let config = instance->Compiler.getConfig + + let selected = { + id: version, + apiVersion: apiVersion, + compilerVersion: instance->Compiler.version, + ocamlVersion: instance->Compiler.ocamlVersion, + reasonVersion: instance->Compiler.reasonVersion, + config: config, + libraries: libraries, + instance: instance, + } + + setState(_ => Ready({ + selected: selected, + targetLang: Version.defaultTargetLang(apiVersion), + versions: ready.versions, + errors: [], + result: FinalResult.Nothing, + })) + | Error(errs) => + let msg = Js.Array2.joinWith(errs, "; ") + + dispatchError(CompilerLoadingError(msg)) + } + ) + | Compiling(ready, (lang, code)) => + let apiVersion = ready.selected.apiVersion + let instance = ready.selected.instance + + let compResult = switch apiVersion { + | Version.V1 => + switch lang { + | Lang.OCaml => instance->Compiler.ocamlCompile(code) + | Lang.Reason => instance->Compiler.reasonCompile(code) + | Lang.Res => instance->Compiler.resCompile(code) + } + | UnknownVersion(apiVersion) => + CompilationResult.UnexpectedError( + j`Can't handle result of compiler API version "$apiVersion"`, + ) + } + + setState(_ => Ready({...ready, result: FinalResult.Comp(compResult)})) + | SetupFailed(_) + | Ready(_) => () + } + None + }, [state]) + + (state, dispatch) +} diff --git a/common/DateStr.js b/common/DateStr.js new file mode 100644 index 000000000..a106f08d4 --- /dev/null +++ b/common/DateStr.js @@ -0,0 +1,17 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + + +function fromDate(date) { + return date.toString(); +} + +function toDate(dateStr) { + return new Date(dateStr.replace(/-/g, "/")); +} + +export { + fromDate , + toDate , + +} +/* No side effect */ diff --git a/common/DateStr.re b/common/DateStr.re deleted file mode 100644 index 462b8de89..000000000 --- a/common/DateStr.re +++ /dev/null @@ -1,14 +0,0 @@ -/* JSON doesn't support a native date type, so we need to codify dates as strings */ -type t = string; - -// Used to prevent issues with webkit based date representations -let parse = (dateStr: string): Js.Date.t => { - dateStr->Js.String2.replaceByRe([%re "/-/g"], "/")->Js.Date.fromString; -}; - -let fromDate = date => Js.Date.toString(date); -let toDate = dateStr => { - parse(dateStr); -}; - -external fromString: string => t = "%identity"; diff --git a/common/DateStr.rei b/common/DateStr.rei deleted file mode 100644 index 130686b7f..000000000 --- a/common/DateStr.rei +++ /dev/null @@ -1,4 +0,0 @@ -type t; -external fromString: string => t = "%identity"; -let fromDate: Js.Date.t => t; -let toDate: t => Js.Date.t diff --git a/common/DateStr.res b/common/DateStr.res new file mode 100644 index 000000000..15d80e65c --- /dev/null +++ b/common/DateStr.res @@ -0,0 +1,11 @@ +/* JSON doesn't support a native date type, so we need to codify dates as strings */ +type t = string + +// Used to prevent issues with webkit based date representations +let parse = (dateStr: string): Js.Date.t => + dateStr->Js.String2.replaceByRe(%re("/-/g"), "/")->Js.Date.fromString + +let fromDate = date => Js.Date.toString(date) +let toDate = dateStr => parse(dateStr) + +external fromString: string => t = "%identity" diff --git a/common/DateStr.resi b/common/DateStr.resi new file mode 100644 index 000000000..bda58400a --- /dev/null +++ b/common/DateStr.resi @@ -0,0 +1,4 @@ +type t +external fromString: string => t = "%identity" +let fromDate: Js.Date.t => t +let toDate: t => Js.Date.t diff --git a/common/DocFrontmatter.js b/common/DocFrontmatter.js new file mode 100644 index 000000000..06c4e8e94 --- /dev/null +++ b/common/DocFrontmatter.js @@ -0,0 +1,40 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as Js_null from "bs-platform/lib/es6/js_null.js"; +import * as Json_decode from "@glennsl/bs-json/src/Json_decode.js"; +import * as Caml_js_exceptions from "bs-platform/lib/es6/caml_js_exceptions.js"; + +function decode(json) { + try { + return { + TAG: 0, + _0: { + title: Json_decode.field("title", Json_decode.string, json), + description: Js_null.fromOption(Json_decode.optional((function (param) { + return Json_decode.field("description", Json_decode.string, param); + }), json)), + canonical: Js_null.fromOption(Json_decode.optional((function (param) { + return Json_decode.field("canonical", Json_decode.string, param); + }), json)) + }, + [Symbol.for("name")]: "Ok" + }; + } + catch (raw_errMsg){ + var errMsg = Caml_js_exceptions.internalToOCamlException(raw_errMsg); + if (errMsg.RE_EXN_ID === Json_decode.DecodeError) { + return { + TAG: 1, + _0: errMsg._1, + [Symbol.for("name")]: "Error" + }; + } + throw errMsg; + } +} + +export { + decode , + +} +/* No side effect */ diff --git a/common/DocFrontmatter.re b/common/DocFrontmatter.re deleted file mode 100644 index 826eec266..000000000 --- a/common/DocFrontmatter.re +++ /dev/null @@ -1,20 +0,0 @@ -type t = { - title: string, - description: Js.null(string), - canonical: Js.null(string), -}; - -let decode = (json): result(t, string) => { - open! Json.Decode; - try( - Ok({ - title: field("title", string, json), - description: - optional(field("description", string), json)->Js.Null.fromOption, - canonical: - optional(field("canonical", string), json)->Js.Null.fromOption, - }) - ) { - | DecodeError(errMsg) => Error(errMsg) - }; -}; diff --git a/common/DocFrontmatter.res b/common/DocFrontmatter.res new file mode 100644 index 000000000..263c73838 --- /dev/null +++ b/common/DocFrontmatter.res @@ -0,0 +1,16 @@ +type t = { + title: string, + description: Js.null, + canonical: Js.null, +} + +let decode = (json): result => { + open! Json.Decode + try Ok({ + title: field("title", string, json), + description: optional(field("description", string), json)->Js.Null.fromOption, + canonical: optional(field("canonical", string), json)->Js.Null.fromOption, + }) catch { + | DecodeError(errMsg) => Error(errMsg) + } +} diff --git a/common/HighlightJs.js b/common/HighlightJs.js new file mode 100644 index 000000000..18498e686 --- /dev/null +++ b/common/HighlightJs.js @@ -0,0 +1,54 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as React from "react"; +import * as Js_exn from "bs-platform/lib/es6/js_exn.js"; +import * as Belt_Array from "bs-platform/lib/es6/belt_Array.js"; +import * as Caml_option from "bs-platform/lib/es6/caml_option.js"; +import * as Caml_js_exceptions from "bs-platform/lib/es6/caml_js_exceptions.js"; +import * as Highlight from "highlight.js/lib/highlight"; + +function renderHLJS(highlightedLinesOpt, darkmodeOpt, code, lang, param) { + var highlightedLines = highlightedLinesOpt !== undefined ? highlightedLinesOpt : []; + var darkmode = darkmodeOpt !== undefined ? darkmodeOpt : false; + var match; + try { + match = [ + lang, + Highlight.highlight(lang, code).value + ]; + } + catch (raw_exn){ + var exn = Caml_js_exceptions.internalToOCamlException(raw_exn); + if (exn.RE_EXN_ID === Js_exn.$$Error) { + match = [ + "text", + code + ]; + } else { + throw exn; + } + } + var highlighted = match[1]; + var highlighted$1 = highlightedLines.length !== 0 ? Belt_Array.mapWithIndex(highlighted.split("\n"), (function (i, line) { + if (Caml_option.undefined_to_opt(highlightedLines.find(function (lnum) { + return lnum === (i + 1 | 0); + })) === undefined) { + return "" + (line + ""); + } + var content = line === "" ? " " : line; + return "" + (content + ""); + })).join("\n") : highlighted; + var dark = darkmode ? "dark" : ""; + return React.createElement("code", { + className: "hljs lang-" + (match[0] + (" " + dark)), + dangerouslySetInnerHTML: { + __html: highlighted$1 + } + }); +} + +export { + renderHLJS , + +} +/* react Not a pure module */ diff --git a/common/HighlightJs.re b/common/HighlightJs.re deleted file mode 100644 index a5716cd8e..000000000 --- a/common/HighlightJs.re +++ /dev/null @@ -1,47 +0,0 @@ -[@bs.deriving abstract] -type highlightResult = {value: string}; - -[@bs.module "highlight.js/lib/highlight"] -external highlight: (~lang: string, ~value: string) => highlightResult = - "highlight"; - -let renderHLJS = - ( - ~highlightedLines=[||], - ~darkmode=false, - ~code: string, - ~lang: string, - (), - ) => { - // If the language couldn't be parsed, we will fall back to text - let (lang, highlighted) = - try((lang, highlight(~lang, ~value=code)->valueGet)) { - | Js.Exn.Error(_) => ("text", code) - }; - - // Add line highlighting as well - let highlighted = - if (Belt.Array.length(highlightedLines) > 0) { - Js.String2.split(highlighted, "\n") - ->Belt.Array.mapWithIndex((i, line) => - if (Js.Array2.find(highlightedLines, lnum => lnum === i + 1) !== None) { - let content = line === "" ? " " : line; - "" ++ content ++ ""; - } else { - "" - ++ line - ++ ""; - } - ) - ->Js.Array2.joinWith("\n"); - } else { - highlighted; - }; - - let dark = darkmode ? "dark" : ""; - - ; -}; diff --git a/common/HighlightJs.res b/common/HighlightJs.res new file mode 100644 index 000000000..3e27889ad --- /dev/null +++ b/common/HighlightJs.res @@ -0,0 +1,33 @@ +@bs.deriving(abstract) +type highlightResult = {value: string} + +@bs.module("highlight.js/lib/highlight") +external highlight: (~lang: string, ~value: string) => highlightResult = "highlight" + +let renderHLJS = (~highlightedLines=[], ~darkmode=false, ~code: string, ~lang: string, ()) => { + // If the language couldn't be parsed, we will fall back to text + let (lang, highlighted) = try (lang, highlight(~lang, ~value=code)->valueGet) catch { + | Js.Exn.Error(_) => ("text", code) + } + + // Add line highlighting as well + let highlighted = if Belt.Array.length(highlightedLines) > 0 { + Js.String2.split(highlighted, "\n")->Belt.Array.mapWithIndex((i, line) => + if Js.Array2.find(highlightedLines, lnum => lnum === i + 1) !== None { + let content = line === "" ? " " : line + "" ++ (content ++ "") + } else { + "" ++ (line ++ "") + } + )->Js.Array2.joinWith("\n") + } else { + highlighted + } + + let dark = darkmode ? "dark" : "" + + +} diff --git a/common/Mdx.js b/common/Mdx.js new file mode 100644 index 000000000..83447b223 --- /dev/null +++ b/common/Mdx.js @@ -0,0 +1,101 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as Belt_Array from "bs-platform/lib/es6/belt_Array.js"; + +var getMdxType = (element => { + if(element == null || element.props == null) { + return 'unknown'; + } + return element.props.mdxType; + }); + +var getMdxClassName = (element => { + if(element == null || element.props == null) { + return; + } + return element.props.className; + }); + +function classify(v) { + if ((function (a) { return a instanceof Array})(v)) { + return { + TAG: 2, + _0: v, + [Symbol.for("name")]: "Array" + }; + } else if (typeof v === "string") { + return { + TAG: 0, + _0: v, + [Symbol.for("name")]: "String" + }; + } else if (typeof v === "object") { + return { + TAG: 1, + _0: v, + [Symbol.for("name")]: "Element" + }; + } else { + return { + TAG: 3, + _0: v, + [Symbol.for("name")]: "Unknown" + }; + } +} + +var getMdxChildren = (element => { + if(typeof element === 'string') { + return element; + } + if(element == null || element.props == null || element.props.children == null) { + return; + } + return element.props.children; + }); + +function flatten(_mdxComp) { + while(true) { + var mdxComp = _mdxComp; + var str = classify(getMdxChildren(mdxComp)); + switch (str.TAG | 0) { + case /* String */0 : + return [str._0]; + case /* Element */1 : + _mdxComp = str._0; + continue ; + case /* Array */2 : + return Belt_Array.reduce(str._0, [], (function (acc, next) { + return Belt_Array.concat(acc, flatten(next)); + })); + case /* Unknown */3 : + return []; + + } + }; +} + +function MdxChildren_toReactElement(prim) { + return prim; +} + +var MdxChildren = { + classify: classify, + getMdxChildren: getMdxChildren, + flatten: flatten, + toReactElement: MdxChildren_toReactElement +}; + +var Components = {}; + +var Provider = {}; + +export { + getMdxType , + getMdxClassName , + MdxChildren , + Components , + Provider , + +} +/* No side effect */ diff --git a/common/Mdx.re b/common/Mdx.re deleted file mode 100644 index a539782cb..000000000 --- a/common/Mdx.re +++ /dev/null @@ -1,211 +0,0 @@ -/* - Abstract type for representing mdx - components mostly passed as children to - the component context API - */ -type mdxComponent; - -external fromReactElement: React.element => mdxComponent = "%identity"; -external toReactElement: mdxComponent => React.element = "%identity"; - -external arrToReactElement: array(mdxComponent) => React.element = - "%identity"; - -/* Useful for getting the type of a certain mdx component, such as - "inlineCode" | "p" | "ul" | etc. - - Will return "unknown" if either given element is not an mdx component, - or if there is no mdxType property found */ -let getMdxType: mdxComponent => string = [%raw - "element => { - if(element == null || element.props == null) { - return 'unknown'; - } - return element.props.mdxType; - }" -]; - -let getMdxClassName: mdxComponent => option(string) = [%raw - "element => { - if(element == null || element.props == null) { - return; - } - return element.props.className; - }" -]; - -module MdxChildren: { - type unknown; - type t; - type case = - | String(string) - | Element(mdxComponent) - | Array(array(mdxComponent)) - | Unknown(unknown); - let classify: t => case; - let getMdxChildren: mdxComponent => t; - let flatten: mdxComponent => array(string); - let toReactElement: t => React.element; -} = { - type unknown; - - [@unboxed] - type t = - | Any('a): t; - - type case = - | String(string) - | Element(mdxComponent) - | Array(array(mdxComponent)) - | Unknown(unknown); - - let classify = (Any(v): t): case => - if ([%raw {|function (a) { return a instanceof Array}|}](v)) { - Array(Obj.magic(v): array(mdxComponent)); - } else if (Js.typeof(v) == "string") { - String(Obj.magic(v): string); - } else if (Js.typeof(v) == "object") { - Element(Obj.magic(v): mdxComponent); - } else { - Unknown(Obj.magic(v): unknown); - }; - - external toReactElement: t => React.element = "%identity"; - - // Sometimes an mdxComponent element can be a string - // which means it doesn't have any children. - // We will return the element as its own child then - let getMdxChildren: mdxComponent => t = [%raw - "element => { - if(typeof element === 'string') { - return element; - } - if(element == null || element.props == null || element.props.children == null) { - return; - } - return element.props.children; - }" - ]; - - // Flattens a tree of a mdx component to an array of leaf strings - let rec flatten = (mdxComp: mdxComponent): array(string) => { - switch (getMdxChildren(mdxComp)->classify) { - | String(str) => [|str|] - | Array(arr) => - Belt.Array.reduce(arr, [||], (acc, next) => { - Belt.Array.concat(acc, flatten(next)) - }) - | Element(el) => flatten(el) - | Unknown(_) => [||] - }; - }; -}; - -module Components = { - type props = {. "children": ReasonReact.reactElement}; - - type headerProps = { - . - "id": string, // Used for anchor tags - "children": React.element, - }; - - // Used for reflection based logic in - // components such as `code` or `ul` - // with runtime reflection - type unknown; - - [@bs.deriving abstract] - type t = { - /* MDX shortnames for more advanced components */ - [@bs.as "Cite"] [@bs.optional] - cite: - React.component({ - . - "author": option(string), - "children": React.element, - }), - [@bs.as "Info"] [@bs.optional] - info: React.component(props), - [@bs.as "Warn"] [@bs.optional] - warn: React.component(props), - [@bs.as "Intro"] [@bs.optional] - intro: React.component(props), - [@bs.as "UrlBox"] [@bs.optional] - urlBox: - React.component({ - . - "text": string, - "href": string, - "children": MdxChildren.t, - }), - [@bs.as "CodeTab"] [@bs.optional] - codeTab: - React.component({ - . - "children": MdxChildren.t, - "labels": option(array(string)), - }), - /* Common markdown elements */ - [@bs.optional] - p: React.component(props), - [@bs.optional] - li: React.component(props), - [@bs.optional] - h1: React.component(props), - [@bs.optional] - h2: React.component(headerProps), - [@bs.optional] - h3: React.component(headerProps), - [@bs.optional] - h4: React.component(headerProps), - [@bs.optional] - h5: React.component(headerProps), - [@bs.optional] - ul: React.component(props), - [@bs.optional] - ol: React.component(props), - [@bs.optional] - table: React.component(props), - [@bs.optional] - thead: React.component(props), - [@bs.optional] - th: React.component(props), - [@bs.optional] - td: React.component(props), - [@bs.optional] - blockquote: React.component(props), - [@bs.optional] - inlineCode: React.component(props), - [@bs.optional] - strong: React.component(props), - [@bs.optional] - hr: React.component(Js.t({.})), - [@bs.optional] - code: - React.component({ - . - "className": option(string), - "metastring": option(string), - "children": unknown, - }), - [@bs.optional] - pre: React.component(props), - [@bs.optional] - a: - React.component({ - . - "children": ReasonReact.reactElement, - "target": option(string), - "href": string, - }), - }; -}; - -module Provider = { - [@bs.module "@mdx-js/react"] [@react.component] - external make: - (~components: Components.t, ~children: ReasonReact.reactElement=?) => - React.element = - "MDXProvider"; -}; diff --git a/common/Mdx.res b/common/Mdx.res new file mode 100644 index 000000000..3eceec600 --- /dev/null +++ b/common/Mdx.res @@ -0,0 +1,195 @@ +/* + Abstract type for representing mdx + components mostly passed as children to + the component context API + */ +type mdxComponent + +external fromReactElement: React.element => mdxComponent = "%identity" +external toReactElement: mdxComponent => React.element = "%identity" + +external arrToReactElement: array => React.element = "%identity" + +/* Useful for getting the type of a certain mdx component, such as + "inlineCode" | "p" | "ul" | etc. + + Will return "unknown" if either given element is not an mdx component, + or if there is no mdxType property found */ +let getMdxType: mdxComponent => string = %raw( + "element => { + if(element == null || element.props == null) { + return 'unknown'; + } + return element.props.mdxType; + }" +) + +let getMdxClassName: mdxComponent => option = %raw( + "element => { + if(element == null || element.props == null) { + return; + } + return element.props.className; + }" +) + +module MdxChildren: { + type unknown + type t + type case = + | String(string) + | Element(mdxComponent) + | Array(array) + | Unknown(unknown) + let classify: t => case + let getMdxChildren: mdxComponent => t + let flatten: mdxComponent => array + let toReactElement: t => React.element +} = { + type unknown + + @unboxed + type rec t = Any('a): t + + type case = + | String(string) + | Element(mdxComponent) + | Array(array) + | Unknown(unknown) + + let classify = (Any(v): t): case => + if %raw(`function (a) { return a instanceof Array}`)(v) { + Array((Obj.magic(v): array)) + } else if Js.typeof(v) == "string" { + String((Obj.magic(v): string)) + } else if Js.typeof(v) == "object" { + Element((Obj.magic(v): mdxComponent)) + } else { + Unknown((Obj.magic(v): unknown)) + } + + external toReactElement: t => React.element = "%identity" + + // Sometimes an mdxComponent element can be a string + // which means it doesn't have any children. + // We will return the element as its own child then + let getMdxChildren: mdxComponent => t = %raw( + "element => { + if(typeof element === 'string') { + return element; + } + if(element == null || element.props == null || element.props.children == null) { + return; + } + return element.props.children; + }" + ) + + // Flattens a tree of a mdx component to an array of leaf strings + let rec flatten = (mdxComp: mdxComponent): array => + switch getMdxChildren(mdxComp)->classify { + | String(str) => [str] + | Array(arr) => Belt.Array.reduce(arr, [], (acc, next) => Belt.Array.concat(acc, flatten(next))) + | Element(el) => flatten(el) + | Unknown(_) => [] + } +} + +module Components = { + type props = {"children": ReasonReact.reactElement} + + type headerProps = { + "id": string, + "children": // Used for anchor tags + React.element, + } + + // Used for reflection based logic in + // components such as `code` or `ul` + // with runtime reflection + type unknown + + @bs.deriving(abstract) + type t = { + /* MDX shortnames for more advanced components */ + @bs.as("Cite") @bs.optional + cite: React.component<{ + "author": option, + "children": React.element, + }>, + @bs.as("Info") @bs.optional + info: React.component, + @bs.as("Warn") @bs.optional + warn: React.component, + @bs.as("Intro") @bs.optional + intro: React.component, + @bs.as("UrlBox") @bs.optional + urlBox: React.component<{ + "text": string, + "href": string, + "children": MdxChildren.t, + }>, + @bs.as("CodeTab") @bs.optional + codeTab: React.component<{ + "children": MdxChildren.t, + "labels": option>, + }>, + /* Common markdown elements */ + @bs.optional + p: React.component, + @bs.optional + li: React.component, + @bs.optional + h1: React.component, + @bs.optional + h2: React.component, + @bs.optional + h3: React.component, + @bs.optional + h4: React.component, + @bs.optional + h5: React.component, + @bs.optional + ul: React.component, + @bs.optional + ol: React.component, + @bs.optional + table: React.component, + @bs.optional + thead: React.component, + @bs.optional + th: React.component, + @bs.optional + td: React.component, + @bs.optional + blockquote: React.component, + @bs.optional + inlineCode: React.component, + @bs.optional + strong: React.component, + @bs.optional + hr: React.component<{.}>, + @bs.optional + code: React.component<{ + "className": option, + "metastring": option, + "children": unknown, + }>, + @bs.optional + pre: React.component, + @bs.optional + a: React.component<{ + "children": ReasonReact.reactElement, + "target": option, + "href": string, + }>, + } +} + +module Provider = { + @bs.module("@mdx-js/react") @react.component + external make: ( + ~components: Components.t, + ~children: ReasonReact.reactElement=?, + ) => React.element = "MDXProvider" +} diff --git a/common/MetaFrontmatter.js b/common/MetaFrontmatter.js new file mode 100644 index 000000000..2de49633d --- /dev/null +++ b/common/MetaFrontmatter.js @@ -0,0 +1,16 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + + +function decode(json) { + return { + TAG: 1, + _0: "Not implemented", + [Symbol.for("name")]: "Error" + }; +} + +export { + decode , + +} +/* No side effect */ diff --git a/common/MetaFrontmatter.re b/common/MetaFrontmatter.res similarity index 74% rename from common/MetaFrontmatter.re rename to common/MetaFrontmatter.res index 53e7d60d5..ad4f147c3 100644 --- a/common/MetaFrontmatter.re +++ b/common/MetaFrontmatter.res @@ -5,10 +5,6 @@ // as Js.Null.t, since it will be used for JSON serialization // within Next's static generation +type t -type t; - -let decode = (json: Js.Json.t): result(t, string) => { - - Error("Not implemented"); -}; +let decode = (json: Js.Json.t): result => Error("Not implemented") diff --git a/common/ProcessEnv.js b/common/ProcessEnv.js new file mode 100644 index 000000000..d856702bf --- /dev/null +++ b/common/ProcessEnv.js @@ -0,0 +1,2 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE +/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ diff --git a/common/ProcessEnv.re b/common/ProcessEnv.re deleted file mode 100644 index ae4c8e553..000000000 --- a/common/ProcessEnv.re +++ /dev/null @@ -1,7 +0,0 @@ -[@bs.val] [@bs.scope "process.env"] external env: string = "ENV"; - -[@bs.inline] -let development = "development"; - -[@bs.inline] -let production = "production"; diff --git a/common/ProcessEnv.res b/common/ProcessEnv.res new file mode 100644 index 000000000..6bead8768 --- /dev/null +++ b/common/ProcessEnv.res @@ -0,0 +1,7 @@ +@bs.val @bs.scope("process.env") external env: string = "ENV" + +@bs.inline +let development = "development" + +@bs.inline +let production = "production" diff --git a/common/SimpleRequest.js b/common/SimpleRequest.js new file mode 100644 index 000000000..6b4b0ed75 --- /dev/null +++ b/common/SimpleRequest.js @@ -0,0 +1,70 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as Curry from "bs-platform/lib/es6/curry.js"; +import * as Belt_Option from "bs-platform/lib/es6/belt_Option.js"; +import * as Caml_option from "bs-platform/lib/es6/caml_option.js"; + +function make(completed, method_Opt, contentTypeOpt, url) { + var method_ = method_Opt !== undefined ? method_Opt : /* Get */0; + var contentType = contentTypeOpt !== undefined ? contentTypeOpt : /* Json */0; + var xhr = new XMLHttpRequest(); + var method_$1 = method_ ? "POST" : "GET"; + xhr.onload = (function (param) { + var status = xhr.status; + var text = Belt_Option.getWithDefault(Caml_option.nullable_to_opt(xhr.responseText), ""); + if (status === 200) { + return Curry._1(completed, { + TAG: 0, + _0: { + status: status, + text: text + }, + [Symbol.for("name")]: "Ok" + }); + } else { + return Curry._1(completed, { + TAG: 1, + _0: { + status: status, + text: text + }, + [Symbol.for("name")]: "Error" + }); + } + }); + xhr.onerror = (function (param) { + return Curry._1(completed, { + TAG: 1, + _0: { + status: xhr.status, + text: "Connection error" + }, + [Symbol.for("name")]: "Error" + }); + }); + xhr.open(method_$1, url); + if (contentType) { + xhr.setRequestHeader("Content-Type", "text/plain"); + } else { + xhr.setRequestHeader("Content-Type", "application/json"); + } + return xhr; +} + +function send(req) { + req.send(); + +} + +function abort(req) { + req.abort(); + +} + +export { + make , + send , + abort , + +} +/* No side effect */ diff --git a/common/SimpleRequest.re b/common/SimpleRequest.re deleted file mode 100644 index 4bf9d2c2b..000000000 --- a/common/SimpleRequest.re +++ /dev/null @@ -1,61 +0,0 @@ -type t = XmlHttpRequest.t; - -type contentType = - | Json - | Plain; - -type method = - | Get - | Post; - -type response = { - status: int, - text: string, -}; - -let make = - ( - ~completed: result(response, response) => unit, - ~method=Get, - ~contentType=Json, - url: string, - ) - : t => { - open XmlHttpRequest; - let xhr = make(); - - let method = - switch (method) { - | Post => "POST" - | Get => "GET" - }; - - xhr->onLoad(_ => { - let status = xhr->status; - - let text = - xhr->responseText->Js.Nullable.toOption->Belt.Option.getWithDefault(""); - - if (status === 200) { - completed(Ok({status, text})); - } else { - completed(Error({status, text})); - }; - }); - - xhr->onError(_ => - completed(Error({status: xhr->status, text: "Connection error"})) - ); - - xhr->open_(~method, ~url); - - switch (contentType) { - | Json => xhr->setRequestHeader("Content-Type", "application/json") - | Plain => xhr->setRequestHeader("Content-Type", "text/plain") - }; - - xhr; -}; - -let send = (req: t) => req->XmlHttpRequest.send; -let abort = (req: t) => req->XmlHttpRequest.abort; diff --git a/common/SimpleRequest.res b/common/SimpleRequest.res new file mode 100644 index 000000000..bfdd58ae2 --- /dev/null +++ b/common/SimpleRequest.res @@ -0,0 +1,55 @@ +type t = XmlHttpRequest.t + +type contentType = + | Json + | Plain + +type method_ = + | Get + | Post + +type response = { + status: int, + text: string, +} + +let make = ( + ~completed: result => unit, + ~method as method_=Get, + ~contentType=Json, + url: string, +): t => { + open XmlHttpRequest + let xhr = make() + + let method_ = switch method_ { + | Post => "POST" + | Get => "GET" + } + + xhr->onLoad(_ => { + let status = xhr->status + + let text = xhr->responseText->Js.Nullable.toOption->Belt.Option.getWithDefault("") + + if status === 200 { + completed(Ok({status: status, text: text})) + } else { + completed(Error({status: status, text: text})) + } + }) + + xhr->onError(_ => completed(Error({status: xhr->status, text: "Connection error"}))) + + xhr->open_(~method=method_, ~url) + + switch contentType { + | Json => xhr->setRequestHeader("Content-Type", "application/json") + | Plain => xhr->setRequestHeader("Content-Type", "text/plain") + } + + xhr +} + +let send = (req: t) => req->XmlHttpRequest.send +let abort = (req: t) => req->XmlHttpRequest.abort diff --git a/common/Url.js b/common/Url.js new file mode 100644 index 000000000..2b1e3d006 --- /dev/null +++ b/common/Url.js @@ -0,0 +1,68 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as Util from "./Util.js"; +import * as Belt_Array from "bs-platform/lib/es6/belt_Array.js"; +import * as Belt_Option from "bs-platform/lib/es6/belt_Option.js"; +import * as Caml_option from "bs-platform/lib/es6/caml_option.js"; + +function isVersion(str) { + return Belt_Option.isSome(Caml_option.null_to_opt(str.match(/latest|v\d+(\.\d+)?(\.\d+)?/))); +} + +function prettyString(str) { + return Util.$$String.capitalize(Util.$$String.camelCase(str)); +} + +function parse(route) { + var fullpath = Belt_Array.keep(route.split("/"), (function (s) { + return s !== ""; + })); + var match = Belt_Array.reduce(fullpath, [ + [], + /* NoVersion */1, + [] + ], (function (acc, next) { + var pagepath = acc[2]; + var version = acc[1]; + var base = acc[0]; + if (version === /* NoVersion */1) { + if (isVersion(next)) { + var version$1 = next === "latest" ? /* Latest */0 : ({ + _0: next, + [Symbol.for("name")]: "Version" + }); + return [ + base, + version$1, + pagepath + ]; + } + var base$1 = Belt_Array.concat(base, [next]); + return [ + base$1, + version, + pagepath + ]; + } + var pagepath$1 = Belt_Array.concat(pagepath, [next]); + return [ + base, + version, + pagepath$1 + ]; + })); + return { + fullpath: fullpath, + base: match[0], + version: match[1], + pagepath: match[2] + }; +} + +export { + isVersion , + prettyString , + parse , + +} +/* No side effect */ diff --git a/common/Url.re b/common/Url.re deleted file mode 100644 index 07d0e1ff6..000000000 --- a/common/Url.re +++ /dev/null @@ -1,94 +0,0 @@ -type version = - | Latest - | NoVersion - | Version(string); - -/* - Example 1: - Url: "/docs/manual/latest/advanced/introduction" - - Results in: - fullpath: ["docs", "manual", "latest", "advanced", "introduction"] - base: ["docs", "manual"] - version: Latest - pagepath: ["advanced", "introduction"] - */ - -/* - Example 2: - Url: "/apis/" - - Results in: - fullpath: ["apis"] - base: ["apis"] - version: None - pagepath: [] - */ - -/* - Example 3: - Url: "/apis/javascript/v7.1.1/node" - - Results in: - fullpath: ["apis", "javascript", "v7.1.1", "node"] - base: ["apis", "javascript"] - version: Version("v7.1.1"), - pagepath: ["node"] - */ - -type t = { - fullpath: array(string), - base: array(string), - version, - pagepath: array(string), -}; - -// Moved here from original SidebarLayout.UrlPath -type breadcrumb = { - name: string, - href: string, -}; - -let isVersion = str => - Js.String2.match(str, [%re "/latest|v\\d+(\\.\\d+)?(\\.\\d+)?/"]) - ->Belt.Option.isSome; - -/* Beautifies url based string to somewhat acceptable representation */ -let prettyString = (str: string) => { - Util.String.(str->camelCase->capitalize); -}; - -let parse = (route: string): t => { - let fullpath = - Js.String2.(route->split("/")->Belt.Array.keep(s => s !== "")); - - let (base, version, pagepath) = - Belt.Array.reduce( - fullpath, - ([||], NoVersion, [||]), - (acc, next) => { - let (base, version, pagepath) = acc; - - if (version === NoVersion) { - if (isVersion(next)) { - let version = - if (next === "latest") { - Latest; - } else { - Version(next); - }; - (base, version, pagepath); - } else { - let base = Belt.Array.concat(base, [|next|]); - (base, version, pagepath); - }; - } else { - let pagepath = Belt.Array.concat(pagepath, [|next|]); - - (base, version, pagepath); - }; - }, - ); - - {fullpath, base, version, pagepath}; -}; diff --git a/common/Url.res b/common/Url.res new file mode 100644 index 000000000..c0c30fe7f --- /dev/null +++ b/common/Url.res @@ -0,0 +1,90 @@ +type version = + | Latest + | NoVersion + | Version(string) + +/* + Example 1: + Url: "/docs/manual/latest/advanced/introduction" + + Results in: + fullpath: ["docs", "manual", "latest", "advanced", "introduction"] + base: ["docs", "manual"] + version: Latest + pagepath: ["advanced", "introduction"] + */ + +/* + Example 2: + Url: "/apis/" + + Results in: + fullpath: ["apis"] + base: ["apis"] + version: None + pagepath: [] + */ + +/* + Example 3: + Url: "/apis/javascript/v7.1.1/node" + + Results in: + fullpath: ["apis", "javascript", "v7.1.1", "node"] + base: ["apis", "javascript"] + version: Version("v7.1.1"), + pagepath: ["node"] + */ + +type t = { + fullpath: array, + base: array, + version: version, + pagepath: array, +} + +// Moved here from original SidebarLayout.UrlPath +type breadcrumb = { + name: string, + href: string, +} + +let isVersion = str => + Js.String2.match_(str, %re("/latest|v\\d+(\\.\\d+)?(\\.\\d+)?/"))->Belt.Option.isSome + +/* Beautifies url based string to somewhat acceptable representation */ +let prettyString = (str: string) => { + open Util.String + str->camelCase->capitalize +} + +let parse = (route: string): t => { + let fullpath = { + open Js.String2 + route->split("/")->Belt.Array.keep(s => s !== "") + } + + let (base, version, pagepath) = Belt.Array.reduce(fullpath, ([], NoVersion, []), (acc, next) => { + let (base, version, pagepath) = acc + + if version === NoVersion { + if isVersion(next) { + let version = if next === "latest" { + Latest + } else { + Version(next) + } + (base, version, pagepath) + } else { + let base = Belt.Array.concat(base, [next]) + (base, version, pagepath) + } + } else { + let pagepath = Belt.Array.concat(pagepath, [next]) + + (base, version, pagepath) + } + }) + + {fullpath: fullpath, base: base, version: version, pagepath: pagepath} +} diff --git a/common/Util.js b/common/Util.js new file mode 100644 index 000000000..ead3cb92b --- /dev/null +++ b/common/Util.js @@ -0,0 +1,128 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as Curry from "bs-platform/lib/es6/curry.js"; +import * as Caml_option from "bs-platform/lib/es6/caml_option.js"; +import * as IntlDateTimeFormat from "../bindings/IntlDateTimeFormat.js"; + +function debounce(wait, fn) { + var timeout = { + contents: undefined + }; + return function (param) { + var unset = function (param) { + timeout.contents = undefined; + + }; + var id = timeout.contents; + if (id !== undefined) { + clearTimeout(Caml_option.valFromOption(id)); + } else { + Curry._1(fn, undefined); + } + timeout.contents = Caml_option.some(setTimeout(unset, wait)); + + }; +} + +function debounce3(wait, immediateOpt, fn) { + var immediate = immediateOpt !== undefined ? immediateOpt : false; + var timeout = { + contents: undefined + }; + return function (a1, a2, a3) { + var unset = function (param) { + timeout.contents = undefined; + if (immediate) { + return Curry._3(fn, a1, a2, a3); + } + + }; + var id = timeout.contents; + if (id !== undefined) { + clearTimeout(Caml_option.valFromOption(id)); + } else { + Curry._3(fn, a1, a2, a3); + } + timeout.contents = Caml_option.some(setTimeout(unset, wait)); + if (immediate && timeout.contents === undefined) { + return Curry._3(fn, a1, a2, a3); + } + + }; +} + +var Debounce = { + debounce: debounce, + debounce3: debounce3 +}; + +function s(prim) { + return prim; +} + +function ate(prim) { + return prim; +} + +var Unsafe = {}; + +var Suspense = {}; + +var ReactStuff = { + s: s, + ate: ate, + Unsafe: Unsafe, + Style: undefined, + Suspense: Suspense +}; + +var camelCase = (str => { + return str.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); }); + }); + +var capitalize = (str => { + return str && str.charAt(0).toUpperCase() + str.substring(1); + }); + +var $$String = { + camelCase: camelCase, + capitalize: capitalize +}; + +var Json = {}; + +var isAbsolute = (function(str) { + var r = new RegExp('^(?:[a-z]+:)?//', 'i'); + if (r.test(str)) + { + return true + } + return false; + }); + +var Url = { + isAbsolute: isAbsolute +}; + +function toDayMonthYear(date) { + return IntlDateTimeFormat.$$Date.make("US", { + year: Curry._1(IntlDateTimeFormat.$$Date.Year.make, "numeric"), + day: Curry._1(IntlDateTimeFormat.$$Date.Day.make, "numeric"), + month: Curry._1(IntlDateTimeFormat.$$Date.Month.make, "short") + }, date); +} + +var $$Date = { + toDayMonthYear: toDayMonthYear +}; + +export { + Debounce , + ReactStuff , + $$String , + Json , + Url , + $$Date , + +} +/* No side effect */ diff --git a/common/Util.re b/common/Util.res similarity index 57% rename from common/Util.re rename to common/Util.res index 36c3d8e83..f91572b0e 100644 --- a/common/Util.re +++ b/common/Util.res @@ -1,86 +1,84 @@ +// This file was automatically converted to ReScript from 'Util.re' +// Check the output and make sure to delete the original file module Debounce = { // See: https://davidwalsh.name/javascript-debounce-function let debounce = (~wait, fn) => { - let timeout = ref(None); + let timeout = ref(None) () => { - let unset = () => { - timeout := None; - }; + let unset = () => timeout := None - switch (timeout^) { + switch timeout.contents { | Some(id) => Js.Global.clearTimeout(id) | None => fn() - }; - timeout := Some(Js.Global.setTimeout(unset, wait)); - }; - }; + } + timeout := Some(Js.Global.setTimeout(unset, wait)) + } + } let debounce3 = (~wait, ~immediate=false, fn) => { - let timeout = ref(None); + let timeout = ref(None) (a1, a2, a3) => { let unset = () => { - timeout := None; - immediate ? fn(a1, a2, a3) : (); - }; + timeout := None + immediate ? fn(a1, a2, a3) : () + } - switch (timeout^) { + switch timeout.contents { | Some(id) => Js.Global.clearTimeout(id) | None => fn(a1, a2, a3) - }; - timeout := Some(Js.Global.setTimeout(unset, wait)); + } + timeout := Some(Js.Global.setTimeout(unset, wait)) - if (immediate && timeout^ === None) { - fn(a1, a2, a3); + if immediate && timeout.contents === None { + fn(a1, a2, a3) } else { - (); - }; - }; - }; -}; + () + } + } + } +} module ReactStuff = { - let s = ReasonReact.string; - let ate = ReasonReact.array; + let s = ReasonReact.string + let ate = ReasonReact.array module Unsafe = { - external elementAsString: React.element => string = "%identity"; - }; - module Style = ReactDOMRe.Style; + external elementAsString: React.element => string = "%identity" + } + module Style = ReactDOMRe.Style - [@bs.module "react"] - external lazy_: (unit => Js.Promise.t('a)) => 'a = "lazy"; + @bs.module("react") + external lazy_: (unit => Js.Promise.t<'a>) => 'a = "lazy" module Suspense = { - [@bs.module "react"] [@react.component] - external make: (~children: React.element) => React.element = "Suspense"; - }; -}; + @bs.module("react") @react.component + external make: (~children: React.element) => React.element = "Suspense" + } +} module String = { - let camelCase: string => string = [%raw + let camelCase: string => string = %raw( "str => { return str.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); }); }" - ]; + ) - let capitalize: string => string = [%raw + let capitalize: string => string = %raw( "str => { return str && str.charAt(0).toUpperCase() + str.substring(1); }" - ]; -}; + ) +} module Json = { - [@bs.val] [@bs.scope "JSON"] - external prettyStringify: - (Js.Json.t, [@bs.as {json|null|json}] _, [@bs.as 4] _) => string = - "stringify"; -}; + @bs.val @bs.scope("JSON") + external prettyStringify: (Js.Json.t, @bs.as(json`null`) _, @bs.as(4) _) => string = "stringify" +} module Url = { - let isAbsolute: string => bool = [%raw - {| + let isAbsolute: string => bool = %raw( + ` function(str) { var r = new RegExp('^(?:[a-z]+:)?//', 'i'); if (r.test(str)) @@ -89,9 +87,9 @@ module Url = { } return false; } - |} - ]; -}; + ` //', 'i'); + ) +} module Date = { /* @@ -130,22 +128,19 @@ module Date = { {j|$month $day, $year|j}; }; - */ + */ let toDayMonthYear = (date: Js.Date.t) => { - IntlDateTimeFormat.( - Date.( - make( - ~locale=`US, - ~options= - options( - ~month=Month.make(`short), - ~day=Day.make(`numeric), - ~year=Year.make(`numeric), - (), - ), - date, - ) - ) - ); - }; -}; + open IntlDateTimeFormat + open Date + make( + ~locale=#US, + ~options=options( + ~month=Month.make(#short), + ~day=Day.make(#numeric), + ~year=Year.make(#numeric), + (), + ), + date, + ) + } +} diff --git a/common/WarningFlagDescription.js b/common/WarningFlagDescription.js new file mode 100644 index 000000000..7f21f8b08 --- /dev/null +++ b/common/WarningFlagDescription.js @@ -0,0 +1,581 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as Js_dict from "bs-platform/lib/es6/js_dict.js"; +import * as Belt_Int from "bs-platform/lib/es6/belt_Int.js"; +import * as Belt_Array from "bs-platform/lib/es6/belt_Array.js"; +import * as Belt_Option from "bs-platform/lib/es6/belt_Option.js"; +import * as Caml_option from "bs-platform/lib/es6/caml_option.js"; +import * as Caml_exceptions from "bs-platform/lib/es6/caml_exceptions.js"; +import * as Caml_js_exceptions from "bs-platform/lib/es6/caml_js_exceptions.js"; + +var numeric = [ + [ + 1, + "Suspicious-looking start-of-comment mark." + ], + [ + 2, + "Suspicious-looking end-of-comment mark." + ], + [ + 3, + "Deprecated feature." + ], + [ + 4, + "Fragile pattern matching: matching that will remain complete even if additional constructors are added to one of the variant types matched." + ], + [ + 5, + "Partially applied function: expression whose result has function type and is ignored." + ], + [ + 6, + "Label omitted in function application." + ], + [ + 7, + "Method overridden." + ], + [ + 8, + "Partial match: missing cases in pattern-matching." + ], + [ + 9, + "Missing fields in a record pattern." + ], + [ + 10, + "Expression on the left-hand side of a sequence that doesn't have type \"unit\" (and that is not a function, see warning number 5)." + ], + [ + 11, + "Redundant case in a pattern matching (unused match case)." + ], + [ + 12, + "Redundant sub-pattern in a pattern-matching." + ], + [ + 13, + "Instance variable overridden." + ], + [ + 14, + "Illegal backslash escape in a string constant." + ], + [ + 15, + "Private method made public implicitly." + ], + [ + 16, + "Unerasable optional argument." + ], + [ + 17, + "Undeclared virtual method." + ], + [ + 18, + "Non-principal type." + ], + [ + 19, + "Type without principality." + ], + [ + 20, + "Unused function argument." + ], + [ + 21, + "Non-returning statement." + ], + [ + 22, + "Preprocessor warning." + ], + [ + 23, + "Useless record \"with\" clause." + ], + [ + 24, + "Bad module name: the source file name is not a valid OCaml module name." + ], + [ + 25, + "Deprecated: now part of warning 8." + ], + [ + 26, + "Suspicious unused variable: unused variable that is bound with \"let\" or \"as\", and doesn't start with an underscore (\"_\") character." + ], + [ + 27, + "Innocuous unused variable: unused variable that is not bound with \"let\" nor \"as\", and doesn't start with an underscore (\"_\") character." + ], + [ + 28, + "Wildcard pattern given as argument to a constant constructor." + ], + [ + 29, + "Unescaped end-of-line in a string constant (non-portable code)." + ], + [ + 30, + "Two labels or constructors of the same name are defined in two mutually recursive types." + ], + [ + 31, + "A module is linked twice in the same executable." + ], + [ + 32, + "Unused value declaration." + ], + [ + 33, + "Unused open statement." + ], + [ + 34, + "Unused type declaration." + ], + [ + 35, + "Unused for-loop index." + ], + [ + 36, + "Unused ancestor variable." + ], + [ + 37, + "Unused constructor." + ], + [ + 38, + "Unused extension constructor." + ], + [ + 39, + "Unused rec flag." + ], + [ + 40, + "Constructor or label name used out of scope." + ], + [ + 41, + "Ambiguous constructor or label name." + ], + [ + 42, + "Disambiguated constructor or label name (compatibility warning)." + ], + [ + 43, + "Nonoptional label applied as optional." + ], + [ + 44, + "Open statement shadows an already defined identifier." + ], + [ + 45, + "Open statement shadows an already defined label or constructor." + ], + [ + 46, + "Error in environment variable." + ], + [ + 47, + "Illegal attribute payload." + ], + [ + 48, + "Implicit elimination of optional arguments." + ], + [ + 49, + "Absent cmi file when looking up module alias." + ], + [ + 50, + "Unexpected documentation comment." + ], + [ + 51, + "Warning on non-tail calls if @tailcall present." + ], + [ + 52, + "Fragile constant pattern." + ], + [ + 53, + "Attribute cannot appear in this context" + ], + [ + 54, + "Attribute used more than once on an expression" + ], + [ + 55, + "Inlining impossible" + ], + [ + 56, + "Unreachable case in a pattern-matching (based on type information)." + ], + [ + 57, + "Ambiguous or-pattern variables under guard" + ], + [ + 58, + "Missing cmx file" + ], + [ + 59, + "Assignment to non-mutable value" + ], + [ + 60, + "Unused module declaration" + ], + [ + 61, + "Unboxable type in primitive declaration" + ], + [ + 62, + "Type constraint on GADT type declaration" + ], + [ + 101, + "BuckleScript warning: Unused bs attributes" + ], + [ + 102, + "BuckleScript warning: polymorphic comparison introduced (maybe unsafe)" + ], + [ + 103, + "BuckleScript warning: about fragile FFI definitions" + ], + [ + 104, + "BuckleScript warning: bs.deriving warning with customized message " + ], + [ + 105, + "BuckleScript warning: the external name is inferred from val name is unsafe from refactoring when changing value name" + ], + [ + 106, + "BuckleScript warning: Unimplemented primitive used:" + ], + [ + 107, + "BuckleScript warning: Integer literal exceeds the range of representable integers of type int" + ], + [ + 108, + "BuckleScript warning: Uninterpreted delimiters (for unicode)" + ] +]; + +var ret = []; + +for(var i = 1; i <= 108; ++i){ + ret.push(i); +} + +function letter(l) { + switch (l) { + case "a" : + return ret; + case "c" : + return [ + 1, + 2 + ]; + case "d" : + return [3]; + case "e" : + return [4]; + case "f" : + return [5]; + case "k" : + return [ + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39 + ]; + case "l" : + return [6]; + case "m" : + return [7]; + case "p" : + return [8]; + case "r" : + return [9]; + case "s" : + return [10]; + case "u" : + return [ + 11, + 12 + ]; + case "v" : + return [13]; + case "x" : + return [ + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 30 + ]; + case "y" : + return [26]; + case "z" : + return [27]; + default: + return []; + } +} + +var letterDescriptions = [[ + "a", + "All flags" + ]]; + +function getDescription(num) { + return Belt_Option.map(Caml_option.undefined_to_opt(numeric.find(function (param) { + return num === param[0]; + })), (function (param) { + return param[1]; + })); +} + +function lookupAll(param) { + var nums = Belt_Array.map(numeric, (function (param) { + return [ + String(param[0]), + param[1] + ]; + })); + return Belt_Array.concat(letterDescriptions, nums); +} + +function lookup(str) { + var num = Belt_Int.fromString(str); + if (num !== undefined) { + var description = getDescription(num); + if (description !== undefined) { + return [[ + str, + description + ]]; + } else { + return []; + } + } + var search = str.toLowerCase(); + return Belt_Array.keep(letterDescriptions, (function (param) { + return param[0] === search; + })); +} + +function fuzzyLookup(str) { + var letters = Belt_Array.keep(letterDescriptions, (function (param) { + return param[0].startsWith(str); + })); + var numbers = Belt_Array.map(Belt_Array.keep(numeric, (function (param) { + return String(param[0]).startsWith(str); + })), (function (param) { + return [ + String(param[0]), + param[1] + ]; + })); + return Belt_Array.concat(letters, numbers); +} + +var InvalidInput = Caml_exceptions.create("WarningFlagDescription.Parser.InvalidInput"); + +function isModifier(str) { + if (str === "+") { + return true; + } else { + return str === "-"; + } +} + +function parseExn(input) { + var ret = []; + var pos = 0; + var state = /* ParseModifier */0; + var last = input.length - 1 | 0; + while(pos <= last) { + var cur = input[pos]; + var match = state; + var newState; + if (match) { + var acc = match.acc; + var modifier = match.modifier; + var next = (pos + 1 | 0) < last ? input[pos + 1 | 0] : cur; + if (isModifier(cur)) { + throw { + RE_EXN_ID: InvalidInput, + _1: "'+' and '-' not allowed in flag name on pos " + String(pos), + Error: new Error() + }; + } + if (next === "+" || next === "-" || pos >= last) { + var token_enabled = modifier === "+"; + var token_flag = acc + cur; + var token = { + enabled: token_enabled, + flag: token_flag + }; + ret.push(token); + newState = /* ParseModifier */0; + } else { + newState = { + modifier: modifier, + acc: acc + cur, + [Symbol.for("name")]: "ParseFlag" + }; + } + } else if (cur === "+" || cur === "-") { + newState = { + modifier: cur, + acc: "", + [Symbol.for("name")]: "ParseFlag" + }; + } else { + throw { + RE_EXN_ID: InvalidInput, + _1: "Expected '+' or '-' on pos " + String(pos), + Error: new Error() + }; + } + state = newState; + pos = pos + 1 | 0; + }; + var match$1 = state; + if (match$1 && match$1.acc === "") { + throw { + RE_EXN_ID: InvalidInput, + _1: "Expected flag name after '" + (match$1.modifier + ("' on pos " + String(pos))), + Error: new Error() + }; + } + return ret; +} + +function parse(input) { + try { + return { + TAG: 0, + _0: parseExn(input), + [Symbol.for("name")]: "Ok" + }; + } + catch (raw_str){ + var str = Caml_js_exceptions.internalToOCamlException(raw_str); + if (str.RE_EXN_ID === InvalidInput) { + return { + TAG: 1, + _0: str._1, + [Symbol.for("name")]: "Error" + }; + } + throw str; + } +} + +function merge(base, other) { + var dict = Js_dict.fromArray(Belt_Array.map(base.slice(), (function (token) { + return [ + token.flag, + token + ]; + }))); + Belt_Array.forEach(other, (function (token) { + dict[token.flag] = token; + + })); + return Js_dict.values(dict).sort(function (t1, t2) { + var f1 = t1.flag; + var f2 = t2.flag; + var match = isNaN(Number(f1)); + var match$1 = isNaN(Number(f2)); + if (match) { + if (match$1) { + return f1.localeCompare(f2) | 0; + } else { + return -1; + } + } else if (match$1) { + return 1; + } else { + return f1.localeCompare(f2) | 0; + } + }); +} + +function tokensToString(tokens) { + return Belt_Array.reduce(tokens, "", (function (acc, token) { + var modifier = token.enabled ? "+" : "-"; + return acc + (modifier + token.flag); + })); +} + +var Parser = { + InvalidInput: InvalidInput, + isModifier: isModifier, + parseExn: parseExn, + parse: parse, + merge: merge, + tokensToString: tokensToString +}; + +var lastWarningNumber = 108; + +var letterAll = ret; + +export { + numeric , + lastWarningNumber , + letterAll , + letter , + letterDescriptions , + getDescription , + lookupAll , + lookup , + fuzzyLookup , + Parser , + +} +/* Not a pure module */ diff --git a/common/WarningFlagDescription.re b/common/WarningFlagDescription.re deleted file mode 100644 index 0fdad7688..000000000 --- a/common/WarningFlagDescription.re +++ /dev/null @@ -1,335 +0,0 @@ -// Extracted from BuckleScript's ocaml/utils/warnings.ml - -let numeric = [| - (1, "Suspicious-looking start-of-comment mark."), - (2, "Suspicious-looking end-of-comment mark."), - (3, "Deprecated feature."), - ( - 4, - "Fragile pattern matching: matching that will remain complete even if additional constructors are added to one of the variant types matched.", - ), - ( - 5, - "Partially applied function: expression whose result has function type and is ignored.", - ), - (6, "Label omitted in function application."), - (7, "Method overridden."), - (8, "Partial match: missing cases in pattern-matching."), - (9, "Missing fields in a record pattern."), - ( - 10, - "Expression on the left-hand side of a sequence that doesn't have type \"unit\" (and that is not a function, see warning number 5).", - ), - (11, "Redundant case in a pattern matching (unused match case)."), - (12, "Redundant sub-pattern in a pattern-matching."), - (13, "Instance variable overridden."), - (14, "Illegal backslash escape in a string constant."), - (15, "Private method made public implicitly."), - (16, "Unerasable optional argument."), - (17, "Undeclared virtual method."), - (18, "Non-principal type."), - (19, "Type without principality."), - (20, "Unused function argument."), - (21, "Non-returning statement."), - (22, "Preprocessor warning."), - (23, "Useless record \"with\" clause."), - ( - 24, - "Bad module name: the source file name is not a valid OCaml module name.", - ), - (25, "Deprecated: now part of warning 8."), - ( - 26, - "Suspicious unused variable: unused variable that is bound with \"let\" or \"as\", and doesn't start with an underscore (\"_\") character.", - ), - ( - 27, - "Innocuous unused variable: unused variable that is not bound with \"let\" nor \"as\", and doesn't start with an underscore (\"_\") character.", - ), - (28, "Wildcard pattern given as argument to a constant constructor."), - (29, "Unescaped end-of-line in a string constant (non-portable code)."), - ( - 30, - "Two labels or constructors of the same name are defined in two mutually recursive types.", - ), - (31, "A module is linked twice in the same executable."), - (32, "Unused value declaration."), - (33, "Unused open statement."), - (34, "Unused type declaration."), - (35, "Unused for-loop index."), - (36, "Unused ancestor variable."), - (37, "Unused constructor."), - (38, "Unused extension constructor."), - (39, "Unused rec flag."), - (40, "Constructor or label name used out of scope."), - (41, "Ambiguous constructor or label name."), - (42, "Disambiguated constructor or label name (compatibility warning)."), - (43, "Nonoptional label applied as optional."), - (44, "Open statement shadows an already defined identifier."), - (45, "Open statement shadows an already defined label or constructor."), - (46, "Error in environment variable."), - (47, "Illegal attribute payload."), - (48, "Implicit elimination of optional arguments."), - (49, "Absent cmi file when looking up module alias."), - (50, "Unexpected documentation comment."), - (51, "Warning on non-tail calls if @tailcall present."), - (52, "Fragile constant pattern."), - (53, "Attribute cannot appear in this context"), - (54, "Attribute used more than once on an expression"), - (55, "Inlining impossible"), - (56, "Unreachable case in a pattern-matching (based on type information)."), - (57, "Ambiguous or-pattern variables under guard"), - (58, "Missing cmx file"), - (59, "Assignment to non-mutable value"), - (60, "Unused module declaration"), - (61, "Unboxable type in primitive declaration"), - (62, "Type constraint on GADT type declaration"), - (101, "BuckleScript warning: Unused bs attributes"), - ( - 102, - "BuckleScript warning: polymorphic comparison introduced (maybe unsafe)", - ), - (103, "BuckleScript warning: about fragile FFI definitions"), - (104, "BuckleScript warning: bs.deriving warning with customized message "), - ( - 105, - "BuckleScript warning: the external name is inferred from val name is unsafe from refactoring when changing value name", - ), - (106, "BuckleScript warning: Unimplemented primitive used:"), - ( - 107, - "BuckleScript warning: Integer literal exceeds the range of representable integers of type int", - ), - (108, "BuckleScript warning: Uninterpreted delimiters (for unicode)"), -|]; - -let lastWarningNumber = 108; -let letterAll = { - let ret = [||]; - for (i in 1 to lastWarningNumber) { - Js.Array2.push(ret, i)->ignore; - }; - ret; -}; - -// we keep the original variable name `letter` like in warnings.ml -let letter = l => - switch (l) { - | "a" => letterAll - /*| "b" => [||]*/ - | "c" => [|1, 2|] - | "d" => [|3|] - | "e" => [|4|] - | "f" => [|5|] - /*| "g" => [||]*/ - /*| "h" => [||]*/ - /*| "i" => [||]*/ - /*| "j" => [||]*/ - | "k" => [|32, 33, 34, 35, 36, 37, 38, 39|] - | "l" => [|6|] - | "m" => [|7|] - /*| "n" => [||]*/ - /*| "o" => [||]*/ - | "p" => [|8|] - /*| "q" => [||]*/ - | "r" => [|9|] - | "s" => [|10|] - /*| "t" => [||]*/ - | "u" => [|11, 12|] - | "v" => [|13|] - /*| "w" => [||]*/ - | "x" => [|14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 30|] - | "y" => [|26|] - | "z" => [|27|] - | _ => [||] - }; - -let letterDescriptions = [|("a", "All flags")|]; - -let getDescription = (num: int): option(string) => { - numeric - ->Js.Array2.find(((n, _)) => num === n) - ->Belt.Option.map(((_, desc)) => desc); -}; - -// returns all possible key / description pairs -let lookupAll = (): array((string, string)) => { - let nums = - numeric->Belt.Array.map(((num, desc)) => - (Belt.Int.toString(num), desc) - ); - - Belt.Array.concat(letterDescriptions, nums); -}; - -// str: a...z or a string number, returns (numStr, description) -let lookup = (str: string): array((string, string)) => { - switch (Belt.Int.fromString(str)) { - | Some(num) => - switch (getDescription(num)) { - | Some(description) => [|(str, description)|] - | None => [||] - } - | None => - /*str*/ - /*->Js.String2.toLowerCase*/ - /*->letter*/ - /*->Belt.Array.reduce([||], (acc, num) => {*/ - /*switch (getDescription(num)) {*/ - /*| Some(description) =>*/ - /*Js.Array2.push(acc, (Belt.Int.toString(num), description))*/ - /*->ignore;*/ - /*acc;*/ - /*| None => acc*/ - /*}*/ - /*});*/ - let search = str->Js.String2.toLowerCase; - Belt.Array.keep(letterDescriptions, ((l, _)) => l === search); - }; -}; - -// matches all numbers that start with str -let fuzzyLookup = (str: string): array((string, string)) => { - let letters = - Belt.Array.keep(letterDescriptions, ((l, _)) => - l->Js.String2.startsWith(str) - ); - - let numbers = - numeric - ->Belt.Array.keep(((n, _)) => - Belt.Int.toString(n)->Js.String2.startsWith(str) - ) - ->Belt.Array.map(((n, desc)) => (Belt.Int.toString(n), desc)); - - Belt.Array.concat(letters, numbers); -}; - -module Parser = { - type token = { - enabled: bool, - flag: string, - }; - - exception InvalidInput(string); - - type state = - | ParseFlag({ - modifier: string, - acc: string, - }) - | ParseModifier; - - let isModifier = str => str === "+" || str === "-"; - - let parseExn = (input: string): array(token) => { - let ret = [||]; - - let pos = ref(0); - - let state = ref(ParseModifier); - let last = Js.String2.length(input) - 1; - - while (pos^ <= last) { - let cur = Js.String2.get(input, pos^); - let newState = - switch (state^) { - | ParseModifier => - if (cur === "+" || cur === "-") { - ParseFlag({modifier: cur, acc: ""}); - } else { - raise( - InvalidInput( - "Expected '+' or '-' on pos " ++ Belt.Int.toString(pos^), - ), - ); - } - | ParseFlag({modifier, acc}) => - let next = - if (pos^ + 1 < last) { - Js.String2.get(input, pos^ + 1); - } else { - cur; - }; - - if (cur->isModifier) { - raise( - InvalidInput( - "'+' and '-' not allowed in flag name on pos " - ++ Belt.Int.toString(pos^), - ), - ); - } else if (next === "+" || next === "-" || pos^ >= last) { - let token = {enabled: modifier === "+", flag: acc ++ cur}; - Js.Array2.push(ret, token)->ignore; - ParseModifier; - } else { - ParseFlag({modifier, acc: acc ++ cur}); - }; - }; - - state := newState; - pos := pos^ + 1; - }; - - // Last sanity check for the edge case where there - // might be a tangling empty flag - switch (state^) { - | ParseFlag({modifier, acc: ""}) => - raise( - InvalidInput( - "Expected flag name after '" - ++ modifier - ++ "' on pos " - ++ Belt.Int.toString(pos^), - ), - ) - | _ => () - }; - - ret; - }; - - let parse = (input: string): result(array(token), string) => - try(Ok(parseExn(input))) { - | InvalidInput(str) => Error(str) - }; - - /*// other will override flags within base*/ - let merge = (base: array(token), other: array(token)) => { - let dict = - Js.Array2.copy(base) - ->Belt.Array.map(token => (token.flag, token)) - ->Js.Dict.fromArray; - - Belt.Array.forEach(other, token => { - dict->Js.Dict.set(token.flag, token) - }); - - Js.Dict.values(dict) - ->Js.Array2.sortInPlaceWith((t1, t2) => { - open Js.Float; - let f1 = t1.flag; - let f2 = t2.flag; - switch (f1->fromString->isNaN, f2->fromString->isNaN) { - | (false, false) - | (true, true) => Js.String2.localeCompare(f1, f2)->Belt.Float.toInt - | (true, false) => (-1) - | (false, true) => 1 - }; - }); - }; - - // Creates a compiler compatible warning flag string - let tokensToString = (tokens: array(token)): string => { - Belt.Array.reduce( - tokens, - "", - (acc, token) => { - let modifier = token.enabled ? "+" : "-"; - - acc ++ modifier ++ token.flag; - }, - ); - }; -}; diff --git a/common/WarningFlagDescription.res b/common/WarningFlagDescription.res new file mode 100644 index 000000000..c5c63c970 --- /dev/null +++ b/common/WarningFlagDescription.res @@ -0,0 +1,291 @@ +// This file was automatically converted to ReScript from 'WarningFlagDescription.re' +// Check the output and make sure to delete the original file + +let numeric = [ + (1, "Suspicious-looking start-of-comment mark."), + (2, "Suspicious-looking end-of-comment mark."), + (3, "Deprecated feature."), + ( + 4, + "Fragile pattern matching: matching that will remain complete even if additional constructors are added to one of the variant types matched.", + ), + (5, "Partially applied function: expression whose result has function type and is ignored."), + (6, "Label omitted in function application."), + (7, "Method overridden."), + (8, "Partial match: missing cases in pattern-matching."), + (9, "Missing fields in a record pattern."), + ( + 10, + "Expression on the left-hand side of a sequence that doesn't have type \"unit\" (and that is not a function, see warning number 5).", + ), + (11, "Redundant case in a pattern matching (unused match case)."), + (12, "Redundant sub-pattern in a pattern-matching."), + (13, "Instance variable overridden."), + (14, "Illegal backslash escape in a string constant."), + (15, "Private method made public implicitly."), + (16, "Unerasable optional argument."), + (17, "Undeclared virtual method."), + (18, "Non-principal type."), + (19, "Type without principality."), + (20, "Unused function argument."), + (21, "Non-returning statement."), + (22, "Preprocessor warning."), + (23, "Useless record \"with\" clause."), + (24, "Bad module name: the source file name is not a valid OCaml module name."), + (25, "Deprecated: now part of warning 8."), + ( + 26, + "Suspicious unused variable: unused variable that is bound with \"let\" or \"as\", and doesn't start with an underscore (\"_\") character.", + ), + ( + 27, + "Innocuous unused variable: unused variable that is not bound with \"let\" nor \"as\", and doesn't start with an underscore (\"_\") character.", + ), + (28, "Wildcard pattern given as argument to a constant constructor."), + (29, "Unescaped end-of-line in a string constant (non-portable code)."), + (30, "Two labels or constructors of the same name are defined in two mutually recursive types."), + (31, "A module is linked twice in the same executable."), + (32, "Unused value declaration."), + (33, "Unused open statement."), + (34, "Unused type declaration."), + (35, "Unused for-loop index."), + (36, "Unused ancestor variable."), + (37, "Unused constructor."), + (38, "Unused extension constructor."), + (39, "Unused rec flag."), + (40, "Constructor or label name used out of scope."), + (41, "Ambiguous constructor or label name."), + (42, "Disambiguated constructor or label name (compatibility warning)."), + (43, "Nonoptional label applied as optional."), + (44, "Open statement shadows an already defined identifier."), + (45, "Open statement shadows an already defined label or constructor."), + (46, "Error in environment variable."), + (47, "Illegal attribute payload."), + (48, "Implicit elimination of optional arguments."), + (49, "Absent cmi file when looking up module alias."), + (50, "Unexpected documentation comment."), + (51, "Warning on non-tail calls if @tailcall present."), + (52, "Fragile constant pattern."), + (53, "Attribute cannot appear in this context"), + (54, "Attribute used more than once on an expression"), + (55, "Inlining impossible"), + (56, "Unreachable case in a pattern-matching (based on type information)."), + (57, "Ambiguous or-pattern variables under guard"), + (58, "Missing cmx file"), + (59, "Assignment to non-mutable value"), + (60, "Unused module declaration"), + (61, "Unboxable type in primitive declaration"), + (62, "Type constraint on GADT type declaration"), + (101, "BuckleScript warning: Unused bs attributes"), + (102, "BuckleScript warning: polymorphic comparison introduced (maybe unsafe)"), + (103, "BuckleScript warning: about fragile FFI definitions"), + (104, "BuckleScript warning: bs.deriving warning with customized message "), + ( + 105, + "BuckleScript warning: the external name is inferred from val name is unsafe from refactoring when changing value name", + ), + (106, "BuckleScript warning: Unimplemented primitive used:"), + ( + 107, + "BuckleScript warning: Integer literal exceeds the range of representable integers of type int", + ), + (108, "BuckleScript warning: Uninterpreted delimiters (for unicode)"), +] + +let lastWarningNumber = 108 +let letterAll = { + let ret = [] + for i in 1 to lastWarningNumber { + Js.Array2.push(ret, i)->ignore + } + ret +} + +// we keep the original variable name `letter` like in warnings.ml +let letter = l => + switch l { + | "a" => letterAll + /* | "b" => [||] */ + | "c" => [1, 2] + | "d" => [3] + | "e" => [4] + | "f" => [5] + /* | "g" => [||] */ + /* | "h" => [||] */ + /* | "i" => [||] */ + /* | "j" => [||] */ + | "k" => [32, 33, 34, 35, 36, 37, 38, 39] + | "l" => [6] + | "m" => [7] + /* | "n" => [||] */ + /* | "o" => [||] */ + | "p" => [8] + /* | "q" => [||] */ + | "r" => [9] + | "s" => [10] + /* | "t" => [||] */ + | "u" => [11, 12] + | "v" => [13] + /* | "w" => [||] */ + | "x" => [14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 30] + | "y" => [26] + | "z" => [27] + | _ => [] + } + +let letterDescriptions = [("a", "All flags")] + +let getDescription = (num: int): option => + numeric->Js.Array2.find(((n, _)) => num === n)->Belt.Option.map(((_, desc)) => desc) + +// returns all possible key / description pairs +let lookupAll = (): array<(string, string)> => { + let nums = numeric->Belt.Array.map(((num, desc)) => (Belt.Int.toString(num), desc)) + + Belt.Array.concat(letterDescriptions, nums) +} + +// str: a...z or a string number, returns (numStr, description) +let lookup = (str: string): array<(string, string)> => + switch Belt.Int.fromString(str) { + | Some(num) => + switch getDescription(num) { + | Some(description) => [(str, description)] + | None => [] + } + | None => + /* str */ + /* ->Js.String2.toLowerCase */ + /* ->letter */ + /* ->Belt.Array.reduce([||], (acc, num) => { */ + /* switch (getDescription(num)) { */ + /* | Some(description) => */ + /* Js.Array2.push(acc, (Belt.Int.toString(num), description)) */ + /* ->ignore; */ + /* acc; */ + /* | None => acc */ + /* } */ + /* }); */ + let search = str->Js.String2.toLowerCase + Belt.Array.keep(letterDescriptions, ((l, _)) => l === search) + } + +// matches all numbers that start with str +let fuzzyLookup = (str: string): array<(string, string)> => { + let letters = Belt.Array.keep(letterDescriptions, ((l, _)) => l->Js.String2.startsWith(str)) + + let numbers = + numeric + ->Belt.Array.keep(((n, _)) => Belt.Int.toString(n)->Js.String2.startsWith(str)) + ->Belt.Array.map(((n, desc)) => (Belt.Int.toString(n), desc)) + + Belt.Array.concat(letters, numbers) +} + +module Parser = { + type token = { + enabled: bool, + flag: string, + } + + exception InvalidInput(string) + + type state = + | ParseFlag({modifier: string, acc: string}) + | ParseModifier + + let isModifier = str => str === "+" || str === "-" + + let parseExn = (input: string): array => { + let ret = [] + + let pos = ref(0) + + let state = ref(ParseModifier) + let last = Js.String2.length(input) - 1 + + while pos.contents <= last { + let cur = Js.String2.get(input, pos.contents) + let newState = switch state.contents { + | ParseModifier => + if cur === "+" || cur === "-" { + ParseFlag({modifier: cur, acc: ""}) + } else { + raise(InvalidInput("Expected '+' or '-' on pos " ++ Belt.Int.toString(pos.contents))) + } + | ParseFlag({modifier, acc}) => + let next = if pos.contents + 1 < last { + Js.String2.get(input, pos.contents + 1) + } else { + cur + } + + if cur->isModifier { + raise( + InvalidInput( + "'+' and '-' not allowed in flag name on pos " ++ Belt.Int.toString(pos.contents), + ), + ) + } else if next === "+" || (next === "-" || pos.contents >= last) { + let token = {enabled: modifier === "+", flag: acc ++ cur} + Js.Array2.push(ret, token)->ignore + ParseModifier + } else { + ParseFlag({modifier: modifier, acc: acc ++ cur}) + } + } + + state := newState + pos := pos.contents + 1 + } + + // Last sanity check for the edge case where there + // might be a tangling empty flag + switch state.contents { + | ParseFlag({modifier, acc: ""}) => + raise( + InvalidInput( + "Expected flag name after '" ++ + (modifier ++ + ("' on pos " ++ Belt.Int.toString(pos.contents))), + ), + ) + | _ => () + } + + ret + } + + let parse = (input: string): result, string> => + try Ok(parseExn(input)) catch { + | InvalidInput(str) => Error(str) + } + + /* // other will override flags within base */ + let merge = (base: array, other: array) => { + let dict = Js.Array2.copy(base)->Belt.Array.map(token => (token.flag, token))->Js.Dict.fromArray + + Belt.Array.forEach(other, token => dict->Js.Dict.set(token.flag, token)) + + Js.Dict.values(dict)->Js.Array2.sortInPlaceWith((t1, t2) => { + open Js.Float + let f1 = t1.flag + let f2 = t2.flag + switch (f1->fromString->isNaN, f2->fromString->isNaN) { + | (false, false) + | (true, true) => + Js.String2.localeCompare(f1, f2)->Belt.Float.toInt + | (true, false) => -1 + | (false, true) => 1 + } + }) + } + + // Creates a compiler compatible warning flag string + let tokensToString = (tokens: array): string => + Belt.Array.reduce(tokens, "", (acc, token) => { + let modifier = token.enabled ? "+" : "-" + + acc ++ (modifier ++ token.flag) + }) +} diff --git a/common/XmlHttpRequest.js b/common/XmlHttpRequest.js new file mode 100644 index 000000000..fbb1d53a3 --- /dev/null +++ b/common/XmlHttpRequest.js @@ -0,0 +1,24 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + + +var Upload = {}; + +function decodeReadyState(x) { + if (x > 4 || x < 0) { + return /* Unknown */5; + } else { + return x; + } +} + +function readyState(xhr) { + return decodeReadyState(xhr.readyState); +} + +export { + Upload , + decodeReadyState , + readyState , + +} +/* No side effect */ diff --git a/common/XmlHttpRequest.re b/common/XmlHttpRequest.re deleted file mode 100644 index 820bdc688..000000000 --- a/common/XmlHttpRequest.re +++ /dev/null @@ -1,305 +0,0 @@ -// FULL XMLHttpRequest SPEC -// https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest - -// ORIGINAL SOURCE OF THIS MODIFIED MODULE: -// https://github.com/stefanduberg/bs-xmlhttprequest/blob/master/src/XmlHttpRequest.re - -module Upload = { - type t; - - [@bs.set] - external onAbort: (t, Dom.progressEvent => unit) => unit = "onabort"; - - [@bs.set] - external onError: (t, Dom.progressEvent => unit) => unit = "onerror"; - - [@bs.set] external onLoad: (t, Dom.progressEvent => unit) => unit = "onload"; - - [@bs.set] - external onLoadEnd: (t, Dom.progressEvent => unit) => unit = "onloadend"; - - [@bs.set] - external onLoadStart: (t, Dom.progressEvent => unit) => unit = "onloadstart"; - - [@bs.set] - external onProgress: (t, Dom.progressEvent => unit) => unit = "onprogress"; - - [@bs.set] - external onTimeout: (t, Dom.progressEvent => unit) => unit = "ontimeout"; - - [@bs.send] - external addEventListener: - ( - t, - [@bs.string] [ - | `abort(Dom.progressEvent => unit) - | `error(Dom.progressEvent => unit) - | `load(Dom.progressEvent => unit) - | [@bs.as "loadend"] `loadEnd(Dom.progressEvent => unit) - | [@bs.as "loadstart"] `loadStart(Dom.progressEvent => unit) - | `progress(Dom.progressEvent => unit) - | `timeout(Dom.progressEvent => unit) - ] - ) => - unit = - "addEventListener"; - - [@bs.send] - external addEventListenerWithOptions: - ( - t, - [@bs.string] [ - | `abort(Dom.progressEvent => unit) - | `error(Dom.progressEvent => unit) - | `load(Dom.progressEvent => unit) - | [@bs.as "loadend"] `loadEnd(Dom.progressEvent => unit) - | [@bs.as "loadstart"] `loadStart(Dom.progressEvent => unit) - | `progress(Dom.progressEvent => unit) - | `timeout(Dom.progressEvent => unit) - ], - { - . - "capture": bool, - "once": bool, - "passive": bool, - } - ) => - unit = - "addEventListener"; - - [@bs.send] - external removeEventListener: - ( - t, - [@bs.string] [ - | `abort(Dom.progressEvent => unit) - | `error(Dom.progressEvent => unit) - | `load(Dom.progressEvent => unit) - | [@bs.as "loadend"] `loadEnd(Dom.progressEvent => unit) - | [@bs.as "loadstart"] `loadStart(Dom.progressEvent => unit) - | `progress(Dom.progressEvent => unit) - | `timeout(Dom.progressEvent => unit) - ] - ) => - unit = - "removeEventListener"; - - [@bs.send] - external removeEventListenerWithOptions: - ( - t, - [@bs.string] [ - | `abort(Dom.progressEvent => unit) - | `error(Dom.progressEvent => unit) - | `load(Dom.progressEvent => unit) - | [@bs.as "loadend"] `loadEnd(Dom.progressEvent => unit) - | [@bs.as "loadstart"] `loadStart(Dom.progressEvent => unit) - | `progress(Dom.progressEvent => unit) - | `timeout(Dom.progressEvent => unit) - ], - { - . - "capture": bool, - "passive": bool, - } - ) => - unit = - "removeEventListener"; -}; - -type t; - -type readyState = - | Unsent - | Opened - | HeadersReceived - | Loading - | Done - | Unknown; - -let decodeReadyState = - fun - | 0 => Unsent - | 1 => Opened - | 2 => HeadersReceived - | 3 => Loading - | 4 => Done - | _ => Unknown; - -[@bs.new] external make: unit => t = "XMLHttpRequest"; - -// The original readyState representation -[@bs.get] external readyStateNum: t => int = "readyState"; - -let readyState = (xhr: t) => decodeReadyState(readyStateNum(xhr)); - -[@bs.get] -external responseArrayBuffer: t => Js.Nullable.t(Js.Typed_array.array_buffer) = - "response"; - -// Response property with different encodings -[@bs.get] -external responseDocument: t => Js.Nullable.t(Dom.document) = "response"; -[@bs.get] external responseJson: t => Js.Nullable.t(Js.Json.t) = "response"; -[@bs.get] external responseText: t => Js.Nullable.t(string) = "responseText"; -[@bs.get] external responseType: t => string = "responseType"; -[@bs.get] external responseUrl: t => Js.Nullable.t(string) = "responseUrl"; -[@bs.get] -external responseXml: t => Js.Nullable.t(Dom.xmlDocument) = "responseXml"; - -[@bs.set] -external setResponseType: - ( - t, - [@bs.string] [ - | [@bs.as "arraybuffer"] `arrayBuffer - | `document - | `json - | `text - ] - ) => - string = - "responseType"; - -[@bs.get] external status: t => int = "status"; - -[@bs.get] external statusText: t => string = "statusText"; - -[@bs.get] external timeout: t => int = "timeout"; - -[@bs.get] external upload: t => Upload.t = "upload"; - -[@bs.set] external setTimeout: (t, int) => int = "timeout"; - -[@bs.get] external withCredentials: t => bool = "withCredentials"; - -[@bs.set] external setWithCredentials: (t, bool) => bool = "withCredentials"; - -[@bs.send] external abort: t => unit = "abort"; - -[@bs.send] -external getAllResponseHeaders: t => Js.Nullable.t(string) = - "getAllResponseHeaders"; - -[@bs.send] -external getResponseHeader: (t, string) => Js.Nullable.t(string) = - "getResponseHeader"; - -[@bs.send] external open_: (t, ~method: string, ~url: string) => unit = "open"; - -[@bs.send] external overrideMimeType: (t, string) => unit = "overrideMimeType"; - -[@bs.send] external send: t => unit = "send"; - -[@bs.send] -external sendArrayBuffer: (t, Js.Typed_array.array_buffer) => unit = "send"; - -[@bs.send] external sendDocument: (t, Dom.document) => unit = "send"; - -[@bs.send] external sendString: (t, string) => unit = "send"; - -[@bs.send] -external setRequestHeader: (t, string, string) => unit = "setRequestHeader"; - -[@bs.set] -external onReadyStateChange: (t, Dom.event => unit) => unit = - "onreadystatechange"; - -[@bs.set] external onAbort: (t, Dom.progressEvent => unit) => unit = "onabort"; -[@bs.set] external onError: (t, Dom.progressEvent => unit) => unit = "onerror"; -[@bs.set] external onLoad: (t, Dom.progressEvent => unit) => unit = "onload"; - -[@bs.set] -external onLoadEnd: (t, Dom.progressEvent => unit) => unit = "onloadend"; - -[@bs.set] -external onLoadStart: (t, Dom.progressEvent => unit) => unit = "onloadstart"; - -[@bs.set] -external onProgress: (t, Dom.progressEvent => unit) => unit = "onprogress"; - -[@bs.set] -external onTimeout: (t, Dom.progressEvent => unit) => unit = "ontimeout"; - -[@bs.send] -external addEventListener: - ( - t, - [@bs.string] [ - | `abort(Dom.progressEvent => unit) - | `error(Dom.progressEvent => unit) - | `load(Dom.progressEvent => unit) - | [@bs.as "loadend"] `loadEnd(Dom.progressEvent => unit) - | [@bs.as "loadstart"] `loadStart(Dom.progressEvent => unit) - | `progress(Dom.progressEvent => unit) - | [@bs.as "readystatechange"] `readyStateChange(Dom.event => unit) - | `timeout(Dom.progressEvent => unit) - ] - ) => - unit = - "addEventListener"; - -[@bs.send] -external addEventListenerWithOptions: - ( - t, - [@bs.string] [ - | `abort(Dom.progressEvent => unit) - | `error(Dom.progressEvent => unit) - | `load(Dom.progressEvent => unit) - | [@bs.as "loadend"] `loadEnd(Dom.progressEvent => unit) - | [@bs.as "loadstart"] `loadStart(Dom.progressEvent => unit) - | `progress(Dom.progressEvent => unit) - | [@bs.as "readystatechange"] `readyStateChange(Dom.event => unit) - | `timeout(Dom.progressEvent => unit) - ], - { - . - "capture": bool, - "once": bool, - "passive": bool, - } - ) => - unit = - "addEventListener"; - -[@bs.send] -external removeEventListener: - ( - t, - [@bs.string] [ - | `abort(Dom.progressEvent => unit) - | `error(Dom.progressEvent => unit) - | `load(Dom.progressEvent => unit) - | [@bs.as "loadend"] `loadEnd(Dom.progressEvent => unit) - | [@bs.as "loadstart"] `loadStart(Dom.progressEvent => unit) - | `progress(Dom.progressEvent => unit) - | [@bs.as "readystatechange"] `readyStateChange(Dom.event => unit) - | `timeout(Dom.progressEvent => unit) - ] - ) => - unit = - "removeEventListener"; - -[@bs.send] -external removeEventListenerWithOptions: - ( - t, - [@bs.string] [ - | `abort(Dom.progressEvent => unit) - | `error(Dom.progressEvent => unit) - | `load(Dom.progressEvent => unit) - | [@bs.as "loadend"] `loadEnd(Dom.progressEvent => unit) - | [@bs.as "loadstart"] `loadStart(Dom.progressEvent => unit) - | `progress(Dom.progressEvent => unit) - | [@bs.as "readystatechange"] `readyStateChange(Dom.event => unit) - | `timeout(Dom.progressEvent => unit) - ], - { - . - "capture": bool, - "passive": bool, - } - ) => - unit = - "removeEventListener"; diff --git a/common/XmlHttpRequest.res b/common/XmlHttpRequest.res new file mode 100644 index 000000000..fa4ad297b --- /dev/null +++ b/common/XmlHttpRequest.res @@ -0,0 +1,260 @@ +// FULL XMLHttpRequest SPEC +// https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest + +// ORIGINAL SOURCE OF THIS MODIFIED MODULE: +// https://github.com/stefanduberg/bs-xmlhttprequest/blob/master/src/XmlHttpRequest.re + +module Upload = { + type t + + @bs.set + external onAbort: (t, Dom.progressEvent => unit) => unit = "onabort" + + @bs.set + external onError: (t, Dom.progressEvent => unit) => unit = "onerror" + + @bs.set external onLoad: (t, Dom.progressEvent => unit) => unit = "onload" + + @bs.set + external onLoadEnd: (t, Dom.progressEvent => unit) => unit = "onloadend" + + @bs.set + external onLoadStart: (t, Dom.progressEvent => unit) => unit = "onloadstart" + + @bs.set + external onProgress: (t, Dom.progressEvent => unit) => unit = "onprogress" + + @bs.set + external onTimeout: (t, Dom.progressEvent => unit) => unit = "ontimeout" + + @bs.send + external addEventListener: ( + t, + @bs.string + [ + | #abort(Dom.progressEvent => unit) + | #error(Dom.progressEvent => unit) + | #load(Dom.progressEvent => unit) + | @bs.as("loadend") #loadEnd(Dom.progressEvent => unit) + | @bs.as("loadstart") #loadStart(Dom.progressEvent => unit) + | #progress(Dom.progressEvent => unit) + | #timeout(Dom.progressEvent => unit) + ], + ) => unit = "addEventListener" + + @bs.send + external addEventListenerWithOptions: ( + t, + @bs.string + [ + | #abort(Dom.progressEvent => unit) + | #error(Dom.progressEvent => unit) + | #load(Dom.progressEvent => unit) + | @bs.as("loadend") #loadEnd(Dom.progressEvent => unit) + | @bs.as("loadstart") #loadStart(Dom.progressEvent => unit) + | #progress(Dom.progressEvent => unit) + | #timeout(Dom.progressEvent => unit) + ], + {"capture": bool, "once": bool, "passive": bool}, + ) => unit = "addEventListener" + + @bs.send + external removeEventListener: ( + t, + @bs.string + [ + | #abort(Dom.progressEvent => unit) + | #error(Dom.progressEvent => unit) + | #load(Dom.progressEvent => unit) + | @bs.as("loadend") #loadEnd(Dom.progressEvent => unit) + | @bs.as("loadstart") #loadStart(Dom.progressEvent => unit) + | #progress(Dom.progressEvent => unit) + | #timeout(Dom.progressEvent => unit) + ], + ) => unit = "removeEventListener" + + @bs.send + external removeEventListenerWithOptions: ( + t, + @bs.string + [ + | #abort(Dom.progressEvent => unit) + | #error(Dom.progressEvent => unit) + | #load(Dom.progressEvent => unit) + | @bs.as("loadend") #loadEnd(Dom.progressEvent => unit) + | @bs.as("loadstart") #loadStart(Dom.progressEvent => unit) + | #progress(Dom.progressEvent => unit) + | #timeout(Dom.progressEvent => unit) + ], + {"capture": bool, "passive": bool}, + ) => unit = "removeEventListener" +} + +type t + +type readyState = + | Unsent + | Opened + | HeadersReceived + | Loading + | Done + | Unknown + +let decodeReadyState = x => + switch x { + | 0 => Unsent + | 1 => Opened + | 2 => HeadersReceived + | 3 => Loading + | 4 => Done + | _ => Unknown + } + +@bs.new external make: unit => t = "XMLHttpRequest" + +// The original readyState representation +@bs.get external readyStateNum: t => int = "readyState" + +let readyState = (xhr: t) => decodeReadyState(readyStateNum(xhr)) + +@bs.get +external responseArrayBuffer: t => Js.Nullable.t = "response" + +// Response property with different encodings +@bs.get +external responseDocument: t => Js.Nullable.t = "response" +@bs.get external responseJson: t => Js.Nullable.t = "response" +@bs.get external responseText: t => Js.Nullable.t = "responseText" +@bs.get external responseType: t => string = "responseType" +@bs.get external responseUrl: t => Js.Nullable.t = "responseUrl" +@bs.get +external responseXml: t => Js.Nullable.t = "responseXml" + +@bs.set +external setResponseType: ( + t, + @bs.string [@bs.as("arraybuffer") #arrayBuffer | #document | #json | #text], +) => string = "responseType" + +@bs.get external status: t => int = "status" + +@bs.get external statusText: t => string = "statusText" + +@bs.get external timeout: t => int = "timeout" + +@bs.get external upload: t => Upload.t = "upload" + +@bs.set external setTimeout: (t, int) => int = "timeout" + +@bs.get external withCredentials: t => bool = "withCredentials" + +@bs.set external setWithCredentials: (t, bool) => bool = "withCredentials" + +@bs.send external abort: t => unit = "abort" + +@bs.send +external getAllResponseHeaders: t => Js.Nullable.t = "getAllResponseHeaders" + +@bs.send +external getResponseHeader: (t, string) => Js.Nullable.t = "getResponseHeader" + +@bs.send external open_: (t, ~method: string, ~url: string) => unit = "open" + +@bs.send external overrideMimeType: (t, string) => unit = "overrideMimeType" + +@bs.send external send: t => unit = "send" + +@bs.send +external sendArrayBuffer: (t, Js.Typed_array.array_buffer) => unit = "send" + +@bs.send external sendDocument: (t, Dom.document) => unit = "send" + +@bs.send external sendString: (t, string) => unit = "send" + +@bs.send +external setRequestHeader: (t, string, string) => unit = "setRequestHeader" + +@bs.set +external onReadyStateChange: (t, Dom.event => unit) => unit = "onreadystatechange" + +@bs.set external onAbort: (t, Dom.progressEvent => unit) => unit = "onabort" +@bs.set external onError: (t, Dom.progressEvent => unit) => unit = "onerror" +@bs.set external onLoad: (t, Dom.progressEvent => unit) => unit = "onload" + +@bs.set +external onLoadEnd: (t, Dom.progressEvent => unit) => unit = "onloadend" + +@bs.set +external onLoadStart: (t, Dom.progressEvent => unit) => unit = "onloadstart" + +@bs.set +external onProgress: (t, Dom.progressEvent => unit) => unit = "onprogress" + +@bs.set +external onTimeout: (t, Dom.progressEvent => unit) => unit = "ontimeout" + +@bs.send +external addEventListener: ( + t, + @bs.string + [ + | #abort(Dom.progressEvent => unit) + | #error(Dom.progressEvent => unit) + | #load(Dom.progressEvent => unit) + | @bs.as("loadend") #loadEnd(Dom.progressEvent => unit) + | @bs.as("loadstart") #loadStart(Dom.progressEvent => unit) + | #progress(Dom.progressEvent => unit) + | @bs.as("readystatechange") #readyStateChange(Dom.event => unit) + | #timeout(Dom.progressEvent => unit) + ], +) => unit = "addEventListener" + +@bs.send +external addEventListenerWithOptions: ( + t, + @bs.string + [ + | #abort(Dom.progressEvent => unit) + | #error(Dom.progressEvent => unit) + | #load(Dom.progressEvent => unit) + | @bs.as("loadend") #loadEnd(Dom.progressEvent => unit) + | @bs.as("loadstart") #loadStart(Dom.progressEvent => unit) + | #progress(Dom.progressEvent => unit) + | @bs.as("readystatechange") #readyStateChange(Dom.event => unit) + | #timeout(Dom.progressEvent => unit) + ], + {"capture": bool, "once": bool, "passive": bool}, +) => unit = "addEventListener" + +@bs.send +external removeEventListener: ( + t, + @bs.string + [ + | #abort(Dom.progressEvent => unit) + | #error(Dom.progressEvent => unit) + | #load(Dom.progressEvent => unit) + | @bs.as("loadend") #loadEnd(Dom.progressEvent => unit) + | @bs.as("loadstart") #loadStart(Dom.progressEvent => unit) + | #progress(Dom.progressEvent => unit) + | @bs.as("readystatechange") #readyStateChange(Dom.event => unit) + | #timeout(Dom.progressEvent => unit) + ], +) => unit = "removeEventListener" + +@bs.send +external removeEventListenerWithOptions: ( + t, + @bs.string + [ + | #abort(Dom.progressEvent => unit) + | #error(Dom.progressEvent => unit) + | #load(Dom.progressEvent => unit) + | @bs.as("loadend") #loadEnd(Dom.progressEvent => unit) + | @bs.as("loadstart") #loadStart(Dom.progressEvent => unit) + | #progress(Dom.progressEvent => unit) + | @bs.as("readystatechange") #readyStateChange(Dom.event => unit) + | #timeout(Dom.progressEvent => unit) + ], + {"capture": bool, "passive": bool}, +) => unit = "removeEventListener" diff --git a/components/AnsiPre.js b/components/AnsiPre.js new file mode 100644 index 000000000..7575a1a17 --- /dev/null +++ b/components/AnsiPre.js @@ -0,0 +1,94 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as Ansi from "../common/Ansi.js"; +import * as Util from "../common/Util.js"; +import * as React from "react"; +import * as Belt_Array from "bs-platform/lib/es6/belt_Array.js"; +import * as Caml_option from "bs-platform/lib/es6/caml_option.js"; + +function mapColor(target, c) { + if (target) { + switch (c) { + case /* Black */0 : + return "bg-black"; + case /* Red */1 : + return "bg-fire"; + case /* Green */2 : + return "bg-dark-code-3"; + case /* Yellow */3 : + return "bg-dark-code-1"; + case /* Magenta */5 : + return "bg-berry"; + case /* Blue */4 : + case /* Cyan */6 : + return "bg-dark-code-2"; + case /* White */7 : + return "bg-snow-dark"; + + } + } else { + switch (c) { + case /* Black */0 : + return "text-black"; + case /* Red */1 : + return "text-fire"; + case /* Green */2 : + return "text-dark-code-3"; + case /* Yellow */3 : + return "text-dark-code-1"; + case /* Magenta */5 : + return "text-berry"; + case /* Blue */4 : + case /* Cyan */6 : + return "text-dark-code-2"; + case /* White */7 : + return "text-snow-dark"; + + } + } +} + +function renderSgrString(key, sgrStr) { + var className = Belt_Array.reduce(sgrStr.params, "", (function (acc, p) { + if (typeof p === "number") { + return acc + " bold"; + } + switch (p.TAG | 0) { + case /* Fg */0 : + return acc + (" " + mapColor(/* Fg */0, p._0)); + case /* Bg */1 : + return acc + (" " + mapColor(/* Bg */1, p._0)); + case /* Unknown */2 : + return acc; + + } + })); + return React.createElement("span", { + key: key, + className: className + }, Util.ReactStuff.s(sgrStr.content)); +} + +function AnsiPre(Props) { + var className = Props.className; + var children = Props.children; + var spans = Belt_Array.mapWithIndex(Ansi.SgrString.fromTokens(Ansi.parse(children)), (function (i, str) { + var key = String(i); + return renderSgrString(key, str); + })); + var tmp = {}; + if (className !== undefined) { + tmp.className = Caml_option.valFromOption(className); + } + return React.createElement("pre", tmp, Util.ReactStuff.ate(spans)); +} + +var make = AnsiPre; + +export { + mapColor , + renderSgrString , + make , + +} +/* react Not a pure module */ diff --git a/components/AnsiPre.re b/components/AnsiPre.res similarity index 50% rename from components/AnsiPre.re rename to components/AnsiPre.res index b6a8c4cec..0541cc441 100644 --- a/components/AnsiPre.re +++ b/components/AnsiPre.res @@ -1,11 +1,13 @@ -open Util.ReactStuff; -open Ansi; +// This file was automatically converted to ReScript from 'AnsiPre.re' +// Check the output and make sure to delete the original file +open Util.ReactStuff +open Ansi type colorTarget = | Fg - | Bg; + | Bg -let mapColor = (~target: colorTarget, c: Color.t): string => { +let mapColor = (~target: colorTarget, c: Color.t): string => switch (target, c) { | (Fg, Black) => "text-black" | (Fg, Red) => "text-fire" @@ -23,35 +25,30 @@ let mapColor = (~target: colorTarget, c: Color.t): string => { | (Bg, Magenta) => "bg-berry" | (Bg, Cyan) => "bg-dark-code-2" | (Bg, White) => "bg-snow-dark" - }; -}; + } let renderSgrString = (~key: string, sgrStr: SgrString.t): React.element => { - let {SgrString.content, params} = sgrStr; + let {SgrString.content: content, params} = sgrStr - let className = - Belt.Array.reduce(params, "", (acc, p) => { - switch (p) { - | Sgr.Bold => acc ++ " bold" - | Fg(c) => acc ++ " " ++ mapColor(~target=Fg, c) - | Bg(c) => acc ++ " " ++ mapColor(~target=Bg, c) - | _ => acc - } - }); + let className = Belt.Array.reduce(params, "", (acc, p) => + switch p { + | Sgr.Bold => acc ++ " bold" + | Fg(c) => acc ++ (" " ++ mapColor(~target=Fg, c)) + | Bg(c) => acc ++ (" " ++ mapColor(~target=Bg, c)) + | _ => acc + } + ) - content->s ; -}; + {content->s} +} -[@react.component] +@react.component let make = (~className=?, ~children: string) => { - let spans = - Ansi.parse(children) - ->SgrString.fromTokens - ->Belt.Array.mapWithIndex((i, str) => { - let key = Belt.Int.toString(i); - renderSgrString(~key, str); - }); + let spans = Ansi.parse(children)->SgrString.fromTokens->Belt.Array.mapWithIndex((i, str) => { + let key = Belt.Int.toString(i) + renderSgrString(~key, str) + }) // Note: pre is essential here, otherwise whitespace and newlines are not respected -
 spans->ate 
; -}; +
 {spans->ate} 
+} diff --git a/components/ApiIntro.js b/components/ApiIntro.js new file mode 100644 index 000000000..04817ca2c --- /dev/null +++ b/components/ApiIntro.js @@ -0,0 +1,18 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as React from "react"; + +function ApiIntro(Props) { + var children = Props.children; + return React.createElement("div", { + className: "my-10" + }, children); +} + +var make = ApiIntro; + +export { + make , + +} +/* react Not a pure module */ diff --git a/components/ApiIntro.re b/components/ApiIntro.re deleted file mode 100644 index 7cf2118ef..000000000 --- a/components/ApiIntro.re +++ /dev/null @@ -1,4 +0,0 @@ -[@react.component] -let make = (~children) => { -
children
; -}; diff --git a/components/ApiIntro.res b/components/ApiIntro.res new file mode 100644 index 000000000..f74f3fc88 --- /dev/null +++ b/components/ApiIntro.res @@ -0,0 +1,2 @@ +@react.component +let make = (~children) =>
children
diff --git a/components/ApiMarkdown.js b/components/ApiMarkdown.js new file mode 100644 index 000000000..f76c506f3 --- /dev/null +++ b/components/ApiMarkdown.js @@ -0,0 +1,79 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as React from "react"; +import * as Markdown from "./Markdown.js"; + +function ApiMarkdown$InvisibleAnchor(Props) { + var id = Props.id; + var style = { + position: "absolute", + top: "-1rem" + }; + return React.createElement("span", { + "aria-hidden": true, + className: "relative" + }, React.createElement("a", { + id: id, + style: style + })); +} + +var InvisibleAnchor = { + make: ApiMarkdown$InvisibleAnchor +}; + +function ApiMarkdown$H1(Props) { + var children = Props.children; + return React.createElement("h1", { + className: "text-6xl leading-1 mb-2 font-sans font-medium text-night-dark" + }, children); +} + +var H1 = { + make: ApiMarkdown$H1 +}; + +function ApiMarkdown$H2(Props) { + var id = Props.id; + var children = Props.children; + return React.createElement(React.Fragment, undefined, React.createElement("div", { + className: "border-b border-gray-10 mb-10 mt-20" + }), React.createElement(Markdown.H2.make, { + id: id, + children: children + })); +} + +var H2 = { + make: ApiMarkdown$H2 +}; + +var $$default = { + Intro: Markdown.Intro.make, + p: Markdown.P.make, + li: Markdown.Li.make, + h1: ApiMarkdown$H1, + h2: ApiMarkdown$H2, + h3: Markdown.H3.make, + h4: Markdown.H4.make, + h5: Markdown.H5.make, + ul: Markdown.Ul.make, + ol: Markdown.Ol.make, + thead: Markdown.Thead.make, + th: Markdown.Th.make, + td: Markdown.Td.make, + inlineCode: Markdown.InlineCode.make, + code: Markdown.Code.make, + pre: Markdown.Pre.make, + a: Markdown.A.make +}; + +export { + InvisibleAnchor , + H1 , + H2 , + $$default , + $$default as default, + +} +/* react Not a pure module */ diff --git a/components/ApiMarkdown.re b/components/ApiMarkdown.re deleted file mode 100644 index 68d4b6ec9..000000000 --- a/components/ApiMarkdown.re +++ /dev/null @@ -1,59 +0,0 @@ -open Util.ReactStuff; - -/* - This module is intended for the ODOC like documentation layout. - It hides the h2 tags and does - */ -open Markdown; - -module InvisibleAnchor = { - [@react.component] - let make = (~id: string) => { - let style = ReactDOMRe.Style.make(~position="absolute", ~top="-1rem", ()); - ; - }; -}; - -module H1 = { - [@react.component] - let make = (~children) => { -

- children -

; - }; -}; - -module H2 = { - // We will currently hide the headline, to keep the structure, - // but having an Elm like documentation - [@react.component] - let make = (~id, ~children) => { - <> -
- children - ; - }; -}; - -let default = - Mdx.Components.t( - ~intro=Intro.make, - ~p=P.make, - ~li=Li.make, - ~h1=H1.make, - ~h2=H2.make, - ~h3=H3.make, - ~h4=H4.make, - ~h5=H5.make, - ~ul=Ul.make, - ~ol=Ol.make, - ~a=A.make, - ~thead=Thead.make, - ~th=Th.make, - ~td=Td.make, - ~pre=Pre.make, - ~inlineCode=InlineCode.make, - ~code=Code.make, - (), - ); diff --git a/components/ApiMarkdown.res b/components/ApiMarkdown.res new file mode 100644 index 000000000..78e8f884b --- /dev/null +++ b/components/ApiMarkdown.res @@ -0,0 +1,53 @@ +// This file was automatically converted to ReScript from 'ApiMarkdown.re' +// Check the output and make sure to delete the original file +open Util.ReactStuff + +/* + This module is intended for the ODOC like documentation layout. + It hides the h2 tags and does + */ +open Markdown + +module InvisibleAnchor = { + @react.component + let make = (~id: string) => { + let style = ReactDOMRe.Style.make(~position="absolute", ~top="-1rem", ()) + + } +} + +module H1 = { + @react.component + let make = (~children) => +

children

+} + +module H2 = { + // We will currently hide the headline, to keep the structure, + // but having an Elm like documentation + @react.component + let make = (~id, ~children) => <> +
children + +} + +let default = Mdx.Components.t( + ~intro=Intro.make, + ~p=P.make, + ~li=Li.make, + ~h1=H1.make, + ~h2=H2.make, + ~h3=H3.make, + ~h4=H4.make, + ~h5=H5.make, + ~ul=Ul.make, + ~ol=Ol.make, + ~a=A.make, + ~thead=Thead.make, + ~th=Th.make, + ~td=Td.make, + ~pre=Pre.make, + ~inlineCode=InlineCode.make, + ~code=Code.make, + (), +) diff --git a/components/Button.js b/components/Button.js new file mode 100644 index 000000000..d7cb4ee41 --- /dev/null +++ b/components/Button.js @@ -0,0 +1,18 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as React from "react"; + +function Button(Props) { + var children = Props.children; + return React.createElement("button", { + className: "transition-colors duration-200 inline-block text-base text-fire hover:text-white hover:bg-fire rounded border border-fire-80 px-5 py-2" + }, children); +} + +var make = Button; + +export { + make , + +} +/* react Not a pure module */ diff --git a/components/Button.re b/components/Button.res similarity index 67% rename from components/Button.re rename to components/Button.res index 17c8f39b4..f3fa964fb 100644 --- a/components/Button.re +++ b/components/Button.res @@ -1,9 +1,8 @@ -open Util.ReactStuff; +open Util.ReactStuff -[@react.component] -let make = (~children) => { +@react.component +let make = (~children) => ; -}; + diff --git a/components/CodeExample.js b/components/CodeExample.js new file mode 100644 index 000000000..53911fe26 --- /dev/null +++ b/components/CodeExample.js @@ -0,0 +1,127 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as Util from "../common/Util.js"; +import * as Curry from "bs-platform/lib/es6/curry.js"; +import * as React from "react"; +import * as Belt_Array from "bs-platform/lib/es6/belt_Array.js"; +import * as Belt_Option from "bs-platform/lib/es6/belt_Option.js"; +import * as HighlightJs from "../common/HighlightJs.js"; + +function langShortname(lang) { + switch (lang) { + case "bash" : + return "sh"; + case "ocaml" : + return "ml"; + case "reason" : + case "reasonml" : + return "re"; + case "text" : + return ""; + default: + return lang; + } +} + +function CodeExample(Props) { + var highlightedLinesOpt = Props.highlightedLines; + var code = Props.code; + var showLabelOpt = Props.showLabel; + var langOpt = Props.lang; + var highlightedLines = highlightedLinesOpt !== undefined ? highlightedLinesOpt : []; + var showLabel = showLabelOpt !== undefined ? showLabelOpt : true; + var lang = langOpt !== undefined ? langOpt : "text"; + var children = HighlightJs.renderHLJS(highlightedLines, undefined, code, lang, undefined); + var label; + if (showLabel) { + var label$1 = langShortname(lang); + label = React.createElement("div", { + className: "flex self-end font-sans mb-4 text-sm font-bold text-night-light px-4" + }, Util.ReactStuff.s(label$1.toUpperCase())); + } else { + label = React.createElement("div", { + className: "mt-4" + }); + } + return React.createElement("div", { + className: "flex w-full flex-col rounded-none xs:rounded border-t border-b xs:border border-snow-dark bg-snow-light py-2 text-night-dark" + }, label, React.createElement("div", { + className: "px-4 text-base pb-2 overflow-x-auto -mt-2" + }, children)); +} + +function CodeExample$Toggle(Props) { + var tabs = Props.tabs; + var match = React.useState(function () { + return 0; + }); + var setSelected = match[1]; + var selected = match[0]; + if (tabs.length !== 1) { + var numberOfItems = tabs.length; + var tabElements = Belt_Array.mapWithIndex(tabs, (function (i, tab) { + var label = tab.label; + var label$1; + if (label !== undefined) { + label$1 = label; + } else { + var lang = tab.lang; + label$1 = lang !== undefined ? langShortname(lang).toUpperCase() : String(i); + } + var activeClass = selected === i ? "font-bold text-gray-100 bg-snow-light border border-b-0 border-snow-dark border-gray-20" : "border-gray-20 border-b hover:cursor-pointer"; + var onClick = function (evt) { + evt.preventDefault(); + return Curry._1(setSelected, (function (param) { + return i; + })); + }; + var key = label$1 + ("-" + String(i)); + var paddingX = numberOfItems >= 3 ? ( + numberOfItems >= 4 ? "" : "lg:px-8" + ) : ( + numberOfItems > 0 ? "sm:px-16" : "" + ); + return React.createElement("span", { + key: key, + className: paddingX + (" flex-none px-4 inline-block p-2 bg-gray-10 rounded-tl rounded-tr " + activeClass), + onClick: onClick + }, Util.ReactStuff.s(label$1)); + })); + var children = Belt_Option.getWithDefault(Belt_Option.map(Belt_Array.get(tabs, selected), (function (tab) { + var lang = Belt_Option.getWithDefault(tab.lang, "text"); + return HighlightJs.renderHLJS(tab.highlightedLines, undefined, tab.code, lang, undefined); + })), null); + return React.createElement("div", { + className: "flex w-full flex-col rounded-none text-night-dark" + }, React.createElement("div", { + className: "flex w-full overflow-auto scrolling-touch font-sans bg-transparent text-sm text-gray-60-tr" + }, React.createElement("div", { + className: "flex" + }, Util.ReactStuff.ate(tabElements)), React.createElement("div", { + className: "flex-1 border-b border-gray-20" + }, Util.ReactStuff.s("\u00A0"))), React.createElement("div", { + className: "px-4 text-base pb-4 pt-4 overflow-x-auto bg-snow-light border-snow-dark xs:rounded-b border border-t-0" + }, React.createElement("pre", undefined, children))); + } + var tab = tabs[0]; + return CodeExample({ + highlightedLines: tab.highlightedLines, + code: tab.code, + lang: tab.lang, + showLabel: true + }); +} + +var Toggle = { + make: CodeExample$Toggle +}; + +var make = CodeExample; + +export { + langShortname , + make , + Toggle , + +} +/* react Not a pure module */ diff --git a/components/CodeExample.re b/components/CodeExample.re deleted file mode 100644 index 483812514..000000000 --- a/components/CodeExample.re +++ /dev/null @@ -1,129 +0,0 @@ -open Util.ReactStuff; - -let langShortname = (lang: string) => { - switch (lang) { - | "ocaml" => "ml" - | "reasonml" - | "reason" => "re" - | "bash" => "sh" - | "text" => "" - | rest => rest - }; -}; - -[@react.component] -let make = (~highlightedLines=[||], ~code: string, ~showLabel=true, ~lang="text") => { - let children = HighlightJs.renderHLJS(~highlightedLines, ~code, ~lang, ()); - - let label = if(showLabel) { - let label = langShortname(lang); -
- {Js.String2.toUpperCase(label)->s} -
- } - else { -
- }; - -
- label -
children
-
; -}; - -module Toggle = { - type tab = { - highlightedLines: option(array(int)), - label: option(string), - lang: option(string), - code: string, - }; - - [@react.component] - let make = (~tabs: array(tab)) => { - let (selected, setSelected) = React.useState(_ => 0); - - switch (tabs) { - | [|tab|] => - make({ - "highlightedLines": tab.highlightedLines, - "code": tab.code, - "lang": tab.lang, - "showLabel": Some(true), - }) - | multiple => - let numberOfItems = Js.Array.length(multiple); - let tabElements = - Belt.Array.mapWithIndex( - multiple, - (i, tab) => { - // if there's no label, infer the label from the language - let label = - switch (tab.label) { - | Some(label) => label - | None => - switch (tab.lang) { - | Some(lang) => langShortname(lang)->Js.String2.toUpperCase - | None => Belt.Int.toString(i) - } - }; - - let activeClass = - selected === i ? "font-bold text-gray-100 bg-snow-light border border-b-0 border-snow-dark border-gray-20" : "border-gray-20 border-b hover:cursor-pointer"; - - let onClick = evt => { - ReactEvent.Mouse.preventDefault(evt); - setSelected(_ => i); - }; - let key = label ++ "-" ++ Belt.Int.toString(i); - - let paddingX = switch(numberOfItems) { - | 1 - | 2 => "sm:px-16" - | 3 => "lg:px-8" - | _ => "" - }; - - label->s - ; - }, - ); - - let children = - Belt.Array.get(multiple, selected) - ->Belt.Option.map(tab => { - let lang = Belt.Option.getWithDefault(tab.lang, "text"); - HighlightJs.renderHLJS( - ~highlightedLines=?tab.highlightedLines, - ~code=tab.code, - ~lang, - (), - ); - }) - ->Belt.Option.getWithDefault(React.null); - -
-
-
- tabElements->ate -
-
- {j|\u00A0|j}->s -
-
-
-
 children 
-
-
; - }; - }; -}; diff --git a/components/CodeExample.res b/components/CodeExample.res new file mode 100644 index 000000000..e9592b65b --- /dev/null +++ b/components/CodeExample.res @@ -0,0 +1,110 @@ +open Util.ReactStuff + +let langShortname = (lang: string) => + switch lang { + | "ocaml" => "ml" + | "reasonml" + | "reason" => "re" + | "bash" => "sh" + | "text" => "" + | rest => rest + } + +@react.component +let make = (~highlightedLines=[], ~code: string, ~showLabel=true, ~lang="text") => { + let children = HighlightJs.renderHLJS(~highlightedLines, ~code, ~lang, ()) + + let label = if showLabel { + let label = langShortname(lang) +
+ {Js.String2.toUpperCase(label)->s} +
+ } else { +
+ } + +
+ label
children
+
+} + +module Toggle = { + type tab = { + highlightedLines: option>, + label: option, + lang: option, + code: string, + } + + @react.component + let make = (~tabs: array) => { + let (selected, setSelected) = React.useState(_ => 0) + + switch tabs { + | [tab] => + make({ + "highlightedLines": tab.highlightedLines, + "code": tab.code, + "lang": tab.lang, + "showLabel": Some(true), + }) + | multiple => + let numberOfItems = Js.Array.length(multiple) + let tabElements = Belt.Array.mapWithIndex(multiple, (i, tab) => { + // if there's no label, infer the label from the language + let label = switch tab.label { + | Some(label) => label + | None => + switch tab.lang { + | Some(lang) => langShortname(lang)->Js.String2.toUpperCase + | None => Belt.Int.toString(i) + } + } + + let activeClass = + selected === i + ? "font-bold text-gray-100 bg-snow-light border border-b-0 border-snow-dark border-gray-20" + : "border-gray-20 border-b hover:cursor-pointer" + + let onClick = evt => { + ReactEvent.Mouse.preventDefault(evt) + setSelected(_ => i) + } + let key = label ++ ("-" ++ Belt.Int.toString(i)) + + let paddingX = switch numberOfItems { + | 1 + | 2 => "sm:px-16" + | 3 => "lg:px-8" + | _ => "" + } + + {label->s} + + }) + + let children = Belt.Array.get(multiple, selected)->Belt.Option.map(tab => { + let lang = Belt.Option.getWithDefault(tab.lang, "text") + HighlightJs.renderHLJS(~highlightedLines=?tab.highlightedLines, ~code=tab.code, ~lang, ()) + })->Belt.Option.getWithDefault(React.null) + +
+
+
{tabElements->ate}
+
{j`\\u00A0`->s}
+
+
+
 children 
+
+
+ } + } +} diff --git a/components/CodeMirror.js b/components/CodeMirror.js new file mode 100644 index 000000000..ceb9f5845 --- /dev/null +++ b/components/CodeMirror.js @@ -0,0 +1,336 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as Curry from "bs-platform/lib/es6/curry.js"; +import * as React from "react"; +import * as Belt_Int from "bs-platform/lib/es6/belt_Int.js"; +import * as Belt_Array from "bs-platform/lib/es6/belt_Array.js"; +import * as Codemirror from "codemirror"; +import * as Belt_Option from "bs-platform/lib/es6/belt_Option.js"; +import * as Caml_option from "bs-platform/lib/es6/caml_option.js"; + +import "codemirror/lib/codemirror.css"; +import "../styles/cm.css"; + +if (typeof window !== "undefined" && typeof window.navigator !== "undefined") { + require("codemirror/mode/javascript/javascript"); + require("codemirror/addon/scroll/simplescrollbars"); + require("../plugins/cm-reason-mode"); +} +; + +var useWindowWidth = (() => { + const isClient = typeof window === 'object'; + + function getSize() { + return { + width: isClient ? window.innerWidth : 0, + height: isClient ? window.innerHeight : 0 + }; + } + + const [windowSize, setWindowSize] = React.useState(getSize); + + let throttled = false; + React.useEffect(() => { + if (!isClient) { + return false; + } + + function handleResize() { + if(!throttled) { + setWindowSize(getSize()); + + throttled = true; + setTimeout(() => { throttled = false }, 300); + } + } + + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); + }, []); // Empty array ensures that effect is only run on mount and unmount + + if(windowSize) { + return windowSize.width; + } + return null; + }); + +var errorGutterId = "errors"; + +var Options = {}; + +var TextMarker = {}; + +var $$Attr = {}; + +var MarkTextOption = { + $$Attr: $$Attr +}; + +var CM = { + errorGutterId: errorGutterId, + Options: Options, + TextMarker: TextMarker, + MarkTextOption: MarkTextOption +}; + +var $$Event = {}; + +var DomUtil = { + $$Event: $$Event +}; + +var $$Error = {}; + +function make(rowCol, kind, wrapper, param) { + var marker = document.createElement("div"); + var colorClass = kind === "Error" ? "text-fire bg-fire-15" : "text-gold bg-gold-15"; + marker.id = "gutter-marker_" + rowCol[0] + "-" + rowCol[1]; + marker.className = "flex items-center justify-center text-14 text-center ml-1 h-6 font-bold hover:cursor-pointer " + colorClass; + marker.innerHTML = "!"; + return marker; +} + +var GutterMarker = { + make: make +}; + +function clearMarks(state) { + Belt_Array.forEach(state.marked, (function (mark) { + mark.clear(); + + })); + state.marked = []; + +} + +function extractRowColFromId(id) { + var match = id.split("_"); + if (match.length !== 2) { + return ; + } + var rowColStr = match[1]; + var match$1 = rowColStr.split("-"); + if (match$1.length !== 2) { + return ; + } + var rowStr = match$1[0]; + var colStr = match$1[1]; + var row = Belt_Int.fromString(rowStr); + var col = Belt_Int.fromString(colStr); + if (row !== undefined && col !== undefined) { + return [ + row, + col + ]; + } + +} + +function updateErrors(state, onMarkerFocus, onMarkerFocusLeave, cm, errors) { + Belt_Array.forEach(state.marked, (function (mark) { + mark.clear(); + + })); + state.marked = []; + cm.clearGutter(errorGutterId); + var wrapper = cm.getWrapperElement(); + Belt_Array.forEachWithIndex(errors, (function (_idx, e) { + var marker = make([ + e.row, + e.column + ], e.kind, wrapper, undefined); + wrapper.appendChild(marker); + var row = e.row - 1 | 0; + var endRow = e.endRow - 1 | 0; + cm.setGutterMarker(row, errorGutterId, marker); + var from_ch = e.column; + var from = { + line: row, + ch: from_ch + }; + var to__ch = e.endColumn; + var to_ = { + line: endRow, + ch: to__ch + }; + var match = e.kind; + var markTextColor = match === "Error" ? "border-fire" : "border-gold"; + var __x = cm.markText(from, to_, { + className: "border-b border-dotted hover:cursor-pointer " + markTextColor, + attributes: { + id: "text-marker_" + (String(e.row) + ("-" + (String(e.column) + ""))) + } + }); + state.marked.push(__x); + + })); + var isMarkerId = function (id) { + if (id.startsWith("gutter-marker")) { + return true; + } else { + return id.startsWith("text-marker"); + } + }; + wrapper.onmouseover = (function (evt) { + var target = evt.target; + var id = target.id; + if (!isMarkerId(id)) { + return ; + } + var rowCol = extractRowColFromId(id); + if (rowCol !== undefined) { + return Belt_Option.forEach(onMarkerFocus, (function (cb) { + return Curry._1(cb, rowCol); + })); + } + + }); + wrapper.onmouseout = (function (evt) { + var target = evt.target; + var id = target.id; + if (!isMarkerId(id)) { + return ; + } + var rowCol = extractRowColFromId(id); + if (rowCol !== undefined) { + return Belt_Option.forEach(onMarkerFocusLeave, (function (cb) { + return Curry._1(cb, rowCol); + })); + } + + }); + +} + +function CodeMirror(Props) { + var errorsOpt = Props.errors; + var minHeight = Props.minHeight; + var maxHeight = Props.maxHeight; + var className = Props.className; + var style = Props.style; + var onChange = Props.onChange; + var onMarkerFocus = Props.onMarkerFocus; + var onMarkerFocusLeave = Props.onMarkerFocusLeave; + var value = Props.value; + var mode = Props.mode; + var readOnlyOpt = Props.readOnly; + var lineNumbersOpt = Props.lineNumbers; + var scrollbarStyleOpt = Props.scrollbarStyle; + var lineWrappingOpt = Props.lineWrapping; + var errors = errorsOpt !== undefined ? errorsOpt : []; + var readOnly = readOnlyOpt !== undefined ? readOnlyOpt : false; + var lineNumbers = lineNumbersOpt !== undefined ? lineNumbersOpt : true; + var scrollbarStyle = scrollbarStyleOpt !== undefined ? scrollbarStyleOpt : "overlay"; + var lineWrapping = lineWrappingOpt !== undefined ? lineWrappingOpt : false; + var inputElement = React.useRef(null); + var cmRef = React.useRef(undefined); + var cmStateRef = React.useRef({ + marked: [] + }); + var windowWidth = Curry._1(useWindowWidth, undefined); + React.useEffect((function () { + var input = inputElement.current; + if (input == null) { + return ; + } + var options = { + theme: "material", + gutters: [ + errorGutterId, + "CodeMirror-linenumbers" + ], + mode: mode, + lineNumbers: lineNumbers, + readOnly: readOnly, + lineWrapping: lineWrapping, + fixedGutter: false, + scrollbarStyle: scrollbarStyle + }; + var cm = Codemirror.fromTextArea(input, options); + Belt_Option.forEach(minHeight, (function (minHeight) { + cm.getScrollerElement().style.minHeight = minHeight; + + })); + Belt_Option.forEach(maxHeight, (function (maxHeight) { + cm.getScrollerElement().style.maxHeight = maxHeight; + + })); + Belt_Option.forEach(onChange, (function (onValueChange) { + cm.on("change", (function (instance) { + return Curry._1(onValueChange, instance.getValue()); + })); + + })); + cm.setValue(value); + cmRef.current = Caml_option.some(cm); + return (function (param) { + cm.toTextArea(); + cmRef.current = undefined; + + }); + }), []); + var cm = cmRef.current; + if (cm !== undefined) { + var cm$1 = Caml_option.valFromOption(cm); + if (cm$1.getValue() !== value) { + var state = cmStateRef.current; + cm$1.operation(function () { + return updateErrors(state, onMarkerFocus, onMarkerFocusLeave, cm$1, errors); + }); + cm$1.setValue(value); + } + + } + var errorsFingerprint = Belt_Array.map(errors, (function (e) { + return "" + e.row + "-" + e.column; + })).join(";"); + React.useEffect((function () { + var state = cmStateRef.current; + var cm = cmRef.current; + if (cm !== undefined) { + var cm$1 = Caml_option.valFromOption(cm); + cm$1.operation(function () { + return updateErrors(state, onMarkerFocus, onMarkerFocusLeave, cm$1, errors); + }); + } + + }), [errorsFingerprint]); + React.useEffect((function () { + var cm = cmRef.current; + if (cm !== undefined) { + Caml_option.valFromOption(cm).refresh(); + } + + }), [ + className, + windowWidth + ]); + var tmp = {}; + if (className !== undefined) { + tmp.className = Caml_option.valFromOption(className); + } + if (style !== undefined) { + tmp.style = Caml_option.valFromOption(style); + } + return React.createElement("div", tmp, React.createElement("textarea", { + ref: inputElement, + className: "hidden" + })); +} + +var make$1 = CodeMirror; + +export { + useWindowWidth , + CM , + DomUtil , + $$Error , + GutterMarker , + clearMarks , + extractRowColFromId , + updateErrors , + make$1 as make, + +} +/* Not a pure module */ diff --git a/components/CodeMirror.re b/components/CodeMirror.re deleted file mode 100644 index 6e2c1ea10..000000000 --- a/components/CodeMirror.re +++ /dev/null @@ -1,545 +0,0 @@ -/* - CodeMirrorBase is a clientside only component that can not be used in a isomorphic environment. - Within our Next project, you will be required to use the component instead, which - will lazily import the CodeMirrorBase via Next's `dynamic` component loading mechanisms. - - ! If you load this component in a Next page without using dynamic loading, you will get a SSR error ! - - This file is providing the core functionality and logic of our CodeMirror instances. - */ - -%raw -{| -import "codemirror/lib/codemirror.css"; -import "../styles/cm.css"; - -if (typeof window !== "undefined" && typeof window.navigator !== "undefined") { - require("codemirror/mode/javascript/javascript"); - require("codemirror/addon/scroll/simplescrollbars"); - require("../plugins/cm-reason-mode"); -} -|}; - -let useWindowWidth: unit => int = [%raw - {j| () => { - const isClient = typeof window === 'object'; - - function getSize() { - return { - width: isClient ? window.innerWidth : 0, - height: isClient ? window.innerHeight : 0 - }; - } - - const [windowSize, setWindowSize] = React.useState(getSize); - - let throttled = false; - React.useEffect(() => { - if (!isClient) { - return false; - } - - function handleResize() { - if(!throttled) { - setWindowSize(getSize()); - - throttled = true; - setTimeout(() => { throttled = false }, 300); - } - } - - window.addEventListener('resize', handleResize); - return () => window.removeEventListener('resize', handleResize); - }, []); // Empty array ensures that effect is only run on mount and unmount - - if(windowSize) { - return windowSize.width; - } - return null; - } - |j} -]; - -/* The module for interacting with the imperative CodeMirror API */ -module CM = { - type t; - - let errorGutterId = "errors"; - - module Options = { - [@bs.deriving {abstract: light}] - type t = { - theme: string, - [@bs.optional] - gutters: array(string), - mode: string, - [@bs.optional] - lineNumbers: bool, - [@bs.optional] - readOnly: bool, - [@bs.optional] - lineWrapping: bool, - [@bs.optional] - fixedGutter: bool, - [@bs.optional] - scrollbarStyle: string, - }; - }; - - [@bs.module "codemirror"] - external fromTextArea: (Dom.element, Options.t) => t = "fromTextArea"; - - [@bs.send] - external getScrollerElement: t => Dom.element = "getScrollerElement"; - - [@bs.send] - external getWrapperElement: t => Dom.element = "getWrapperElement"; - - [@bs.send] external refresh: t => unit = "refresh"; - - [@bs.send] - external onChange: - (t, [@bs.as "change"] _, [@bs.uncurry] (t => unit)) => unit = - "on"; - [@bs.send] external toTextArea: t => unit = "toTextArea"; - - [@bs.send] external setValue: (t, string) => unit = "setValue"; - - [@bs.send] external getValue: t => string = "getValue"; - - [@bs.send] - external operation: (t, [@bs.uncurry] (unit => unit)) => unit = "operation"; - - [@bs.send] - external setGutterMarker: (t, int, string, Dom.element) => unit = - "setGutterMarker"; - - [@bs.send] external clearGutter: (t, string) => unit; - - type markPos = { - line: int, - ch: int, - }; - - module TextMarker = { - type t; - - [@bs.send] external clear: t => unit = "clear"; - }; - - module MarkTextOption = { - type t; - - module Attr = { - type t; - [@bs.obj] external make: (~id: string=?, unit) => t; - }; - - [@bs.obj] - external make: (~className: string=?, ~attributes: Attr.t, unit) => t; - }; - - [@bs.send] - external markText: (t, markPos, markPos, MarkTextOption.t) => TextMarker.t = - "markText"; -}; - -module DomUtil = { - module Event = { - type t; - - [@bs.get] external target: t => Dom.element = "target"; - }; - - [@bs.val] [@bs.scope "document"] - external createElement: string => Dom.element = "createElement"; - - [@bs.send] - external appendChild: (Dom.element, Dom.element) => unit = "appendChild"; - - [@bs.set] [@bs.scope "style"] - external setMinHeight: (Dom.element, string) => unit = "minHeight"; - - [@bs.set] [@bs.scope "style"] - external setMaxHeight: (Dom.element, string) => unit = "maxHeight"; - - [@bs.set] [@bs.scope "style"] - external setDisplay: (Dom.element, string) => unit = "display"; - - [@bs.set] [@bs.scope "style"] - external setTop: (Dom.element, string) => unit = "top"; - - [@bs.set] [@bs.scope "style"] - external setLeft: (Dom.element, string) => unit = "left"; - - [@bs.set] external setInnerHTML: (Dom.element, string) => unit = "innerHTML"; - - [@bs.set] external setId: (Dom.element, string) => unit = "id"; - [@bs.set] external setClassName: (Dom.element, string) => unit = "className"; - - [@bs.set] - external setOnMouseOver: (Dom.element, Event.t => unit) => unit = - "onmouseover"; - - [@bs.set] - external setOnMouseLeave: (Dom.element, Event.t => unit) => unit = - "onmouseleave"; - - [@bs.set] - external setOnMouseOut: (Dom.element, Event.t => unit) => unit = - "onmouseout"; - - [@bs.get] external getId: Dom.element => string = "id"; - - type clientRect = { - x: int, - y: int, - width: int, - height: int, - }; - - [@bs.send] - external getBoundingClientRect: Dom.element => clientRect = - "getBoundingClientRect"; -}; - -module Error = { - type kind = [ | `Error | `Warning]; - - type t = { - row: int, - column: int, - endRow: int, - endColumn: int, - text: string, - kind, - }; -}; - -module GutterMarker = { - // Note: this is not a React component - let make = - ( - ~rowCol: (int, int), // row, col - ~kind: Error.kind, - ~wrapper: Dom.element, - (), - ) - : Dom.element => { - open DomUtil; - - let marker = createElement("div"); - let colorClass = - switch (kind) { - | `Warning => "text-gold bg-gold-15" - | `Error => "text-fire bg-fire-15" - }; - - let (row, col) = rowCol; - marker->setId({j|gutter-marker_$row-$col|j}); - marker->setClassName( - "flex items-center justify-center text-14 text-center ml-1 h-6 font-bold hover:cursor-pointer " - ++ colorClass, - ); - marker->setInnerHTML("!"); - - marker; - }; -}; - -type state = {mutable marked: array(CM.TextMarker.t)}; - -let clearMarks = (state: state): unit => { - Belt.Array.forEach(state.marked, mark => {mark->CM.TextMarker.clear}); - state.marked = [||]; -}; - -let extractRowColFromId = (id: string): option((int, int)) => { - switch (Js.String2.split(id, "_")) { - | [|_, rowColStr|] => - switch (Js.String2.split(rowColStr, "-")) { - | [|rowStr, colStr|] => - let row = Belt.Int.fromString(rowStr); - let col = Belt.Int.fromString(colStr); - switch (row, col) { - | (Some(row), Some(col)) => Some((row, col)) - | _ => None - }; - | _ => None - } - | _ => None - }; -}; - -let updateErrors = - ( - ~state: state, - ~onMarkerFocus=?, - ~onMarkerFocusLeave=?, - ~cm: CM.t, - errors, - ) => { - Belt.Array.forEach(state.marked, mark => {mark->CM.TextMarker.clear}); - state.marked = [||]; - cm->CM.(clearGutter(errorGutterId)); - - let wrapper = cm->CM.getWrapperElement; - - Belt.Array.forEachWithIndex( - errors, - (_idx, e) => { - open DomUtil; - open Error; - - let marker = - GutterMarker.make( - ~rowCol=(e.row, e.column), - ~kind=e.kind, - ~wrapper, - (), - ); - - wrapper->appendChild(marker); - - // CodeMirrors line numbers are (strangely enough) zero based - let row = e.row - 1; - let endRow = e.endRow - 1; - - cm->CM.setGutterMarker(row, CM.errorGutterId, marker); - - let from = {CM.line: row, ch: e.column}; - let to_ = {CM.line: endRow, ch: e.endColumn}; - - let markTextColor = - switch (e.kind) { - | `Error => "border-fire" - | `Warning => "border-gold" - }; - - cm - ->CM.markText( - from, - to_, - CM.MarkTextOption.make( - ~className= - "border-b border-dotted hover:cursor-pointer " ++ markTextColor, - ~attributes= - CM.MarkTextOption.Attr.make( - ~id= - "text-marker_" - ++ Belt.Int.toString(e.row) - ++ "-" - ++ Belt.Int.toString(e.column) - ++ "", - (), - ), - (), - ), - ) - ->Js.Array2.push(state.marked, _) - ->ignore; - (); - }, - ); - - let isMarkerId = id => - Js.String2.startsWith(id, "gutter-marker") - || Js.String2.startsWith(id, "text-marker"); - - wrapper->DomUtil.( - setOnMouseOver(evt => { - let target = Event.target(evt); - - let id = getId(target); - if (isMarkerId(id)) { - switch (extractRowColFromId(id)) { - | Some(rowCol) => - Belt.Option.forEach(onMarkerFocus, cb => cb(rowCol)) - | None => () - }; - }; - }) - ); - - wrapper->DomUtil.( - setOnMouseOut(evt => { - let target = Event.target(evt); - - let id = getId(target); - if (isMarkerId(id)) { - switch (extractRowColFromId(id)) { - | Some(rowCol) => - Belt.Option.forEach(onMarkerFocusLeave, cb => cb(rowCol)) - | None => () - }; - }; - }) - ); -}; - -[@react.component] -let make = - // props relevant for the react wrapper - ( - ~errors: array(Error.t)=[||], - ~minHeight: option(string)=?, - ~maxHeight: option(string)=?, - ~className: option(string)=?, - ~style: option(ReactDOMRe.Style.t)=?, - ~onChange: option(string => unit)=?, - ~onMarkerFocus: option(((int, int)) => unit)=?, // (row, column) - ~onMarkerFocusLeave: option(((int, int)) => unit)=?, // (row, column) - ~value: string, - // props for codemirror options - ~mode, - ~readOnly=false, - ~lineNumbers=true, - ~scrollbarStyle="overlay", - ~lineWrapping=false, - ) - : React.element => { - let inputElement = React.useRef(Js.Nullable.null); - let cmRef: React.Ref.t(option(CM.t)) = React.useRef(None); - let cmStateRef = React.useRef({marked: [||]}); - - let windowWidth = useWindowWidth(); - - React.useEffect0(() => { - switch (inputElement->React.Ref.current->Js.Nullable.toOption) { - | Some(input) => - let options = - CM.Options.t( - ~theme="material", - ~gutters=[|CM.errorGutterId, "CodeMirror-linenumbers"|], - ~mode, - ~lineWrapping, - ~fixedGutter=false, - ~readOnly, - ~lineNumbers, - ~scrollbarStyle, - (), - ); - let cm = CM.fromTextArea(input, options); - /*Js.log2("cm", cm);*/ - - Belt.Option.forEach(minHeight, minHeight => - cm->CM.getScrollerElement->DomUtil.setMinHeight(minHeight) - ); - - Belt.Option.forEach(maxHeight, maxHeight => - cm->CM.getScrollerElement->DomUtil.setMaxHeight(maxHeight) - ); - - Belt.Option.forEach(onChange, onValueChange => { - cm->CM.onChange(instance => {onValueChange(instance->CM.getValue)}) - }); - - // For some reason, injecting value with the options doesn't work - // so we need to set the initial value imperatively - cm->CM.setValue(value); - - React.Ref.setCurrent(cmRef, Some(cm)); - - let cleanup = () => { - /*Js.log2("cleanup", options->CM.Options.mode);*/ - - // This will destroy the CM instance - cm->CM.toTextArea; - React.Ref.setCurrent(cmRef, None); - }; - - Some(cleanup); - | None => None - } - }); - - /* - Previously we did this in a useEffect([|value|) setup, but - this issues for syncing up the current editor value state - with the passed value prop. - - Example: Let's assume you press a format code button for a - piece of code that formats to the same value as the previously - passed value prop. Even though the source code looks different - in the editor (as observed via getValue) it doesn't recognize - that there is an actual change. - - By checking if the local state of the CM instance is different - to the input value, we can sync up both states accordingly - */ - switch (cmRef->React.Ref.current) { - | Some(cm) => - if (CM.getValue(cm) === value) { - (); - } else { - let state = cmStateRef->React.Ref.current; - cm->CM.operation(() => { - updateErrors( - ~onMarkerFocus?, - ~onMarkerFocusLeave?, - ~state, - ~cm, - errors, - ) - }); - cm->CM.setValue(value); - } - | None => () - }; - - /* - This is required since the incoming error - array is not guaranteed to be the same instance, - so we need to make a single string that React's - useEffect is able to act on for equality checks - */ - let errorsFingerprint = - Belt.Array.map( - errors, - e => { - let {Error.row, column} = e; - {j|$row-$column|j}; - }, - ) - ->Js.Array2.joinWith(";"); - - React.useEffect1( - () => { - let state = cmStateRef->React.Ref.current; - switch (cmRef->React.Ref.current) { - | Some(cm) => - cm->CM.operation(() => { - updateErrors( - ~onMarkerFocus?, - ~onMarkerFocusLeave?, - ~state, - ~cm, - errors, - ) - }) - | None => () - }; - None; - }, - [|errorsFingerprint|], - ); - - /* - Needed in case the className visually hides / shows - a codemirror instance, or the window has been resized. - */ - React.useEffect2( - () => { - switch (cmRef->React.Ref.current) { - | Some(cm) => cm->CM.refresh - | None => () - }; - None; - }, - (className, windowWidth), - ); - -
-