Skip to main content
Trevor:
Legend
April 16, 2018
Question

JSX.js

  • April 16, 2018
  • 1 reply
  • 1712 views

I've published version 2 of JSX.js

See http://creative-scripts.com/jsx-js/ for details

It's basically an alternative to using csInterface.evalScript with in my opinion some big benefits.

Has 2 methods jsx.evalScript and jsx.evalFile

The update has meaningful error messages so instead of receiving the standard csInterface.evalScript "Eval Error" message you'll get to something like:

ReferenceError 2: bug is undefined

File: /Users/Trevor/repositories/CSTK/com.creative-scripts.cstk/jsx/1.jsx

Line: 2-> bug+2

or

SyntaxError 25: Expected: ;

File: /Users/Trevor/repositories/CSTK/com.creative-scripts.cstk/jsx/1.jsx

Line: 2-> 1 + 2bug + 3

Bug: 2bug

This topic has been closed for replies.

1 reply

Trevor:
Trevor:Author
Legend
April 16, 2018

/*

        _ ______  __  _

       | / ___\ \/ /  (_)___

    _  | \___ \\  /  | / __|

   | |_| |___) /  \ _ | \__ \

    \___/|____/_/\_(_)/ |___/

                    |__/

                       _              ____

    /\  /\___ _ __ ___(_) ___  _ __  |___ \

    \ \ / / _ \ '__/ __| |/ _ \| '_ \    __) |

     \ V /  __/ |  \__ \ | (_) | | | |  / __/

      \_/ \___|_|  |___/_|\___/|_| |_| |_____|

*/

//////////////////////////////////////////////////////////////////////////////////

// JSX.js © and writtent by Trevor http://creative-scripts.com/jsx-js          //

// If you turn over is less the $50,000,000 then you don't have to pay anything //

// License MIT, don't complain, don't sue NO MATTER WHAT                        //

// If you turn over is more the $50,000,000 then you DO have to pay            //

// Contact me http://creative-scripts.com/contact for pricing and licensing    //

// Don't remove these commented lines                                          //

// For simple and effective calling of jsx from the js engine                  //

// Version 2 last modified April 18 2018                                        //

//////////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////////////////////////////////

// Change log:                                                                                          //

// JSX.js V2 is now independent of NodeJS and CSInterface.js :-)                                        //

// forceEval is now by default true                                                                      //

// It wraps the scripts in a try catch and an eval providing useful error handling                      //

// One can set in the jsx engine $.includeStack = true to return the call stack in the event of an error //

///////////////////////////////////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////////////////////////////////

// JSX.js for calling jsx code from the js engine                                                        //

// 2 methods included                                                                                    //

// 1) jsx.evalScript AKA jsx.eval                                                                        //

// 2) jsx.evalFile AKA jsx.file                                                                          //

// Special features                                                                                      //

// 1) Allows all changes in your jsx code to be reloaded into your extension at the click of a button    //

// 2) Can enable the $.fileName property to work and provides a $.__fileName() method as an alternative  //

// 3) Can force a callBack result from InDesign                                                          //

// 4) No more csInterface.evalScript('alert("hello "' + title + " " + name + '");')                      //

//    use jsx.evalScript('alert("hello __title__ __name__");', {title: title, name: name});              //

// 5) execute jsx files from your jsx folder like this jsx.evalFile('myFabJsxScript.jsx');              //

//    or from a relative path jsx.evalFile('../myFabScripts/myFabJsxScript.jsx');                        //

//    or from an absolute url jsx.evalFile('/Path/to/my/FabJsxScript.jsx'); (mac)                        //

//    or from an absolute url jsx.evalFile('C:Path/to/my/FabJsxScript.jsx'); (windows)                  //

// 6) Parameter can be entered in the from of a parameter list which can be in any order or as an object //

// 7) Not camelCase sensitive (very useful for the illiterate)                                          //

// 8) Dead easy to use BUT SPEND THE 3 TO 5 MINUTES IT SHOULD TAKE TO READ THE INSTRUCTIONS              //

///////////////////////////////////////////////////////////////////////////////////////////////////////////

/* jshint undef:true, unused:true, esversion:6 */

//////////////////////////////////////

