replaced maskOnly with new syntax to enable user to configure same mask separately for files and folders

Massimo Melina committed Dec 29, 2024 at 11:37 UTC 91b84583b56b418cb1045ce88a67a6960314b067
3 files changed +27 -13
config.md
+3 -2
@@ -172,8 +172,9 @@ Valid keys in a node are:
172 mime: auto
173 ```
174 Rules on top have priority over bottom rules. Inner rules have priority over parent's rules.
175 - A mask can carry any node property (even property "masks") plus a special property `maskOnly` (optional) to restrict
176 - the application of the mask to only files or folders, just by specifying exactly `files` or `folders`
175 + A mask can carry any node property (even property "masks" itself).
176 + If the mask ends with `|files|`, then it will match only files and not folders.
177 + You can get the opposite effect with suffix `|folders|`.
178
179 Permissions set on an inner element will override inherited permissions. This means that you can restrict access to folder1,
180 and yet decide to give free access to folder1/subfolder2.
src/vfs.ts
+20 -5
@@ -18,7 +18,7 @@ import fswin from 'fswin'
18
19 const showHiddenFiles = defineConfig('show_hidden_files', false)
20
21 -type Masks = Record<string, VfsNode & { maskOnly?: 'files' | 'folders' }>
21 +type Masks = Record<string, VfsNode>
22
23 export interface VfsNodeStored extends VfsPerms {
24 name?: string
@@ -168,6 +168,14 @@ export async function getNodeByName(name: string, parent: VfsNode) {
168 export let vfs: VfsNode = {}
169 defineConfig<VfsNode>('vfs', {}).sub(data =>
170 vfs = (function recur(node) {
171 + const {masks} = node
172 + _.each(masks, (v: any, mask) => { // legacy pre-0.56: convert from property to key suffix
173 + if (v.maskOnly) {
174 + masks![`${mask}|${v.maskOnly}|`] = v = _.omit(v, 'maskOnly')
175 + delete masks![mask]
176 + }
177 + recur(v)
178 + })
179 if (node.children)
180 for (const c of node.children)
181 recur(c)
@@ -346,11 +354,18 @@ export function masksCouldGivePermission(masks: Masks | undefined, perm: keyof V
354 }
355
356 export function parentMaskApplier(parent: VfsNode) {
349 - const matchers = onlyTruthy(_.map(parent.masks, (v, k) => {
357 + const matchers = onlyTruthy(_.map(parent.masks, (mods, k) => {
358 + if (!mods) return
359 + const mustBeFolder = (() => { // undefined if no restriction is requested
360 + if (k.at(-1) !== '|') return // parse special flag syntax as suffix |FLAG| inside the key. This allows specifying different flags with the same mask using separate keys. To avoid syntax conflicts with the rest of the file-mask, we look for an ending pipe, as it has no practical use. Ending-pipe was preferred over starting-pipe to leave the rest of the logic (inheritMasks) untouched.
361 + const i = k.lastIndexOf('|', k.length - 2)
362 + if (i < 0) return
363 + const type = k.slice(i + 1, -1)
364 + k = k.slice(0, i) // remove
365 + return type === 'folders'
366 + })()
367 k = k.startsWith('**/') ? k.slice(3) : !k.includes('/') ? k : '' // ** globstar matches also zero subfolders, so this mask must be applied here too
351 - const { maskOnly, ...mods } = v || {}
352 - // k is stored into the object for debugging purposes
353 - return k && { k, mods, matcher: makeMatcher(k), mustBeFolder: maskOnly && (maskOnly === 'folders') }
368 + return k && { mods, matcher: makeMatcher(k), mustBeFolder }
369 }))
370 return async (item: VfsNode, virtualBasename=getNodeName(item)) => {
371 let isFolder: boolean | undefined = undefined
tests/config.yaml
+4 -6
@@ -109,17 +109,15 @@ vfs:
109 - name: hi
110 source: tests
111 masks:
112 - "**/*":
113 - maskOnly: files
114 - can_list: false
115 - can_read: false
112 "*/page":
113 can_list: false
114 + "**/*|files|":
115 + can_list: false
116 + can_read: false
117 - name: cantSeeThisButChildrenMasks
118 can_see: false
119 masks:
121 - "*":
122 - maskOnly: folders
120 + "*|folders|":
121 can_see: true
122 children:
123 - name: hi