Skip to content

Commit 663225a

Browse files
committed
Hot reload: handle template files located in subdirectories
1 parent 8109c3a commit 663225a

File tree

9 files changed

+72
-17
lines changed

9 files changed

+72
-17
lines changed

src/Bolero.Server/Templating.fs

+7-2
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,11 @@ module Impl =
6969
match config.dir with
7070
| Some dir -> Path.Combine(env.ContentRootPath, dir)
7171
| None -> env.ContentRootPath
72+
|> Path.Canonicalize
7273

7374
let fullPathOf filename =
7475
Path.Combine(dir, filename)
76+
|> Path.Canonicalize
7577

7678
let getFileContent fullPath =
7779
asyncRetry 3 <| async {
@@ -83,7 +85,7 @@ module Impl =
8385

8486
let onchange (fullPath: string) =
8587
async {
86-
let filename = Path.GetFileName(fullPath) // TODO: what if it's in a subdirectory?
88+
let filename = Path.GetRelativePath dir fullPath
8789
match! getFileContent fullPath with
8890
| None ->
8991
log.LogWarning("Bolero HotReload: failed to reload {0}", fullPath)
@@ -106,7 +108,10 @@ module Impl =
106108
getFileContent fullPath
107109

108110
member this.Start() =
109-
let fsw = new FileSystemWatcher(dir, "*.html", EnableRaisingEvents = true)
111+
let fsw =
112+
new FileSystemWatcher(dir, "*.html",
113+
IncludeSubdirectories = true,
114+
EnableRaisingEvents = true)
110115
fsw.Created.Add(callback)
111116
fsw.Changed.Add(callback)
112117
fsw.Renamed.Add(callback)

src/Bolero.Templating.Provider/Bolero.Templating.Provider.fsproj

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
<Compile Include="..\Bolero\TemplatingInternals.fs"
1818
Link="TemplatingInternals.fs" />
1919
<Compile Include="Utilities.fs" />
20+
<Compile Include="..\Bolero.Templating\Path.fs"
21+
Link="Path.fs" />
2022
<Compile Include="..\Bolero.Templating\Parsing.fs"
2123
Link="Parsing.fs" />
2224
<Compile Include="ConvertExpr.fs" />

src/Bolero.Templating/Bolero.Templating.fsproj

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
</PropertyGroup>
99
<ItemGroup>
1010
<Compile Include="..\..\build\AssemblyInfo.fs" />
11+
<Compile Include="Path.fs" />
1112
<Compile Include="Parsing.fs" />
1213
<Compile Include="Settings.fs" />
1314
<Compile Include="ConvertNode.fs" />

src/Bolero.Templating/Client.fs

+2
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@ module Program =
142142
{ program with
143143
init = fun comp ->
144144
let client =
145+
// In server mode, the IClient service is set by services.AddHotReload().
146+
// In client mode, it is not set, so we create it here.
145147
match comp.Services.GetService<IClient>() with
146148
| null -> registerClient comp
147149
| client -> client

src/Bolero.Templating/Parsing.fs

+4-2
Original file line numberDiff line numberDiff line change
@@ -370,8 +370,10 @@ let GetDoc (fileOrContent: string) (rootFolder: string) : option<string> * HtmlD
370370
doc.LoadHtml(fileOrContent)
371371
None, doc
372372
else
373-
doc.Load(Path.Combine(rootFolder, fileOrContent))
374-
Some fileOrContent, doc
373+
let rootFolder = Path.Canonicalize rootFolder
374+
let fullPath = Path.Combine(rootFolder, fileOrContent) |> Path.Canonicalize
375+
doc.Load(fullPath)
376+
Some (Path.GetRelativePath rootFolder fullPath), doc
375377

376378
/// Parse a type provider argument into a set of templates.
377379
let ParseFileOrContent (fileOrContent: string) (rootFolder: string) : ParsedTemplates =

src/Bolero.Templating/Path.fs

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// $begin{copyright}
2+
//
3+
// This file is part of Bolero
4+
//
5+
// Copyright (c) 2018 IntelliFactory and contributors
6+
//
7+
// Licensed under the Apache License, Version 2.0 (the "License"); you
8+
// may not use this file except in compliance with the License. You may
9+
// obtain a copy of the License at
10+
//
11+
// http://www.apache.org/licenses/LICENSE-2.0
12+
//
13+
// Unless required by applicable law or agreed to in writing, software
14+
// distributed under the License is distributed on an "AS IS" BASIS,
15+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
16+
// implied. See the License for the specific language governing
17+
// permissions and limitations under the License.
18+
//
19+
// $end{copyright}
20+
21+
module Bolero.Templating.Path
22+
23+
open System.IO
24+
25+
/// Canonicalize a path: remove . and .. components, unify slashes.
26+
let Canonicalize (path: string) =
27+
FileInfo(path).FullName
28+
29+
/// Given a base directory and a full path, return the corresponding relative path.
30+
/// eg: if baseDir = "c:/foo" and fullPath = "c:/foo/bar/baz.html", then return "bar/baz.html".
31+
/// Assumes that both fullPath and baseDir are canonical (see Canonicalize above).
32+
/// Fails if fullPath is not a subdirectory of baseDir.
33+
let GetRelativePath (baseDir: string) (fullPath: string) =
34+
let rec go (thisDir: string) =
35+
if thisDir = baseDir then
36+
fullPath.[thisDir.Length + 1..]
37+
elif thisDir.Length <= baseDir.Length then
38+
invalidArg "fullPath" (sprintf "'%s' is not a subdirectory of '%s'" fullPath baseDir)
39+
else
40+
go (Path.GetDirectoryName thisDir)
41+
go fullPath

tests/Remoting.Client/Main.fs

+10-5
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ let Update (myApi: MyApi) msg model =
8686
{ model with lastError = Some exn }, []
8787

8888
type Tpl = Template<"main.html">
89+
type Form = Template<"subdir/form.html">
8990

9091
type Item() =
9192
inherit ElmishComponent<KeyValuePair<int, string>, Message>()
@@ -96,12 +97,16 @@ type Item() =
9697
.Elt()
9798

9899
let Display model dispatch =
100+
let form =
101+
Form()
102+
.key(string model.currentKey)
103+
.setKey(fun e -> dispatch (SetCurrentKey (int (e.Value :?> string))))
104+
.value(string model.currentValue)
105+
.setValue(fun e -> dispatch (SetCurrentValue (e.Value :?> string)))
106+
.add(fun _ -> dispatch AddItem)
107+
.Elt()
99108
Tpl()
100-
.key(string model.currentKey)
101-
.setKey(fun e -> dispatch (SetCurrentKey (int (e.Value :?> string))))
102-
.value(string model.currentValue)
103-
.setValue(fun e -> dispatch (SetCurrentValue (e.Value :?> string)))
104-
.add(fun _ -> dispatch AddItem)
109+
.form(form)
105110
.refresh(fun _ -> dispatch RefreshItems)
106111
.items(concat [for item in model.items -> ecomp<Item, _, _> item dispatch])
107112
.error(

tests/Remoting.Client/main.html

+2-8
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
1-
<div>
2-
<input type="number" placeholder="Key" value="${key}" onchange="${setKey}">
3-
<input placeholder="Value" value="${value}" onchange="${setValue}">
4-
<button onclick="${add}">Add</button>
5-
</div>
6-
<div>
7-
<button onclick="${refresh}">Refresh</button>
8-
</div>
1+
<div>${form}</div>
2+
<div><button onclick="${refresh}">Refresh</button></div>
93
<ul>
104
${items}
115
<template id="item">
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<input type="number" placeholder="Key" value="${key}" onchange="${setKey}">
2+
<input placeholder="Value" value="${value}" onchange="${setValue}">
3+
<button onclick="${add}">Add it</button>

0 commit comments

Comments
 (0)