Tech Is Hard

Credibility = Talent x Years of experience + Proven hardcore accomplishment

Category Archives: REBOL

Difference ‘tween skip and pick

REBOL series are the foundation of all data structures, so there are a lot of functions to deal with them. Often, under a given circumstance, more than one will do what’s needed. In the REBOL core series documentation, there’s sort of a fluid discussion of functions like first and next. And in another section, a comparison between first/second/third/etc and pick. I was changing values in a series and encountered the distinction between pick and skip. Some functions, like first and pick, are extraction functions, giving the series item as its own thing, while others, like next and skip, are in the context of the series. Often it might not matter, but due to the general applicability of the change function to all types of series!, it did for me. I want to change the first item in a:

>> a: copy ["abc" 123]
== ["abc" 123]
>> a/1
== "abc"
>> change a/1 "b"
== "bc"
>> a
== ["bbc" 123]
>> change pick a 1 "c"
== "bc"
>> a
== ["cbc" 123]
>> change first a "d"
== "bc"
>> a
== ["dbc" 123]
>> change head a "e"
== [123]
>> a
== ["e" 123]

Ah! Until I used head (or next or skip or back), I am referencing the first item (originally “abc”), but the change function sees change string! string!. Some of the items I need to change are blocks I need to append to:

>> a: copy ["abc" [123]]
== ["abc" [123]]
>> append skip a 1 'foobar
== ["abc" [123] foobar]

Oops, that’s not it. Get rid of that with remove.

>> remove last a
** Script Error: remove expected series argument of type: series port bitset none
** Where: halt-view
** Near: remove last a

Same issue here. Last is giving the word! foobar which isn’t something remove can operate on.

>> remove back tail a
== []
>> a
== ["abc" [123]]
>> append last a 'foobar
== [123 foobar]

There it is. Any of the “extraction” functions will do for the append:

>> a
== ["abc" [123 foobar]]
>> append pick a 2 'foo
== [123 foobar foo]
>> append a/2 'bar
== [123 foobar foo bar]
>> a
== ["abc" [123 foobar foo bar]]

REBOL/GnuCash: Listing the Transactions

With a couple additions to the end-states block!, the end-element function now lists the transacions in my GnuCash file, with the account references (contained in the <trn:splits> element of each transaction) dereferenced:

>> do %gc2iam.r
3-Jan-2011/13:02:43-7:00 Hobby Lobby fix chair pic glass 
[[4106 100] ["Home Repair" "EXPENSE" 3b7ddd7ff3110b9bc94f6425bba8fc83] 
[-4106 100] ["Chase MC" "CREDIT" 74bee4c0f2e95ff570291a9d3ea3a3ba]]
12-Jun-2011/10:19:19-6:00 BEST carpet cleaning 
[[12000 100] ["Home Repair" "EXPENSE" 3b7ddd7ff3110b9bc94f6425bba8fc83] 
[-12000 100] ["Discover" "CREDIT" 74bee4c0f2e95ff570291a9d3ea3a3ba]]
10-Sep-2011/0:46:22-6:00 lunch meeting
[[200 100] ["Parking" "EXPENSE" b0b714a0163f90222c5a6769c78ca791] 
[-200 100] ["Cash" "CASH" 74bee4c0f2e95ff570291a9d3ea3a3ba]]
16-Mar-2011/21:02:47-6:00 Jenny's Market 
[[3255 100] ["Gas" "EXPENSE" b0b714a0163f90222c5a6769c78ca791] 
[-3255 100] ["Discover" "CREDIT" 74bee4c0f2e95ff570291a9d3ea3a3ba]

Here’s the relevant part of the handler. I added a /local word to end-element, value, for some manipulation of the string that GnuCash stores, which is a fraction. For instance, $328.23 is represented as 32823/100. There is other currency metadata used to make conversions between these amounts and stock prices, for example. Right now I’m just going to be concerned about “normal” transactions like spending and transfers, but I don’t want to lose any precision until I know more, so I split the string using “/” and convert each component to an integer. I changed the to use to make; the implication is that make will give me a new item of my desired type, while to technically converts the argument. Remembering the admonition to use copy when initializing from something that will change, I think make must be safer here.

trnAccts: copy []
guid:               name:               class: 
parent:             trnDate:            description:  

end-states: [
    "act:id"          [guid:           make word! content]
    "act:name"        [name:                 copy content]
    "act:type"        [class:                copy content]
    "act:parent"      [parent:         make word! content]
    "trn:description" [description:          copy content]
    "ts:date"         [trnDate:        make date! content]
    "split:value"     [
        value: split content #"/" 
        append/only trnAccts reduce [make integer! value/1 make integer! value/2]
    "split:account"   [append trnAccts to word! content]

    "gnc:account"     [set guid reduce [name class parent]]
    "gnc:transaction" [
        print [trnDate description remold trnAccts]
        clear head trnAccts
end-element: func [
    ns-uri		[string! none!]  ns-prefix [string! none!]
    local-name	[string! none!]  q-name    [string!]
    /local value
    switch q-name end-states
    clear head content

REBOL/GnuCash: using REBOL words

This work is going through our GnuCash data again, but not trying to be anything generic. Instead we’re going to set REBOL words to values that we mine and see how that works as an automatic dereferencing setup. REBOL has a lot in common with Lisp, or so is my understanding of the matter, and I took a cue from Paul Graham:

Lisp’s symbol type is useful in manipulating databases of words, because it lets you test for equality by just comparing pointers. (If you find yourself using checksums to identify words, it’s symbols you mean to be using.)

We’re going to parse the XML with the statementgnucash/parse-xml read %2011.xml. So let’s look at this incarnation of the parser/handler:

gnucash: make xml-parse/parser [
    set-namespace-aware true
    handler: make xml-parse/xml-parse-handler [

        content: copy ""

        characters: func [ characters [string! none!] /local trimmedContent][
            if all [found? characters not empty? (trimmedContent: trim characters)][
                append content trimmedContent

        name: class: description: ""
        guid: parent: amount: act: none

        end-states: [
            "act:id"          [guid:   to word! content]
            "act:name"        [name:       copy content]
            "act:type"        [class:      copy content]
            "act:parent"      [parent: to word! content]
            "gnc:account"     [set guid reduce [name class parent]]

        end-element: func [
	    ns-uri	[string! none!]  ns-prefix [string! none!]
	    local-name	[string! none!]  q-name    [string!]
            switch q-name end-states
            clear head content
    ] ; handler
] ; gnucash

It’s a lot shorter. Granted, it’s specifically coded for certain elements, but it should be apparent from the definition of end-states how we could build it dynamically. end-states is just a block! that end-element uses as an argument to its switch function. And because of the schema, I can process everything in the end-element function. In other words I know the other elements in my end-states are all inside gnc:account. As end-element encounters any of the element names listed in end-states, it performs the corresponding block of actions. I used the fully qualified name, because many local-names are reused among GnuCash’s namespaces.

BTW, I really like how this works: if all [found? characters not empty? (trimmedContent: trim characters)]; we make sure we have a string and then trim whitespace, appending if there’s something left over.  Compare to your favorite language.

For the string! values, I have to copy the content, otherwise what’s being referenced keeps changing.  But for word! values I didn’t bother with copy; my deduction is that to word! gives me a copy of the value as a word! type.  And when we reach the end of “gnc:account” we know we have everything, so we set a word! for this account’s guid and set that word! to the value of this account with:
set guid reduce [name class parent]
If I pick one of the account guids from my file and probe for it, I can see it’s been set in REBOL:

>> probe get to word! "2775e139d4404298cf73e6316db71cbd"
["Taxable" "INCOME" 921eddc8eb3349d0a818ef6e52417b81]

(We have to use to word! and enter the value as a string so REBOL’s console doesn’t try to interpret it as some other type just based on the characters.)
Where it gets interesting is if I print the value in certain ways. The guid “parent” reference (3rd item in the block) gets dereferenced for me automatically. It’s a word and it has a value.

>> print remold get to word! "2775e139d4404298cf73e6316db71cbd"
["Taxable" "INCOME" ["Investment" "INCOME" e84eba6fde334896d99a24c62d1162d3]]

I think that’s pretty neat.

PIM using REBOL 2.1 more parsing

Since REBOL is new to me, I continue to experiment with the syntax to see what feels right to me in terms of the fundamental criteria – low coupling and high cohesion. I simplified the high-level rule and put some of the processing in the lower level matching rules. I added some flexibility in ordering of terms in the input, but you have to have the description last; since it’s intended to allow any input it will greedily match the date. I think the date format needs to eventually change. I’m ignoring the definitions of the lowest level terms from here on (letter, digit, etc.) except to say that the rules for day and month now set local values:

    day:      [copy the-day [ [#"1" | #"2" | #"3"] [#"0" | #"1"] | 
                              [#"1" | #"2"] digit | digit ] ]
    month:    [copy the-month [ "jan" | "feb" | "mar" | "apr" | "may" | "jun" | 
		                "jul" | "aug" | "sep" | "oct" | "nov" | "dec" ] ]

It may not be proper style to be clearing things at the beginning of the sentence rule, but it works and is clear. I got rid of the subject handling we had before, and I’m trying out a handler concept for tags. All we do when the rule completes now is print the parsed terms.

sentence: [( the-date: now/date
             the-subject: copy ""
             the-desc: copy ""
             the-amount: none
             the-tags: copy [] )
    some [ [some ws] | ["on" some ws date] | subject | amount | copy the-desc desc] 
    (print [the-date the-desc the-subject the-amount the-tags])

I changed the main rule to use some around a choice, instead of a bunch of alternative choices. The terms are set by the individual rules like date and desc:

desc:    [some [tag | some name-char | ws]]
tag:     [copy a-tag [ #"#" some name-char ] (tag-handler a-tag append the-tags a-tag)]
subject: [copy the-subject [ #"@" letter any name-char ]]
amount:  [copy the-amount [ opt sign some digit opt [ "." 0 2 digit ] ] 
             (the-amount: to decimal! the-amount)]
date:    [[ day | month ] [ [some ws] | #"/" ] [ month | day ] (
             default: now
             the-date: either (current: to date! rejoin [the-day "-" the-month "-" default/year]) > (default/date + 200)
                 [to date! rejoin [ the-day "-" the-month "-" (default/year - 1) ]]
             clear the-day clear the-month

You can see the tag-handler called in the tag rule. We’ll get to that. The date rule calculates the year automatically; if it’s more than 200 days in advance, we assume last year. My default tag-handler is expecting the current tag, and the accumulated tags block.

tag-handler: func [
    tag [string!]
    tags [block!]
    print ["tag found" tag "/" tags "/"]

There’s multiple ways in REBOL for us to supply that tag-handler, which we’ll look at as our needs become clearer. Run the rule with a reasonably human sentence:

>> parse/all “@visa on 19 Dec -230.21 #statefarm #insurance for #condo interior contents” iAm/sentence
tag found #statefarm / #statefarm /
tag found #insurance / #statefarm #insurance /
tag found #condo / #statefarm #insurance #condo /
19-Dec-2011 #statefarm #insurance for #condo interior contents @visa -230 #statefarm #insurance #condo
== true

Looking at it now, I might like moving the copy commands from the sub-rules back into sentence. I plan soon to import all my GnuCash transactions into a REBOL format I can query and add to. I’ll need to be able to query and edit them, and I’m hoping that it’s unnecessary to have a bunch of hierarchical expense and income accounts.

It hit me today that tagging allows “multiple inheritance” in contrast to hierarchies of organization.

Up until the last few years I’ve been doing my own taxes, and the only useful information I can recall needing is totals for certain categories of spending. Which forces me to categorize the transaction under only that applicable account. I’d rather tag it with meaningful attributes, among them #tax #deduction.

PIM using REBOL 2

I’ve updated the parsing rules. I find the REBOL parse dialect to be a level above regular expressions. I know people who are regex Jedi masters, but it really isn’t comparing apples to oranges. In REBOL’s BNF-like parse rules, one can define a grammar. Recursive and self-referential productions are possible — just like in human language. The parse rules most often contain REBOL code, surrounded by (), to execute at important points.

sentence: [ 
    [subject | date | amount] some ws 
    [subject | date | amount] some ws 
    [subject | date | amount]
        either found? ctx: find subjects the-subject [
            do ctx/2
            append subjects compose/only/deep [ 
                (the-subject) [print ( reform ["!!!! found" the-subject] )] 

        print [ "DATE:" the-month the-day "ACCOUNT" the-subject "AMOUNT" the-amount ]

subject: [copy the-subject [ #"@" letter any name-char ]]
date: 	 ["on" some ws copy the-day day some ws copy the-month month]
amount:  [copy the-amount [ opt sign any digit opt ["."] 0 2 digit ]]

day: 	 [	[#"1" | #"2" | #"3"] [#"0" | #"1"] | 
		[#"1" | #"2"] digit | 
month: 	[ "jan" | "feb" | "mar" | "apr" | "may" | "jun" | 
	  "jul" | "aug" | "sep" | "oct" | "nov" | "dec" 

ws: 	   charset reduce [tab newline #" "]
sign: 	   [ #"+" | #"-" | none ]
name-char: [ letter | digit ]
letter:	   charset [#"A" - #"Z" #"a" - #"z"]
digit:	   charset [#"0" - #"9"]

(I don’t like the way I implemented allowing the terms in any order, but it can wait.)

You can see copy in the definitions for subject, date and amount to set a word (each beginning with the-) to the value that matches the expression that follows. At the end we process or collect the-subject.  If already found we can execute some code that’s unique to that subject. Otherwise I’m saving it and some stupid code to print that it was found. This is the code that will get executed when we find it later. The really cool thing about this (and it could be done in REBOL a number of ways) is that code is built at run time, as specifically tailored as you need it to be.

form, reform, mold, remold, join, rejoin, reduce and compose still confuse me some, so I always have the language reference open :), but here we use compose to reduce (evaluate, sort of) the items in its argument that are surrounded by (). The /only refinement leaves blocks it finds as blocks. Inner blocks are processed with the /deep refinement.

compose/only/deep [ (the-subject) [print ( reform ["!!!! found" the-subject] )] ]

I’ll use a journal block to save an audit trail of the statements being parsed and a block to hold the accumulated accounts/contexts:

journal:  copy [] ; use copy to initialize
subjects: copy []

and a set of sample statements:

samples: [
	"@discover on 11 Dec -13.22"
	"@discover -56.47 on 17 Dec"
	"on 13 Jan -1.20 @visa"
	"on 4 Jan @checking -1.01"

Now call parse with each of the statements, and see that we can gather the information we’ve set up rules for. The formatted lines show the values being copied to the correct words.

>> foreach statement samples [
[    either parse/all statement sentence [repend journal [ now statement ]][ print ["*** Couldn't parse" statement ]]
[    ]
DATE: Dec 11 ACCOUNT @discover AMOUNT -13.22
!!!! found @discover
DATE: Dec 17 ACCOUNT @discover AMOUNT -56.47
DATE: Jan 13 ACCOUNT @visa AMOUNT -1.20
DATE: Jan 4 ACCOUNT @checking AMOUNT -1.01

Print the journal where we saved every statement with a timestamp.

>> forskip journal 2 [print ["timestamp" journal/1 "statement" journal/2]]
timestamp 29-Dec-2011/21:07:48-7:00 statement @discover on 11 Dec -13.22
timestamp 29-Dec-2011/21:07:48-7:00 statement @discover -56.47 on 17 Dec
timestamp 29-Dec-2011/21:07:48-7:00 statement on 13 Jan -1.20 @visa
timestamp 29-Dec-2011/21:07:48-7:00 statement on 4 Jan @checking -1.01

The subjects block which contains the subjects that were encountered and a small code block to execute for each.

>> probe subjects
== ["@discover" [print "!!!! found @discover"] "@visa" [print "!!!! found @visa"] "@checking" [print "!!!! found @checking"]]

Let’s look at some of the other ways we can build the tiny code block that gets appended to subjects. It could be coded:

compose/only/deep [ (the-subject) [print ( reduce ["!!!! found" the-subject] )] ]
>> probe subjects
== ["@discover" [print ["!!!! found" "@discover"]] "@visa" [print ["!!!! found" "@visa"]] "@checking" [print ["!!!! found" "@checki..

The the runtime output looks the same, e.g. !!!! found @discover. But if the block will be executed a lot, having print join the two strings intuitively takes a little longer. Or it could have been coded as:

compose/only/deep [ (the-subject) [print  [ "!!!! found" (the-subject) ]] ]
>> probe subjects
== ["@discover" [print ["!!!! found" "@discover"]] "@visa" [print ["!!!! found" "@visa"]] "@checking" [print ["!!!! found" "@checki..

Which creates the same block as well as the same output.

The trick to picturing the evaluation is the parentheses. Some of my background languages make heavy use of macros, and that’s how I think of the (expression). What’s great to me is the preprocessor language is the same REBOL command dialect I use for everything else (REBOL script source is actually a dialect, too. There are no keywords.) Compose and related block-evaluating functions let me customize code blocks that will be executed many times. I can eliminate any redundant conditionals.

PIM using REBOL 1

OK. Something new. Ideas beget [usually bigger] ideas, before they’re even fully hatched.
I took a look at finance41 this last weekend. Very cool. It gains both simplicity and freedom for the user in terms of input. You can see a resemblance between its syntax and other formats like tweets and RTM for GTD.

It makes me wonder about financial software I use and the style I think we’re accustomed to.  With strict representations not only of specific bank accounts, et al., but also of categories of spending.  Hierarchical categories (categorization is another topic to hit on big time) , arguably the least useful way to find things.  In finance41, you decide what’s an account and what tags you want to apply to a transaction.  In that environment, it makes sense to make accounts only for your assets and liabilities and use tags for expenses and incomes.

It allows a query by multiple tags or defining macro tags, allowing me to slice and dice the data any way (that’s not available in f41, yet).  An example transaction in f41 is

@visa -119.23 on 23 Jul tile cutter from ace hardware #home #diy #kitchen

(Anything that’s not a date, amount, account or tag goes in the description field — but the separate items can appear in any order).  I can’t for the life of me see now why I need to be so formal as I have about the expense and income categories. I know what you’re thinking, “won’t that record the transaction in multiple registers and cause integrity issues?” Not if there’s no register or journal representing an expense or income. Just scan the transactions based on tags, which could be predefined in sets for convenient reporting.

Another thing I’d add is allow the tags and description to interleave.

And vastly expand the idea using REBOL.

I wrote these parse rules:

sentence:    [ copy the-subject subject 
               date-rule amount-rule (find-ctx the-subject)]
subject:     [ #"@" letter-char any name-char ]
date-rule:   [ thru "on" 
               some ws copy the-day day-rule 
               some ws copy the-month month-rule 
amount-rule: [ opt sign-char any digit-char opt ["."] 0 2 digit-char ]
day-rule:    [ [#"1" | #"2" | #"3"] [#"0" | #"1"] |
               [#"1" | #"2"] digit-char |
month-rule:  [ "JAN" | "FEB" | "MAR" | "APR" | "MAY" | "JUN" |
               "JUL" | "AUG" | "SEP" | "OCT" | "NOV" | "DEC"
ws:          charset reduce [tab newline #" "]
name-char: [ letter-char | digit-char ]
letter-char: charset [#"A" - #"Z" #"a" - #"z"]
sign-char: [ #"+" | #"-" | none ]
digit-char: charset [#"0" - #"9"]

and a sample sentence of:

sampleStatement: “@visa on 19 Dec -230.21”

In the sentence rule, we call find-ctx with the value we found for @visa (or anything else beginning with ‘@’). find-ctx looks like

find-ctx: func [{find, inseet or update context} ctx [string!]][
	either found? ctx: find myContexts new-ctx: copy ctx [
		do ctx/2
		print ["new context" new-ctx] 

(BTW the parse is returning false right now, which means it’s not completing properly, but it is getting all my data, and since we’ll be adding a bunch of stuff to this, I’m not going to sweat it.)

>> myContexts: [] parse/all sampleStatement sentence
new context @visa
== false
>> myContexts: ["@visa" [print "a visa charge"]] parse/all sampleStatement sentence
a visa charge
== false

When a context is found we can execute code specifically for it. I hope to gather information as they’re added to make some sort of meta connections.

REBOL interface to GnuCash: Working with the GnuCash XML

Now to take the file we created and start messing with it.

I found an option to turn off compressing the data file for GnuCash, so I want to focus on parsing the XML.

There’s a few REBOL scripts that work with XML; I chose xml-parse.r.  Initially I used its builtin block parsing methods to turn the XML into REBOL blocks that I could work with, but I decided that parsing the entire document into blocks first, just so I could try random REBOL series! functions against it would be a huge waste.  So, I ended up writing a gnucash handler to look for specific elements as they’re parsed and save the child nodes of each as key/value pairs.

As a first try I want to supply the source XML and a request/response block!:
>> gnucash/list read %2011.xml [“gnc:transaction” []]

rebol [
  Title: "Process GnuCash data file"
  File: "%parse-gc-xml.r"
  Date:  13 Dec 2012
  Purpose: {
    interface to gnucash data

do %xml-parse.r
gnucash: make xml-parse/parser [
    subjects: copy []
    content: copy ""
    row: none
    set-namespace-aware false

    list: func [ gc-xml [string!] subjects [block!] ][
        self/subjects: copy subjects
        parse-xml gc-xml
        return self/subjects
    handler: make xml-parse/xml-parse-handler [

        characters: func [ characters [string! none!] ][
            if all [ 
                found? row 
                found? characters 
                append content characters
        start-element: func [
            ns-uri [string! none!]
            local-name [string! none!] q-name [string!]
            attr-list [block!]
            clear head content
            if found? rowset: select subjects q-name [ 
                append/only rowset row: copy []
        end-element: func [
            ns-uri [string! none!]
            local-name [string! none!] q-name [string! ]
            if found? row [ 
                either select subjects q-name 
                    [ row: none ][ repend row [q-name copy content] ]
    ] ; handler
] ; gnucash

Try it out.
>> do %parse-gc-xml.r
>> trns: gnucash/list read %../2011.xml [“gnc:transaction” []]
== [“gnc:transaction” [[“trn:id” “a1ddec4b4fe26e84745a8cddac018620” “cmdty:space” “ISO4217” “cmdty:id” “USD” “trn:currency” “” “ts:…
>> trns/1
== “gnc:transaction”
>> trns/2
== [[“trn:id” “a1ddec4b4fe26e84745a8cddac018620” “cmdty:space” “ISO4217” “cmdty:id” “USD” “trn:currency” “” “ts:date” “2011-01-03 0…
>> trns/2/1
== [“trn:id” “a1ddec4b4fe26e84745a8cddac018620” “cmdty:space” “ISO4217” “cmdty:id” “USD” “trn:currency” “” “ts:date” “2011-01-03 00…
>> trns/2/2
== [“trn:id” “1849b854cd0ad3fbbddc94f3d661672a” “cmdty:space” “ISO4217” “cmdty:id” “USD” “trn:currency” “” “ts:date” “2011-05-20 00…

So we can think of trns as having “rows” of gnc:transaction attributes.  Precisely, its second entry is the returned rowset.  If I want the date from the second transaction, I can.

>> select trns/2/2 “ts:date”
== “2011-05-20 00:00:00 -0600”

We can collect multiple rowsets with one call by adding additional element/block! pairs in the argument.

>> result: gnucash/list read %../2011.xml [“gnc:commodity” [] “gnc:account” []]
== [“gnc:commodity” [[“cmdty:space” “ISO4217” “cmdty:id” “USD” “cmdty:get_quotes” “” “cmdty:quote_source” “currency” “cmdty:quote_t…
>> acts: find result “gnc:account”
== [“gnc:account” [[“act:name” “Root Account” “act:id” “6197eee7c4c8c51c914cd3aa4114ef44” “act:type” “ROOT”] [“act:name” “Expenses”…
>> acts/1
== “gnc:account”
>> select acts/2/1 “act:name”
== “Root Account”

REBOL interface to GnuCash: Uncompressing the data

I first saw REBOL in about 1999 and immediately thought it was about the bitchinest thing since function pointers let me implement polymorphism in C (and assembler).  I am going to try once again, now that I have some free time, to learn it.  As part of a much bigger idea, I want to work with GnuCash files.

GnuCash (I am using version 2.4.8) puts all its data in a compressed file.  I used 7-Zip to check it out and that’s what I’ll use to uncompress it, since I couldn’t get the native REBOL scripts I found to work — but that’s OK, because I can call 7-Zip’s command line version from REBOL.

(Check out how to install on your machine on the REBOL site, please.)

And especially, before I start, I have frankly struggled with this language in the past.  I’ve read that REBOL can be harder to learn if you have a lot of traditional programming experience.

The script should let me select the GnuCash compressed data file to be used as 7-zip’s input and show me the uncompressed data.

Request input file

Ask for GnuCash input file to uncompress

rebol [
    Title: "Process GnuCash data file"
    File: "%rebcash.r"
    Date: 01 Dec 2012
    Purpose: {
        interface to gnucash data

if gc-source-file:
    "Select GnuCash File" "OK" "*.gc" [
    call/console reform [ 
        "C:\Program Files\7-Zip\7z.exe" 'e '-tgzip '-so '-y
            rejoin [ {"} to-local-file clean-path gc-source-file {"} ]

The -so option sends output to stdout.

The /console refinement on call lets me see its output in the REBOL interpreter.

The REBOL rejoin function evaluates the terms in the block and returns a concatenated string of results.  Some magic file system functions are used to turn the REBOL file! type that was returned by request-file into a path the host can be happy with.

So for a simple scripting language, that’s great. Here’s what starts dumping to my screen:

Output from 7-zip

Output in console window

%d bloggers like this: