which I’ll share in due course.
Here is a new draft which returns an array of JS key-value dictionaries, one dictionary for each linkType. It:
- allows us to specify any subset of the available
linkType
attributes,
- and alerts the user in the case where a document has not yet been saved to a .tbx file.
Full set of attributes of a linkType:
[
'name', 'label', 'visible', 'showLabel',
'required', 'color', 'colorString',
'style', 'arrowType', 'onLink'
]
Sample output of main function – array of JS dictionaries
[
{
"name": "*untitled",
"label": "vanilla",
"visible": "1",
"showLabel": "0",
"required": "1",
"color": "#000000",
"colorString": "#000000",
"style": "0",
"arrowType": "0",
"onLink": ""
},
{
"name": "clarify",
"label": "ie",
"visible": "1",
"showLabel": "1",
"required": "0",
"color": "#c0c0c0",
"colorString": "#c0c0c0",
"style": "0",
"arrowType": "0",
"onLink": ""
},
{
"name": "narrate",
"label": "then",
"visible": "1",
"showLabel": "1",
"required": "0",
"color": "#0000c0",
"colorString": "#0000c0",
"style": "0",
"arrowType": "0",
"onLink": ""
},
{
"name": "persuade",
"label": "cos",
"visible": "1",
"showLabel": "1",
"required": "0",
"color": "#ff0000",
"colorString": "#ff0000",
"style": "0",
"arrowType": "0",
"onLink": ""
}
]
JavaScript source
(() => {
'use strict';
// Listing the link types available for the front document
// in Tinderbox 8.
// This version returns a JS dictionary for each linkType.
// Any subset of the available linkType attributes can be specified.
// Rob Trew 2019
// Ver 0.04
// Added dialog for displaying a result derived
// from a list of key-value dictionaries.
const main = () => {
// 'name' is always read and doesn't need to
// be specified, tho it can be.
const additionalAttributesToRead = [
'label', 'color', 'colorString'
];
const
tbx = Application('Tinderbox 8'),
ds = tbx.documents;
return either(
alert('Problem reading link types')
)(linkTypes => {
const
cols = ['name'].concat(
additionalAttributesToRead
),
strTable = cols.join(' -> ') + '\n\n' +
unlines(linkTypes.map(
dct => cols
.map(k => dct[k])
.join(' -> ')
));
return alert('Link types in ' + ds.at(0).name())(
strTable
);
})(bindLR(
0 < ds.length ? (
Right(ds.at(0))
) : Left('No documents open in Tinderbox 8.')
)(doc => {
const docFile = doc.file;
return bindLR(
docFile.exists() ? (
Right(docFile().toString())
) : Left(
'Document "' + doc.name() +
'" not yet saved to file.'
)
)(tbxLinkTypesFromFilePathLR(
additionalAttributesToRead
))
}));
};
// tbxLinkTypesFromFilePathLR :: [String] -> FilePath ->
// Either String [Dict]
const tbxLinkTypesFromFilePathLR = attributeNames => fp => {
// The @name attribute is always read. Additional
// attribute names can include any/all of those in
// knownAttribs below:
const knownAttribs = [
'name', 'label', 'visible', 'showLabel',
'required', 'color', 'colorString',
'style', 'arrowType', 'onLink'
];
const
mbUnknown = find(k => !knownAttribs.includes(k))(
attributeNames
);
return bindLR(
mbUnknown.Nothing ? (
Right(attributeNames)
) : Left(
'Unrecognized attribute name: "' +
mbUnknown.Just + '"'
)
)(ks =>
bindLR(
doesFileExist(fp) ? (
Right(fp)
) : Left('File not found: ' + fp)
)(fp => {
const
uw = ObjC.unwrap,
eXML = $(),
docXML = $.NSXMLDocument.alloc
.initWithXMLStringOptionsError(
readFile(fp),
0, eXML
);
return bindLR(
docXML.isNil() ? (
Left(
uw(eXML.localizedDescription) +
'Not recognized as Tinderbox 8 XML:\n ' +
fp
)
) : Right(docXML)
)(doc => {
const attribQuery = k =>
`'${k}', local:if-empty($x/@${k}, "")`;
const
eXQ = $(),
xq = `
declare function local:if-empty
($k as item()?,
$v as item()*) as item()* {
if (string($k) != '')
then data($k)
else $v
} ;
let $tab := "	" (: tab :)
for $x in //linkType
return string-join(
('name', $x/@name${
0 < ks.length ? (
', ' + ks.map(attribQuery)
.join(', ')
) : ''
}), $tab
)`;
const xs = doc.objectsForXQueryError(xq, eXQ);
return xs.isNil() ? (
Left(uw(eXQ.localizedDescription))
) : Right(
uw(xs).map(
node => chunksOf(2)(
uw(node).split('\t')
).reduce(
(a, [k, v]) =>
Object.assign(a, {
[k]: v
}), {}
)
)
);
})
})
);
};
// 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 FUNCTIONS ----------------------------------
// https://github.com/RobTrew/prelude-jxa
// Just :: a -> Maybe a
const Just = x => ({
type: 'Maybe',
Nothing: false,
Just: x
});
// Left :: a -> Either a b
const Left = x => ({
type: 'Either',
Left: x
});
// Nothing :: Maybe a
const Nothing = () => ({
type: 'Maybe',
Nothing: true,
});
// Right :: b -> Either a b
const Right = x => ({
type: 'Either',
Right: x
});
// bindLR (>>=) :: Either a -> (a -> Either b) -> Either b
const bindLR = m => mf =>
undefined !== m.Left ? (
m
) : mf(m.Right);
// chunksOf :: Int -> [a] -> [[a]]
const chunksOf = n => xs =>
enumFromThenTo(0)(n)(
xs.length - 1
).reduce(
(a, i) => a.concat([xs.slice(i, (n + i))]),
[]
);
// doesFileExist :: FilePath -> IO Bool
const doesFileExist = strPath => {
const ref = Ref();
return $.NSFileManager.defaultManager
.fileExistsAtPathIsDirectory(
$(strPath)
.stringByStandardizingPath, ref
) && 1 !== ref[0];
};
// either :: (a -> c) -> (b -> c) -> Either a b -> c
const either = fl => fr => e =>
'Either' === e.type ? (
undefined !== e.Left ? (
fl(e.Left)
) : fr(e.Right)
) : undefined;
// enumFromThenTo :: Int -> Int -> Int -> [Int]
const enumFromThenTo = x1 => x2 => y => {
const d = x2 - x1;
return Array.from({
length: Math.floor(y - x2) / d + 2
}, (_, i) => x1 + (d * i));
};
// find :: (a -> Bool) -> [a] -> Maybe a
const find = p => xs => {
const i = xs.findIndex(p);
return -1 !== i ? (
Just(xs[i])
) : Nothing();
};
// readFile :: FilePath -> IO String
const readFile = fp => {
const
e = $(),
ns = $.NSString.stringWithContentsOfFileEncodingError(
$(fp).stringByStandardizingPath,
$.NSUTF8StringEncoding,
e
);
return ObjC.unwrap(
ns.isNil() ? (
e.localizedDescription
) : ns
);
};
// unlines :: [String] -> String
const unlines = xs => xs.join('\n');
// MAIN ---
return main();
})();