Created
May 5, 2022 00:53
-
-
Save 71/6384f7d999514b4583e64e2d6e023e7a to your computer and use it in GitHub Desktop.
Generate a "Tags" folder in Google Drive with tags from all files.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function main() { | |
buildTagsDirectory( | |
DriveApp.getRootFolder(), | |
DriveApp.getFolderById("..."), | |
); | |
} | |
/** | |
* Builds a recursive tags structure in `tagsFolder` with all tags found in items in `rootFolder`. | |
*/ | |
function buildTagsDirectory( | |
/** @type DriveApp.Folder */ rootFolder, | |
/** @type DriveApp.Folder */ tagsFolder, | |
) { | |
// Find all items belonging to each tag. | |
/** @type Map<string, { item: DriveApp.Folder | DriveApp.File, tags: Set<string> } | null> */ | |
const itemsById = new Map([[tagsFolder.getId(), null]]); | |
readAll(itemsById); | |
/** @type Map<string, { item: DriveApp.Folder | DriveApp.File, tags: Set<string> }[]> */ | |
const itemsByTag = new Map(); | |
for (const itemAndTags of itemsById.values()) { | |
if (itemAndTags === null) { | |
continue; | |
} | |
for (const tag of itemAndTags.tags) { | |
let items = itemsByTag.get(tag); | |
if (items === undefined) { | |
itemsByTag.set(tag, items = []); | |
} | |
items.push(itemAndTags); | |
} | |
} | |
// Create tag directory. | |
createTagDirectories(() => tagsFolder, itemsByTag, new Set()); | |
} | |
/** | |
* Stores all files and folders in Drive along with their tags into `results`. | |
*/ | |
function readAll( | |
/** @type Map<string, { item: DriveApp.Folder | DriveApp.File, tags: Set<string> } | null> */ results, | |
) { | |
for (const files = DriveApp.getFiles(); files.hasNext();) { | |
const file = files.next(), | |
fileId = file.getId(); | |
if (results.has(fileId) || file.isTrashed()) { | |
continue; | |
} | |
const fileTags = extractTags(file.getDescription()); | |
results.set( | |
fileId, | |
fileTags.size > 0 ? { item: file, tags: fileTags } : null, | |
); | |
} | |
for (const folders = DriveApp.getFolders(); folders.hasNext();) { | |
const folder = folders.next(), | |
folderId = folder.getId(); | |
if (results.has(folderId) || folder.isTrashed()) { | |
return; | |
} | |
const folderTags = extractTags(folder.getDescription()); | |
results.set( | |
folderId, | |
folderTags.size > 0 ? { item: folder, tags: folderTags } : null, | |
); | |
} | |
} | |
/** | |
* Recursively creates the directories for the given tags. | |
*/ | |
function createTagDirectories( | |
/** @type {(force: boolean) => DriveApp.Folder | undefined} */ getParentFolder, | |
/** @type Map<string, { item: DriveApp.Folder | DriveApp.File, tags: Set<string> }[]> */ allTags, | |
/** @type Set<string> */ currentTags, | |
/** @type {string | undefined} */ currentTag, | |
) { | |
const existingParentFolder = getParentFolder(false); | |
if (currentTag !== undefined) { | |
// Save existing shortcuts to avoid recreating shortcuts. | |
/** @type Map<string, DriveApp.File> */ | |
const existingShortcuts = new Map(); | |
if (existingParentFolder !== undefined) { | |
for (const files = existingParentFolder.getFiles(); files.hasNext();) { | |
const file = files.next(), | |
targetId = file.getTargetId(), | |
isShortcut = targetId !== null; | |
if (isShortcut) { | |
existingShortcuts.set(targetId, file); | |
} | |
} | |
} | |
// Create shortcuts to items. | |
const items = allTags.get(currentTag); | |
outer: for (const { item, tags } of items) { | |
for (const tag of currentTags) { | |
if (!tags.has(tag)) { | |
continue outer; | |
} | |
} | |
const itemId = item.getId(), | |
file = existingShortcuts.get(itemId); | |
if (file !== undefined) { | |
existingShortcuts.delete(itemId); | |
file.setTrashed(false); | |
} else { | |
getParentFolder(true).createShortcut(itemId); | |
} | |
} | |
// Delete unused existing shortcuts. | |
for (const file of existingShortcuts.values()) { | |
file.setTrashed(true); | |
} | |
} | |
// Save existing folders to avoid recreating folders. | |
/** @type Map<string, DriveApp.Folder> */ | |
const existingFolders = new Map(); | |
if (existingParentFolder !== undefined) { | |
for (const folders = existingParentFolder.getFolders(); folders.hasNext();) { | |
const folder = folders.next(); | |
existingFolders.set(folder.getId(), folder); | |
} | |
} | |
// Create nested tags. | |
for (const tag of allTags.keys()) { | |
if (currentTags.has(tag)) { | |
continue; | |
} | |
let folder = existingFolders.get(tag), | |
hasExistingFolder = folder !== undefined; | |
function getFolder(/** @type boolean */ force) { | |
if (!force) { | |
return folder; | |
} | |
if (folder !== undefined) { | |
if (hasExistingFolder) { | |
// Make sure to mark that the folder has been used. | |
existingFolders.delete(tag); | |
folder.setTrashed(false); | |
hasExistingFolder = false; | |
} | |
return folder; | |
} | |
// Folder does not exist but has been requested, create it. | |
return folder = getParentFolder(true).createFolder(tag); | |
} | |
currentTags.add(tag); | |
createTagDirectories(getFolder, allTags, currentTags, tag); | |
currentTags.delete(tag); | |
} | |
// Delete unused existing folders. | |
for (const folder of existingFolders.values()) { | |
folder.setTrashed(true); | |
} | |
} | |
/** | |
* Returns the list of all the tags found in the given description. | |
*/ | |
function extractTags(/** @type string */ description) { | |
/** @type Set<string> */ | |
const results = new Set(); | |
for (const re = /\[\[(.+?)\]\]/g;;) { | |
const match = re.exec(description); | |
if (match === null) { | |
return results; | |
} | |
results.add(match[1]); | |
} | |
} | |
/** | |
* Returns `true` if the given `set` has the given `element`, else | |
* adds the element to the set and returns `false`. | |
* | |
* @template T | |
*/ | |
function hasOrAdd(/** @type Set<T> */ set, /** @type T */ element) { | |
if (set.has(element)) { | |
return true; | |
} | |
set.add(element); | |
return false; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment