FWIW a variant which imports and tags several man pages at once (and reports on which, if any, could not be found under the given name).
(JavaScript for Automation, so JavaScript
in the top left language selector of Script Editor)
JS source behind the disclosure triangle below. Copy all the way down to the end, which looks like:
// MAIN
return main();
})();
JavaScript Source :: import and tag man pages (as PDF)
(() => {
'use strict';
// Houthakker @2020
// @complexpoint
// A variant of 'import and tag man pages', which allows
// for several pages to be named and imported
// to DEVONthink 3 at once.
// Ver 0.1
// main :: IO ()
const main = () => {
// tag(s) to assign to imported man pages.
const tagList = ['manpage'];
return either(
alert('man pages not all found')
)(
alert('man page(s) imported')
)(
bindLR(
bashNamesFromDialogLR(
Application('DEVONthink 3')
)
)(
pdfsImportedToAppLR(tagList)
)
);
};
// ------------------- DEVONTHINK --------------------
// bashNamesFromDialogLR :: App
// -> IO Either String (App, String)
const bashNamesFromDialogLR = dt3 => {
try {
return (
dt3.activate(),
Right([
dt3,
dt3.displayNameEditor('Man pages', {
info: 'Names of man pages to import:'
})
])
);
} catch (e) {
return Left(head(lines(e.message)));
};
};
// importedAndTagged :: App -> (String, FilePath) ->
// [String] -> IO String
const importedAndTagged = app =>
namePaths => tags => namePaths.map(
namePath => {
const [page, fp] = Array.from(namePath);
return (
// In DEVONthink 3,
app.stepProgressIndicator(
`Importing ${page}.pdf ...`
),
app.import(fp, {
to: app.currentGroup
}).tags = tags,
app.stepProgressIndicator(
`${page}.pdf imported.`
),
// and in JS.
page
);
}
);
// pdfPageCreatedLR :: App -> String ->
// FilePath -> IO Either String String
const pdfPageCreatedLR = app =>
page => {
const fp = tempFilePath(`${page}.pdf`);
try {
return (
// In the shell,
Object.assign(
Application.currentApplication(), {
includeStandardAdditions: true
}).doShellScript(
`man -t '${page}' | ` + (
`pstopdf -i -o ${fp}`
)
),
doesFileExist(fp) ? (
// in DEVONthink,
app.stepProgressIndicator(
`PDF created :: ${page}`
),
// and in JS.
Right(Tuple(page)(fp))
) : (
// In DEVONthink,
app.stepProgressIndicator(
`No PDF :: ${page}`
),
// and in JS.
Left(Tuple('PDF was not created.')(page))
)
);
} catch (e) {
return Left(
Tuple(page)(
head(lines(e.message))
)
);
}
};
// pdfsForCommandNamesLR :: App ->
// String -> IO Either String [FilePath]
const pdfsForCommandNamesLR = app =>
pageNames => {
const pages = pageNames.split(/[\W]+/);
return (
// in Application,
app.showProgressIndicator(
`Converting ${pages.length} man page(s) to PDF ...`, {
steps: -1
}
),
// and in JS.
pages.map(pdfPageCreatedLR(app))
);
};
// pdfsImportedToAppLR :: [String] ->
// (App, String) -> IO Either String String
const pdfsImportedToAppLR = tagList =>
([app, pageNames]) => {
const [missing, found] = Array.from(
partitionEithers(
pdfsForCommandNamesLR(app)(pageNames)
)
);
const
imported = importedAndTagged(app)(
found
)(tagList);
return (
app.hideProgressIndicator(),
missing.length ? Left(
'Not found:\n\n' + bulleted(' ')(
missing.map(
x => `'${fst(x)}'`
).join('\n')
) + (
0 < imported.length ? (
'\n\nimported and tagged :: ' + (
imported.join(' ')
)
) : ''
)
) : Right(
'Imported:\n\n' + (
bulleted('')(
found.map(fst).join('\n')
)
)
)
);
};
// ----------------------- 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',
withIcon: sa.pathToResource(
'DEVONthink 3.icns', {
inBundle: 'Applications/DEVONthink 3.app'
})
}),
s
);
};
// --------------------- GENERIC ---------------------
// https: //github.com/RobTrew/prelude-jxa
// Left :: a -> Either a b
const Left = x => ({
type: 'Either',
Left: x
});
// 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,
length: 2
});
// bindLR (>>=) :: Either a ->
// (a -> Either b) -> Either b
const bindLR = m =>
mf => undefined !== m.Left ? (
m
) : mf(m.Right);
// bulleted :: String -> String -> String
const bulleted = strTab =>
s => s.split(/[\r\n]/).map(
x => '' !== x ? strTab + '- ' + x : x
).join('\n');
// doesFileExist :: FilePath -> IO Bool
const doesFileExist = fp => {
const ref = Ref();
return $.NSFileManager.defaultManager
.fileExistsAtPathIsDirectory(
$(fp)
.stringByStandardizingPath, ref
) && 1 !== ref[0];
};
// 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 => 'Either' === e.type ? (
undefined !== e.Left ? (
fl(e.Left)
) : fr(e.Right)
) : undefined;
// fst :: (a, b) -> a
const fst = tpl =>
// First member of a pair.
tpl[0];
// head :: [a] -> a
const head = xs =>
xs.length ? (
xs[0]
) : undefined;
// last :: [a] -> a
const last = xs =>
// The last item of a list.
0 < xs.length ? (
xs.slice(-1)[0]
) : undefined;
// lines :: String -> [String]
const lines = s =>
// A list of strings derived from a single
// string delimited by newline and or CR.
0 < s.length ? (
s.split(/[\r\n]+/)
) : [];
// partitionEithers :: [Either a b] -> ([a],[b])
const partitionEithers = xs =>
xs.reduce(
(a, x) => undefined !== x.Left ? (
Tuple(a[0].concat(x.Left))(a[1])
) : Tuple(a[0])(a[1].concat(x.Right)),
Tuple([])([])
);
// tempFilePath :: String -> IO FilePath
const tempFilePath = fileName =>
// File name template for temporary path.
ObjC.unwrap($.NSTemporaryDirectory()) + fileName;
// zip :: [a] -> [b] -> [(a, b)]
const zip = xs =>
// The paired members of xs and ys, up to
// the length of the shorter of the two lists.
ys => Array.from({
length: Math.min(xs.length, ys.length)
}, (_, i) => [xs[i], ys[i]]);
// MAIN
return main();
})();