IFCX Wings for Scripting
The WYSIWYG Literate Programming System
Version 0.6-SNAPSHOT
this is Free Software, copying authorized per the GNU General Public License Version 2.
Table of Contents
IFCX Wings (http://www.ifcx.org/wiki/Wings.html) aims to bring the aesthetics and ease-of-use of Mathematica's notebook-oriented environment to the Open Source Software world and to do so for arbitrary programming and scripting languages. As a preliminary proof-of-concept, this OpenOffice Writer document contains a macro implemented using Groovy For OpenOffice (http://www.ifcx.org/wiki/GroovyForOpenOffice.html) that enables Groovy scripts to be evaluated in-place. This little mash-up currently does nothing intelligent with syntax, nor does it do any of the cool display formatting that is possible (it simply displays the result of the last expression as a string). Also it is limited to just Groovy script (whereas the idea is to support any language) and the execution environment is within OpenOffice (which has some special potential uses, but a client/server implementation will have more general uses and fewer quirks).
The way Wings works is that you enter some Groovy script into paragraphs with the 'GroovyCode' style, then invoke the 'Wings:WingsEval.groovy' macro (which is bound here via 'Tool:Customize...' to the cmd-Enter key as well as the 'Wings:Evaluate' menu). The result of the last expression will then be displayed as a string in a paragraph with the 'GroovyResult' style. The cursor will then move to the next 'GroovyCode' paragraph, unless an exception occurs in which case it will not move.
If the key binding doesn't work for you (they don't seem to be preserved very well across releases and/or platforms), then you can recreate it (unless you're using OOo 2.2 on which key binding to macros stored in documents is broken) via the 'Tools:Customize...' dialog. Choose the "Keyboard" tab, then scroll the "Category" pane all the way to the bottom to find the 'OOo Macros' entry. Twiddle the triangles to locate the 'Wings:GroovyEval.groovy' macro in this 'Wings-0_x' document and set the key chord you like.
A simple example:
1 + 2 * 3
7
1 / 2
0.5
If an exception or syntax occurs, that will be displayed in a 'GroovyException' styled paragraph and the cursor will remain where it was.
1 / 0
java.lang.ArithmeticException: / by zero
If you try to do an evaluation and the cursor is not located in a 'GroovyCode' paragraph, then an empty one will be inserted at the cursor's location.
Beware that you should turn off quote replacement in 'Tools:AutoCorrect...:Custom Quotes' or you will get funky syntax errors. Automatic "uncorrection" is one of the features that need to be added to the macro. Another desirable feature is to make undo work as a single action, unfortunately the OpenOffice API does not currently allow that.
(0..10).collect { it ** 10 }
[0, 1, 1024, 59049, 1048576, 9765625, 60466176, 282475249, 1073741824, 3486784401, 10000000000]
((Integer.MAX_VALUE-5)..Integer.MAX_VALUE).collect { Math.pow(it, 2) }
[4.6116859926575841E18, 4.6116859969525514E18, 4.6116860012475187E18, 4.611686005542486E18, 4.6116860098374533E18, 4.6116860141324206E18]
def factorial(n) {
BigInteger
f = 1
for (i in 2..n) { f *= i
}
f
}
/* The limit here is just over
5000! because OpenOffice currently can't wrap a single "word"
longer than 16383 (2**14 - 1) characters. */
def r = factorial(5)
120
def ackermann(m, n) {
if
(m <= 0)
return n + 1;
if
(n <= 0) /* m will be > 0 here */
return
ackermann(m-1, 1);
/* both m and n will be > 0
*/
return ackermann(m-1, ackermann(m, n -
1));
}
ackermann(3, 5)
253
//BigInteger big(BigInteger n) { n }
//
Integer is big enough because we'll run out of space before the
numbers get big.
Integer big(Integer n) { n }
hackCache =
[([big(0), big(0)]):big(1)]
//def hackermann(BigInteger m,
BigInteger n) {
def hackermann(Integer m, Integer n) {
def
cache = hackCache
def
stack = [[m, n]]
while (stack) {
def
top = stack.pop()
if
(cache[top]) {
} else if
(top[0] == 0) {
cache[top]
= big(top[1] + 1)
} else if
(top[1] == 0) {
def
r = cache[[big(top[0].minus(1)), big(1)]]
if
(r) {
cache[top]
= r
} else
{
stack.push(top)
stack.push([big(top[0].minus(1)),
big(1)])
}
}
else {
def r
= cache[[top[0], big(top[1].minus(1))]]
if
(r) {
def
s = cache[[big(top[0].minus(1)), r]]
if
(s) {
cache[top]
= s
}
else
{
stack.push(top)
stack.push([big(top[0].minus(1)),
r])
}
}
else {
stack.push(top)
stack.push([top[0],
big(top[1].minus(1))])
}
}
}
return
cache[[m, n]]
}
// With some hacking we can get a little
further. Careful, this takes a while to run...
// hackermann(4,
1)
hackermann(2, 1)
5
results = []
10.times { results <<
it } // Change the 10 to something else to see an assertion failure.
assert results == [0,1,2,3,4,5,6,7,8,9]
results
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Wings can handle standard output and error now. Input is RSN.
10.times { println it }
0
1
2
3
4
5
6
7
8
9
The Iterable.grep(Filter) method returns a list of those elements where the filter.isCase(element) returns Groovy true.
zf = new java.util.zip.ZipFile(new
File('/Users/jim/Projects/Groovy/groovy-1.0/embeddable/groovy-all-1.0.jar'))
zf.entries().grep
{ it =~ /Ant/ }.sort()
[org/codehaus/groovy/antlr/AntlrParserPluginFactory.class, org/codehaus/groovy/antlr/AntlrParserPlugin$1.class, org/codehaus/groovy/antlr/AntlrASTProcessor.class, org/codehaus/groovy/antlr/syntax/AntlrClassSource.class, org/codehaus/groovy/antlr/AntlrParserPlugin.class, org/codehaus/groovy/antlr/AntlrSourceSummary.class, org/codehaus/groovy/antlr/AntlrASTProcessSnippets.class, org/codehaus/groovy/ant/AntProjectPropertiesDelegate.class, groovy/util/AntBuilder.class, groovy/util/AntBuilderLocator.class]
Ant is a great way to do things with files. The Groovy AntBuilder supplies the fileScanner pseudo-task that lets you iterate over filesets. Filesets have filtering options to cover pretty much every conceivable situation. Note that the type to supply to attributes like 'basedir', 'dir', or 'file' is java.io.File (rather than simply a path string).
ant = new AntBuilder()
(ant.fileScanner
{
fileset(dir:'${user.home}/Projects/Groovy/groovy-1.0/src/test/gls',
includes:'**/*.groovy')
}).collect { it }
[/Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/CompilableTestSupport.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/ch06/s05/GName1Test.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/ch08/s04/FormalParameterTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/ch08/s04/RepetitiveMethodTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/scope/BlockScopeVisibilityTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/scope/ClassVariableHidingTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/scope/CompilableTestSupport.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/scope/MultipleDefinitionOfSameVariableTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/scope/NameResolvingTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/scope/StaticScopeTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/syntax/AssertTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/syntax/OldClosureSyntaxRemovalTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/syntax/OldPropertySyntaxRemovalTest.groovy]
def glob(File dir, String pat)
{
(new AntBuilder().fileScanner {
fileset(dir:dir,
includes:pat)
}).collect { it }
}
glob
(new File('/Users/jim/Projects/Groovy'),
'groovy-1.0/src/*/gls/*/*.groovy')
[/Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/scope/BlockScopeVisibilityTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/scope/ClassVariableHidingTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/scope/CompilableTestSupport.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/scope/MultipleDefinitionOfSameVariableTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/scope/NameResolvingTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/scope/StaticScopeTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/syntax/AssertTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/syntax/OldClosureSyntaxRemovalTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/syntax/OldPropertySyntaxRemovalTest.groovy]
If you like a method so much that you want to do Groovy's trick of "adding methods" to an existing class that you can't otherwise extend, you can use the Category feature. A Category is a class, which when specified in a 'use (category [, category]*) { statements }' statement, causes the static methods to be matched by their first parameter.
class GlobberCat {
static
Iterator glob(File dir, String pat) {
(new
AntBuilder().fileScanner {
fileset(dir:dir,
includes:pat)
}).iterator()
}
}
use
(GlobberCat) {
new
File('/Users/jim/Projects/Groovy').glob('groovy-1.0/src/*/gls/*/*Test.groovy').collect
{ it }
}
[/Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/scope/BlockScopeVisibilityTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/scope/ClassVariableHidingTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/scope/MultipleDefinitionOfSameVariableTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/scope/NameResolvingTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/scope/StaticScopeTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/syntax/AssertTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/syntax/OldClosureSyntaxRemovalTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/syntax/OldPropertySyntaxRemovalTest.groovy]
This is some ordinary text.
class Country
{
String
name
String capital
}
def world = [new
Country(name:"Austria", capital:"Vienna")
,
new Country(name:"Canada", capital:"Ottawa")
,
new Country(name:"United States of America",
capital:"Washington, DC")]
world.collect { country
-> "The capitol of ${country.name} is *in*
${country.capital}." }
[The capitol of Austria is *in* Vienna., The capitol of Canada is *in* Ottawa., The capitol of United States of America is *in* Washington, DC.]
Groovy supports literal values for Maps and Lists. A new instance is created for each evaluation of the literal (unlike String, GString?, and Class literals).
def v = [*0..2]
def u = [*0..2]
def
x = [:]
2.times { u[it] = [:] ; v[it] = x }
[u[0].is(u[1]),
v[0].is(v[1])]
[false, true]
Fun with closures. Collection.inject takes an initial value and a closure for two parameters. The first parameter is the previous (or initial) value and the second is the current element of the collection. The result is the last value returned.
def max =
[1,2,31,-5,99,15].inject(null) { lastNum, num ->
(lastNum
< num) ? num : lastNum
}
assert max == 99
max
99
Null is less than any number.
null > Integer.MIN_VALUE
false
The java.lang.String.split method chops up a string given a string that is a regex pattern and returns an array of strings. Notice the curly braces in the printed result. Also Groovy's string quote stripping doesn't descend into the array elememts.
def twister = "She sells sea
shells at the sea shore of Seychelles."
twister.split(/ /)
{"She", "sells", "sea", "shells", "at", "the", "sea", "shore", "of", "Seychelles."}
This uses Groovy's regex matcher operator and a pattern which matches runs of "word" class characters. A matcher is iterable, but doesn't have a helpful printed form, so we run collect on it to gather up the matching strings into a list. Notice the square brackets indicating a list and the quotes are stripped from the elements that are strings.
def twister = "She sells sea
shells at the sea shore of Seychelles."
(twister =~
/\w+/).collect { it }
[She, sells, sea, shells, at, the, sea, shore, of, Seychelles]
The following is adapted from Mastering Regular Expressions (3rd Edition) by Jeffrey Friedl (Chapter 8; Java, pg 378):
String url =
'http://regex.info:9090/blog'
String regex = '(?x) ^(https?)://
([^/:]+) (?::(\\d+))?'
java.util.regex.Matcher m =
java.util.regex.Pattern.compile(regex).matcher(url);
def
result
if (m.find())
{
result = """Overall
[${m.group()}] (from ${m.start()} to ${m.end()})
Protocol
[${m.group(1)}] (from ${m.start(1)} to ${m.end(1)})
Hostname
[${m.group(2)}] (from ${m.start(2)} to ${m.end(2)})
"""
+
// Group #3 might not have participated, so we must
be careful here
((m.group(3) == null) ? "No
port; default of '80' is assumed"
:
"Port is [${m.group(3)}] (from ${m.start(3)} to
${m.end(3)})")
}
result
Overall [http://regex.info:9090]
(from 0 to 22)
Protocol [http] (from 0 to 4)
Hostname
[regex.info] (from 7 to 17)
Port is [9090] (from 18 to 22)
This is a slightly modified version with a few more Groovyisms.
String url =
'http://regex.info/blog'
// Note a correction from original regex
in the text which ended: ... (?:(\\d+))?
String regex = '(?x)
^(https?):// ([^/:]+) (?::(\\d+))?'
def matcher = url =~
regex
def result
if (matcher.find())
{
result
= """Overall [${matcher.group()}] (from
${matcher.start()} to ${matcher.end()})
Protocol
[${matcher[0][1]}] (from ${matcher.start(1)} to
${matcher.end(1)})
Hostname [${matcher[0][2]}] (from
${matcher.start(2)} to ${matcher.end(2)})
""" +
// Group #3 might not have participated, so we must
be careful here
((matcher[0][3] == null) ? "No
port; default of '80' is assumed"
:
"Port is [${matcher[0][3]}] (from ${matcher.start(3)} to
${matcher.end(3)})")
}
result
Overall [http://regex.info] (from 0
to 17)
Protocol [http] (from 0 to 4)
Hostname [regex.info]
(from 7 to 17)
No port; default of '80' is assumed
This CSV regex processor is also from Mastering Regular Expressions.
def csvregex =
/\G(?:^|,)(?:"([^"]*+(?:""[^"]*+)*+)"|([^",]*+))/
def
matcher = '1," 4311400","GOURMET COFFEE SYSTEMS,
INC", 33046' =~ csvregex
def quotes = '' =~ /""/
def
fields = []
while (matcher.find()) {
if
(matcher.start(2) >= 0) {
fields.add(matcher.group(2))
}
else
{
fields.add(quotes.reset(matcher.group(1)).replaceAll(/"/))
}
}
fields.collect
{ field -> "*$field*" }
[*1*, * 4311400*, *GOURMET COFFEE SYSTEMS, INC*, * 33046*]
Here it is in a few less lines...
def instring = '1,"
4311400","GOURMET COFFEE SYSTEMS, INC", 33046'
def
csvregex = /\G(?:^|,)(?:"([^"]*+(?:""[^"]*+)*+)"|([^",]*+))/
def
quotes = '' =~ /""/
def fields = []
def
matcher = instring =~ csvregex
while (matcher.find())
{
fields.add((matcher.start(2) >= 0) ?
matcher.group(2) :
quotes.reset(matcher.group(1)).replaceAll(/"/))
}
fields.collect
{ field -> "*$field*" }
[*1*, * 4311400*, *GOURMET COFFEE SYSTEMS, INC*, * 33046*]
The plus operator is overloaded for Date.
new Date() + 7
Tue May 13 14:58:14 PDT 2008
But oddly enough minus is not (yet).
new Date() - new Date('1/1/2007')
groovy.lang.MissingMethodException: No signature of method: java.util.Date.minus() is applicable for argument types: (java.util.Date) values: {Mon Jan 01 00:00:00 PST 2007}
map = ['a':1, 'b':2, 'c':3]
doubler
= { key, value -> map[key] = value * 2 }
map.each (doubler)
map
{a=2, b=4, c=6}
Modifying the value property of a Map.Entry modifies the underlying map (unless it is immutable of course).
Iterating on a Map using a closure of one argument gives the argument the Map.Entry.
map = ['a':1, 'b':2, 'c':3,
'd':5]
map.each { it.value *= 4 }
map
{a=4, b=8, c=12, d=20}
map = ['a':1, 'b':2, 'c':3,
'd':5]
map.each { it.value += it.key.toUpperCase() as int }
map
{a=66, b=68, c=70, d=73}
If the closure has two arguments, then the first gets the Map.Entry.key and the second gets the Map.Entry.value.
['a':1, 'b':2, 'c':3].collect { key, value -> value + 10 }
[11, 12, 13]
Subscripting a list using a range results in a sublist. Negative numbers in ranges refer to the end of the list (the last element is -1, the one before that is -2, etc.).
If you have a collection and then access it as a property or a method call, and that property or method call doesn't exist for the collection's class, then the property acces or method call will be performed on each element of the collection and the resulting values are collected and returned as a list.
def tokens = [[num:1], [num:2],
[num:3], [num:4], [num:5]]
assert tokens[-1..-3] == [[num:5],
[num:4], [num:3]]
assert tokens[-1..-3].num == [5, 4, 3]
assert
tokens[-3..-1].num == [3, 4, 5]
assert tokens[-1..-3].num.sum() ==
12
Ranges are lists.
[(10..20).contains(5), (10..20).contains(21)]
[false, false]
[(10..20).contains(15), (10..20).contains(20), (10..20).contains(10)]
[true, true, true]
The elements of a range are not ranges.
(10..20).contains(14..16)
false
So to find out if one range subsumes another, then test if all the elements are in the list (Collection.containsAll).
(10..20).containsAll(14..19)
true
(10..20).containsAll(15..25)
false
Groovy has inclusive and exclusive range limits. It also has relative-to-the-end-of-a-list indicies which are negative values. Ranges can have negative limits of course, but you can’t combine the two in quite the way you might expect.
[(14..-1), (-14..-1), (14..<-1), (-14..<-1), (14..0), (-14..0), (14..<0), (-14..<0)]
[14..-1, -14..-1, 14..0, -14..-2, 14..0, -14..0, 14..1, -14..-1]
(1..3).class
class groovy.lang.IntRange
(1..3).properties
{reverse=false, to=3, class=class groovy.lang.IntRange, from=1, toInt=3, fromInt=1, empty=false}
[(14..<-1), (-14..<-1), (14..<0), (-14..<0)]
[14..0, -14..-2, 14..1, -14..-1]
def a = [1,2,3,4,5]
a[1..-2]
[2, 3, 4]
def a = [1,2,3,4,5]
def r =
1..<-1
a[r]
[2, 1]
At the SDJUG they neeeded a random number for the raffle...
list =
[*1..33]
Collections.shuffle(list)
list
[24, 8, 9, 5, 29, 23, 2, 3, 30, 27, 15, 32, 25, 26, 7, 19, 11, 13, 17, 20, 33, 21, 10, 14, 6, 18, 4, 16, 31, 12, 28, 1, 22]
One of Groovy's more amusing features is the 'execute' method on strings, which returns a j.l.Process. Groovy then has the helper method 'getText' which will gather up the output of the process into a string. These little examples usually won't do much on Windows (unless you've done something clever with Cygwin), but you can substitute the name of your favorite executable (like command.exe) too make it go.
'uname'.execute().text
Darwin
'ls /Users/jim/Movies'.execute().text
2004_Strictly_West_Coast.wmv
48InchCrash.mpg
Easter
Bunnies.mpg
MicroFlyingBot.wmv
Other
Robotics.mov
Thumbs.db
fillmaff.wmv
genesis.mov
jtgma.wmv
santa1.mpeg
santa2.mpeg
Of course that uses the system's exec mechanism, so we miss nice things shell features like wild cards. Working through a shell is a way to solve that.
commandList = [ 'ls -d ~/[a-l]*', 'set
| grep HOME' ]
process = [ 'bash' , '-c' , commandList.join ( ';'
) ].execute ( )
process.text
/Users/jim/environment.plist
/Users/jim/filter.groovy
/Users/jim/foo.html
/Users/jim/groovy
/Users/jim/ifcx-properties.xml
BASH_EXECUTION_STRING='ls
-d ~/[a-l]*;set | grep
HOME'
HOME=/Users/jim
JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home
OPENOFFICE_MOZILLA_FIVE_HOME=/Applications/NeoOffice.app/Contents/MacOS
For mathematical folks, being able to handle formulas for both input and output by integrating with symbolic math engines, which can be done using the scripting engine framework API just like we do for any other language, would be the cat's meow. Of course there are other language processors that Wings aims to integrate such as Scilab, GNU Octave, Rlab, Matrex, Matlab, etc. that would be of more interest to folks who like formulas.
// Formula's text form: (199 + 2.6
%e) over %pi
(199 + 2.6 * Math.E) / Math.PI
65.59333289709824
words = []
['one two three', 'four',
'+', 'five six'].join().eachMatch(/\w+/) { words += it[0] }
assert
words == ['one', 'two', 'threefour', 'five', 'six']
println words
[one, two, threefour, five, six]
print 'Hello World'
Hello World
([*0..4]).each { println it }
0
1
2
3
4
[0, 1, 2, 3, 4]
println "123".readLines()
[123]
Wings integrates Apache Ivy ("the agile package manager") for adding other packages to the classpath. By default Ivy configured to access the public Maven 2 repository (Ibiblio) along with a local per-user cache.
The current form of integration is that you specify an Ivy File using a Groovy MarkupBuilder embedded in the global 'XWINGS.IVY'. That will also result in the dependencies being resolved and added to the Wings classpath for this document. No progress feedback is currently provided by Wings, so you will have to watch your system console to find out what is happening when it takes a long time (which may happen for example if it has to download a large artifact). If the resolution is successful, the array of artifacts is the result so you can see what was set on the classpath.
A future IFCX development will be annotation processors (both Javadoc-style and Java 5 Annotations) to simplify and automate dependency management. So that the "@use" annotation in this script would be equivalent to the following Ivy file.
/**
* @use
org:'commons-lang', name:'commons-lang',
rev:'2.4'
*/
xwings._use(org:'commons-lang',
name:'commons-lang', rev:'2.4')
[file:/Users/jim/.ivy2/cache/commons-lang/commons-lang/jars/commons-lang-2.4.jar]
import
org.apache.commons.lang.WordUtils;
String message = "hello
ivy !";
[ "standard message" :message
,
"capitalized by " : WordUtils.class.name
,
"capitalized" : WordUtils.capitalizeFully(message)]
{standard message=hello ivy !, capitalized by =org.apache.commons.lang.WordUtils, capitalized=Hello Ivy !}
/**
* @use
org:'net.sf.json-lib', name:'json-lib', rev:'2.2.1',
conf:'default->runtime',
classifier:'jdk15'
*/
/*
xwings.context.loglevel =
'warn'
// This is basically what it looks like
internally.
XWINGS.IVY {
info(organisation:"org.ifcx",
module:"WingsIvyTest")
dependencies
{
dependency(org:'net.sf.json-lib',
name:'json-lib', rev:'2.2.1', conf:'default->runtime')
{
artifact(name:'json-lib',
type:'jar', 'm2:classifier':'jdk15')
//,
url:'http://repo1.maven.org/maven2/net/sf/json-lib/json-lib/2.2.1/json-lib-2.2.1-jdk15.jar')
}
}
}
*/
//
xwings._use (org:'net.sf.json-lib', name:'json-lib', rev:'2.2.1',
conf:'default->runtime', classifier:'jdk13')
xwings._use
(org:'net.sf.json-lib', name:'json-lib', rev:'2.2.1',
conf:'default->runtime', classifier:'jdk15')
[file:/Users/jim/.ivy2/cache/avalon-framework/avalon-framework/jars/avalon-framework-4.1.3.jar, file:/Users/jim/.ivy2/cache/commons-beanutils/commons-beanutils/jars/commons-beanutils-1.7.0.jar, file:/Users/jim/.ivy2/cache/commons-collections/commons-collections/jars/commons-collections-3.2.jar, file:/Users/jim/.ivy2/cache/commons-lang/commons-lang/jars/commons-lang-2.3.jar, file:/Users/jim/.ivy2/cache/commons-logging/commons-logging/jars/commons-logging-1.1.jar, file:/Users/jim/.ivy2/cache/javax.servlet/servlet-api/jars/servlet-api-2.3.jar, file:/Users/jim/.ivy2/cache/log4j/log4j/jars/log4j-1.2.12.jar, file:/Users/jim/.ivy2/cache/logkit/logkit/jars/logkit-1.0.1.jar, file:/Users/jim/.ivy2/cache/net.sf.ezmorph/ezmorph/jars/ezmorph-1.0.4.jar, file:/Users/jim/.ivy2/cache/net.sf.json-lib/json-lib/jars/json-lib-2.2.1-jdk15.jar]
import net.sf.json.*
//def
strAsJsonArray = "[1,2,3]" as
JSONArray
[JSONArray.fromObject("[1, 2, 3]"),
JSONObject.fromObject("{integer:1, boolx: true}")]
[[1,2,3], {"integer":1,"boolx":true}]
It is dangerous to use property access ('object.class') when trying to get the class of an object of unknown type. That is because if the object happens to be a map, then the property name will be used to do a get on the map and not fall through to the getter (Object.getClass()) as you're intending with the shorthand. Ditto for Object.getMetaClass(). But since we know we're dealing with an enumeration here, we can favor the terse style. This example shows calling a literal closure, which also illustrates why Groovy doesn't permit statement blocks simply enclosed by curly braces (as it would be ambiguous with closures).
enum Day { mon, tue, wed }
{ e ->
"${e.class.name}.$e" } (Day.mon)
Day.mon
Day.tue
tue
// Can't do this because we can't know
if it is a statement or a closure...
{ 2 }
org.codehaus.groovy.control.MultipleCompilationErrorsException:
startup failed, Script66.groovy: 2: Ambiguous expression could be
either a parameterless closure expression or an isolated open code
block;
solution: Add an explicit closure
parameter list, e.g. {it -> ...}, or force it to be treated as an
open block by giving it a label, e.g. L:{...} @ line 2, column 1.
1
error
Sample code from http://groovy.codehaus.org/Embedded+Derby+DB+examples.
First we add the JDBC driver to the classpath using the public Maven artifact.
xwings._use (groupId:'hsqldb', artifactId:'hsqldb', version:'1.8.0.7')
[file:/Users/jim/.ivy2/cache/hsqldb/hsqldb/jars/hsqldb-1.8.0.7.jar]
The original batch style:
import
groovy.sql.*
import
java.sql.*
Class.forName('org.hsqldb.jdbcDriver')
protocol
= "jdbc:hsqldb:";
def props = new Properties();
//
props.put("user", "user1");
//
props.put("password", "user1");
sql =
Sql.newInstance(protocol + "mem:people", props);
/*
Creating table, adding few lines, updating one */
sql.execute("""create table people(id int, name
varchar(40), second_name
varchar(40), phone varchar(30), email
varchar(50))""");
println("Created table
'people'");
sql.execute("insert into people values
(1,'John', 'Doe',
'123456','johndoe@company.com')");
sql.execute("insert
into people values (2,'Bill', 'Brown',
'324235','billbrown@company.com')");
sql.execute("insert
into people values (3,'Jack', 'Daniels',
'443323','jackdaniels@company.com')");
println("Inserted
people");
sql.execute("update people set
phone='443322', second_name='Daniel''s'where
id=3");
println("Updated person");
/*
Simple query */
def rows = sql.rows("SELECT * FROM people
ORDER BY id");
rows.each {println it}
/* Dropping
table 'people' */
sql.execute("drop table people")
println
("Table 'people' dropped")
Created table 'people'
Inserted
people
Updated person
{ID=1, NAME=John, SECOND_NAME=Doe,
PHONE=123456, EMAIL=johndoe@company.com}
{ID=2, NAME=Bill,
SECOND_NAME=Brown, PHONE=324235, EMAIL=billbrown@company.com}
{ID=3,
NAME=Jack, SECOND_NAME=Daniel's, PHONE=443322,
EMAIL=jackdaniels@company.com}
Table 'people' dropped
Converted to Wings-style. Here we can simply chop up the script into pieces that do "just enough" to move along to where we're interested in the result. Not only does that eliminate the need for printlns or asserts to check on what has happened, it also allows the bits of script to be reevaluated in various orders. That is common for dealing with something that fails, you can just redo that part, or to verify that some code does what is expected you might leave it out when manually evaluating a particular sequence.
import
groovy.sql.*
import java.sql.*
protocol =
"jdbc:hsqldb:";
def props = new Properties();
//
props.put("user", "user1");
//
props.put("password", "user1");
sql =
Sql.newInstance(protocol + "mem:people", props);
groovy.sql.Sql@41d3f3
/* Creating table, adding few lines,
updating one */
sql.execute("""create table
people(id int, name varchar(40), second_name
varchar(40), phone
varchar(30), email varchar(50))""")
java.sql.SQLException: Table already exists: PEOPLE in statement [create table people]
sql.execute("insert
into people values (1,'John', 'Doe',
'123456','johndoe@company.com')");
sql.execute("insert
into people values (2,'Bill', 'Brown',
'324235','billbrown@company.com')");
sql.execute("insert
into people values (3,'Jack', 'Daniels',
'443323','jackdaniels@company.com')");
false
sql.execute("update people set phone='443322', second_name='Daniel''s'where id=3");
false
/* Simple query */
def rows =
sql.rows("SELECT * FROM people ORDER BY id");
rows.each
{println it}
{ID=1, NAME=John, SECOND_NAME=Doe,
PHONE=123456, EMAIL=johndoe@company.com}
{ID=2, NAME=Bill,
SECOND_NAME=Brown, PHONE=324235, EMAIL=billbrown@company.com}
{ID=3,
NAME=Jack, SECOND_NAME=Daniel's, PHONE=443322,
EMAIL=jackdaniels@company.com}
[{ID=1, NAME=John, SECOND_NAME=Doe, PHONE=123456, EMAIL=johndoe@company.com}, {ID=2, NAME=Bill, SECOND_NAME=Brown, PHONE=324235, EMAIL=billbrown@company.com}, {ID=3, NAME=Jack, SECOND_NAME=Daniel's, PHONE=443322, EMAIL=jackdaniels@company.com}]
/*
Dropping table 'people' */
sql.execute("drop table people")
false
Groovy DataSet.
peopleSet = sql.dataSet('People')
groovy.sql.DataSet@859eae
peopleSet.each { println it }
[ID:1, NAME:John, SECOND_NAME:Doe,
PHONE:123456, EMAIL:johndoe@company.com]
[ID:2, NAME:Bill,
SECOND_NAME:Brown, PHONE:324235, EMAIL:billbrown@company.com]
[ID:3,
NAME:Jack, SECOND_NAME:Daniel's, PHONE:443322,
EMAIL:jackdaniels@company.com]
[ID:1, NAME:John, SECOND_NAME:Doe,
PHONE:123456, EMAIL:johndoe@company.com]
[ID:2, NAME:Bill,
SECOND_NAME:Brown, PHONE:324235, EMAIL:billbrown@company.com]
[ID:3,
NAME:Jack, SECOND_NAME:Daniels, PHONE:443323,
EMAIL:jackdaniels@company.com]
Something awry here. (FIXME)
def bills = peopleSet.findAll { it.NAME
== 'Bill' }
bills.each { println it }
groovy.lang.GroovyRuntimeException: Could not find the ClassNode for MetaClass: org.codehaus.groovy.runtime.metaclass.ClosureMetaClass@64807d[class Script75$_run_closure1]
Not having any joy with Derby at the moment for some reason.
xwings._use(groupId:'org.apache.derby', artifactId:'derby', version:'10.1.3.1', type:'jar')
[file:/Users/jim/.ivy2/cache/org.apache.derby/derby/jars/derby-10.1.3.1.jar]
org.apache.debry.jdbc.EmbeddedDriver x
org.codehaus.groovy.control.MultipleCompilationErrorsException:
startup failed, Script80.groovy: 1: unable to resolve class
org.apache.debry.jdbc.EmbeddedDriver
@ line 1, column 38.
1
error
zf = new java.util.zip.ZipFile(new
File("/Users/jim/.ivy2/cache/org.apache.derby/derby/jars/derby-10.1.3.1.jar"))
zf.entries().grep
{ it =~ /Driver/ }.sort()
[org/apache/derby/jdbc/Driver169.class, org/apache/derby/jdbc/Driver20.class, org/apache/derby/jdbc/EmbeddedDriver.class, org/apache/derby/jdbc/InternalDriver.class, org/apache/derby/jdbc/Driver30.class]
This is the Groovy PLEAC from http://pleac.sourceforge.net/pleac_groovy/datesandtimes.html copied May 5, 2008.
Batch style:
//----------------------------------------------------------------------------------
//
use Date to get the current time
println new Date()
// =>
Mon Jan 01 07:12:32 EST 2007
// use Calendar to compute year,
month, day, hour, minute, and second values
cal =
Calendar.instance
println 'Today is day ' +
cal.get(Calendar.DAY_OF_YEAR) + ' of the current year.'
// =>
Today is day 1 of the current year.
// there are other Java
Date/Time packages with extended capabilities, e.g.:
//
http://joda-time.sourceforge.net/
// there is a special Grails
(grails.codehaus.org) time DSL (see
below)
//----------------------------------------------------------------------------------
Tue May 06 15:02:14 PDT 2008
Today
is day 127 of the current year.
Wings style:
new Date()
Tue May 06 15:02:18 PDT 2008
cal
= Calendar.instance
'Today is day ' +
cal.get(Calendar.DAY_OF_YEAR) + ' of the current year.'
Today is day 127 of the current year.
Batch style:
//----------------------------------------------------------------------------------
cal
= Calendar.instance
Y = cal.get(Calendar.YEAR)
M =
cal.get(Calendar.MONTH) + 1
D = cal.get(Calendar.DATE)
println
"The current date is $Y $M $D"
// => The current date
is 2006 04
28
//----------------------------------------------------------------------------------
The current date is 2008 5 6
Wings style:
cal
= Calendar.instance
Y = cal.get(Calendar.YEAR)
M =
cal.get(Calendar.MONTH) + 1
D = cal.get(Calendar.DATE)
"The
current date is $Y $M $D"
The current date is 2008 5 6
Batch style:
//----------------------------------------------------------------------------------
import
java.text.SimpleDateFormat
import java.text.DateFormat
df = new
SimpleDateFormat('E M d hh:mm:ss z yyyy')
cal.set(2007, 0,
1)
println 'Customized format gives: ' + df.format(cal.time)
//
=> Mon 1 1 09:02:29 EST 2007 (differs depending on your
timezone)
df = DateFormat.getDateInstance(DateFormat.FULL,
Locale.FRANCE)
println 'Customized format gives: ' +
df.format(cal.time)
// => lundi 1 janvier
2007
//----------------------------------------------------------------------------------
Customized format gives: Mon 1 1
03:02:24 PST 2007
Customized format gives: lundi 1 janvier 2007
Wings style:
import
java.text.SimpleDateFormat
df = new SimpleDateFormat('E M d
hh:mm:ss z yyyy')
cal.set(2007, 0, 1)
df.format(cal.time)
Mon 1 1 03:02:24 PST 2007
import
java.text.DateFormat
df =
DateFormat.getDateInstance(DateFormat.FULL,
Locale.FRANCE)
df.format(cal.time)
lundi 1 janvier 2007
I was watching a lightning talk about Scala illustrated with the classic shopping cart example, so I whipped up this version while they showed the Scala version. While Groovy doesn't have Scala's type inferencing and static typing performance, it is even terser. This version doesn't follow their example of initializing all the quantities to zero because that isn't plausible if the catalog can be large. It does however use strong typing on the catalog to show we can have that if we want it. Also the Scala version showed the special price handling in the total function, and while I could do that too (and even put it all on one line), I think it is a better separation of concerns to have that it a separate function.
The famous Elvis operator (':?') is used which demonstrates a key difference between Java and Groovy which is the notion of "true". Groovy Truth is weakly typed and treats not only false, and Boolean.FALSE as false, but also null, empty strings, empty collections, numbers with a zero value, and regex matchers with no match. Anything else is true. So Elvis evaluates the left-hand side, and if it is true then returns that value (which could of course be something like a non-zero number or non-empty string). Otherwise it returns the value of the right-hand side. This is not simply the ternary operator because the LHS appears only once and is evaluated only once. There is an example of Collection.inject earlier in this document.
class Catalog extends HashMap {
}
class CashRegister {
Catalog
catalog
def quantities = [:]
def
scan(String code) { quantities[code] = (quantities[code] ?: 0) + 1
}
def price(String code) {
catalog[code].specialPrice ?: catalog[code].price }
def
total() { (quantities.collect { item -> item.value ? item.value *
price(item.key) : 0 }).sum() }
// def total() {
quantities.inject(0) { total, item -> total + (item.value ?
item.value * price(item.key) : 0) } }
}
register = new
CashRegister(catalog: [gum:[price:1.00], comb:[price:2.33],
apple:[price:0.45, specialPrice:0.30]] as Catalog)
CashRegister@3d902f
register.scan('gum')
1
register.total()
1.00
register.scan('comb')
1
register.scan('apple')
1
register.scan('apple')
2
register.total()
3.93
register.scan('gum')
2
register.quantities
{gum=2, comb=1, apple=2}
This is the first bit of Groovy I wrote (well, almost, SwingBuilder.build was my first contribution to Groovy as a replacement for the boilerplate previously needed to use SwingBuilder) to find out how binding of a model to a GUI view would be done in Groovy (the acid test for any language). The trick here is actually just Java Collections in that the map entry you get when iterating over a mutable map will modify the map if you change the value part. Once again this example doesn't show well in Wings (although the frame will get displayed) because it doesn't (yet) handle standard output.
javax.swing.SwingUtilities.isEventDispatchThread()
false
def
someBean=['abc':1,'def':2,'xyz':3,'foo':99]
// def
MYFRAME
groovy.swing.SwingBuilder.build
{
MYFRAME=frame(title:'My Frame')
{
def map=[:] // from text field widget to
pair entry in someBean
panel()
{
// initialize
map with labels and inital values from someBean
for
(pair in someBean) {
t=textField(text:pair.value)
label(text:pair.key).labelFor=t
//
map textfield to someBean label/value pair
map[t]=pair
}
//
Notice that I can supply a closure and it gets adapted to an
ActionListener.
button(text:'OK',
actionPerformed:{
//
update map with current value from widgets
for
(pair in map)
pair.value.value=pair.key.text
//
show we got updated
for
(pair in someBean)
println("${pair.key}:
${pair.value}")
//
We should look for our parent frame to close, but this is
handy:
MYFRAME.hide()
})
}
}
MYFRAME.pack()
MYFRAME.show()
}
while
(MYFRAME.isVisible()) { System.sleep(1000) }
abc: 1
def: 2
xyz: 3
foo: 99
MYFRAME
javax.swing.JFrame[frame3,4,22,306x61,hidden,layout=java.awt.BorderLayout,title=My Frame,resizable,normal,defaultCloseOperation=HIDE_ON_CLOSE,rootPane=javax.swing.JRootPane[,0,22,306x39,layout=javax.swing.JRootPane$RootLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=449,maximumSize=,minimumSize=,preferredSize=],rootPaneCheckingEnabled=true]
javax.swing.SwingUtilities.invokeAndWait
{ MYFRAME.pack() ; MYFRAME.show() }
while (MYFRAME.isVisible()) {
System.sleep(1000) }
abc: 1
def: 2
xyz: 3
foo: 99
Someone on groovy-user asked how to do pretty-printing of lists and maps in Groovy. Although Groovy has a bunch of stuff dealing with output and formatting, none of it is really suited to pretty-printing. This is the start of a way to do it (but I'm gonna go a different way since hypertext is better for this sort of thing). It is factored into an improved indenter (writer rather than stream based, it's a value object, and written as Java so it's useful in less groovy environments) and simple formatter.
import java.io.PrintWriter;
import
java.io.Writer;
import
org.codehaus.groovy.tools.Utilities;
public class IndentWriter
extends PrintWriter
{
protected boolean
needIndent = true;
protected String
indentString;
protected int indentLevel =
0;
public
IndentWriter(Writer w) { this(w, " ", 0, true);
}
public IndentWriter(Writer w, String indent,
int level, boolean needs)
{ super(w, true);
indentString = indent; indentLevel = level; needIndent = needs
}
public int getIndent() {
return indentLevel; }
public
IndentWriter plus(int i) {
return
new IndentWriter(out, indentString, indentLevel + i,
needIndent);
}
public
IndentWriter minus(int i) {
return
(plus(-i));
}
public
IndentWriter next() { return plus(1); }
public
IndentWriter previous() { return minus(1); }
protected
void printIndent() {
needIndent
= false;
super.print(Utilities.repeatString(indentString,
indentLevel));
}
protected
void checkIndent() { if (needIndent) { needIndent = false;
printIndent(); }; }
public
void println() { super.println(); needIndent = true; }
public
void print(boolean b) { checkIndent(); super.print(b); }
public
void print(char c) { checkIndent(); super.print(c); }
public
void print(char[] s) { checkIndent(); super.print(s); }
public
void print(double d) { checkIndent(); super.print(d); }
public
void print(float f) { checkIndent(); super.print(f); }
public
void print(int i) { checkIndent(); super.print(i); }
public
void print(long l) { checkIndent(); super.print(l); }
public
void print(Object obj) { checkIndent(); super.print(obj);
}
public void print(String s) { checkIndent();
super.print(s); }
// public void close() { }
// public
void closeForReal() { super.close() }
}
class PrettyWriter
extends IndentWriter
{
public
PrettyWriter(Writer w) { super(w, ' ', 0, true); }
public
PrettyWriter(Writer w, String ins, int level, boolean needsIt) {
super(w, ins, level, needsIt); }
public
PrettyWriter plus(int i) { return new PrettyWriter(out, indentString,
indentLevel + i, needIndent) }
public void
print(Collection list) {
println('[')
def
indent = (this + 1)
list.each
{ indent.print it ; indent.println ';'
}
indent.flush()
print(']')
}
public
void println( Collection list) { print list; println() }
public
void print(Map map) {
println('[')
def
indent = (this + 1)
map.entrySet().each
{
indent.print
it.key; indent.print ' : '; (indent + 1).print it.value ;
indent.println ';'
}
indent.flush()
print(']')
}
public
void println(Map map) { print map; println() }
}
if (!(out
instanceof PrettyWriter)) {
// We can change
this, but it isn’t sticking currently...
out =
new PrettyWriter(out)
}
//println out.getClass()
//print
([1, 2, [3, [4, [a:1, b:2, c:3], 5, 6]], [aaa:111, bbb:222, ccc:[1,
2, 3,4]], 7])
//println out.getClass()
//13.times { println
"$it .....................ABCDEF" }
//out.flush()
println
([1, 2, [3, [4, [a:1, b:2, c:3], 5, 6]], [aaa:111, bbb:222, ccc:[1,
2, 3,4]], 7])
[
1;
2;
[
3;
[
4;
[
a
: 1;
b
: 2;
c
:
3;
];
5;
6;
];
];
[
aaa
: 111;
bbb :
222;
ccc :
[
1;
2;
3;
4;
];
];
7;
]
At the moment setting the 'out' binding isn't sticky between evals. But we can just set it again.
out = new PrettyWriter(out)
println
([1, 2, [3, [4, [a:1, b:2, c:3], 5, 6]], [aaa:111, bbb:222, ccc:[1,
2, 3,4]], 7])
[
1;
2;
[
3;
[
4;
[
a
: 1;
b
: 2;
c
:
3;
];
5;
6;
];
];
[
aaa
: 111;
bbb :
222;
ccc :
[
1;
2;
3;
4;
];
];
7;
]
The current version of the Wings prototype supports any language with a JSR-223 Java scripting engine (other sorts of language APIs are planned of course).
Some of these engines take a while to initialize, and at the moment Wings doesn’t provide any feedback or control with long running scripts. So you should be patient and perhaps check your CPU activity monitor if you think Wings is stuck. After the first hit though, all these engines zip right along.
/** @use org=com.sun.script
module=jruby version=1.1+ */
// xwings._use (org:’com.sun.script’,
module:’jruby’, version:’1.1+’)
xwings._use
([url:”${xwings.enginesURL}jruby/lib/jruby.jar”]
,
[url:”${xwings.enginesURL}jruby/lib/backport-util-concurrent.jar”]
//
,
[url:”${xwings.enginesURL}jruby/lib/asm-2.2.3.jar”]
,
[url:”${xwings.enginesURL}jruby/build/jruby-engine.jar”])
[file:/Users/jim/.ivy2/cache/org.ifcx.wings.use_url/http___ifcx.svn.sourceforge.net_svnroot_ifcx_thirdparty_scripting_engines_jruby_build_jruby-engine.jar/jars/http___ifcx.svn.sourceforge.net_svnroot_ifcx_thirdparty_scripting_engines_jruby_build_jruby-engine.jar-.jar, file:/Users/jim/.ivy2/cache/org.ifcx.wings.use_url/http___ifcx.svn.sourceforge.net_svnroot_ifcx_thirdparty_scripting_engines_jruby_lib_backport-util-concurrent.jar/jars/http___ifcx.svn.sourceforge.net_svnroot_ifcx_thirdparty_scripting_engines_jruby_lib_backport-util-concurrent.jar-.jar, file:/Users/jim/.ivy2/cache/org.ifcx.wings.use_url/http___ifcx.svn.sourceforge.net_svnroot_ifcx_thirdparty_scripting_engines_jruby_lib_jruby.jar/jars/http___ifcx.svn.sourceforge.net_svnroot_ifcx_thirdparty_scripting_engines_jruby_lib_jruby.jar-.jar]
xwings.enginesURL
http://ifcx.svn.sourceforge.net/svnroot/ifcx/thirdparty/scripting/engines/
xwings.register(stylePrefix:'Ruby', engineName:'jruby')
{engineName=jruby, codeStyle=RubyCode, exceptionalStyle=RubyException, resultStyle=RubyResult, outputStyle=WingsOutput, errorsStyle=WingsErrors, inputStyle=WingsInput}
require 'java'
include_class 'java.util.TreeSet'
set
= TreeSet.new
set.add "foo"
set.add "Bar"
set.add
"baz"
set.each do |v|
puts "value:
#{v}"
end
value: Bar
value: baz
value: foo
/** @use org=com.sun.script
module=engine-jython version=2.2+ */
xwings._use
([org:'org.python', name:'jython', rev:'2.2X',
url:”${xwings.enginesURL}jython/lib/jython.jar”]
,
[org:'com.sun.script', name:'engine-jython', rev:'1.0X',
url:”${xwings.enginesURL}jython/build/jython-engine.jar”])
[file:/Users/jim/.ivy2/cache/com.sun.script/engine-jython/jars/engine-jython-1.0X.jar, file:/Users/jim/.ivy2/cache/org.python/jython/jars/jython-2.2X.jar]
xwings.register(stylePrefix:'Jython', engineName:'jython')
{engineName=jython, codeStyle=JythonCode, exceptionalStyle=JythonException, resultStyle=JythonResult, outputStyle=WingsOutput, errorsStyle=WingsErrors, inputStyle=WingsInput}
print
1 + 2 + 3 + 4
print [sum(range(5)), range(6), range(10, 1, -3)]
10
[10, [0, 1, 2, 3, 4, 5], [10, 7,
4]]
A little database example (TODO:refind that URL).
First we add the JDBC driver to the classpath using the public Maven artifact. We do this in GroovyCode at the moment.
xwings._use (groupId:'hsqldb', artifactId:'hsqldb', version:'1.8.0.7')
[file:/Users/jim/.ivy2/cache/hsqldb/hsqldb/jars/hsqldb-1.8.0.7.jar]
##
@use (groupId:'hsqldb', artifactId:'hsqldb',
version:'1.8.0.7')
Class.forName("org.hsqldb.jdbcDriver")
from
java.sql import DriverManager
from java.lang import Class
db
= DriverManager.getConnection("jdbc:hsqldb:mem:people",
None, None)
c = db.createStatement()
rs =
c.executeQuery("select * from People")
#_types =
{Types.INTEGER:rs.getInt, Types.FLOAT:rs.getFloat}
while
rs.next():
row = []
meta =
rs.getMetaData()
for i in
range(meta.getColumnCount()):
col = i +
1
datatype = meta.getColumnType(col)
v
= _types.get(datatype, rs.getString)(col)
row.append(v)
print
tuple(row)
rs.close()
c.close()
db.close()
Traceback (innermost last):
File
"<unknown>", line 20, in ?
NameError: _types
java.sql.SQLException:
java.sql.SQLException: User not found: BZIMMER
from
com.ziclix.python.sql import zxJDBC
#conn =
DriverManager.getConnection("jdbc:hsqldb:mem:people", None,
None)
db = zxJDBC.connect("jdbc:hsqldb:mem:people",
None, None, "org.hsqldb.jdbcDriver")
c =
db.cursor()
c.execute("select * from bz")
for row in
c:
print row
c.close()
db.close()
Traceback (innermost last):
File
"<unknown>", line 3, in ?
xwings._use
([url:”${xwings.enginesURL}jaskell/build/jaskell-engine.jar”]
,
[url:”${xwings.enginesURL}jaskell/lib/jfunutil.jar”]
,
[url:”${xwings.enginesURL}jaskell/lib/jparsec.jar”]
,
[url:”${xwings.enginesURL}jaskell/lib/jaskell-1.0.jar”])
[file:/Users/jim/.ivy2/cache/org.ifcx.wings.use_url/http___ifcx.svn.sourceforge.net_svnroot_ifcx_thirdparty_scripting_engines_jaskell_build_jaskell-engine.jar/jars/http___ifcx.svn.sourceforge.net_svnroot_ifcx_thirdparty_scripting_engines_jaskell_build_jaskell-engine.jar-.jar, file:/Users/jim/.ivy2/cache/org.ifcx.wings.use_url/http___ifcx.svn.sourceforge.net_svnroot_ifcx_thirdparty_scripting_engines_jaskell_lib_jaskell-1.0.jar/jars/http___ifcx.svn.sourceforge.net_svnroot_ifcx_thirdparty_scripting_engines_jaskell_lib_jaskell-1.0.jar-.jar, file:/Users/jim/.ivy2/cache/org.ifcx.wings.use_url/http___ifcx.svn.sourceforge.net_svnroot_ifcx_thirdparty_scripting_engines_jaskell_lib_jfunutil.jar/jars/http___ifcx.svn.sourceforge.net_svnroot_ifcx_thirdparty_scripting_engines_jaskell_lib_jfunutil.jar-.jar, file:/Users/jim/.ivy2/cache/org.ifcx.wings.use_url/http___ifcx.svn.sourceforge.net_svnroot_ifcx_thirdparty_scripting_engines_jaskell_lib_jparsec.jar/jars/http___ifcx.svn.sourceforge.net_svnroot_ifcx_thirdparty_scripting_engines_jaskell_lib_jparsec.jar-.jar]
xwings.register(stylePrefix:'Haskell', engineName:'jaskell')
{engineName=jaskell, codeStyle=HaskellCode, exceptionalStyle=HaskellException, resultStyle=HaskellResult, outputStyle=WingsOutput, errorsStyle=WingsErrors, inputStyle=WingsInput}
The following examples are copied from http://docs.codehaus.org/display/JASKELL/Using+Jaskell.
$$<<hello
$name>>$$ where
name="Tom";
end
hello Tom
tuple
A tuple is an associative array.
{} is an empty tuple.
{name="tom"; age=1} is a tuple with two members.
{str="abc", list=[1,2,'3'], tuple={}} is a tuple with 3 members.
',' or ';' can be used to seperate tuple members.
Tuple member can be de-referenced with a '.'. For example:
{name="tom"; age=1}.name
tom
evaluates to "tom".
if-then-else
if-then-else is the only native conditional statement in Jaskell.
Like many imperative languages such as Java, the "else" clause is optional. When 'else' clause is omitted, 'null' is used as the value of this clause.
The following expression evaluates to 5:
if 1==1 then 1 else 5
1
switch-case
There's no native switch-case support in the Jaskell programming language.
However, the 'jaskell.prelude.switch' function can be used for this purpose. For example:
switch
(3-2)
.case(0, "zero")
.case(1,
"one")
.default("unknown")
.end
one
evaluates to "one".
Operators
Similar to Java, Jaskell supports the following binary operators with natural semantics: '==', '!=', '>', '<', '>=', '<=', '+', '-', '*', '/', '%', '&&', '||', 'and', 'or'.
Slightly different from Java though, '==' and '!=' calls "Object.equals()", and comparison operators such as '>', '<' calls "Comparable.compareTo()".
Hence, the following expressions all evaluate to true:
"abc"=="abc"
true
"abc"!="ABC"
true
"10"<"2"
true
"abc"=="abc" and "abc"!="ABC"
true
"abc"!="ABC" or "10"=="2"
true
'and' and '&&', 'or' and '||' are equivalent.
Unary operators such as '!', '', 'not' are also supported. 'not' and '!' are equivalent. They are both the logical negation operator. '' reads "negative". "~1" is same as "-1".
Jaskell supports ':', '++', '@', '#' operators for list operation. ':' is used to prepend an element to a list.
1:[2,3]
[1,2,3]
evaluates to [1,2,3]
'++' is used to concatenate two lists together:
[1,2]++[3,4]
[1,2,3,4]
evaluates to [1,2,3,4]
'@' is used to get an element from a list/tuple by index/key:
[1,2,3,4]@1
2
evaluates to 2.
{name="tom";age=10}@"age"
10
evaluates to 10.
Besides (@), jaskell also supports the familiar "[]" notion as a syntax sugar to indicate a list/array subscript. For example,
[1,2,3,4][1]
2
also evaluates to 2, which is exactly the same as the (@) operator.
Another use of the "[]" syntax sugar is to indicate an array type, so one can say:
int[]
'#' is used to get the size of a list/tuple:
#[1,2]
2
evaluates to 2.
For array and list, the "length" method can also be called to read the length, which is more familiar to most Java programmers:
[1,2].length
jfun.jaskell.AbstractMemberException: abstract member: length
'=>' operator is a variant of "if-then'. "cond => x" is equivalent to "if cond then x". '=>' is typically overridden to provide "deduction" kind of logic.
':=' is an operator with no predefined meaning. It can be overloaded to provide custom semantics though.
let
'let' is a keyword that allows definition of variable and function.
let
a = 1;
b
= 2;
a+b;
end
3
is an expression that evaluates to 3.
pattern match
When defining function, different function body can be provided based on the pattern of certain parameters.
Symbol '|' is used to seperate different patterns. For example, the following function reverse a list:
reverse
[] = [] //in case of empty list
| x:xl = reverse xl ++ [x];
// in case of non-empty list.
reverse [1, 2, 3, 4]
[4,3,2,1]
The following function tests if a parameter is a tuple with a member named "ind":
hasMember
{ind} = true
| _ = false;
[hasMember {ind=a}, hasMember { foo=ind }, hasMember { }]
[true,false,false]
It is possible to name a pattern, so that this name can be used to reference the object of this pattern. Special symbol '@' can be used to name a pattern. The following function returns the tuple itself if it contains member "ind", an empty tuple is returned otherwise:
test t@{ind} = t
| _ = {};
[test{ind=a}, test{a=1, b=2}]
[{ind=a},{}]
xwings._use([[url:"${xwings.enginesURL}scala/build/scala-engine.jar",
rev:"2008-05-12"]
,
[url:"${xwings.enginesURL}scala/lib/scala-library.jar",
rev:"2008-05-11"]
,
[url:"${xwings.enginesURL}scala/lib/scala-compiler.jar",
rev:"2008-05-11"]])
[file:/Users/jim/.ivy2/cache/org.ifcx.wings.use_url/http___ifcx.svn.sourceforge.net_svnroot_ifcx_thirdparty_scripting_engines_scala_build_scala-engine.jar/jars/http___ifcx.svn.sourceforge.net_svnroot_ifcx_thirdparty_scripting_engines_scala_build_scala-engine.jar-2008-05-12.jar, file:/Users/jim/.ivy2/cache/org.ifcx.wings.use_url/http___ifcx.svn.sourceforge.net_svnroot_ifcx_thirdparty_scripting_engines_scala_lib_scala-compiler.jar/jars/http___ifcx.svn.sourceforge.net_svnroot_ifcx_thirdparty_scripting_engines_scala_lib_scala-compiler.jar-2008-05-11.jar, file:/Users/jim/.ivy2/cache/org.ifcx.wings.use_url/http___ifcx.svn.sourceforge.net_svnroot_ifcx_thirdparty_scripting_engines_scala_lib_scala-library.jar/jars/http___ifcx.svn.sourceforge.net_svnroot_ifcx_thirdparty_scripting_engines_scala_lib_scala-library.jar-2008-05-11.jar]
def factory = new
org.ifcx.scripting.scala.ScalaScriptEngineFactory()
[factory.languageVersion,
factory.engineVersion]
[2.7.1.final, 1.0d3]
Here we tell Wings to associate some paragraph styles with the Scala engine. We set the name of both the resultStyle and outputStyle to ScalaOutput here because we're only getting one type of output from the Scala interpreter right now and it is likely the manner of doing that will change in the future. This will make it easier for such changes to be backward compatible. Uh, nevermind. Turns out the formatting logic gets a bit confused if the output and result styles have the same name and we wind up with extra blank paragraphs getting inserted.
xwings.register(stylePrefix:'Scala', engineName:'scala', resultStyle:"ScalaOutput")
{engineName=scala, codeStyle=ScalaCode, exceptionalStyle=ScalaException, resultStyle=ScalaOutput, outputStyle=WingsOutput, errorsStyle=WingsErrors, inputStyle=WingsInput}
Once this initialization stuff is working reliablity, it can be embedded in the WingsEval Groovy macro. You can see how that's done by looking at Tools:Macros:Organize Macros:Groovy... then twiddling the triangles to navigate to Wings.odt (this document name):Wings:WingsEval.groovy. A soon to come enhancement will be including the Ivy dependencies in the registration so it all happens lazily and out-of-sight.
Okay! We're ready to evaluate some Scala! This will take a while the first time, but if you've gotten this far without errors, odds are good that you'll eventually get the right results.
1 + 2
res0: Int = 3
Here's the array quicksort from Scala By Example.
def
sort(xs: Array[Int]) {
def
swap(i: Int, j: Int) {
val
t = xs(i); xs(i) = xs(j); xs(j) = t
}
def sort1(l: Int,
r: Int) {
val
pivot = xs((l + r) / 2)
var
i = l; var j = r
while
(i <= j) {
while
(xs(i) < pivot) i += 1
while
(xs(j) > pivot) j -= 1
if
(i <= j) {
swap(i,
j)
i
+= 1
j
-= 1
}
}
if
(l < j) sort1(l, j)
if
(j < r) sort1(i, r)
}
sort1(0,
xs.length - 1)
}
sort: (Array[Int])Unit
val x = Array(4,3, 77, 77, 55, 3, 1)
x: Array[Int] = Array(4, 3, 77, 77, 55, 3, 1)
sort(x)
Notice we got no result from the sort method. We look at the array which was sorted in place.
x
res2: Array[Int] = Array(1, 3, 3, 4, 55, 77, 77)
Here's the functional version.
def
sort(xs: Array[Int]): Array[Int] =
if
(xs.length <= 1) xs
else
{
val
pivot = xs(xs.length / 2)
Array.concat(
sort(xs
filter (pivot >)),
xs
filter (pivot ==),
sort(xs
filter (pivot <)))
}
sort: (Array[Int])Array[Int]
Now we get an array value as the result of calling sort.
sort(Array(1, 22, 11, 99, -283, -100, 1000, 10))
res3: Array[Int] = Array(-283, -100, 1, 10, 11, 22, 99, 1000)
xwings._use ([org:'org.dashnine',
name:'sleep', rev:'2.1rc2X',
url:'http://sleep.dashnine.org/download/sleep.jar']
,
[org:'com.sun.script', name:'engine-sleep', rev:'1.0X',
url:”${xwings.enginesURL}sleep/build/sleep-engine.jar”])
[file:/Users/jim/.ivy2/cache/com.sun.script/engine-sleep/jars/engine-sleep-1.0X.jar, file:/Users/jim/.ivy2/cache/org.dashnine/sleep/jars/sleep-2.1rc2X.jar]
XWINGS.register(stylePrefix:'Sleep', engineName:'sleep')
{engineName=sleep, codeStyle=SleepCode, exceptionalStyle=SleepException, resultStyle=SleepResult, outputStyle=WingsOutput, errorsStyle=WingsErrors, inputStyle=WingsInput}
Manual: http://sleep.dashnine.org/manual/
Developer’s blog: http://rsmudge.livejournal.com/
Sleep isn’t working because the scripting engine adapter doesn’t have input/output implemented.
import
java.awt.Point;
$p = [new Point];
setField($p, x => 33,
y => 45);
println($p);
There is a JSR-223 engine for Java in the scripting.dev.java.net suite, but it requires JDK 1.6 to build and run. It probably works