spooder 4.2.18 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +52 -18
  2. package/package.json +1 -1
  3. package/src/api.ts +62 -12
package/README.md CHANGED
@@ -200,7 +200,9 @@ When starting or restarting a server process, `spooder` can automatically update
200
200
  {
201
201
  "spooder": {
202
202
  "update": [
203
- "git pull",
203
+ "git reset --hard",
204
+ "git clean -fd",
205
+ "git pull origin main",
204
206
  "bun install"
205
207
  ]
206
208
  }
@@ -210,7 +212,7 @@ When starting or restarting a server process, `spooder` can automatically update
210
212
  Each command should be a separate entry in the array and will be executed in sequence. The server process will be started once all commands have resolved.
211
213
 
212
214
  > [!IMPORTANT]
213
- > Chainging commands using `&&` or `||` operators does not work.
215
+ > Chaining commands using `&&` or `||` operators does not work.
214
216
 
215
217
  If a command in the sequence fails, the remaining commands will not be executed, however the server will still be started. This is preferred over entering a restart loop or failing to start the server at all.
216
218
 
@@ -435,7 +437,7 @@ In addition to the information provided by the developer, `spooder` also include
435
437
  "modules": "108"
436
438
  },
437
439
  "bun": {
438
- "version": "0.6.4",
440
+ "version": "0.6.5",
439
441
  "rev": "f02561530fda1ee9396f51c8bc99b38716e38296",
440
442
  "memory_usage": {
441
443
  "rss": 99672064,
@@ -778,7 +780,7 @@ function default_directory_handler(file_path: string, file: BunFile, stat: DirSt
778
780
  ```
779
781
 
780
782
  > [!NOTE]
781
- > Uncaught `ENOENT` errors throw from the directory handler will return a `404` response, other errors will return a `500` response.
783
+ > Uncaught `ENOENT` errors thrown from the directory handler will return a `404` response, other errors will return a `500` response.
782
784
 
783
785
  > [!NOTE]
784
786
  > The call to `apply_range` in the default directory handler will automatically slice the file based on the [`Range`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Range) header. This function is also exposed as part of the `spooder` API for use in your own handlers.
@@ -1064,6 +1066,24 @@ const html = parse_template(template, replacements);
1064
1066
  </html>
1065
1067
  ```
1066
1068
 
1069
+ Object properties are supported by using the dot syntax.
1070
+
1071
+ ```ts
1072
+ const template = `<span>{$foo.bar}</span>`;
1073
+
1074
+ const replacements = {
1075
+ foo: {
1076
+ bar: 'Hello, world!';
1077
+ }
1078
+ };
1079
+
1080
+ const html = parse_template(template, replacements);
1081
+ ```
1082
+
1083
+ ```html
1084
+ <span>Hello, world!</span>
1085
+ ```
1086
+
1067
1087
  By default, placeholders that do not appear in the replacement object will be left as-is. Set `drop_missing` to `true` to remove them.
1068
1088
 
1069
1089
  ```ts
@@ -1105,16 +1125,15 @@ parse_template('Hello {$world}', replacer);
1105
1125
  </body>
1106
1126
  </html>
1107
1127
  ```
1108
-
1109
1128
  `parse_template` supports looping arrays with the following syntax.
1110
1129
 
1111
1130
  ```html
1112
- {$for:foo}My colour is %s{/for}
1131
+ {$for:foo as bar}My colour is {$bar}{/for}
1113
1132
  ```
1114
1133
  ```ts
1115
1134
  const template = `
1116
1135
  <ul>
1117
- {$for:foo}<li>%s</li>{/for}
1136
+ {$for:foo as bar}<li>{$bar}</li>{/for}
1118
1137
  </ul>
1119
1138
  `;
1120
1139
 
@@ -1133,24 +1152,39 @@ const html = parse_template(template, replacements);
1133
1152
  </ul>
1134
1153
  ```
1135
1154
 
1136
- All placeholders inside a `{$for:}` loop are substituted, but only if the loop variable exists.
1137
-
1138
- In the following example, `missing` does not exist, so `test` is not substituted inside the loop, but `test` is still substituted outside the loop.
1155
+ Loops also support object properties using the dot syntax.
1139
1156
 
1140
1157
  ```html
1141
- <div>Hello {$test}!</div>
1142
- {$for:missing}<div>Loop {$test}</div>{/for}
1158
+ {$for:foo as bar}My colour is {$bar.baz}{/for}
1143
1159
  ```
1144
-
1145
1160
  ```ts
1146
- parse_template(..., {
1147
- test: 'world'
1148
- });
1161
+ const template = `
1162
+ <ul>
1163
+ {$for:foo as bar}<li>My colour is {$bar.baz}</li>{/for}
1164
+ </ul>
1165
+ `;
1166
+ ```
1167
+
1168
+ If/else statements are supported in templates, which include blocks based on the existence/truthiness of a property.
1169
+
1170
+ ```html
1171
+ {$if:is_on_earth}
1172
+ Hello, world!
1173
+ {else}
1174
+ Hello, Mars!
1175
+ {/if}
1149
1176
  ```
1150
1177
 
1178
+ This also works with nested properties and loops.
1179
+
1151
1180
  ```html
1152
- <div>Hello world!</div>
1153
- {$for}Loop <div>{$test}</div>{/for}
1181
+ {$for:countries as country}
1182
+ {$if:country.is_in_europe}
1183
+ Europe: {$country.name}
1184
+ {else}
1185
+ Not Europe: {$country.name}
1186
+ {/if}
1187
+ {/for}
1154
1188
  ```
1155
1189
 
1156
1190
  <a id="api-content-generate-hash-subs"></a>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "spooder",
3
3
  "type": "module",
4
- "version": "4.2.18",
4
+ "version": "4.3.0",
5
5
  "exports": {
6
6
  ".": {
7
7
  "bun": "./src/api.ts",
package/src/api.ts CHANGED
@@ -108,8 +108,8 @@ export async function safe(target_fn: Callable) {
108
108
  }
109
109
  }
110
110
 
111
- type ReplacerFn = (key: string) => string | Array<string> | undefined;
112
- type Replacements = Record<string, string | Array<string>> | ReplacerFn;
111
+ type ReplacerFn = (key: string) => string | Array<any> | Record<string, any> | undefined;
112
+ type Replacements = Record<string, string | Array<any> | Record<string, any>> | ReplacerFn;
113
113
 
114
114
  export function parse_template(template: string, replacements: Replacements, drop_missing = false): string {
115
115
  let result = '';
@@ -118,6 +118,28 @@ export function parse_template(template: string, replacements: Replacements, dro
118
118
 
119
119
  const is_replacer_fn = typeof replacements === 'function';
120
120
 
121
+ function getValue(key: string): any {
122
+ const var_parts = key.split('.');
123
+ let value: any = is_replacer_fn ? replacements(var_parts[0]) : replacements[var_parts[0]];
124
+
125
+ for (let j = 1; j < var_parts.length; j++) {
126
+ if (value && typeof value === 'object') {
127
+ value = value[var_parts[j]];
128
+ } else {
129
+ value = undefined;
130
+ break;
131
+ }
132
+ }
133
+ return value;
134
+ }
135
+
136
+ function parseBlock(content: string, local_replacements: Replacements): string {
137
+ return parse_template(content, {
138
+ ...replacements,
139
+ ...local_replacements
140
+ }, drop_missing);
141
+ }
142
+
121
143
  const template_length = template.length;
122
144
  for (let i = 0; i < template_length; i++) {
123
145
  const char = template[i];
@@ -130,9 +152,8 @@ export function parse_template(template: string, replacements: Replacements, dro
130
152
  buffer_active = false;
131
153
 
132
154
  if (buffer.startsWith('for:')) {
133
- const loop_key = buffer.substring(4);
134
-
135
- const loop_entries = is_replacer_fn ? replacements(loop_key) : replacements[loop_key];
155
+ const [loop_key, loop_var] = buffer.substring(4).split(' as ');
156
+ const loop_entries = getValue(loop_key);
136
157
  const loop_content_start_index = i + 1;
137
158
  const loop_close_index = template.indexOf('{/for}', loop_content_start_index);
138
159
 
@@ -141,19 +162,48 @@ export function parse_template(template: string, replacements: Replacements, dro
141
162
  result += '{$' + buffer + '}';
142
163
  } else {
143
164
  const loop_content = template.substring(loop_content_start_index, loop_close_index);
144
- if (loop_entries !== undefined) {
145
- for (const loop_entry of loop_entries) {
146
- const inner_content = loop_content.replaceAll('%s', loop_entry);
147
- result += parse_template(inner_content, replacements, drop_missing);
148
- }
165
+ if (Array.isArray(loop_entries)) {
166
+ for (const loop_entry of loop_entries)
167
+ result += parseBlock(loop_content, { [loop_var]: loop_entry });
149
168
  } else {
150
169
  if (!drop_missing)
151
170
  result += '{$' + buffer + '}' + loop_content + '{/for}';
152
171
  }
153
- i += loop_content.length + 6;
172
+ i = loop_close_index + 5; // Move past {/for}
173
+ }
174
+ } else if (buffer.startsWith('if:')) {
175
+ const condition_key = buffer.substring(3);
176
+ const condition_value = getValue(condition_key);
177
+ const if_content_start_index = i + 1;
178
+ const if_close_index = template.indexOf('{/if}', if_content_start_index);
179
+
180
+ if (if_close_index === -1) {
181
+ if (!drop_missing)
182
+ result += '{$' + buffer + '}';
183
+ } else {
184
+ const if_content = template.substring(if_content_start_index, if_close_index);
185
+ const else_index = if_content.indexOf('{else}');
186
+
187
+ if (else_index === -1) {
188
+ // No else block
189
+ if (condition_value) {
190
+ result += parseBlock(if_content, {});
191
+ }
192
+ } else {
193
+ // Has else block
194
+ const true_content = if_content.substring(0, else_index);
195
+ const false_content = if_content.substring(else_index + 6); // +6 to move past {else}
196
+
197
+ if (condition_value)
198
+ result += parseBlock(true_content, {});
199
+ else
200
+ result += parseBlock(false_content, {});
201
+ }
202
+ i = if_close_index + 4; // Move past {/if}
154
203
  }
155
204
  } else {
156
- const replacement = is_replacer_fn ? replacements(buffer) : replacements[buffer];
205
+ const replacement = getValue(buffer);
206
+
157
207
  if (replacement !== undefined)
158
208
  result += replacement;
159
209
  else if (!drop_missing)