Quick Script to Migrate from SimpleNotes

Moving hundreds or thousands of notes from SimpleNotes (to Apple Notes in my case)? This script aught to help clean up a few things for a better structure after importing.

  • Converts tags in *.txt files to #tag format for easier searching. Preserves original modified timestamp.
  • Updates the modified timestamps to a UTC offset (if specified)
  • Moves each note file into a subfolder based on its first tag (if specified)

This will only modify txt files, and should be run against an extracted export of notes from SimpleNote, so it should be fairly safe. But keep the archive and/or backups just in case. Use as follows:

chmod +x convert-tags.php
./convert-tags.php --path ./notes --timezone "-0500" --usefolders

Then import the notes folder through Notes.app.


#!/usr/bin/env php
<?
/**
 * This script helps migrate from SimpleNotes to Apple Notes 
 * (and potentially others) in a few ways:
 * 
 * 1) Converts tags in exported SimpleNotes *.txt files
 * to #tag format for easier searching. Preserves original modified timestamp.
 * 2) Updates the modified timestamps to a UTC offset (if specified)
 * 3) Moves each note file into a subfolder based on its first tag (if specified)
 * 
 * Arguments: (in this order)
 * --path - Location of the exported txt files
 * --timezone - an offset value (eg: -0500) from UTC to correct timestamps
 * --usefolders - moves notes into a subfolder based on their first tag
 */

$tags_pattern = '/Tags:\n\s\s(.+)$/';
$updated_files = 0;
$updated_timestamps = 0;
$moved_files = 0;
$errors = 0;
$skipped_files = 0;

$options = getopt(null, ['path:', 'timezone:', 'usefolders:']);

if (!isset($options['path']) || is_dir($options['path']) !== true) {
    throw new Exception('--path argument is missing or invalid.');
}

$base = trim($options['path'], '/') . '/';

if ( isset($options['timezone']) ) {
    if ( preg_match('/[-+]\d\d:?\d\d/', $options['timezone']) === 1 ) {
        // Calculate interval in seconds for the supplied timezone offset 
        $dtz_utc = new DateTimeZone('UTC');
        $dtz_offset = new DateTimeZone($options['timezone']);
        $dt_utc = new DateTime('now', $dtz_utc);
        $utc_offset_seconds = $dtz_offset->getOffset($dt_utc);

        $di_offset = DateInterval::createFromDateString( abs($utc_offset_seconds) . ' seconds');

        if ($utc_offset_seconds < 0) {
            $di_offset->invert = 1;
        }

        echo "Modified timestamps will be adjusted by {$utc_offset_seconds} seconds.\n";
    } else {
        throw new Exception('Timezone format should be -0500.');
    }
} else {
    echo "Not adjusting timezone of modified timestamps.\n";
    $di_offset = null;
}

$use_folders = in_array('--usefolders', $argv);

if ($use_folders) {
    echo "Moving notes into subfolders\n";
} else {
    echo "Leaving notes in $base\n";
}


if ($handle = opendir($base)) {
    echo "Updating *.txt in $base\n";
    echo "---\n";

    while (false !== ($entry = readdir($handle))):

        // Skip non-txt files
        if (preg_match('/^.*\.txt$/', $entry) !== 1) {
            echo "Skipping '$entry'\n";
            $skipped_files++;
            continue;
        }

        if ($contents = file_get_contents($base . $entry) ):
            $tags = [];

            // Find the tags string, replace with new format, and stash tags for later use
            $new_contents = preg_replace_callback($tags_pattern, function ($matches) use (&$tags) {
                $tags = explode(', ', $matches[1]);
                return '#' . implode(' #', $tags);
            }, $contents);

            $original_mod_time = filemtime($base.$entry);

            if ($contents !== $new_contents) {
                if (file_put_contents($base . $entry, $new_contents) !== false) {
                    echo "Updated '$entry'\n";
                    $updated_files++;
                }
            }
            
            // Resetting modified timestamp back to original 
            $date = new DateTime( '@'.$original_mod_time );
            
            // Correct modified timestamp's timezone
            if ($di_offset) {
                $date->add($di_offset);
            }
            
            // Set the timestamp
            if ( touch($base.$entry, $date->getTimestamp()) ) {
                $updated_timestamps++;
            }

            // If specified, move the note into a folder based on the first tag
            if ($use_folders && count($tags)) {
                $folder = $base . $tags[0] . '/';

                if (!is_dir($folder)) {
                    mkdir ($folder);
                }

                if ( rename($base.$entry, $folder.$entry) ) {
                    echo "Moved '$entry' to '" . $folder . $entry . "'\n";
                    $moved_files++;
                } else {
                    echo "*** Could not move '$entry' to '" . $folder . $entry . "'\n";
                    $errors++;
                }
            }
        endif;
    endwhile;

    closedir($handle);
}

echo "---\n";
echo "Skipped $skipped_files files/folders.\n";
echo "Updated $updated_files files.\n";
if ($use_folders) {
    echo "Moved $moved_files files.\n";
}
if ($di_offset) {
    echo "Modified and reset timestamps on $updated_timestamps files.\n";
} else {
    echo "Reset timestamps on $updated_timestamps files.\n";
}
if ($errors) {
    echo "** Encountered $errors errors.\n";
}

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.