Search This Blog

Wednesday, July 31, 2019

Date to/from string

To help with javascript Date manipulation in OpenAF there are several helper functions in the ow.format library. To use them do load the format library first:
ow.loadFormat();
One of the most used date functions is the ow.format.toDate and the ow.format.fromDate. These helper functions let you convert strings into Dates and Dates into strings.
var someDate = ow.format.toDate("20170504", "yyyyMMdd");
print(someDate); // "2017-05-04T00:00:00.000Z"

var aString = ow.format.fromDate(new Date(), "yyyyMMdd-HHmmss");
print(aString); // "20190730-000000"

They are very similar, in spirit, to the SQL to_date and to_char functions but you have to be careful that they use the Java mask format:
  G - Era descriptor (AD)
  y - Year (1996; 96)
  Y - Week year (2009; 09)
  M - Month in year (July; Jul; 07)
  w - Week in year (27)
  W - Week in month (2)
  D - Day in year (189)
  d - Day in month (10)
  F - Day of week in month (2)
  E - Day name in week (Tuesday; Tue)
  u - Day number of week (1 = Monday, ..., 7 = Sunday) (1)
  a - Am/pm number (PM)
  H - Hour in day (0-23)
  k - Hour in day (1-24)
  K - Hour in am/pm (0-11)
  h - Hour in am/pm (1-12)
  m - Minute in hour (30)
  s - Second in minute (55)
  S - Millisecond (978)
  z - Time zone (Pacific Standard Time; PST; GMT-08:00)
  Z - Time zone (-0800)
  X - Time zone (-08; -0800; -08:00)

Tuesday, July 30, 2019

Making it easy to read with $from

When you start to write several OpenAF scripts you will usually start to write a log while/for/do cycles to go through an array and perform so task. Take the following example (which might be hard to read for a beginner):

What this piece of code does is:
var list = io.listFiles("."); 
var found = false, i = 0;
while(!found) {
   var file = list.files[i];
   if (file.isFile && file.filename == "myScript.js") 
      found = true;
   else
      i++;
}
var myRecord = list.files[i];

  • Get an array with all the files and folders on the folder "." (through io.listFiles). This will result in an array of maps. Similar to:
    {
      "isDirectory": true,
      "isFile": false,
      "filename": "somefile.txt",
      "filepath": "./somefile.txt",
      "canonicalPath": "/some/folder/somefile",
      "lastModified": 1560515651403,
      "createTime": 1560515651374,
      "lastAccess": 1564194441636,
      "size": 640,
      "permissions": "xrw"
    },
    
    
  • Iterate through each element of the array trying to find a filename equal to "myScript.js" and ensuring it's a file and not a folder.
  • Once found, break the cycle and store the corresponding map in the variable myRecord
Now let's rewrite it using $from (a shortcut to the JLinq library):
var myRecord = $from(io.listFiles(".").files)
        .equals("isFile", true)
        .equals("filename", "myScript.js")
        .select();

Doesn't it look a little bit easier to read? It's structured like a SQL query where "from" points to the array origin, the "where" comes next is the functions equals to test if it's a file and for the filename "myScript.js" and finally "select"s all fields keeping the result in myRecord.

Some more examples:
// From a listOfThings array of maps, where type equals "item" and barcode starts with "501"
// return true if there is any matching map.
$from(listOfThings).equals("type", "item").starts("barcode", "501").any();

// From a list of files of the current folder, where it's a directory, count the corresponding
// matching maps.
$from(io.listFiles(".").files).equals("isDirectory", true).count();

// From a files of files of the current folder, where filename matches the regular expression 
// "work.+", select only the field filename from the map (if it doesn't exist replace the 
// missing value with "")
$from(io.listFiles(".").files).match("filename", "work.+").select({ filename: "" });

// From a listOfThings array of maps, where type equals "item", sort the results by name and 
// select the result by running a function passing to it each matching map. The returned result
// of executing the function with each matching map will be returned in an array.
$from(listOfThings)
.equals("type", "item")
.sort("name")
.select((record) => {
   return r.barcode + " - " + r.name;
});
You can check more examples on the jLinq demo or typing, in the openaf-console, "desc $from([])".

Monday, July 29, 2019

Executing code asynchronously (promises)

The execution of an OpenAF script is inherently sequential. Although it's easy to understand the execution flow (e.g. after a executes b) it might not have the best performance since it needs to wait for the end of the execution of the previous instruction to execute the next. Sometimes it makes sense because there is a dependency but in other cases it could be already executing something else asynchronously. OpenAF, through the Threads plugin, allows you access to the underneath great Java Threads functionality but the code may become hard to understand. An easy way to keep it simple but run asynchronously code is to use "promises". If you know how to use promises in other languages you will find it similar after checking the help information ("help $do"). For those that are not so familiar OpenAF tries to provide a easy and simple implementation.

You start be creating a promise for the code you want to run asynchronously:
var promise = $do(() => { 
   // my code
});

The difference is that no matter what "my code" is OpenAF will return you immediately a promise object. "my code" will execute asynchronously a.s.a.p. and the promise object will be updated accordingly. But once "my code" executes you usually want to chain other actions. To chain more code you can use ".then":
var promise = $do(() => { 
                 // my code
                 
                 return aResult;
              }).then((someResult) => {
                 // process the result

                 return processSuccess; 
              }).then((processStatus) => {
                 // etc... you get the point.
              });

