Last active
October 3, 2022 18:22
-
-
Save RobTrew/a6ef7c551f0874f6f3374e3bfbeb0b42 to your computer and use it in GitHub Desktop.
Selected DEVONthink Sheet -> DEVONthink markdown record (pretty-printed MMD table)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
((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