Skip to content

Instantly share code, notes, and snippets.

@RobTrew
Last active October 3, 2022 18:22
Show Gist options
  • Save RobTrew/a6ef7c551f0874f6f3374e3bfbeb0b42 to your computer and use it in GitHub Desktop.
Save RobTrew/a6ef7c551f0874f6f3374e3bfbeb0b42 to your computer and use it in GitHub Desktop.
Selected DEVONthink Sheet -> DEVONthink markdown record (pretty-printed MMD table)
((options) => {
'use strict';
// (NB JavaScript for Automation – JXA: Save as .scpt )
// Selected DEVONthink Sheet -> DEVONthink markdown record (MMD table)
// (New record containing MMD table created is same group as selection)
// Uses original fileName stem, with '.md' appended.
// This is the *whitespace pretty-printed* version
// for the |\t tab version (as in MMD Composer 'Clean up selected Table')
// see:
// https://gist.github.com/RobTrew/3df0110db67282f200a3908cb0a4695e
// Rob Trew 2017
// Ver 0.05
// ALIGNMENTS adjustable in OPTIONS dictionary at foot of script:
// -1 -> left aligned :---
// 0 -> centered :---:
// 1 -> right aligned ----:
// Note that this copy is in ES6 JS and thus for Sierra onwards only.
// For a (Yosemite-onward) ES5 copy, paste into the Babel JS repl at
// https://babeljs.io/repl/
// GENERIC FUNCTIONS -----------------------------------------------------
// (++) :: [a] -> [a] -> [a]
const append = (xs, ys) => xs.concat(ys);
// Size of space -> filler Char -> Text -> Centered Text
// center :: Int -> Char -> Text -> Text
const center = (n, c, s) => {
const [q, r] = quotRem(n - s.length, 2);
return concat(concat([replicate(q, c), s, replicate(q + r, c)]));
};
// concat :: [[a]] -> [a] | [String] -> String
const concat = xs =>
xs.length > 0 ? (() => {
const unit = typeof xs[0] === 'string' ? '' : [];
return unit.concat.apply(unit, xs);
})() : [];
// curry :: Function -> Function
const curry = (f, ...args) => {
const go = xs => xs.length >= f.length ? (f.apply(null, xs)) :
function () {
return go(xs.concat(Array.from(arguments)));
};
return go([].slice.call(args, 1));
};
// drop :: Int -> [a] -> [a]
// drop :: Int -> String -> String
const drop = (n, xs) => xs.slice(n);
// filter :: (a -> Bool) -> [a] -> [a]
const filter = (f, xs) => xs.filter(f);
// findIndex :: (a -> Bool) -> [a] -> Maybe Int
const findIndex = (p, xs) =>
xs.reduce((a, x, i) =>
a.nothing ? (
p(x) ? {
just: i,
nothing: false
} : a
) : a, {
nothing: true
});
// intercalate :: String -> [a] -> String
const intercalate = curry((s, xs) => xs.join(s));
// justifyLeft :: Int -> Char -> Text -> Text
const justifyLeft = (n, cFiller, strText) =>
n > strText.length ? (
(strText + cFiller.repeat(n))
.substr(0, n)
) : strText;
// justifyRight :: Int -> Char -> Text -> Text
const justifyRight = (n, cFiller, strText) =>
n > strText.length ? (
(cFiller.repeat(n) + strText)
.slice(-n)
) : strText;
// length :: [a] -> Int
const length = xs => xs.length;
// lines :: String -> [String]
const lines = s => s.split(/[\r\n]/);
// map :: (a -> b) -> [a] -> [b]
const map = (f, xs) => xs.map(f);
// maximum :: [a] -> a
const maximum = xs =>
xs.reduce((a, x) => (x > a || a === undefined ? x : a), undefined);
// quotRem :: Integral a => a -> a -> (a, a)
const quotRem = (m, n) => [Math.floor(m / n), m % n];
// replicate :: Int -> a -> [a]
const replicate = (n, x) =>
Array.from({
length: n
}, () => x);
// show :: Int -> a -> Indented String
// show :: a -> String
const show = (...x) =>
JSON.stringify.apply(
null, x.length > 1 ? [x[1], null, x[0]] : x
);
// (-1 justifyLeft | 0 center | 1 justifyRight)
// justifyTableCells :: Enum (-1|0|1) -> String -> [[String]] -> [[String]]
const justifyTableCells = (eAlign, rows) =>
transpose(map(col =>
map(curry(eAlign < 0 ? (
justifyLeft
) : (eAlign > 0 ? (
justifyRight
) : center))
(maximum(map(length, col)))(' '), col),
transpose(rows)));
// splitOn :: a -> [a] -> [[a]]
// splitOn :: String -> String -> [String]
const splitOn = curry((needle, haystack) =>
typeof haystack === 'string' ? (
haystack.split(needle)
) : (function sp_(ndl, hay) {
const mbi = findIndex(x => ndl === x, hay);
return mbi.nothing ? (
[hay]
) : append(
[take(mbi.just, hay)],
sp_(ndl, drop(mbi.just + 1, hay))
);
})(needle, haystack));
// transpose :: [[a]] -> [[a]]
const transpose = xs =>
xs[0].map((_, col) => xs.map(row => row[col]));
// unconsMay :: [a] -> Maybe (a, [a])
const unconsMay = xs => xs.length > 0 ? {
just: [xs[0], xs.slice(1)],
nothing: false
} : {
nothing: true
};
// unlines :: [String] -> String
const unlines = xs => xs.join('\n');
// MMD TABLE -------------------------------------------------------------
// cellRowsFromTabbed :: LeftCenterRight (-1|0|1) -> String -> [[String]]
const cellRowsFromTabbed = (enumJustify, s) => {
const cellRows = map(
splitOn('\t'),
filter(x => length(x) > 0, lines(s))
);
return justifyTableCells(enumJustify, cellRows);
};
// mmdTableFromCells :: LeftCenterRight (-1|0|1) -> [[String]] -> String
const mmdTableFromCells = (eJustify, cellRows) => {
const mbHeadTail = unconsMay(cellRows);
return mbHeadTail.nothing ? (
''
) : (() => {
const
ht = mbHeadTail.just,
hs = ht[0];
return unlines(map(x => '|' + intercalate('|', x) + '|',
append([
hs,
map(h => (eJustify !== 1 ? ':' : '-') +
concat(replicate(length(h) - 2, '-')) +
(eJustify !== -1 ? ':' : '-'),
hs
)
],
ht[1]
)))
})();
};
// SELECTED DT SHEET -> NEW MD RECORD WITH SAME FILESTEM + '.md'
const
dt = Application('DEVONthink Pro'),
seln = dt.selection(),
mbRec = seln.length > 0 ? {
just: seln[0]
} : {
nothing: true
},
eAlign = options.justify;
const mbMD = mbRec.nothing ? (
mbRec
) : mbRec.just.type() === 'sheet' ? {
just: mmdTableFromCells(
eAlign, cellRowsFromTabbed(
eAlign,
mbRec.just.richText.text()
)
)
} : {
nothing: true,
msg: 'No DEVONthink Sheet selected'
};
return mbMD.nothing ? mbMD.msg : (
dt.createRecordWith({
name: mbRec.just.name() + '.md',
type: 'markdown',
content: mbMD.just
}, { in: dt.currentGroup()
}),
mbMD.just
);
})({ // Alignment option - edit as required,
// or set from another script
justify: 1 // (Left: -1, Center: 0, Right: 1)
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment