Search This Blog

Showing posts with label openaf. Show all posts
Showing posts with label openaf. Show all posts

Tuesday, January 14, 2020

Relaxed JSON parser

In OpenAF, when using the jsonParser function, the parsing sticks to the strict JSON definition.

For example the following behaves as expected:

> jsonParser("{ \"a\": 1 }");
{
   "a": 1
}
> JSON.parse("{ \"a\": 1 }");
{
   "a": 1
}

But using a more "relaxed" JSON definition, the same functions will fail:

> jsonParser("{ a: 1 }");
{ a: 1 }
> JSON.parse("{ a: 1 }");
-- SyntaxError: Unexpected token in object literal

The jsonParser function will return the text string representation as it's unable to parse the JSON string. The native JSON.parse will actually throw an execption.

Using GSON

OpenAF includes the GSON library which can parse more "relaxed" JSON definitions. Since OpenAF version 20200108, the OpenAF jsonParser function can also use the GSON library. There is a new second boolean argument that if true alternates to GSON for parsing the string provided on the first argument:

> jsonParse("{ a: 1 }", true);
{
   "a": 1
}

Sunday, January 12, 2020

Using channel peers

If you use OpenAF channels there are some cases where you would like to connect them across OpenAF scripts.

To achieve this you can use the $ch.expose and the $ch.createRemote functions that allow you to expose an internal OpenAF to be accessible by other OpenAF scripts remotely.

But if the original OpenAF script which exposed the channel "dies" all the others will no longer be able to access the corresponding data. This might not be the desired scenario in some cases.

Peering

But making a mix of expose and createRemote functions between several OpenAF scripts you can achieve what is provided by the \$ch.peer function.

The \$ch.peer will expose an OpenAF channel and exchange sync data with a set of "peer" OpenAF scripts that just have to execute the same similar command:

$ch("myChannel").peer(12340, 
                      "/myURI", 
                      [ "http://script01:12340/myURI", 
                        "http://script02:12341/myURI", 
                        "http://script03:12343/myURI"]);

The signature is:

$ch.peer(aLocalPortOrServer, aPath, aRemoteURL, aAuthFunc, aUnAuthFunc)

The parameters are similar to the $ch.expose function:

Parameter Type Description
aLocalPortOrServer Object/Number The local port or a HTTPServer object to hold the HTTP/HTTPs server.
aPath String The URI where the channel interaction will be performed.
aRemoteURL Array An array of peer URLs
aAuthFunc Function This function will be called with user and password. If returns true the authentication is successful.
aUnAuthFunc Function This function will be called with the http server and the http request.

If you call it, after the first time, with a different array of aRemoteURL it will replace the existing. If the array references itself it will be ignored.

Unpeering

To remove a peering you can call:

$ch.unpeer(aRemoteURL)

Testing

Setting up data channels in each peer

First script (on host h1):

$ch("data").peer(9080, "/data", [ "http://h1:9080/data", "http://h1:9090/data", "http://h2:9080/data" ]);

Second script (on host h1):

$ch("data").peer(9090, "/data", [ "http://h1:9080/data", "http://h1:9090/data", "http://h2:9080/data" ]);

Third script (on host h2):

$ch("data").peer(9080, "/data", [ "http://h1:9080/data", "http://h1:9090/data", "http://h2:9080/data" ]);

Changing and retrieving data

  1. On host h2:
> $ch("data").size()
0
  1. On host h1:
> $ch("data").size()
0
> $ch("data").setAll(["canonicalPath"], io.listFiles(".").files);
> $ch("data").size()
25
  1. On host h2:
> $ch("data").size()
25

Sunday, December 8, 2019

Logging to log files

There are several benefits of using the OpenAF's log* functions. One of them is to quickly have a logs folder with automated housekeeping with just a couple of lines of code.

Let's start with a simple script with log functions:

log("Init");

try {
    log("Writing file...");
    io.writeFileString("test.txt", "test");
    log("File test.txt written.");
} catch(e) {
    logErr("Couldn't write file test.txt. Error: " + String(e));
}

log("Done");

Executing you will get the expected standard output:

2019-12-08 09:19:00.540 | INFO | Init
2019-12-08 09:19:00.750 | INFO | Writing file...
2019-12-08 09:19:00.780 | INFO | File test.txt written.
2019-12-08 09:19:00.785 | INFO | Done

Log to a folder

Let's log to a folder:

ow.loadCh();
// Setting default logging to the logs folder
ow.ch.utils.setLogToFile({ logFolder: "logs" });
// Ensuring logs are written synchronously
setLog({ async: true });

log("Init");

try {
    log("Writing file...");
    io.writeFileString("test.txt", "test");
    log("File test.txt written.");
} catch(e) {
    logErr("Couldn't write file test.txt. Error: " + String(e));
}

log("Done");

Executing again the result seems similar but a new "logs" folder was created with a daily log file containing all the log messages. When creating a new daily log file it will automatically gzip all old daily log files.

But that's the standard behaviour that can, of course, be customizable. Let's add housekeeping to delete files older than one week:

ow.loadCh();
ow.ch.utils.setLogToFile({ 
    logFolder: "logs",
    HKhowLongAgoInMinutes: 7200
});
//...

p.s.: you can specify a different folder to hold the previous compressed log files using _backupFolder. You can also control if old log files should be compressed or not with dontCompress._

Logging to files by hour

The default is logging to daily files but you can customize to any date time grouping. To group files by hour can simply add a specific fileDateFormat. Additionally let's change the log filenames generated using filenameTemplate (that uses handlebars notation).

Since we are changing the log filenames for the housekeeping process to be able to identify the new log filenames you need to provide a HKRegExPattern regular expression pattern also.

ow.loadCh();
ow.ch.utils.setLogToFile({ 
    logFolder: "logs",
    filenameTemplate: "hourly-logs-{{timedate}}.log",
    fileDateFormat: "yyyy-MM-dd-HH",
    HKhowLongAgoInMinutes: 7200,
    HKRegExPattern: "hourly-log-\\d{4}-\\d{2}-\\d{2}-\\d{2]\\.log"
});
//...

Logging as CSV files

To log into CSV files you just need to change the filenameTemplate and the lineTemplate:

ow.loadCh();
ow.ch.utils.setLogToFile({ 
    logFolder: "logs",
    filenameTemplate: "log-{{timedate}}.csv",
    lineTemplate: "\"{{timedate}}\";\"{{type}}\";\"{{{message}}}\"\n",
    HKhowLongAgoInMinutes: 7200
});
//...

Logging as NDJSON files

To log as NDJSON:

ow.loadCh();
ow.ch.utils.setLogToFile({ 
    logFolder: "logs",
    filenameTemplate: "log-{{timedate}}.csv",
    lineTemplate: "{d:\"{{timedate}}\",t:\"{{type}}\",m:\"{{{message}}}\"}\n",
    HKhowLongAgoInMinutes: 7200
});

Keeping log entries only on files

You can also specify that log entries should only be recorded in the log files and not shown on the standard output console:

ow.loadCh();
ow.ch.utils.setLogToFile({
    logFolder: "logs",
    setLogOff: true
});

And more...

You can check all the available options by executing:

> help ow.ch.utils.setLogToFile

on an openaf-console prompt.

Friday, October 25, 2019

Appending to files

Let's start with a simple question: how to append content to an existing file?

The answer is: check the "help" for the io.writeFile* function you need. Most of them already support an extra parameter to append to existing files.

io.writeFileString example

To write to a custom log you would do something like:

io.writeFileString("mylog.log", new Date() + " | Just did something\n");

But using this example the file mylog.log would get overwritten every time you execute it. To get around it simple add an extra flag:

io.writeFileString("mylog.log", new Date() + " | Just did something\n", void 0, true);

And everytime you run it, it will add a new line to the mylog.log file.

Appending several files into one

A one_liner that is usually helpfull is:

> $from(io.listFilenames("my/path")).ends(".js").select(r => { io.writeFileString("all.js", io.readFileString(r), void 0, true); });

This will actually create all.js file with the contents of all files in my/path with suffix ".js". Of course you now can change the $from conditions to whatever special conditions you might have.

Friday, October 18, 2019

How to run OpenAF code from shell script

To run an OpenAF script you just need to execute: 'openaf -f myScript.js'. But what if you wanted to include some OpenAF code without having to create a separate file. There are several ways to achived this with OpenAF.

Inline code

