Announce: primitive tool for migrating from Things to TaskPaper


#1

Hi,

Total TaskPaper n00b here.

I got sick of being locked into the Things ecosystem – the appeal of all text is quite high for me. So I decided to take the plunge.

The problem is, I’m a heavy Things user, and the switching cost is high.

So, I started working on a tool for converting a Things database into TaskPaper. It’s a long way, probably, from actually being useful to more than just me, but since I was wishing for this as I pondered this task, maybe somebody else will find it useful.

Feedback & contributors welcome. I won’t really be able to devote much time to this, and since mostly my Things db is converted, this is almost practically abandonware already, but it was fun to write and hopefully is useful to somebody.

cheers,
Eric


#2

Looks very good.

FWIW, if you are curious about side-stepping Scala and the JVM etc, you can call the SQLite3 functions directly from JavaScript for Automation (JXA) by writing:

ObjC.import('sqlite3');

and then things like (MAIN only here, full snippet below):

return main(() => {
    'use strict';

    ObjC.import('sqlite3');

    const
        strPath = '~/Library/Containers/com.culturedcode.ThingsMac/' +
        'Data/Library/Application\ Support/' +
        'Cultured\ Code/Things/Things.sqlite3',

        strSQL = 'SELECT title FROM TMTask';

    const
        SQLITE_OK = parseInt($.SQLITE_OK, 10),
        SQLITE_ROW = parseInt($.SQLITE_ROW, 10),
        ppDb = Ref();

    const rows = (
        bindEither(
            bindEither(
                $.sqlite3_open(filePath(strPath), ppDb) !== SQLITE_OK ? (
                    Left($.sqlite3_errmsg(ppDb[0]))
                ) : Right(ppDb[0]),

                db => {
                    const ppStmt = Ref();
                    return $.sqlite3_prepare_v2(
                        db, strSQL, -1, ppStmt, Ref()
                    ) !== SQLITE_OK ? (
                        Left($.sqlite3_errmsg(db))
                    ) : Right(Tuple(db, ppStmt[0]));
                }
            ),

            tpl => unfoldr(
                stmt => SQLITE_ROW === $.sqlite3_step(stmt) ? (
                    Just(Tuple(
                        $.sqlite3_column_text(stmt, 0),
                        stmt
                    ))
                ) : Nothing(),
                tpl[1]
            )
        )
    );

    return showJSON(rows);
    });
})();

Yielding, for example:

[
  "Add this to-do to your Today list",
  "Convert a to-do into a project",
  "Get Things for iPhone and iPad",
  "Plan your evening",
  "Create a new to-do",
  "Sync your devices",
  "Add to-dos from anywhere",
  "Please rate the app",
  "Tune your setup",
  "You’re done!",
  "Go step by step with checklists",
  "Tag your to-dos",
  "Before you go…",
  "Learn the basics",
  "Open multiple windows",
  "Enable the Today Widget",
  "Add a reminder so you won’t forget",
  "Create a project",
  "Make your to-dos repeat",
  "Stay in the loop",
  "Show your calendar events",
  "Search and navigate with Quick Find",
  "Hide the sidebar to focus on your work",
  "Meet Things Mac",
  "Plan this to-do for later",
  "Any questions? We’re here to help!",
  "Create a new heading",
  "Double-click this to-do",
  "Boost your productivity",
  "Learn some keyboard shortcuts",
  "Link to emails, files, and web pages",
  "Alpha Project",
  "Something for the Inbox"
]
[Finished in 0.093s]

Full standalone snippet for simple table reading from JXA:

(() => {
    'use strict';

    ObjC.import('sqlite3');

    // GENERIC FUNCTIONS -----------------------------------------------------

    // Just :: a -> Just a
    const Just = x => ({
        type: 'Maybe',
        Nothing: false,
        Just: x
    });

    // Left :: a -> Either a b
    const Left = x => ({
        type: 'Either',
        Left: x
    });

    // Nothing :: () -> Nothing
    const Nothing = () => ({
        type: 'Maybe',
        Nothing: true,
    });

    // Right :: b -> Either a b
    const Right = x => ({
        type: 'Either',
        Right: x
    });

    // Tuple (,) :: a -> b -> (a, b)
    const Tuple = (a, b) => ({
        type: 'Tuple',
        '0': a,
        '1': b
    });

    // bindEither (>>=) :: Either a -> (a -> Either b) -> Either b
    const bindEither = (m, mf) =>
        m.Right !== undefined ? (
            mf(m.Right)
        ) : m;

    // filePath :: String -> FilePath
    const filePath = s =>
        ObjC.unwrap(ObjC.wrap(s)
            .stringByStandardizingPath);

    // showJSON :: a -> String
    const showJSON = x => JSON.stringify(x, null, 2);

    // (x => Maybe [value, remainder] -> initial value -> values
    // unfoldr :: (b -> Maybe (a, b)) -> b -> [a]
    const unfoldr = (f, v) => {
        let xs = [];
        return (
            until(
                mb => mb.Nothing,
                mb => (
                    xs.push(mb.Just[0]),
                    f(mb.Just[1])
                ), Just(Tuple(v, v))
            ),
            xs.slice(1)
        );
    };

    // until :: (a -> Bool) -> (a -> a) -> a -> a
    const until = (p, f, x) => {
        let v = x;
        while (!p(v)) v = f(v);
        return v;
    };

    //------------------------------------------------------------------------
    // MAIN: SIMPLE EXAMPLE OF TABLE-READING FROM JAVASCRIPT FOR AUTOMATION

    const
        strPath = '~/Library/Containers/com.culturedcode.ThingsMac/' +
        'Data/Library/Application\ Support/' +
        'Cultured\ Code/Things/Things.sqlite3',

        strSQL = 'SELECT title FROM TMTask';

    const
        SQLITE_OK = parseInt($.SQLITE_OK, 10),
        SQLITE_ROW = parseInt($.SQLITE_ROW, 10),
        ppDb = Ref();

    const rows = (
        bindEither(
            bindEither(
                $.sqlite3_open(filePath(strPath), ppDb) !== SQLITE_OK ? (
                    Left($.sqlite3_errmsg(ppDb[0]))
                ) : Right(ppDb[0]),

                db => {
                    const ppStmt = Ref();
                    return $.sqlite3_prepare_v2(
                        db, strSQL, -1, ppStmt, Ref()
                    ) !== SQLITE_OK ? (
                        Left($.sqlite3_errmsg(db))
                    ) : Right(Tuple(db, ppStmt[0]));
                }
            ),

            tpl => unfoldr(
                stmt => SQLITE_ROW === $.sqlite3_step(stmt) ? (
                    Just(Tuple(
                        $.sqlite3_column_text(stmt, 0),
                        stmt
                    ))
                ) : (
                    $.sqlite3_finalize(stmt),
                    $.sqlite3_close(tpl[0]),
                    Nothing()
                ),
                tpl[1]
            )
        )
    );

    return showJSON(rows);
})();