// jsx is the interface for the API //

//////////////////////////////////////

var jsx;

// Wrap everything in an anonymous function to prevent leeks

(function() {

    /////////////////////////////////////////////////////////////////////

    // Substitute some CSInterface functions to avoid dependency on it //

    /////////////////////////////////////////////////////////////////////

    var __dirname = (function() {

        var path, isMac;

        path = decodeURI(window.__adobe_cep__.getSystemPath('extension'));

        isMac = navigator.platform[0] === 'M'; // ac

        path = path.replace('file://' + (isMac ? '' : '/'), '');

        return path;

    })();

    var evalScript = function(script, callback) {

        callback = callback || function() {};

        window.__adobe_cep__.evalScript(script, callback);

    };

    ////////////////////////////////////////////

    // In place of using the node path module //

    ////////////////////////////////////////////

    // jshint undef: true, unused: true

    // A very minified version of the NodeJs Path module!!

    // For use outside of NodeJs

    // Majorly nicked by Trevor from Joyent

    var path = (function() {

        var isString = function(arg) {

            return typeof arg === 'string';

        };

        // var isObject = function(arg) {

        //    return typeof arg === 'object' && arg !== null;

        // };

        var basename = function(path) {

            if (!isString(path)) {

                throw new TypeError('Argument to path.basename must be a string');

            }

            var bits = path.split(/[\/\\]/g);

            return bits[bits.length - 1];

        };

        // jshint undef: true

        // Regex to split a windows path into three parts: [*, device, slash,

        // tail] windows-only

        var splitDeviceRe =

            /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?([\\\/])?([\s\S]*?)$/;

        // Regex to split the tail part of the above into [*, dir, basename, ext]

        // var splitTailRe =

        //    /^([\s\S]*?)((?:\.{1,2}|[^\\\/]+?|)(\.[^.\/\\]*|))(?:[\\\/]*)$/;

        var win32 = {};

        // Function to split a filename into [root, dir, basename, ext]

        // var win32SplitPath = function(filename) {

        //    // Separate device+slash from tail

        //    var result = splitDeviceRe.exec(filename),

        //        device = (result[1] || '') + (result[2] || ''),

        //        tail = result[3] || '';

        //    // Split the tail into dir, basename and extension

        //    var result2 = splitTailRe.exec(tail),

        //        dir = result2[1],

        //        basename = result2[2],

        //        ext = result2[3];

        //    return [device, dir, basename, ext];

        // };

        var win32StatPath = function(path) {

            var result = splitDeviceRe.exec(path),

                device = result[1] || '',

                isUnc = !!device && device[1] !== ':';

            return {

                device: device,

                isUnc: isUnc,

                isAbsolute: isUnc || !!result[2], // UNC paths are always absolute

                tail: result[3]

            };

        };

        var normalizeUNCRoot = function(device) {

            return '\\\\' + device.replace(/^[\\\/]+/, '').replace(/[\\\/]+/g, '\\');

        };

        var normalizeArray = function(parts, allowAboveRoot) {

            var res = [];

            for (var i = 0; i < parts.length; i++) {

                var p = parts;

                // ignore empty parts

                if (!p || p === '.')

                    continue;

                if (p === '..') {

                    if (res.length && res[res.length - 1] !== '..') {

                        res.pop();

                    } else if (allowAboveRoot) {

                        res.push('..');

                    }

                } else {

                    res.push(p);

                }

            }

            return res;

        };

        win32.normalize = function(path) {

            var result = win32StatPath(path),

                device = result.device,

                isUnc = result.isUnc,

                isAbsolute = result.isAbsolute,

                tail = result.tail,

                trailingSlash = /[\\\/]$/.test(tail);

            // Normalize the tail path

            tail = normalizeArray(tail.split(/[\\\/]+/), !isAbsolute).join('\\');

            if (!tail && !isAbsolute) {

                tail = '.';

            }

            if (tail && trailingSlash) {

                tail += '\\';

            }

            // Convert slashes to backslashes when `device` points to an UNC root.

            // Also squash multiple slashes into a single one where appropriate.

            if (isUnc) {

                device = normalizeUNCRoot(device);

            }

            return device + (isAbsolute ? '\\' : '') + tail;

        };

        win32.join = function() {

            var paths = [];

            for (var i = 0; i < arguments.length; i++) {

                var arg = arguments;

                if (!isString(arg)) {

                    throw new TypeError('Arguments to path.join must be strings');

                }

                if (arg) {

                    paths.push(arg);

                }

            }

            var joined = paths.join('\\');

            // Make sure that the joined path doesn't start with two slashes, because

            // normalize() will mistake it for an UNC path then.

            //

            // This step is skipped when it is very clear that the user actually

            // intended to point at an UNC path. This is assumed when the first

            // non-empty string arguments starts with exactly two slashes followed by

            // at least one more non-slash character.

            //

            // Note that for normalize() to treat a path as an UNC path it needs to

            // have at least 2 components, so we don't filter for that here.

            // This means that the user can use join to construct UNC paths from

            // a server name and a share name; for example:

            //  path.join('//server', 'share') -> '\\\\server\\share\')

            if (!/^[\\\/]{2}[^\\\/]/.test(paths[0])) {

                joined = joined.replace(/^[\\\/]{2,}/, '\\');

            }

            return win32.normalize(joined);

        };

        var posix = {};

        // posix version

        posix.join = function() {

            var path = '';

            for (var i = 0; i < arguments.length; i++) {

                var segment = arguments;

                if (!isString(segment)) {

                    throw new TypeError('Arguments to path.join must be strings');

                }

                if (segment) {

                    if (!path) {

                        path += segment;

                    } else {

                        path += '/' + segment;

                    }

                }

            }

            return posix.normalize(path);

        };

        // path.normalize(path)

        // posix version

        posix.normalize = function(path) {

            var isAbsolute = path.charAt(0) === '/',

                trailingSlash = path && path[path.length - 1] === '/';

            // Normalize the path

            path = normalizeArray(path.split('/'), !isAbsolute).join('/');

            if (!path && !isAbsolute) {

                path = '.';

            }

            if (path && trailingSlash) {

                path += '/';

            }

            return (isAbsolute ? '/' : '') + path;

        };

        win32.basename = posix.basename = basename;

        this.win32 = win32;

        this.posix = posix;

        return (navigator.platform[0] === 'M') ? posix : win32;

    })();

    ////////////////////////////////////////////////////////////////////////////////////////////////////////

    // The is the  "main" function which is to be prototyped                                              //

    // It run a small snippet in the jsx engine that                                                      //

    // 1) Assigns $.__dirname with the value of the extensions __dirname base path                        //

    // 2) Sets up a method $.__fileName() for retrieving from within the jsx script it's $.fileName value //

    //    more on that method later                                                                      //

    // At the end of the script the global declaration jsx = new Jsx(); has been made.                    //

    // If you like you can remove that and include in your relevant functions                            //

    // var jsx = new Jsx(); You would never call the Jsx function without the "new" declaration          //

    ////////////////////////////////////////////////////////////////////////////////////////////////////////

    var Jsx = function() {

        var jsxScript;

        // Setup jsx function to enable the jsx scripts to easily retrieve their file location

        jsxScript = [

            '$.level = 0;',

            'if(!$.__fileNames){',

            '    $.__fileNames = {};',

            '    $.__dirname = "__dirname__";'.replace('__dirname__', __dirname),

            '    $.__fileName = function(name){',

            '        name = name || $.fileName;',

            '        return ($.__fileNames && $.__fileNames[name]) || $.fileName;',

            '    };',

            '}'

        ].join('');

        evalScript(jsxScript);

        return this;

    };

    /**

    * [evalScript] For calling jsx scripts from the js engine

    *

    *        The jsx.evalScript method is used for calling jsx scripts directly from the js engine

    *        Allows for easy replacement i.e. variable insertions and for forcing eval.

    *        For convenience jsx.eval or jsx.script or jsx.evalscript can be used instead of calling jsx.evalScript

    *

    * @param  {String} jsxScript

    *                            The string that makes up the jsx script

    *                            it can contain a simple template like syntax for replacements

    *                            'alert("__foo__");'

    *                            the __foo__ will be replaced as per the replacements parameter

    *

    * @param  {Function} callback

    *                            The callback function you want the jsx script to trigger on completion

    *                            The result of the jsx script is passed as the argument to that function

    *                            The function can exist in some other file.

    *                            Note that InDesign does not automatically pass the callBack as a string.

    *                            Either write your InDesign in a way that it returns a sting the form of

    *                            return 'this is my result surrounded by quotes'

    *                            or use the force eval option

    *                            [Optional DEFAULT no callBack]

    *

    * @param  {Object} replacements

    *                            The replacements to make on the jsx script

    *                            given the following script (template)

    *                            'alert("__message__: " + __val__);'

    *                            and we want to change the script to

    *                            'alert("I was born in the year: " + 1234);'

    *                            we would pass the following object

    *                            {"message": 'I was born in the year', "val": 1234}

    *                            or if not using reserved words like do we can leave out the key quotes

    *                            {message: 'I was born in the year', val: 1234}

    *                            [Optional DEFAULT no replacements]

    *

    * @param  {Bolean} forceEval

    *                            If the script should be wrapped in an eval and try catch

    *                            This will 1) provide useful error feedback if heaven forbid it is needed

    *                            2) The result will be a string which is required for callback results in InDesign

    *                            [Optional DEFAULT true]

    *

    * Note 1) The order of the parameters is irrelevant

    * Note 2) One can pass the arguments as an object if desired

    *        jsx.evalScript(myCallBackFunction, 'alert("__myMessage__");', true);

    *        is the same as

    *        jsx.evalScript({

    *            script: 'alert("__myMessage__");',

    *            replacements: {myMessage: 'Hi there'},

    *            callBack: myCallBackFunction,

    *            eval: true

    *        });

    *        note that either lower or camelCase key names are valid

    *        i.e. both callback or callBack will work

    *

    *      The following keys are the same jsx || script || jsxScript || jsxscript || file

    *      The following keys are the same callBack || callback

    *      The following keys are the same replacements || replace

    *      The following keys are the same eval || forceEval || forceeval

    *      The following keys are the same forceEvalScript || forceevalscript || evalScript || evalscript;

    *

    * @return {Boolean} if the jsxScript was executed or not

    */

    Jsx.prototype.evalScript = function() {

        var arg, i, key, replaceThis, withThis, args, callback, forceEval, replacements, jsxScript, isBin;

        //////////////////////////////////////////////////////////////////////////////////////

        // sort out order which arguments into jsxScript, callback, replacements, forceEval //

        //////////////////////////////////////////////////////////////////////////////////////

        args = arguments;

        // Detect if the parameters were passed as an object and if so allow for various keys

        if (args.length === 1 && (arg = args[0]) instanceof Object) {

            jsxScript = arg.jsxScript || arg.jsx || arg.script || arg.file || arg.jsxscript;

            callback = arg.callBack || arg.callback;

            replacements = arg.replacements || arg.replace;

            forceEval = arg.eval || arg.forceEval || arg.forceeval;

        } else {

            for (i = 0; i < 4; i++) {

                arg = args;

                if (arg === undefined) {

                    continue;

                }

                if (arg.constructor === String) {

                    jsxScript = arg;

                    continue;

                }

                if (arg.constructor === Object) {

                    replacements = arg;

                    continue;

                }

                if (arg.constructor === Function) {

                    callback = arg;

                    continue;

                }

                if (arg === false) {

                    forceEval = false;

                }

            }

        }

        // If no script provide then not too much to do!

        if (!jsxScript) {

            return false;

        }

        // Have changed the forceEval default to be true as I prefer the error handling

        if (forceEval !== false) {

            forceEval = true;

        }

        //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        // On Illustrator and other apps the result of the jsx script is automatically passed as a string                                      //

        // if you have a "script" containing the single number 1 and nothing else then the callBack will register as "1"                        //

        // On InDesign that same script will provide a blank callBack                                                                          //

        // Let's say we have a callBack function var callBack = function(result){alert(result);}                                                //

        // On Ai your see the 1 in the alert                                                                                                    //

        // On ID your just see a blank alert                                                                                                    //

        // To see the 1 in the alert you need to convert the result to a string and then it will show                                          //

        // So if we rewrite out 1 byte script to '1' i.e. surround the 1 in quotes then the call back alert will show 1                        //

        // If the scripts planed one can make sure that the results always passed as a string (including errors)                                //

        // otherwise one can wrap the script in an eval and then have the result passed as a string                                            //

        // I have not gone through all the apps but can say                                                                                    //

        // for Ai you never need to set the forceEval to true                                                                                  //

        // for ID you if you have not coded your script appropriately and your want to send a result to the callBack then set forceEval to true //

        // I changed this that even on Illustrator it applies the try catch, Note the try catch will fail if $.level is set to 1                //

        //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        if (forceEval) {

            isBin = (jsxScript.substring(0, 10) === '@JSXBIN@ES') ? '' : '\n';

            jsxScript = (

                // "\n''') + '';} catch(e){(function(e){var n, a=[]; for (n in e){a.push(n + ': ' + e)}; return a.join('\n')})(e)}");

                // "\n''') + '';} catch(e){e + (e.line ? ('\\nLine ' + (+e.line - 1)) : '')}");

                [

                    "$.level = 0;",

                    "try{eval('''" + isBin, // need to add an extra line otherwise #targetengine doesn't work ;-]

                    jsxScript.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/"/g, '\\"') + "\n''') + '';",

                    "} catch (e) {",

                    "    (function(e) {",

                    "        var line, sourceLine, name, description, ErrorMessage, fileName, start, end, bug;",

                    "        line = +e.line" + (isBin === '' ? ';' : ' - 1;'), // To take into account the extra line added

                    "        fileName = File(e.fileName).fsName;",

                    "        sourceLine = line && e.source.split(/[\\r\\n]/)[line];",

                    "        name = e.name;",

                    "        description = e.description;",

                    "        ErrorMessage = name + ' ' + e.number + ': ' + description;",

                    "        if (fileName.length && !(/[\\/\\\\]\\d+$/.test(fileName))) {",

                    "          ErrorMessage += '\\nFile: ' + fileName;",

                    "          line++;",

                    "        }",

                    "        if (line){",

                    "          ErrorMessage += '\\nLine: ' + line +",

                    "              '-> ' + ((sourceLine.length < 300) ? sourceLine : sourceLine.substring(0,300) + '...');",

                    "        }",

                    "        if (e.start) {ErrorMessage += '\\nBug: ' + e.source.substring(e.start - 1, e.end)}",

                    "        if ($.includeStack) {ErrorMessage += '\\nStack:' + $.stack;}",

                    "        return ErrorMessage;",

                    "    })(e);",

                    "}"

                ].join('')

            );

        }

        /////////////////////////////////////////////////////////////

        // deal with the replacements                              //

        // Note it's probably better to use ${template} `literals` //

        /////////////////////////////////////////////////////////////

        if (replacements) {

            for (key in replacements) {

                if (replacements.hasOwnProperty(key)) {

                    replaceThis = new RegExp('__' + key + '__', 'g');

                    withThis = replacements[key];

                    jsxScript = jsxScript.replace(replaceThis, withThis + '');

                }

            }

        }

        try {

            evalScript(jsxScript, callback);

            return true;

        } catch (err) {

            ////////////////////////////////////////////////

            // Do whatever error handling you want here ! //

            ////////////////////////////////////////////////

            var newErr;

            newErr = new Error(err);

            alert('Error Eek: ' + newErr.stack);

            return false;

        }

    };

    /**

    * [evalFile] For calling jsx scripts from the js engine

    *

    *        The jsx.evalFiles method is used for executing saved jsx scripts

    *        where the jsxScript parameter is a string of the jsx scripts file location.

    *        For convenience jsx.file or jsx.evalfile can be used instead of jsx.evalFile

    *

    * @param  {String} file

    *                            The path to jsx script

    *                            If only the base name is provided then the path will be presumed to be the

    *                            To execute files stored in the jsx folder located in the __dirname folder use

    *                            jsx.evalFile('myFabJsxScript.jsx');

    *                            To execute files stored in the a folder myFabScripts located in the __dirname folder use

    *                            jsx.evalFile('./myFabScripts/myFabJsxScript.jsx');

    *                            To execute files stored in the a folder myFabScripts located at an absolute url use

    *                            jsx.evalFile('/Path/to/my/FabJsxScript.jsx'); (mac)

    *                            or jsx.evalFile('C:Path/to/my/FabJsxScript.jsx'); (windows)

    *

    * @param  {Function} callback

    *                            The callback function you want the jsx script to trigger on completion

    *                            The result of the jsx script is passed as the argument to that function

    *                            The function can exist in some other file.

    *                            Note that InDesign does not automatically pass the callBack as a string.

    *                            Either write your InDesign in a way that it returns a sting the form of

    *                            return 'this is my result surrounded by quotes'

    *                            or use the force eval option

    *                            [Optional DEFAULT no callBack]

    *

    * @param  {Object} replacements

    *                            The replacements to make on the jsx script

    *                            give the following script (template)

    *                            'alert("__message__: " + __val__);'

    *                            and we want to change the script to

    *                            'alert("I was born in the year: " + 1234);'

    *                            we would pass the following object

    *                            {"message": 'I was born in the year', "val": 1234}

    *                            or if not using reserved words like do we can leave out the key quotes

    *                            {message: 'I was born in the year', val: 1234}

    *                            By default when possible the forceEvalScript will be set to true

    *                            The forceEvalScript option cannot be true when there are replacements

    *                            To force the forceEvalScript to be false you can send a blank set of replacements

    *                            jsx.evalFile('myFabScript.jsx', {}); Will NOT be executed using the $.evalScript method

    *                            jsx.evalFile('myFabScript.jsx'); Will YES be executed using the $.evalScript method

    *                            see the forceEvalScript parameter for details on this

    *                            [Optional DEFAULT no replacements]

    *

    * @param  {Bolean} forceEval

    *                            If the script should be wrapped in an eval and try catch

    *                            This will 1) provide useful error feedback if heaven forbid it is needed

    *                            2) The result will be a string which is required for callback results in InDesign

    *                            [Optional DEFAULT true]

    *

    *                            If no replacements are needed then the jsx script is be executed by using the $.evalFile method

    *                            This exposes the true value of the $.fileName property 8-)

    *                            In such a case it's best to avoid using the $.__fileName() with no base name as it won't work

    *                            BUT one can still use the $.__fileName('baseName') method which is more accurate than the standard $.fileName property :-)

    *                            Let's say you have a Drive called "Graphics" AND YOU HAVE a root folder on your "main" drive called "Graphics"

    *                            You call a script jsx.evalFile('/Volumes/Graphics/myFabScript.jsx');

    *                            $.fileName will give you '/Graphics/myFabScript.jsx' which is wrong

    *                            $.__fileName('myFabScript.jsx') will give you '/Volumes/Graphics/myFabScript.jsx' which is correct

    *                            $.__fileName() will not give you a reliable result

    *                            Note that if your calling multiple versions of myFabScript.jsx stored in multiple folders then you can get stuffed!

    *                            i.e. if the fileName is important to you then don't do that.

    *                            It also will force the result of the jsx file as a string which is particularly useful for InDesign callBacks

    *

    * Note 1) The order of the parameters is irrelevant

    * Note 2) One can pass the arguments as an object if desired

    *        jsx.evalScript(myCallBackFunction, 'alert("__myMessage__");', true);

    *        is the same as

    *        jsx.evalScript({

    *            script: 'alert("__myMessage__");',

    *            replacements: {myMessage: 'Hi there'},

    *            callBack: myCallBackFunction,

    *            eval: false,

    *        });

    *        note that either lower or camelCase key names or valid

    *        i.e. both callback or callBack will work

    *

    *      The following keys are the same file || jsx || script || jsxScript || jsxscript

    *      The following keys are the same callBack || callback

    *      The following keys are the same replacements || replace

    *      The following keys are the same eval || forceEval || forceeval

    *

    * @return {Boolean} if the jsxScript was executed or not

    */

    Jsx.prototype.evalFile = function() {

        var arg, args, callback, fileName, fileNameScript, forceEval, forceEvalScript,

            i, jsxFolder, jsxScript, newLine, replacements, success;

        success = true; // optimistic :-)

        args = arguments;

        jsxFolder = path.join(__dirname, 'jsx');

        //////////////////////////////////////////////////////////////////////////////////////////////////////////

        // $.fileName does not return it's correct path in the jsx engine for files called from the js engine  //

        // In Illustrator it returns an integer in InDesign it returns an empty string                          //

        // This script injection allows for the script to know it's path by calling                            //

        // $.__fileName();                                                                                      //

        // on Illustrator this works pretty well                                                                //

        // on InDesign it's best to use with a bit of care                                                      //

        // If the a second script has been called the InDesing will "forget" the path to the first script      //

        // 2 work-arounds for this                                                                              //

        // 1) at the beginning of your script add var thePathToMeIs = $.fileName();                            //

        //    thePathToMeIs will not be forgotten after running the second script                              //

        // 2) $.__fileName('myBaseName.jsx');                                                                  //

        //    for example you have file with the following path                                                //

        //    /path/to/me.jsx                                                                                  //

        //    Call $.__fileName('me.jsx') and you will get /path/to/me.jsx even after executing a second script //

        // Note When the forceEvalScript option is used then you just use the regular $.fileName property      //

        //////////////////////////////////////////////////////////////////////////////////////////////////////////

        fileNameScript = [

            // The if statement should not normally be executed

            'if(!$.__fileNames){',

            '    $.__fileNames = {};',

            '    $.__dirname = "__dirname__";'.replace('__dirname__', __dirname),

            '    $.__fileName = function(name){',

            '        name = name || $.fileName;',

            '        return ($.__fileNames && $.__fileNames[name]) || $.fileName;',

            '    };',

            '}',

            '$.__fileNames["__basename__"] = $.__fileNames["" + $.fileName] = "__fileName__";'

        ].join('');

        //////////////////////////////////////////////////////////////////////////////////////

        // sort out order which arguments into jsxScript, callback, replacements, forceEval //

        //////////////////////////////////////////////////////////////////////////////////////

        // Detect if the parameters were passed as an object and if so allow for various keys

        if (args.length === 1 && (arg = args[0]) instanceof Object) {

            jsxScript = arg.jsxScript || arg.jsx || arg.script || arg.file || arg.jsxscript;

            callback = arg.callBack || arg.callback;

            replacements = arg.replacements || arg.replace;

            forceEval = arg.eval || arg.forceEval || arg.forceeval;

        } else {

            for (i = 0; i < 5; i++) {

                arg = args;

                if (arg === undefined) {

                    continue;

                }

                if (arg.constructor.name === 'String') {

                    jsxScript = arg;

                    continue;

                }

                if (arg.constructor.name === 'Object') {

                    //////////////////////////////////////////////////////////////////////////////////////////////////////////////

                    // If no replacements are provided then the $.evalScript method will be used                                //

                    // This will allow directly for the $.fileName property to be used                                          //

                    // If one does not want the $.evalScript method to be used then                                            //

                    // either send a blank object as the replacements {}                                                        //

                    // or explicitly set the forceEvalScript option to false                                                    //

                    // This can only be done if the parameters are passed as an object                                          //

                    // i.e. jsx.evalFile({file:'myFabScript.jsx', forceEvalScript: false});                                    //

                    // if the file was called using                                                                            //

                    // i.e. jsx.evalFile('myFabScript.jsx');                                                                    //

                    // then the following jsx code is called $.evalFile(new File('Path/to/myFabScript.jsx', 10000000000)) + ''; //

                    // forceEval is never needed if the forceEvalScript is triggered                                            //

                    //////////////////////////////////////////////////////////////////////////////////////////////////////////////

                    replacements = arg;

                    continue;

                }

                if (arg.constructor === Function) {

                    callback = arg;

                    continue;

                }

                if (arg === false) {

                    forceEval = false;

                }

            }

        }

        // If no script provide then not too much to do!

        if (!jsxScript) {

            return false;

        }

        forceEvalScript = !replacements;

        //////////////////////////////////////////////////////

        // Get path of script                              //

        // Check if it's literal, relative or in jsx folder //

        //////////////////////////////////////////////////////

        if (/^\/|[a-zA-Z]+:/.test(jsxScript)) { // absolute path Mac  | Windows

            jsxScript = path.normalize(jsxScript);

        } else if (/^\.+\//.test(jsxScript)) {

            jsxScript = path.join(__dirname, jsxScript); // relative path

        } else {

            jsxScript = path.join(jsxFolder, jsxScript); // files in the jsxFolder

        }

        if (forceEvalScript) {

            jsxScript = jsxScript.replace(/"/g, '\\"');

            // Check that the path exist, should change this to asynchronous at some point

            if (!window.cep.fs.stat(jsxScript).err) {

                jsxScript = fileNameScript.replace(/__fileName__/, jsxScript).replace(/__basename__/, path.basename(jsxScript)) +

                    '$.evalFile(new File("' + jsxScript.replace(/\\/g, '\\\\') + '")) + "";';

                return this.evalScript(jsxScript, callback, forceEval);

            } else {

            throw new Error(`The file: ${jsxScript} could not be found / read`);

            }

        }

        ////////////////////////////////////////////////////////////////////////////////////////////////

        // Replacements made so we can't use $.evalFile and need to read the jsx script for ourselves //

        ////////////////////////////////////////////////////////////////////////////////////////////////

        fileName = jsxScript.replace(/\\/g, '\\\\').replace(/"/g, '\\"');

        try {

            jsxScript = window.cep.fs.readFile(jsxScript).data;

        } catch (er) {

            throw new Error(`The file: ${fileName} could not be read`);

        }

        // It is desirable that the injected fileNameScript is on the same line as the 1st line of the script

        // This is so that the $.line or error.line returns the same value as the actual file

        // However if the 1st line contains a # directive then we need to insert a new line and stuff the above problem

        // When possible i.e.  when there's no replacements then $.evalFile will be used and then the whole issue is avoided

        newLine = /^\s*#/.test(jsxScript) ? '\n' : '';

        jsxScript = fileNameScript.replace(/__fileName__/, fileName).replace(/__basename__/, path.basename(fileName)) + newLine + jsxScript;

        try {

            // evalScript(jsxScript, callback);

            return this.evalScript(jsxScript, callback, replacements, forceEval);

        } catch (err) {

            ////////////////////////////////////////////////

            // Do whatever error handling you want here ! //

            ////////////////////////////////////////////////

            var newErr;

            newErr = new Error(err);

            alert('Error Eek: ' + newErr.stack);

            return false;

        }

        return success; // success should be an array but for now it's a Boolean

    };

    ////////////////////////////////////

    // Setup alternative method names //

    ////////////////////////////////////

    Jsx.prototype.eval = Jsx.prototype.script = Jsx.prototype.evalscript = Jsx.prototype.evalScript;

    Jsx.prototype.file = Jsx.prototype.evalfile = Jsx.prototype.evalFile;

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    // Examples                                                                                                                      //

    // jsx.evalScript('alert("foo");');                                                                                              //

    // jsx.evalFile('foo.jsx'); // where foo.jsx is stored in the jsx folder at the base of the extensions directory                //

    // jsx.evalFile('../myFolder/foo.jsx'); // where a relative or absolute file path is given                                      //

    //                                                                                                                              //

    // using conventional methods one would use in the case were the values to swap were supplied by variables                      //

    // csInterface.evalScript('var q = "' + name + '"; alert("' + myString + '" ' + myOp + ' q);q;', callback);                      //

    // Using all the '' + foo + '' is very error prone                                                                              //

    // jsx.evalScript('var q = "__name__"; alert(__string__ __opp__ q);q;',{'name':'Fred', 'string':'Hello ', 'opp':'+'}, callBack); //

    // is much simpler and less error prone                                                                                          //

    //                                                                                                                              //

    // more readable to use object                                                                                                  //

    // jsx.evalFile({                                                                                                                //

    //      file: 'yetAnotherFabScript.jsx',                                                                                        //

    //      replacements: {"this": foo, That: bar, and: "&&", the: foo2, other: bar2},                                              //

    //      eval: true                                                                                                              //

    // })                                                                                                                            //

    // Enjoy :-)                                                                                                                    //

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    jsx = new Jsx();

})();

Ten A
Community Expert
Community Expert
April 16, 2018

Thanks for sharing it!