The simplest way it's using the '-c' option:

$ openaf -c 'var res = $rest().get("https://uinames.com/api"); print(printMap(res, void 0, "plain", true))'
+----------+---------------+
|    name: | John          |
| surname: | Lawrence      |
|  gender: | male          |
|  region: | United States |
+----------+---------------+

Incorporating into a shell script:

#!/bin/sh

name=$(openaf -c 'print($rest().get("https://uinames.com/api").name)')
echo Hi $name

Running:

$ sh sayHi.sh
Hi Andrea

Multiline code

For simple code inline code works well but for multi line code it may become hard to read. Another option is using the '-p' option that will receive input from stdin.

#!/bin/bash

output=$(openaf -i script -p << __endScript

  // OpenAF javascript code
  var res = \$rest().get("https://uinames.com/api/?ext");
  print(res.name + " " + res.surname + ";" + res.phone);

__endScript
)

# interpreting result in bash
IFS='\;'
read -a strarr <<< "$output"

echo Name : ${strarr[0]}
echo Phone: ${strarr[1]}

And the result of mixing shell script and OpenAF:

Name : Angela Johnston
Phone: (134) 382 2457

Saturday, October 12, 2019

Checking arguments

In OpenAF whenever you built a simple function, or you have a piece of code on a oJob that expects arguments or any code block you will eventually need to ensure the type of the input variables/arguments and enforce mandatory arguments.

Let's say that you build a function to add two values and return a verbose result. It receives arguments a and b that must be numbers, but if b is not provided it assumes 0. Argument c should be a string and if not provided a default value is provided. All this in plain javascript is:

function sum(a, b, c) {
    if (typeof a != "number") throw "a needs to be a number";
    if (typeof b == "undefined") b = 0;
    if (typeof b != "number") throw "b needs to be a number";
    if (typeof c == "undefined") c = "the result is ";
    if (typeof c != "string") throw "c needs to be a string";

    return c + String(a + b);
}

If you inspect it carefully you need to have the "if" statements in the right order to achieve the intended result. The more arguments you have the harder to read.

OpenAF as a shortcut to help with readability. It's not intended to be like TypeScript but just a small helper for OpenAF scripting. So the previous function would look like this with these OpenAF shortcuts:

function sum(a, b, c) {
    _$(a, "a").isNumber().$_();

    b = _$(b, "b").isNumber().default(0);
    c = _$(c, "c").isString().default("the result is ");

    return c + String(a + b);
}

Each declaration starts always with _$(variable, stringName). The stringName is actually optional if you want to become "more verbose" (you will see how in a second).

The declaration is then followed by conditions like isNumber and isString. You can provide an argument to each of these "conditions" to have more verbose exceptions than "a needs to be a number.".

The declaration ends with default(aValue) or $_(stringName). If no value is provided default will ensure that the variable will always default to some value while $_ doesn't return any value but will throw an exception indicating that the variable value wasn't provided.

So a more verbose type checking could be:

