From d3d369a366ed5b7f5cbedb05b569be7eeb9a45ba Mon Sep 17 00:00:00 2001 From: "Corin (CWStra)" Date: Mon, 18 Mar 2024 21:01:19 -0400 Subject: [PATCH 1/7] Add custom watchers --- src/RescriptEmbedLang.res | 63 ++++++++++++++++++++++++++++++++------ src/RescriptEmbedLang.resi | 30 ++++++++++++++++-- 2 files changed, 82 insertions(+), 11 deletions(-) diff --git a/src/RescriptEmbedLang.res b/src/RescriptEmbedLang.res index 18bf57b..d925afc 100644 --- a/src/RescriptEmbedLang.res +++ b/src/RescriptEmbedLang.res @@ -28,7 +28,7 @@ module Chokidar = { type watchOptions = {ignored?: array, ignoreInitial?: bool} @send - external watch: (t, string, ~options: watchOptions=?) => Watcher.t = "watch" + external watch: (t, array, ~options: watchOptions=?) => Watcher.t = "watch" } module Glob = { @@ -40,12 +40,21 @@ module Glob = { } @live - type glob = {sync: (array, opts) => array} + type glob = { + sync: (array, opts) => array, + convertPathToPattern: string => string + } @module("fast-glob") external glob: glob = "default" } +module MicroMatch = { + @module("micromatch") + // TODO: Do we need to pass options in? + external makeRe: string => RegExp.t = "makeRe" +} + module Hash = { type createHash @module("crypto") external createHash: createHash = "createHash" @@ -164,7 +173,24 @@ type handleOtherCommandConfig<'config> = { config: 'config, } -type setupConfig = {args: CliArgs.t} +type setupConfig = { + args: CliArgs.t +} + +type watcherOnChangeConfig = { + file: string, + runGeneration: (~files: array=?) => promise, +} + +type watcher = { + filePattern: string, + onChange: watcherOnChangeConfig => promise +} + +type setupResult<'config> = { + config: 'config, + additionalFileWatchers?: array +} type onWatchConfig<'config> = { config: 'config, @@ -174,14 +200,14 @@ type onWatchConfig<'config> = { type t<'config> = { fileName: FileName.t, - setup: setupConfig => promise<'config>, + setup: setupConfig => promise>, generate: generateConfig<'config> => promise>, cliHelpText: string, handleOtherCommand?: handleOtherCommandConfig<'config> => promise, onWatch?: onWatchConfig<'config> => promise, } -let defaultSetup = async _ => () +let defaultSetup = async (_): setupResult<_> => {config: ()} let make = ( ~extensionPattern, @@ -348,6 +374,12 @@ let cleanUpExtraFiles = (t, ~outputDir, ~sourceFileModuleName="*", ~keepThese=[] }) } +type customWatchedFile = { + glob: string, + regexp: RegExp.t, + onChange: watcherOnChangeConfig => promise +} + let runCli = async (t, ~args: option>=?) => { let debugging = ref(false) let debug = msg => @@ -375,8 +407,17 @@ let runCli = async (t, ~args: option>=?) => { open Process process->exitWithCode(0) | Some("generate") => - let config = await t.setup({args: args}) let watch = args->CliArgs.hasArg("--watch") + let {config, ?additionalFileWatchers} = await t.setup({ + args: args, + }) + let customWatchedFiles = + Option.getOr(additionalFileWatchers, []) + ->Array.map(w => { + glob: w.filePattern, + regexp: MicroMatch.makeRe(w.filePattern), + onChange: w.onChange + }) let pathToGeneratedDir = switch args->CliArgs.getArgValue(["--output"]) { | None => panic(`--output must be set. It controls into what directory all generated files are emitted.`) @@ -493,7 +534,8 @@ let runCli = async (t, ~args: option>=?) => { let _theWatcher = Chokidar.watcher ->Chokidar.watch( - `${src}/**/*.res`, + Array.map(customWatchedFiles, f => f.glob) + ->Array.concat([`${src}/**/*.res`]), ~options={ ignored: ["**/node_modules", pathToGeneratedDir], ignoreInitial: true, @@ -501,7 +543,10 @@ let runCli = async (t, ~args: option>=?) => { ) ->Chokidar.Watcher.onChange(async file => { debug(`[changed]: ${file}`) - await runGeneration(~files=[file]) + switch Array.find(customWatchedFiles, w => RegExp.test(w.regexp, file)) { + | Some({onChange}) => await onChange({ runGeneration, file }) + | None => await runGeneration(~files=[file]) + } }) ->Chokidar.Watcher.onAdd(async file => { debug(`[added]: ${file}`) @@ -535,7 +580,7 @@ let runCli = async (t, ~args: option>=?) => { switch t.handleOtherCommand { | None => Console.log(t.cliHelpText) | Some(handleOtherCommand) => - await handleOtherCommand({args, command: otherCommand, config: await t.setup({args: args})}) + await handleOtherCommand({args, command: otherCommand, config: (await t.setup({args: args})).config}) } | None => Console.log(t.cliHelpText) } diff --git a/src/RescriptEmbedLang.resi b/src/RescriptEmbedLang.resi index f0e1e8e..888f313 100644 --- a/src/RescriptEmbedLang.resi +++ b/src/RescriptEmbedLang.resi @@ -139,8 +139,34 @@ type setupConfig = { args: CliArgs.t, } +/** Args given to a custom file watcher when a change occurs */ +type watcherOnChangeConfig = { + /** The specific filepath that triggered the watcher */ + file: string, + /** Trigger code generation. + + `@param files: array=?` - Optional list of files to do generation for. Passing nothing triggers a full generation. + */ + runGeneration: (~files: array=?) => promise, +} + +/** A custom file watcher to register when running in watch mode */ +type watcher = { + /** Pattern to register with the watcher */ + filePattern: string, + /** Callback to trigger when a matching file changes */ + onChange: watcherOnChangeConfig => promise +} + +type setupResult<'config> = { + /** Config object to provide to future calls */ + config: 'config, + /** Additional files to keep an eye on in watch mode */ + additionalFileWatchers?: array +} + /** A default function for `setup`, that just returns nothing. */ -let defaultSetup: setupConfig => promise +let defaultSetup: setupConfig => promise> /** Configuration for the `onWatch` handler. */ type onWatchConfig<'config> = { @@ -173,7 +199,7 @@ type onWatchConfig<'config> = { */ let make: ( ~extensionPattern: extensionPattern, - ~setup: setupConfig => promise<'config>, + ~setup: setupConfig => promise>, ~generate: generateConfig<'config> => promise>, ~cliHelpText: string, ~handleOtherCommand: handleOtherCommandConfig<'config> => promise=?, From 0aaef44585ee701f48482793f71af56dddfc88a6 Mon Sep 17 00:00:00 2001 From: "Corin (CWStra)" Date: Mon, 18 Mar 2024 21:15:31 -0400 Subject: [PATCH 2/7] Add union wrapper --- src/RescriptEmbedLang.res | 19 +++++++++++-------- src/RescriptEmbedLang.resi | 13 +++++++------ 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/RescriptEmbedLang.res b/src/RescriptEmbedLang.res index d925afc..fb7c172 100644 --- a/src/RescriptEmbedLang.res +++ b/src/RescriptEmbedLang.res @@ -187,10 +187,11 @@ type watcher = { onChange: watcherOnChangeConfig => promise } -type setupResult<'config> = { - config: 'config, - additionalFileWatchers?: array -} +type setupResult<'config> = + | SetupResult({ + config: 'config, + additionalFileWatchers?: array + }) type onWatchConfig<'config> = { config: 'config, @@ -207,7 +208,7 @@ type t<'config> = { onWatch?: onWatchConfig<'config> => promise, } -let defaultSetup = async (_): setupResult<_> => {config: ()} +let defaultSetup = async (_): setupResult<_> => SetupResult({config: ()}) let make = ( ~extensionPattern, @@ -408,7 +409,7 @@ let runCli = async (t, ~args: option>=?) => { process->exitWithCode(0) | Some("generate") => let watch = args->CliArgs.hasArg("--watch") - let {config, ?additionalFileWatchers} = await t.setup({ + let SetupResult({config, ?additionalFileWatchers}) = await t.setup({ args: args, }) let customWatchedFiles = @@ -579,8 +580,10 @@ let runCli = async (t, ~args: option>=?) => { | Some(otherCommand) => switch t.handleOtherCommand { | None => Console.log(t.cliHelpText) - | Some(handleOtherCommand) => - await handleOtherCommand({args, command: otherCommand, config: (await t.setup({args: args})).config}) + | Some(handleOtherCommand) => { + let SetupResult({config}) = await t.setup({args: args}) + await handleOtherCommand({args, command: otherCommand, config}) + } } | None => Console.log(t.cliHelpText) } diff --git a/src/RescriptEmbedLang.resi b/src/RescriptEmbedLang.resi index 888f313..71979d0 100644 --- a/src/RescriptEmbedLang.resi +++ b/src/RescriptEmbedLang.resi @@ -158,12 +158,13 @@ type watcher = { onChange: watcherOnChangeConfig => promise } -type setupResult<'config> = { - /** Config object to provide to future calls */ - config: 'config, - /** Additional files to keep an eye on in watch mode */ - additionalFileWatchers?: array -} +type setupResult<'config> = + | SetupResult({ + /** Config object to provide to future calls */ + config: 'config, + /** Additional files to keep an eye on in watch mode */ + additionalFileWatchers?: array + }) /** A default function for `setup`, that just returns nothing. */ let defaultSetup: setupConfig => promise> From 7131cd6b1d96ea32bba623f4125bfa3fea9fff22 Mon Sep 17 00:00:00 2001 From: "Corin (CWStra)" Date: Mon, 18 Mar 2024 21:23:42 -0400 Subject: [PATCH 3/7] Add additionalIgnorePatterns --- src/RescriptEmbedLang.res | 9 ++++++--- src/RescriptEmbedLang.resi | 4 +++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/RescriptEmbedLang.res b/src/RescriptEmbedLang.res index fb7c172..c04e2a6 100644 --- a/src/RescriptEmbedLang.res +++ b/src/RescriptEmbedLang.res @@ -190,7 +190,8 @@ type watcher = { type setupResult<'config> = | SetupResult({ config: 'config, - additionalFileWatchers?: array + additionalFileWatchers?: array, + additionalIgnorePatterns?: array }) type onWatchConfig<'config> = { @@ -409,7 +410,7 @@ let runCli = async (t, ~args: option>=?) => { process->exitWithCode(0) | Some("generate") => let watch = args->CliArgs.hasArg("--watch") - let SetupResult({config, ?additionalFileWatchers}) = await t.setup({ + let SetupResult({config, ?additionalFileWatchers, ?additionalIgnorePatterns}) = await t.setup({ args: args, }) let customWatchedFiles = @@ -538,7 +539,9 @@ let runCli = async (t, ~args: option>=?) => { Array.map(customWatchedFiles, f => f.glob) ->Array.concat([`${src}/**/*.res`]), ~options={ - ignored: ["**/node_modules", pathToGeneratedDir], + ignored: + Option.getOr(additionalIgnorePatterns, []) + ->Array.concat(["**/node_modules", pathToGeneratedDir]), ignoreInitial: true, }, ) diff --git a/src/RescriptEmbedLang.resi b/src/RescriptEmbedLang.resi index 71979d0..f007ab7 100644 --- a/src/RescriptEmbedLang.resi +++ b/src/RescriptEmbedLang.resi @@ -163,7 +163,9 @@ type setupResult<'config> = /** Config object to provide to future calls */ config: 'config, /** Additional files to keep an eye on in watch mode */ - additionalFileWatchers?: array + additionalFileWatchers?: array, + /** Additional file patterns to ignore in watch mode */ + additionalIgnorePatterns?: array }) /** A default function for `setup`, that just returns nothing. */ From 3749317670807f015ba9aa2461444dd7dea2a0f6 Mon Sep 17 00:00:00 2001 From: "Corin (CWStra)" Date: Mon, 18 Mar 2024 21:46:26 -0400 Subject: [PATCH 4/7] mm default value --- src/RescriptEmbedLang.res | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/RescriptEmbedLang.res b/src/RescriptEmbedLang.res index c04e2a6..a4cb9df 100644 --- a/src/RescriptEmbedLang.res +++ b/src/RescriptEmbedLang.res @@ -50,9 +50,11 @@ module Glob = { } module MicroMatch = { + type mm = { + makeRe: string => RegExp.t + } @module("micromatch") - // TODO: Do we need to pass options in? - external makeRe: string => RegExp.t = "makeRe" + external mm: mm = "default" } module Hash = { @@ -417,7 +419,7 @@ let runCli = async (t, ~args: option>=?) => { Option.getOr(additionalFileWatchers, []) ->Array.map(w => { glob: w.filePattern, - regexp: MicroMatch.makeRe(w.filePattern), + regexp: MicroMatch.mm.makeRe(w.filePattern), onChange: w.onChange }) let pathToGeneratedDir = switch args->CliArgs.getArgValue(["--output"]) { From c2273f74df0927978092f8bed1647ec5de3e93c2 Mon Sep 17 00:00:00 2001 From: "Corin (CWStra)" Date: Mon, 18 Mar 2024 21:52:43 -0400 Subject: [PATCH 5/7] Print all watched files --- src/RescriptEmbedLang.res | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/RescriptEmbedLang.res b/src/RescriptEmbedLang.res index a4cb9df..cad4f2b 100644 --- a/src/RescriptEmbedLang.res +++ b/src/RescriptEmbedLang.res @@ -533,13 +533,16 @@ let runCli = async (t, ~args: option>=?) => { if watch { await runGeneration() - Console.log(`Watching for changes in ${src}...`) + let watchedFiles = + Array.map(customWatchedFiles, f => f.glob) + ->Array.concat([`${src}/**/*.res`]) + Console.log(`Watching for changes in:`) + Array.forEach(watchedFiles, f => Console.log(`- ${f}`)) let _theWatcher = Chokidar.watcher ->Chokidar.watch( - Array.map(customWatchedFiles, f => f.glob) - ->Array.concat([`${src}/**/*.res`]), + watchedFiles, ~options={ ignored: Option.getOr(additionalIgnorePatterns, []) From 6c7c4f735679a5db0b49c2c0913604f5365533d0 Mon Sep 17 00:00:00 2001 From: "Corin (CWStra)" Date: Mon, 18 Mar 2024 22:01:05 -0400 Subject: [PATCH 6/7] unboxed + cleanup --- src/RescriptEmbedLang.res | 10 +++------- src/RescriptEmbedLang.resi | 1 + 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/RescriptEmbedLang.res b/src/RescriptEmbedLang.res index cad4f2b..caa61f4 100644 --- a/src/RescriptEmbedLang.res +++ b/src/RescriptEmbedLang.res @@ -40,10 +40,7 @@ module Glob = { } @live - type glob = { - sync: (array, opts) => array, - convertPathToPattern: string => string - } + type glob = {sync: (array, opts) => array} @module("fast-glob") external glob: glob = "default" @@ -175,9 +172,7 @@ type handleOtherCommandConfig<'config> = { config: 'config, } -type setupConfig = { - args: CliArgs.t -} +type setupConfig = {args: CliArgs.t} type watcherOnChangeConfig = { file: string, @@ -189,6 +184,7 @@ type watcher = { onChange: watcherOnChangeConfig => promise } +@unboxed type setupResult<'config> = | SetupResult({ config: 'config, diff --git a/src/RescriptEmbedLang.resi b/src/RescriptEmbedLang.resi index f007ab7..e5eb4d5 100644 --- a/src/RescriptEmbedLang.resi +++ b/src/RescriptEmbedLang.resi @@ -158,6 +158,7 @@ type watcher = { onChange: watcherOnChangeConfig => promise } +@unboxed type setupResult<'config> = | SetupResult({ /** Config object to provide to future calls */ From 67bc6810fee0ed08aab764c77db9987afc16ee3c Mon Sep 17 00:00:00 2001 From: "Corin (CWStra)" Date: Mon, 18 Mar 2024 22:04:31 -0400 Subject: [PATCH 7/7] Tweak watch message --- src/RescriptEmbedLang.res | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/RescriptEmbedLang.res b/src/RescriptEmbedLang.res index caa61f4..64b70b9 100644 --- a/src/RescriptEmbedLang.res +++ b/src/RescriptEmbedLang.res @@ -532,7 +532,7 @@ let runCli = async (t, ~args: option>=?) => { let watchedFiles = Array.map(customWatchedFiles, f => f.glob) ->Array.concat([`${src}/**/*.res`]) - Console.log(`Watching for changes in:`) + Console.log(`Watching the following patterns for file changes...`) Array.forEach(watchedFiles, f => Console.log(`- ${f}`)) let _theWatcher =