An indicative sketch, no guarantees.
This rough draft (to be tested only with dummy data), simply aims to:
-
copy any items in a project named
Archive:
in the front document, to the top of a file named archive.taskpaper
in the same folder as the front document. (The archive file will be created if it doesn’t yet exist), and
- then remove those
Archive:
items from the front document.
Note that in the TaskPaper 3 main menu, the path:
TaskPaper > Tag > Archive @done items
automates the moving of any items with a @done
tag to a project called Archive:
which is created if it doesn’t yet exist.
Important: This draft script deletes items from the front TP3 document. Test this draft carefully on dummy data.
For the installation and use of scripts in TaskPaper 3, see:
[Using Scripts]
( Using Scripts · GitBook )
Expand disclosure triangle to view JS source
(() => {
"use strict";
// For testing only. Not considered ready for active use.
// Rob Trew @2020
// Rough draft 0.03
// Simply issues a message and stops if the name of the
// front document is that of the archive file.
// Rough Draft of a script for archiving the content of
// an Archive: project in the front TaskPaper document
// to the top or bottom of an archive.taskpaper file
// in the same folder.
// Permission is hereby granted, free of charge, to any
// person obtaining a copy of this software and
// associated documentation files (the "Software"),
// to deal in the Software without restriction,
// including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so,
// subject to the following conditions:
// The above copyright notice and this permission notice
// shall be included in all copies or substantial
// portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY
// OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
// BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// --------------------- OPTIONS ---------------------
const archiveFileName = "archive.taskpaper";
// Should archived items go to the top or bottom of
// the archive file ?
const blnArchiveAtTop = true;
// ---------------------- MAIN -----------------------
// main :: IO ()
const main = () => {
const
taskpaper = Application("Taskpaper"),
documents = taskpaper.documents;
return either(
msg => alert("Archiving TaskPaper items")(msg)
)(
x => x
)(
0 < documents.length ? (() => {
const
frontDocument = documents.at(0),
folderPath = takeDirectory(
`${frontDocument.file()}`
),
taskIdPairs = frontDocument.evaluate({
script: `${tp3ReadArchive}`
});
return toLower(archiveFileName) !== (
toLower(frontDocument.name())
) ? (
0 < taskIdPairs.length ? (
archivedAndConfirmedLR(
blnArchiveAtTop
)(
frontDocument
)(
combine(folderPath)(
archiveFileName
)
)(
taskIdPairs
)
) : Left(
"No archived tasks seen in front document."
)
) : Left([
"The front document appears to be the archive itself.",
"",
`${frontDocument.file()}`,
"",
"(Archiving *from* the archive *to* the ",
"archive itself is not a defined option ...)"
].join("\n"));
})() : Left("No document open in TaskPaper")
);
};
// archivedAndConfirmedLR :: Bool -> TP3 Document ->
// FilePath -> [(String, ID)] -> IO Either String String
const archivedAndConfirmedLR = blnTop =>
frontDocument => fpArchive => taskIdPairs => bindLR(
idsAddedToArchiveFileLR(
blnTop
)(
fpArchive
)(
taskIdPairs
)
)(
ids => bindLR(
frontDocument.evaluate({
script: `${tp3ClearArchive}`,
withOptions: {
archivedIDs: ids
}
})
)(
taskList => Right(
`Archived to:\n\t${fpArchive}\n\n${taskList}`
)
)
);
// idsAddedToArchiveFileLR :: Bool -> FilePath ->
// [(String, ID)] -> Either String [ID]
const idsAddedToArchiveFileLR = blnTopOfFile =>
fp => textsWithIDs => {
const
existingLines = either(() => "")(s => s)(
readFileLR(fp)
),
timeStamp = taskPaperDateString(new Date()),
linesToArchive = [
`archived: @at(${timeStamp})`
].concat(
textsWithIDs
.map(x => `\t${x[0]}`)
)
.join("\n"),
updatedContent = blnTopOfFile ? (
`${linesToArchive}\n\n${existingLines}`
) : `${existingLines}\n\n${linesToArchive}`;
return bindLR(
writeFileLR(fp)(updatedContent)
)(
() => bindLR(
readFileLR(fp)
)(
checked => updatedContent === checked ? (
Right(textsWithIDs.map(snd))
) : Left(
"Archive not updated as expected."
)
)
);
};
// ---------------- TASKPAPER CONTEXT ----------------
// tp3ReadArchive :: Editor -> IO String
const tp3ReadArchive = editor => {
const
archivedTasks = editor.outline
.evaluateItemPath("//Archive//*");
return archivedTasks.map(
task => [task.bodyString, task.id]
);
};
// tp3ClearArchive :: IO ()
const tp3ClearArchive = (editor, options) => {
const tp3ClearMain = () => {
const
outline = editor.outline,
archivedNodesToClear = options.archivedIDs
.flatMap(nodeID => {
const
node = outline.getItemForID(
nodeID
);
return Boolean(node) ? (
[node]
) : [];
}),
archivedText = archivedNodesToClear.map(
x => x.bodyString
).join("\n");
return Right((
outline.removeItems(
archivedNodesToClear
),
archivedText
));
};
// Left :: a -> Either a b
// const Left = x => ({
// type: "Either",
// Left: x
// });
// Right :: b -> Either a b
const Right = x => ({
type: "Either",
Right: x
});
return tp3ClearMain();
};
// ----------------------- JXA -----------------------
// alert :: String => String -> IO String
const alert = title =>
s => {
const sa = Object.assign(
Application("System Events"), {
includeStandardAdditions: true
});
return (
sa.activate(),
sa.displayDialog(s, {
withTitle: title,
buttons: ["OK"],
defaultButton: "OK"
}),
s
);
};
// --------------------- GENERIC ---------------------
// Left :: a -> Either a b
const Left = x => ({
type: "Either",
Left: x
});
// Right :: b -> Either a b
const Right = x => ({
type: "Either",
Right: x
});
// bindLR (>>=) :: Either a ->
// (a -> Either b) -> Either b
const bindLR = m =>
mf => m.Left ? (
m
) : mf(m.Right);
// either :: (a -> c) -> (b -> c) -> Either a b -> c
const either = fl =>
// Application of the function fl to the
// contents of any Left value in e, or
// the application of fr to its Right value.
fr => e => e.Left ? (
fl(e.Left)
) : fr(e.Right);
// combine (</>) :: FilePath -> FilePath -> FilePath
const combine = fp =>
// Two paths combined with a path separator.
// Just the second path if that starts with
// a path separator.
fp1 => Boolean(fp) && Boolean(fp1) ? (
"/" === fp1.slice(0, 1) ? (
fp1
) : "/" === fp.slice(-1) ? (
fp + fp1
) : `${fp}/${fp1}`
) : fp + fp1;
// iso8601Local :: Date -> String
const iso8601Local = dte =>
new Date(dte - (6E4 * dte.getTimezoneOffset()))
.toISOString();
// readFileLR :: FilePath -> Either String IO String
const readFileLR = fp => {
// Either a message or the contents of any
// text file at the given filepath.
const
e = $(),
ns = $.NSString
.stringWithContentsOfFileEncodingError(
$(fp).stringByStandardizingPath,
$.NSUTF8StringEncoding,
e
);
return ns.isNil() ? (
Left(ObjC.unwrap(e.localizedDescription))
) : Right(ObjC.unwrap(ns));
};
// taskPaperDateString :: Date -> String
const taskPaperDateString = dte => {
const [d, t] = iso8601Local(dte).split("T");
return [d, t.slice(0, 5)].join(" ");
};
// // showLog :: a -> IO ()
// const showLog = (...args) =>
// // eslint-disable-next-line no-console
// console.log(
// args
// .map(JSON.stringify)
// .join(" -> ")
// );
// snd :: (a, b) -> b
const snd = tpl =>
// Second member of a pair.
tpl[1];
// takeDirectory :: FilePath -> FilePath
const takeDirectory = fp =>
"" !== fp ? (
(xs => xs.length > 0 ? xs.join("/") : ".")(
fp.split("/").slice(0, -1)
)
) : ".";
// toLower :: String -> String
const toLower = s =>
// Lower-case version of string.
s.toLocaleLowerCase();
// writeFileLR :: FilePath -> Either String IO FilePath
const writeFileLR = fp =>
s => {
const
e = $(),
efp = $(fp)
.stringByStandardizingPath;
return $.NSString.alloc.initWithUTF8String(s)
.writeToFileAtomicallyEncodingError(
efp, false,
$.NSUTF8StringEncoding, e
) ? (
Right(ObjC.unwrap(efp))
) : Left(ObjC.unwrap(e.localizedDescription));
};
// ------------ MAIN FUNCTION CALLED HERE ------------
return main();
})();