function sum(a, b, c) {
    _$(a).isNumber("The argument 'a' needs to be a number.').$_('The argument 'a' is mandatory for the sum function.');

    b = _$(b).isNumber("The argument 'b', if defined, must be a number.").default(0);
    c = _$(c).isString("The argument 'c', if defined, must be a string.").default("the result is ");

    return c + String(a + b);
}

You can see the list of all available conditions by executing desc _$() on an openaf-console.

Thursday, September 12, 2019

gzip/gunzip functionality in OpenAF

There are built-in functionality, in OpenAF, to apply gzip or gunzip to files or arrays of bytes. The functionality is available on the object io with the functions: io.gzip, io.gunzip, io.readFileGzipStream and io.writeFileGzipStream. They divide into two ways of using it:

To/From an array of bytes

The simplest way is to gzip/gunzip to/from an array of bytes:

var theLog = io.gunzip(io.readFileBytes("abc.log.gz"));
var gzLog  = io.writeFileBytes("new.log.gz", io.gzip(theLog));

The only issue is that the array of bytes (e.g. theLog, gzLog) will be kept in memory.

To/From streams

To address larger sets of bytes without occuring into memory spending, specially for large files, the are the stream based functions:

var rstream = io.readFileGzipStream("abc.log.gz");
// use the rstream and change it's content
// when ready to write just create a write stream
var wstream = io.writeFileGzipStream("new.log.gz");
// and use it to write to the new gzip file

Compress/Uncompress

To help store big javascript objects (even in memory) OpenAF provides two functions: compress and uncompress.

Of course the gains will be greater the bigger, and more compressable, the object is. Let's see some examples:

> var out = compress(io.listFiles("."));
> out.length
1959
> stringify(io.listFiles("."), void 0, 22).length
11674
> stringify(uncompress(out), void 0, "").length
11674

Of course objects are not stored in memory as their stringify version but, you get the idea. It's specific for cases when you need to keep an object in memory that you won't be acesssing on the medium/long term of the execution of your OpenAF script. Of course, it's also easy to save/load from a binary file:

> io.writeFileBytes("myObj.gz", compress(io.listFiles(".")));
> var theLog = uncompress(io.readFileBytes("myObj.gz"));

Monday, September 2, 2019

Is defined or undefined

Two of the most common functions used in OpenAF are: isDef and isUnDef. The reason is because javascript variables, when created, are "undefined" and only become defined after a value is assigned to it. So, before using that javascript variable is common to test if it's defined or not.

> var abc
> isDef(abc)
false
> abc = 123
123
> isDef(abc)
true

Ok, what happens if it's undefined?

> var xyz
> isUnDef(xyz)
true
> String(xyz + 123)
NaN

Saturday, August 31, 2019

Comparing maps and arrays

In javascript it's easy to compare simple types but the same doesn't apply to maps or arrays. Let's take some examples:

var a = {
  "name": "Line",
  "points": {
    "start": {
      "x": 5,
      "y": 10
    },
    "end": {
      "x": 15,
      "y": 25
    }
  }
};

var b = {
  "name": "Line",
  "points": {
    "start": {
      "x": 5,
      "y": 10
    },
    "end": {
      "x": 15,
      "y": 25
    }
  }
}

The map a and b are almost equal but if you compare them:

> a == b
false
> 1 == 1
true
> "a" = "a"
true

The reason is that a and b are different objects despite having the same keys, strutucture and values. The only way to compare is to compare key by key and value by value. In OpenAF the function compare does just that:

> compare(a, b)
true
>
> p.points.start.x = 0
> p.points.start.y = 0
>
> compare(a, b)
false

Using the openaf console you can actually see the difference:

> diff a with b
 -      "x": 5,
 -      "y": 10
 +      "x": 0,
 +      "y": 0

Comparing arrays

What about arrays? Well, they are also objects. Alone (e.g. [1, 2, 3]) or mixed (e.g. { a: 1, b: [ 1, 2, 3]}). So you can use the compare function in the same way:

> compare([1, 2, 3], [1, 2, 3])
true
> compare([0, 1, 2, 3, 4], [1, 2, 3])
false

Friday, August 30, 2019

Applying selectors to an array

Whenever using an array of maps, and specially when analyzing them on OpenAF-console, you probably want to focus just on some map entries and not have all the other map keys on the screen. To make it easier there is a function mapArray in OpenAF for this case and others.

Let's say you have an array of maps and submaps like this:

> $rest().get("https://jsonplaceholder.typicode.com/users");
[
  {
    "id": 1,
    "name": "Leanne Graham",
    "username": "Bret",
    "email": "Sincere@april.biz",
    "address": {
      "street": "Kulas Light",
      "suite": "Apt. 556",
      "city": "Gwenborough",
      "zipcode": "92998-3874",
      "geo": {
        "lat": "-37.3159",
        "lng": "81.1496"
      }
    },
    "phone": "1-770-736-8031 x56442",
    "website": "hildegard.org",
    "company": {
      "name": "Romaguera-Crona",
      "catchPhrase": "Multi-layered client-server neural-net",
      "bs": "harness real-time e-markets"
    }
  },
  {
    "id": 2,
    "name": "Ervin Howell",
    "username": "Antonette",
    "email": "Shanna@melissa.tv",
    "address": {
[...]

Let's say that you want to compare just the name, the city on the address and the company name. With mapArray that's easy:

> mapArray( $rest().get("https://jsonplaceholder.typicode.com/users"), [ "name", "address.city", "company.name" ] )
[
  {
    "name": "Leanne Graham",
    "address.city": "Gwenborough",
    "company.name": "Romaguera-Crona"
  },
  {
    "name": "Ervin Howell",
    "address.city": "Wisokyburgh",
    "company.name": "Deckow-Crist"
  },
  {
    "name": "Clementine Bauch",
    "address.city": "McKenziehaven",
[...]

Since now you just have the fields you want you can also use table on openaf-console:

> table mapArray( $rest().get("https://jsonplaceholder.typicode.com/users"), [ "name", "address.city", "company.name" ] )
          name          | address.city |   company.name
------------------------+--------------+------------------
Leanne Graham           |Gwenborough   |Romaguera-Crona
Ervin Howell            |Wisokyburgh   |Deckow-Crist
Clementine Bauch        |McKenziehaven |Romaguera-Jacobson
Patricia Lebsack        |South Elvis   |Robel-Corkery
[...]

If you have an array on a map element you can just reference it directly:

> mapArray( someMap, ["id", "items[0].subId"])
[
  {
    "id": 1,
    "items[0].subId": 1
  },
  {
    "id": 2,
    "items[0].subId": 4
  },
  {
    "id": 3,
    "items[0].subId": 43
  }
]

Note: mapArray uses ow.obj.getPath and ow.obj.setPath internally that you can use in other situations directly.

Tuesday, August 27, 2019

Searching map keys and values

The main data structure in OpenAF is, of course, javascript maps. We already cover how to easily search on arrays using $from. But what about maps?

Let's take an example:

> var weather = $rest().get("http://www.metaweather.com/api", { location: 742676 });

The "weather" variable now should have a map with the weather forecast for Lisbon. If you inspect it has sub-arrays with the forecast for the next days, map entries to provide info about sun rise and sun set, an array with the weather sources, etc...

Searching keys

If you now the exact path it's easy to build the map "path" to get the values that you want but the OpenAF function searchKeys will actually help you find those paths. Let's try to find those sun rise and sun set entries:

> searchKeys(weather, "sun")
{
  ".sun_rise": "2019-08-27T07:00:52.671773+01:00",
  ".sun_set": "2019-08-27T20:14:52.385796+01:00"
}

The string search parameter (e.g. sun) is actually a case insensitive regular expression. And it just found you all map entries with the word "sun" anywhere. Now you know that weather.sun_rise and weather.sun_set will get you the info you need. Let's try to find now temperatures:

> searchKeys(weather, "temp")
{
  ".consolidated_weather[0].min_temp": 17.98,
  ".consolidated_weather[0].max_temp": 23.674999999999997,
  ".consolidated_weather[0].the_temp": 24.23,
  ".consolidated_weather[1].min_temp": 17.405,
  ".consolidated_weather[1].max_temp": 23.05,
  ".consolidated_weather[1].the_temp": 23.39,
  ".consolidated_weather[2].min_temp": 16.985,
  ".consolidated_weather[2].max_temp": 24.47,
  ".consolidated_weather[2].the_temp": 24.625,
[...]

Now the temperatures are found within the consolidate_weather array. So you now have a list of all the paths to get the temperatures even within an array.

Searching values

Searching keys of a map wouldn't be complete without being able to search for values. On the initial request we used the Lisbon location code "742676". Let's search for that:

> searchValues(weather, "742676")
{
  ".woeid": 742676
}

As expected the result is the path on where that value was found. Let's now use a regular expression to try to find if there are any lat/log coordinates:

> searchValues(weather, "-?\\d+\\.\\d+,-?\\d+\\.\\d+");
{
  ".parent.latt_long": "39.557919,-7.844810",
  ".latt_long": "38.725670,-9.150370"
}

Other advanced uses

Where these two functions searchKeys and searchValues really shine when you are using the openaf-console to investigate a big and/or complex javascript map. They will quickly filter and find you the data that you need to help you write your scripts.

The previous examples show you the basic use of these functions. There are other options (check, in the openaf-console, "help searchKeys" and "help searchValues") to make the search case sensitive and even to execute a callback function whenever keys or values are found (for example: search & replace).

Sunday, August 25, 2019

Using OpenAF’s merge

OpenAF includes a base function to help on the task of merging maps. Let’s consider a simple example:

var source = {
   x: 1,
   y: -1
}

And you want to merge it with a map with the extra keys you need:

var extra = {
   z: -5
}
Now to merge the two maps simply execute:

> merge(source, extra)
{ 
   x: 1,
   y: -1,
   z: -5
}

Merging into arrays

Now imagine that instead of one map you have an array of maps? No problem, merge will merge with every single map part of the array of maps.

var source = [{
   x: 1,
   y: -1
}, {
   x: -5,
   y:-5
}];

var extra = { 
   z: -10
};

sprint(merge(source, extra))

// The result will be:
//[{
//   x: 1,
//   y -1,
//   z: -10
//},{
//   x: -5,
//   y: -5,
//   z: -10
//}]

Note: the keys of the second parameter map will always override the equivalent keys on the source (either in map or array mode).

Friday, August 23, 2019

Encode/Decode base64 in OpenAF

Base64 is a enconding scheme used to represent binary data in ASCII strings (using only 6-bit instead of the full 8-bit). It's used in several cases: LDAP's LDIF files character representation; encoding binary email attachments; embeeding images and fonts in HTML/CSS; avoiding delimiters been interpreted as delimiters inside a sequence of characters on a field: etc...

So, how to quickly encode or decode a string or an array of bytes to and from Base 64 in OpenAF:

> af.fromBytes2String(af.toBase64Bytes("This is a test"))
"VGhpcyBpcyBhIHRlc3Q="

> af.fromBytes2String(af.fromBase64("VGhpcyBpcyBhIHRlc3Q="))
"This is a test"

Keep in mind that the af.toBase64Bytes & af.fromBase64 return always a byte array. So in order to display it you need to use af.fromBytes2String to convert the byte array back to a string.

Of course, to convert a binary content you can just provide the array of bytes:

> af.fromBytes2String(af.toBase64Bytes(io.readFileBytes("openaf.ico")))
AAABAAQAgIAAAAEAIAAoCAEARgAAAEBAAAABACAAKEIAAG4IAQAgIAAAAQAgAKgQAACWS...

Wednesday, August 21, 2019

Protecting from system exit

Whenever running a script you may want to abort/stop the current execution completely exiting the OpenAF process. You can do that using the function exit:

exit(0);
// Exits the current execution immediatelly with exit code 0

exit(-1);
// Exits the current execution immediatelly with exit code -1

But what if you are calling that script from the openaf-console (using the load function) or similar and you don't want the entire Java process to "die"? You can use the af.protectSystemExit function for that:

> af.protectSystemExit(true, "No can do!: ");
> exit(0);
-- No can do!: 0
> exit(-10);
-- No can do!: -10

Everytime the script tries to end processing by means of a system exit a javascript execption will be raised with the provided text appended with the corresponding exit code.

If you want to "disarm" this protection:

> af.protectSystemExit(false);
> exit(0);
$

Note: this protection uses the Java security manager. So even java system exit code will be affected by this "protection".

Saturday, August 10, 2019

Accessing environment variables

If you previously wrote any type of script you probably, sooner or later, want to access environment variables. OpenAF is no different and access to the operating system environment variables it's very easy with getEnv and getEnvs functions:
> getEnvs()
{
   "PATH": "/some/dir:/usr/bin",
   "SHELL": "/bin/bash",
   "CUSTOM": "something",
//...
getEnvs will retrieve a map of the current environment variables. To retrieve a specific environment variable you can directly access the map:
> getEnvs()["CUSTOM"]
"something"
or use getEnv directly:
> getEnv("CUSTOM")
"something"

Thursday, August 8, 2019

Executing code when an OpenAF script execution is terminated

Usually there is a need to execute specific code whenever a script is ending/shutting down. Either by normal or forced termination. Examples of this are closing connected sessions to other servers (e.g. databases), deallocate used resources, etc...

In OpenAF this can be achieved using addOnOpenAFShutdown:
addOnOpenAFShutdown(function() {
   // Some closing code
   // ...
});
The following example closes the created mini web server upon normal or forced termination:
ow.loadServer();
 
// Setting up
!pidCheckIn("testserver.pid") && !log("Already running") && exit(0);
// Add code to execute on termination
addOnOpenAFShutdown(function() {
   log("Stopping...");
   hs.stop();
   log("Done!");
});
 
// Starting the httpd server
log("Starting...");
 
var hs = ow.server.httpd.start(8888);
hs.add("/", function(r) {
   return hs.replyOKText("Current date: " + new Date());
});
 
log("Ready");
 
// Converting the script into a daemon so that it doesn't terminate upon Ctrl-C or similar
ow.server.daemon();
Do note that every call to addOnOpenAFShutdown will add a new hook. Upon termination, the last added hook will be the first to be executed and backwards to the first added.

Tuesday, August 6, 2019

How to use OpenAF from Notepad++

It's possible to use Notepad++ to run and debug OpenAF scripts. To set it up just follow the steps:


  1. Download and install (if don't have) the NPPExec plugin for Notepad++
    set hglt1 = ERROR [%ABSFILE%] -- *, line: %LINE%, column: %CHAR%
    c:\apps\openaf\openaf.bat -s -i script -f "$(FULL_CURRENT_PATH)"
  2. Inside Notepad++ go to menu: Plugins → NPPExec → Execute…
    1. Enter a similar command (correcting to your OpenAF script path):
      set hglt1 = ERROR [%ABSFILE%] -- *, line: %LINE%, column: %CHAR%
      c:\apps\openaf\openaf.bat -s -i script -f "$(FULL_CURRENT_PATH)"
      
    2. You can save it as OpenAF.
  3. Inside Notepad++ go to menu: Plugins → NPPExec → Console Output Filters…
    1. In the HighLight tab, select the first mask and enter the following text:
      $(hglt1)
    2. Enter FF on the Red component
    3. Check the [B]old checkbox
Mask strings are limited to a few characters in NPP, hence the need to set the $(hglt1) variable on execution.

Now to execute you can hit Ctrl-F6 (to execute immediately) or F6 (to edit the command):


Double-clicking on the error will take to the line where the error occurred.

On the NPPExec plugin menu, click on “Save all files on execute”, so you don't have to save your script before executing it.

Monday, August 5, 2019

Adding color to OpenAF scripts

Black and white scripts can be tedious and many times when something errors out and you are tired you may not even notice it. So color not only helps to make it more "colourful" but also helps errors and warnings to stand out from the rest of the logging or printing.

In OpenAF, if the current terminal supports ANSI color you can use the ansi* functions in any script. These functions will detected if ANSI color is supported and return the proper escape sequences to produce "color".

Here is a quick description of the 3 main functions:

  • ansiStart() - Detects and prepares to output ansi color escape sequences.
  • ansiStop() - Stops the output of ansi color escape sequences.
  • ansiColor(aAnsiSetting, aString, force) - Returns aString within the appropriate ANSI color escape sequences for the provide aAnsiSetting
The attributes are self-explanatory but some might only work on specific terminals (colors will work pretty much everywhere). You can have a set of attributes separated by commas. You can get a list of possible attributes on the ansiColor help:
> help ansiColor
-- ansiColor(aAnsi, aString, force) : String
-- -----------------------------------------
-- Returns the ANSI codes together with aString, if determined that the current terminal can handle ANSI codes (overridden by force = true), with the attributes defined in aAnsi. Please use with ansiStart() and ansiStop(). The attributes separated by commas can be:
 
BLACK; RED; GREEN; YELLOW; BLUE; MAGENTA; CYAN; WHITE;
FG_BLACK; FG_RED; FG_GREEN; FG_YELLOW; FG_BLUE; FG_MAGENTA; FG_CYAN; FG_WHITE;
BG_BLACK; BG_RED; BG_GREEN; BG_YELLOW; BG_BLUE; BG_MAGENTA; BG_CYAN; BG_WHITE;
BOLD; FAINT; INTENSITY_BOLD; INTENSITY_FAINT; ITALIC; UNDERLINE; BLINK_SLOW; BLINK_FAST; BLINK_OFF; NEGATIVE_ON; NEGATIVE_OFF; CONCEAL_ON; CONCEAL_OFF; UNDERLINE_DOUBLE; UNDERLINE_OFF;

Examples of usage:

Writing part of a string with white foreground and red background:


Writing part of a string with black foreground and yellow background:

Using arrays with parallel

OpenAF is a mix of Javascript and Java, but "pure" javascript isn't "thread-safe" in the Java world. Nevertheless be...