Last active
January 9, 2022 16:42
-
-
Save bkonia/b25b27c0c02b5533cf60f0da57c98ebb to your computer and use it in GitHub Desktop.
This file contains hidden or 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
<?php | |
define("PATH_HOME_SOURCE", "/path/to/your/top/level/source/directory"); // The top-level directory to import from. Use forward slashes. | |
define("PATH_HOME_NOTABLE", "/path/to/your/top/level/notable/directory"); // The path to your top-level Notable directory. Use forward slashes. | |
// Do not modify anything below this line, unless you know what you're doing! | |
$pathHomeSource = rtrim(PATH_HOME_SOURCE, "/"); | |
if (!is_dir($pathHomeSource)) { | |
echo "Source directory not found. Aborting!"; | |
exit(); | |
} | |
$pathHomeNotable = rtrim(PATH_HOME_NOTABLE, "/"); | |
if (!is_dir($pathHomeNotable)) { | |
if (!mkdir($pathHomeNotable)) { | |
echo "Unable to create Notable directory. Aborting!"; | |
exit(); | |
} | |
} | |
$pathNotes = "$pathHomeNotable/notes/"; | |
if (!is_dir($pathNotes)) | |
mkdir($pathNotes); | |
$pathAttachments = "$pathHomeNotable/attachments/"; | |
if (!is_dir($pathAttachments)) | |
mkdir($pathAttachments); | |
$paths = new RecursiveIteratorIterator( | |
new RecursiveDirectoryIterator($pathHomeSource, RecursiveDirectoryIterator::UNIX_PATHS), | |
RecursiveIteratorIterator::SELF_FIRST, | |
RecursiveIteratorIterator::CATCH_GET_CHILD | |
); // Creates an iterator containing a list of the full paths of all directories and files below the specified home path. | |
foreach ($paths as $dir) { // Iterate through each path | |
if ($dir == $pathHomeSource . "/.") | |
$dir = $pathHomeSource; // Process the files in the top-level directory. | |
if (is_dir($dir) && substr($dir, -3) != "/.." && substr($dir, -2) != "/.") { // Only process paths that are regular directories. Skip files and dot directories | |
echo "$dir\n"; | |
$tag = trim(str_replace($pathHomeSource, "", $dir), "/"); // Tag part of new directory path | |
$defaultTitle = $tag ? pathinfo($tag, PATHINFO_BASENAME) : "Home"; // Title of the default note for this directory (see below) | |
$linksContent = ""; | |
$attachments = array(); | |
$attachmentsContent = ""; | |
$files = scandir($dir); // Get all the files in this directory | |
$useParentTag = true; | |
foreach ($files as $filename) { | |
$filepath = "$dir/$filename"; | |
if (!is_dir($filepath)) { // We want all files in this directory, but no subdirectories | |
echo "$filepath\n"; | |
$title = pathinfo($filepath, PATHINFO_FILENAME); | |
$ext = pathinfo($filepath, PATHINFO_EXTENSION); | |
if ($ext == "url") { // Windows URL file | |
$html = file_get_contents($filepath); // Get the contents of the link file | |
if (preg_match('/(?<=URL=).*/i', $html, $regs)) { | |
$url = $regs[0]; // Extract the URL of the link | |
$linksContent .= "* [$title]($url)\n"; // Build the Markdown link | |
} | |
} elseif (($ext == "md" || $ext == "txt") && strcasecmp($title, $defaultTitle) != 0) { // This is a Markdown or text file, but its title doesn't match the directory name | |
$useParentTag = false; // Since there are other notes besides the default note, the default note will use this tag, NOT the parent tag | |
$content = file_get_contents($filepath); // Get the note contents | |
$filename = str_replace(".$ext", ".md", $filename); | |
file_put_contents(uniqueFilepath("$pathNotes/$filename"), processNote($title, $tag, $content)); // Add the metadata and copy the file to the new directory | |
} elseif ($ext != "md" && $ext != "txt" && $ext != "tmp" && $ext != "log") { // This is an attachment | |
$isAttachment = true; | |
if ($ext == "html" || $ext == "htm") { | |
$html = file_get_contents($filepath); // Get the contents of the link file | |
if (preg_match('/(?<=window\.location\.href=[\'"])[^\'"]*/i', $html, $regs)) { | |
$isAttachment = false; | |
$url = $regs[0]; // Extract the URL of the link | |
$linksContent .= "* [$title]($url)\n"; // Build the Markdown link | |
} | |
} | |
if ($isAttachment) { | |
$filepathNew = uniqueFilepath("$pathAttachments/$filename"); | |
$filename = pathinfo($filepathNew, PATHINFO_FILENAME) . ".$ext"; | |
$attachments[] = $filename; // Add it to the attachments array | |
$attachmentsContent .= "* [$title](@attachment/" . rawurlencode($filename) . ")\n"; | |
copy($filepath, $filepathNew); // Copy to the attachments directory | |
} | |
} | |
} | |
else { | |
if ($filename != "." && $filename != "..") | |
$useParentTag = false; // If this directory contains subdirectories, the tag must be created for the default note. | |
} | |
} | |
// This section creates the default note for the current tag. The default note has the same name as the | |
// tag's last segment and it stores any links and attachments to files found in the source directory. If a note with this | |
// name already exists, the links and attachments will be inserted at the top of the note. If there are no | |
// links or files, the default note will not be created. | |
$filename = "$defaultTitle.md"; | |
$content = $linksContent . $attachmentsContent; | |
if (in_arrayi($filename, $files)) // The note name matches the last path segment | |
$content .= "\n" . file_get_contents("$dir/$filename"); // Insert links and attachments into the existing note | |
if ($content) { // The default note has content | |
if ($useParentTag) // This is the only note in the directory | |
$tag = pathinfo($tag, PATHINFO_DIRNAME); // Move the default note into its parent tag | |
file_put_contents(uniqueFilepath("$pathNotes/$filename"), processNote($defaultTitle, $tag, $content, $attachments)); // Add the metadata and copy the file to the new directory | |
} | |
} | |
} | |
function processNote($title, $tag, $content, $attachments = array()) | |
{ | |
// Shift headings down by one level, so the note title will use the largest font size. | |
$content = preg_replace("/^### /", "#### ", $content); | |
$content = preg_replace("/^## /", "### ", $content); | |
$content = preg_replace("/^# /", "## ", $content); | |
$tag = ($tag == "." ? "" : $tag); // Top-level files have no tags | |
$tags = ($tag ? "tags: [$tag]\n" : ""); | |
if (count($attachments)) | |
$attachments = "attachments: [" . implode(", ", $attachments) . "]\n"; | |
else | |
$attachments = ""; | |
$date = date("Y-m-d") . "T" . date("H:i:s") . ".000Z"; | |
return "---\ntitle: $title\n$attachments" . $tags . "created: '$date'\nmodified: '$date'\n---\n\n# $title\n\n$content"; | |
} | |
function in_arrayi($needle, $haystack) | |
{ // Case insensitive in_array | |
return in_array(strtolower($needle), array_map('strtolower', $haystack)); | |
} | |
function uniqueFilepath($filepath) | |
{ | |
$dir = pathinfo($filepath, PATHINFO_DIRNAME); | |
$name = pathinfo($filepath, PATHINFO_FILENAME); | |
$ext = pathinfo($filepath, PATHINFO_EXTENSION); | |
if (file_exists($filepath)) { | |
$duplicateCounter = 1; | |
while (file_exists("$dir/$name ($duplicateCounter).$ext")) | |
$duplicateCounter++; | |
return "$dir/$name ($duplicateCounter).$ext"; | |
} | |
return $filepath; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Notable Importer
PHP script that imports entire directory structures into the Notable app.
Why Notable?
Notable is an amazing Markdown-based cross-platform notes organizer It sports a beautiful UI and supports nested tag structures. This means notes can be organized into a traditional hierarchical structure, instead of the flat tags list provided by most other applications. Since a note can have multiple tags, it can appear at multiple locations in the tag hierarchy.
Another unique benefit is that Notable doesn't use a database. Instead, each note contains a hidden metadata section, which stores the title, tags list, attachments and more. Since there's no proprietary data format, it's extremely easy to move notes in and out of Notable and to sync notes with other devices using any cloud sync service.
Importing into Notable
The only problem is, Notable's built-in import feature is rather rudimentary at this point. You can import a list of notes, but Notable doesn't (yet) support importing folder hierarchies. This means all your imported notes will share the same tag, so you'd have to manually organize them after importing. This makes it difficult to migrate from other applications that support exporting to a folder structure. I'm sure Notable will eventually support a more advanced import function, but I had thousands of notes and files in a folder structure and wanted to import them to Notable without losing my structure. My folder structure also contained various non-Markdown files that I wanted to link to a note in each folder. Therefore, I created this importer script in PHP. I intended the script to be for one-time use to import my files, so it's quick and dirty, not the greatest in terms of coding standards, etc... But it does work well and it saved me literally months of work, compared with having to organize thousands of files manually.
Instructions
The script is intended to be run from the command line using the PHP CLI interpreter. Since it's PHP, it will work the same on Windows, Mac and Linux. Obviously, you'll need to install PHP on your computer first.
To use the script, you have to specify the top-level source directory for your import and the location of your top-level Notable directory, which should contain a notes subdirectory and an attachments subdirectory. It will import everything in your source directory and all subdirectories.
The script identifies file types by their extensions. Therefore, if your files don't have extensions, it won't work.
It goes without saying that you should BACKUP YOUR NOTABLE DIRECTORY before performing the real import. Although the script works perfectly for me, I take no responsibility for any data loss it may cause.
When you run the script, it will start with the specified top-level directory and navigate through the entire sub-directory structure.
In each directory, it will process files with
md
ortxt
extensions as Markdown. It will tag these notes according to their location in the folder hierarchy, then copy them into the Notable notes directory. If there are any duplicate note titles, it will rename the duplicate files incrementally, while retaining the original title.The script will automatically create a default note for each folder containing non-Markdown files, using the last part of the tag path as the note title. For example, if the tag is
Software/iOS/Utilities
the default note title would be Utilities. If a note with this title already exists in the folder, it will use that as the default note, instead of creating a new default note.The purpose of the default note is to store links to any URLs and other files contained in the folder. The script automatically parses Windows URL files and adds them as links to the default note. If you have link files store in other formats like webloc on Mac, you can modify the regex to extract the URL based on whatever format you're using.