What if any of these chained pieces of code throws an exception? At that point the promise won't be "fulfilled" but you can add ".catch" to handle it as you would do with a "try..catch":
var promise = $do(() => { 
                 // my code
                 
                 return aResult;
              }).then((someResult) => {
                 // process the result

                 return processSuccess; 
              }).then((processStatus) => {
                 // etc... you get the point.
              }).catch((error) => {
                 // handle the error
              });
So, what can you do with a promise object?
  • You can wait for the end of the execution at any point blocking the current execution with $doWait(promise).
  • If you have an array of promise objects you can use $doFirst (waits for one of the promises to be "fulfilled") and $doAll (waits for all of the promises to be "fulfilled"). Both will return a single promise object that once "fulfilled" resumes the current execution.
How does that look like:
var arrayOfPromises = [];
for (let idx in something) {
   arrayOfPromises.push($do( /* ... */ ));
}

// Yes, doFirst returns a promise, so you can chain more
arrayOfPromises.push($doFirst(arrayOfPromises)
                     .then(() => { 
                        print("One it's done!"); 
                     }));

var allPromises = $doAll(arrayOfPromises)
                  .then(() => {
                     print("Everything is done now!");
                  }));

/* do something else */

// Wait for all of them to finish
$doWait(allPromises);

Ok. But won't that just lunch a ton of threads "burning" down my machine? No. You don't need to worry about that. Underneath it will create a set of threads giving the number of identified compute cores and reuse them. So some promises will actually be waiting for others to finish and to have a thread available for them to execute.
In OpenAF there is also the function parallel4array and others to make it easy (and smarter) to asynchronously process an array since a promise for each might not actually give the best performance. But that will be the topic of another post.

Sunday, July 28, 2019

Get a javascript array from an Excel

In the same way you can write a javascript array to an Excel it's equally easy to retrieve a javascript array from an existing Excel. Let's take this excel sheet as an example (from test.xlsx):


To convert it to a javascript array just create the following OpenAF script (test.js):
plugin("XLS");
var xls = new XLS("test.xlsx");
var sheet = xls.getSheet("Sheet1");
// The second boolean parameter determines if formulas should be evaluated
var myArray = xls.getTable(sheet, true, "B", 3).table;

sprint(myArray);

And execute it:
openaf -f test.js

The result will be:
[
  {
    "ID": 1,
    "Description": "Test 1",
    "Value": 123
  },
  {
    "ID": 2,
    "Description": "Test 2",
    "Value": 321
  },
  {
    "ID": 3,
    "Description": "Test 3",
    "Value": 456
  },
  {
    "ID": 4,
    "Description": "Test 4",
    "Value": 654
  }
]



Saturday, July 27, 2019

Adding an array to an Excel spreadsheet

The OpenAF's XLS plugin offers one of the most handy features: the ability to write a simple javascript array of maps to an Excel spreadsheet. But first a warning: it can't be a complex sub maps/arrays, just plain strings/numbers array which usually is enough (although there is an alternative way that will mention on the end of the post).

The functionality is captured on the setTable function of the XLS plugin. Giving an example, let's say we get an array with all the files and corresponding info from a folder using io.listFiles:

var path = ".";
var outputFile = "test.xlsx";

var listOfFiles = io.listFiles(path).files; 
// files is an array returned by io.listFiles with filesystem details of files & folders on the provided path

plugin("XLS");
var xls = new XLS(); 

// Determines in which sheet the array will be added
var sheet = xls.getSheet("my sheet"); 

// Writes all the array elements and corresponding properties to the provided sheet starting on excel position B2.
xls.setTable(sheet, "B", "2", listOfFiles); 

// Writes the prepared excel to a xls/xlsx file.
xls.writeFile(outputFile);
xls.close(); 
// Don't forget to close the object to free up used files and resources before using the generated excel file.


On the first lines of the code we defined the output file as "test.xlsx". After running this script:
openaf -f test.js
if there wasn't any errors you will find a test.xlsx on the same folder that will look similar to this:


The first instinct is: "can I format it?" The answer is yes. You can add an auto-filter easily:

ow.loadFormat();
ow.format.xls.autoFilter(sheet, "B2:K2")

"Can I change color, font, etc...?": Yes, check out ow.format.xls.getStyle.

"Can I just use a previous excel template and just fill it in?": Yes. The probably the easiest to do. Just change the new XLS line to this:
var xls = new XLS("myTemplate.xlsx");

Friday, July 26, 2019

Micro remote HTTP server file browser

There is an emergency! Time is ticking and you need to quickly solve things. You need to copy files from A to B and there isn't any sftp, scp, ftp, shared volumes/drives, etc... Security? you just need a minute with a quick http server up and running. Should you install Apache, ngnix, ...? Well having the OpenAF "swiss army knife" with me it just a couple of YAML lines and running an OpenAF's oJob. Just copy+paste to a httpd.yaml file:

init:
  port  : &PORT   8888
  folder: &FOLDER .

ojob:
  daemon: true
  opacks:
    - oJob-common

include:
  - oJobHTTPd.yaml


todo:
  # -----------------------
  - name: HTTP Start Server
    args:
      port   : *PORT
      mapLibs: true
  # ------------------
  - name: Browse files

jobs:
  # -----------------
  - name: Browse files
    to  : HTTP File Browse
    args:
      uri : /
      port: *PORT
      path: *FOLDER


Change the PORT and the FOLDER if needed. Execute:
/my/openaf/ojob httpd.yaml
and you have yourself a micro/temporary/easy http file browsing server to copy files from A (running the yaml ojob) to B (pointing the browser to http://ip.address.of.A:8888):



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...