// -*- groovy -*- // The examples make use of Groovy's built-in assert // command so that the script is self-checking // @@PLEAC@@_NAME // @@SKIP@@ Groovy // @@PLEAC@@_WEB // @@SKIP@@ http://groovy.codehaus.org // @@PLEAC@@_1.0 //---------------------------------------------------------------------------------- string = '\\n' // two characters, \ and an n assert string.size() == 2 string = "\n" // a "newline" character string = '\n' // a "newline" character string = "Jon 'Maddog' Orwant" // literal single quote inside double quotes string = 'Jon \'Maddog\' Orwant' // escaped single quotes string = 'Jon "Maddog" Orwant' // literal double quotes inside single quotes string = "Jon \"Maddog\" Orwant" // escaped double quotes string = ''' This is a multiline string declaration using single quotes (you can use double quotes) ''' //---------------------------------------------------------------------------------- // @@PLEAC@@_1.1 //---------------------------------------------------------------------------------- // accessing substrings string = 'hippopotamus' start = 5; end = 7; endplus1 = 8 assert string.substring(start, endplus1) == 'pot' assert string[start..end] == 'pot' assert string.substring(start) == 'potamus' assert string[start..-1] == 'potamus' // String is immutable but new strings can be created in various ways assert string - 'hippo' - 'mus' + 'to' == 'potato' assert string.replace('ppopotam','bisc') == 'hibiscus' assert string.substring(0, 2) + 'bisc' + string[-2..-1] == 'hibiscus' // StringBuffer is mutable sb = new StringBuffer(string) sb[2..-3] = 'bisc' assert sb.toString() == 'hibiscus' // No exact pack/unpack equivalents exist in Groovy. Examples here use a custom // implementation to split an original string into chunks of specified length // the method is a modified version of the Java PLEAC version // get a 5-character string, skip 8, then grab 2 5-character strings // skipping the trailing spaces, then grab the rest data = 'hippopotamus means river horse' def fields = unpack('A5 x8 A5 x1 A5 x1 A*', data) assert fields == ['hippo', 'means', 'river', 'horse'] // On a Java 5 or 6 JVM, Groovy can also make use of Scanners: s = new Scanner(data) s.findInLine(/(.{5}).{8}(.{5}) (.{5}) (.*)/) m = s.match() fields = [] (1..m.groupCount()).each{ fields << m.group(it) } assert fields == ['hippo', 'means', 'river', 'horse'] // another scanner example similar to the javadoc example input = '1 fish 2 fish red fish blue fish' s = new Scanner(input).useDelimiter(/\s*fish\s*/) fields = [] 2.times{ fields << s.nextInt() } 2.times{ fields << s.next() } assert fields == [1, 2, 'red', 'blue'] // split at five characters boundaries String[] fivers = unpack('A5 ' * (data.length() / 5), data) assert fivers == ["hippo", "potam", "us me", "ans r", "iver ", "horse"] // chop string into individual characters assert 'abcd' as String[] == ['a', 'b', 'c', 'd'] string = "This is what you have" // Indexing forwards (left to right) // tens 000000000011111111112 // units +012345678901234567890 // Indexing backwards (right to left) // tens 221111111111000000000 // units 109876543210987654321- assert string[0] == 'T' assert string[5..6] == 'is' assert string[13..-1] == 'you have' assert string[-1] == 'e' assert string[-4..-1] == 'have' assert string[-8, -7, -6] == 'you' data = new StringBuffer(string) data[5..6] = "wasn't" ; assert data.toString() == "This wasn't what you have" data[-12..-1] = "ondrous" ; assert data.toString() == "This wasn't wondrous" data[0..0] = "" ; assert data.toString() == "his wasn't wondrous" data[-10..-1] = "" ; assert data.toString() == "his wasn'" string = "This wasn't wondrous" // check last ten characters match some pattern assert string[-10..-1] =~ /^t\sw.*s$/ string = 'This is a test' assert string[0..4].replaceAll('is', 'at') + string[5..-1] == 'That is a test' // exchange the first and last letters in a string string = 'make a hat' string = string[-1] + string[1..-2] + string[0] assert string == 'take a ham' // extract column with unpack string = 'To be or not to be' // skip 6, grab 6 assert unpack("x6 A6", string) == ['or not'] // forward 6, grab 2, backward 5, grab 2 assert unpack("x6 A2 X5 A2", string) == ['or', 'be'] assert cut2fmt([8, 14, 20, 26, 30]) == 'A7 A6 A6 A6 A4 A*' // utility method (derived from Java PLEAC version) def unpack(String format, String data) { def result = [] int formatOffset = 0, dataOffset = 0 int minDataOffset = 0, maxDataOffset = data.size() new StringTokenizer(format).each{ token -> int tokenLen = token.length() // count determination int count = 0 if (tokenLen == 1) count = 1 else if (token.charAt(1) == '*') count = -1 else count = token[1..-1].toInteger() // action determination char action = token.charAt(0) switch (action) { case 'A': if (count == -1) { start = [dataOffset, maxDataOffset].min() result.add(data[start..-1]) dataOffset = maxDataOffset } else { start = [dataOffset, maxDataOffset].min() end = [dataOffset + count, maxDataOffset].min() result.add(data[start.. // script: checksum = 0 new File(args[0]).eachByte{ checksum += it } checksum %= (int) Math.pow(2, 16) - 1 println checksum //---------------------------------------------------------------------------------- // to run on its own source code: //=> % groovy CheckSum CheckSum.groovy //=> 9349 //---------------------------------------------------------------------------------- // Slowcat.groovy: Emulate a s l o w line printer // Usage: groovy Slowcat // script: delay = args[1].toInteger() new File(args[0]).eachByte{ print ((char) it); Thread.sleep(delay) } //---------------------------------------------------------------------------------- // @@PLEAC@@_1.6 //---------------------------------------------------------------------------------- assert 'string'.reverse() == 'gnirts' string = 'Yoda said, "can you see this?"' revwords = string.split(' ').toList().reverse().join(' ') assert revwords == 'this?" see you "can said, Yoda' words = ['bob', 'alpha', 'rotator', 'omega', 'reviver'] long_palindromes = words.findAll{ w -> w == w.reverse() && w.size() > 5 } assert long_palindromes == ['rotator', 'reviver'] //---------------------------------------------------------------------------------- // @@PLEAC@@_1.7 //---------------------------------------------------------------------------------- s1 = 'abc\t def\tghi \n\tx' s2 = 'abc def ghi \n x' def expand(s) { s.split('\n').toList().collect{ line = it while (line.contains('\t')) { line = line.replaceAll(/([^\t]*)(\t)(.*)/){ all,pre,tab,suf -> pre + ' ' * (8 - pre.size() % 8) + suf } } return line }.join('\n') } def unexpand(s) { s.split('\n').toList().collect{ line = it for (i in line.size()-1..1) { if (i % 8 == 0) { prefix = line[0.. // script: ant = new AntBuilder() ant.mail(from:'manager@grumpybank.com', tolist:'innocent@poorhouse.com', encoding:'plain', mailhost:'mail.someserver.com', subject:'Friendly Letter', message:'this is a test message') // Ant has many options for setting encoding, security, attachments, etc., see: // http://ant.apache.org/manual/CoreTasks/mail.html // Groovy could also use the Java Mail Api directly if required //---------------------------------------------------------------------------------- // @@PLEAC@@_1.11 //---------------------------------------------------------------------------------- def raw = ''' your text goes here ''' def expected = ''' your text goes here ''' assert raw.split('\n').toList().collect{ it.replaceAll(/^\s+/,'') }.join('\n') + '\n' == expected //---------------------------------------------------------------------------------- // @@PLEAC@@_1.12 //---------------------------------------------------------------------------------- input = '''Folding and splicing is the work of an editor, not a mere collection of silicon and mobile electrons!''' expected = '''Folding and splicing is the work of an editor, not a mere collection of silicon and mobile electrons!''' def wrap(text, maxSize) { all = [] line = '' text.eachMatch(/\S+/) { word = it[0] if (line.size() + 1 + word.size() > maxSize) { all += line line = word } else { line += (line == '' ? word : ' ' + word) } } all += line return all.join('\n') } assert wrap(input, 20) == expected //---------------------------------------------------------------------------------- // @@PLEAC@@_1.13 //---------------------------------------------------------------------------------- string = /Mom said, "Don't do that."/ // backslash special chars assert string.replaceAll(/['"]/){/\\/+it[0]} == /Mom said, \"Don\'t do that.\"/ //' // double special chars assert string.replaceAll(/['"]/){it[0]+it[0]} == /Mom said, ""Don''t do that.""/ //' //backslash quote all non-capital letters assert "DIR /?".replaceAll(/[^A-Z]/){/\\/+it[0]} == /DIR\ \/\?/ //---------------------------------------------------------------------------------- // @@PLEAC@@_1.14 //---------------------------------------------------------------------------------- assert ' x '.trim() == 'x' // print what's typed, but surrounded by >< symbols // script: new BufferedReader(new InputStreamReader(System.in)).eachLine{ println(">" + it.trim() + "<"); } //---------------------------------------------------------------------------------- // @@PLEAC@@_1.15 //---------------------------------------------------------------------------------- pattern = /"([^\"\\]*(?:\\.[^\"\\]*)*)",?|([^,]+),?|,/ line = /XYZZY,"","O'Reilly, Inc","Wall, Larry","a \"glug\" bit,",5,"Error, Core Dumped"/ m = line =~ pattern expected = [/XYZZY/, '', /O'Reilly, Inc/, /Wall, Larry/, //' /a \"glug\" bit,/, /5/, /Error, Core Dumped/] for (i in 0.. line = line.replaceAll(/(?<=\W)/ + key + /(?=\W)/, value) } return line }.join('\n') } assert fixstyle(input) == expected //---------------------------------------------------------------------------------- // @@PLEAC@@_1.18 //---------------------------------------------------------------------------------- // Solved in two parts: 'screenscrape' text stream and return stream from process // Part 1: text scraping input = ''' PID PPID PGID WINPID TTY UID STIME COMMAND 4636 1 4636 4636 con 1005 08:24:50 /usr/bin/bash 676 4636 676 788 con 1005 13:53:32 /usr/bin/ps ''' select1 = ''' PID PPID PGID WINPID TTY UID STIME COMMAND 676 4636 676 788 con 1005 13:53:32 /usr/bin/ps ''' select2 = ''' PID PPID PGID WINPID TTY UID STIME COMMAND 4636 1 4636 4636 con 1005 08:24:50 /usr/bin/bash ''' // line below must be configured for your unix - this one's cygwin format = cut2fmt([10, 18, 26, 37, 42, 47, 56]) def psgrep(s) { out = [] lines = input.split('\n').findAll{ it.size() } vars = unpack(format, lines[0]).toList().collect{ it.toLowerCase().trim() } out += lines[0] lines[1..-1].each{ values = unpack(format, it).toList().collect{ try { return it.toInteger() } catch(NumberFormatException e) { return it.trim() } } vars.eachWithIndex{ var, i -> binding.setVariable(var, values[i]) } if (new GroovyShell(binding).evaluate(s)) out += it } return '\n' + out.join('\n') + '\n' } assert psgrep('winpid < 800') == select1 assert psgrep('uid % 5 == 0 && command =~ /sh$/') == select2 // Part 2: obtaining text stream from process // unixScript: input = 'ps'.execute().text // cygwinScript: input = 'path_to_cygwin/bin/ps.exe'.execute().text // windowsScript: // can use something like sysinternal.com s pslist (with minor script tweaks) input = 'pslist.exe'.execute().text //---------------------------------------------------------------------------------- // @@PLEAC@@_2.1 //---------------------------------------------------------------------------------- // four approaches possible (shown for Integers, similar for floats, double etc.): // (1) NumberFormat.getInstance().parse(s) // getInstance() can take locale // (2) Integer.parseInt(s) // (3) new Integer(s) // (4) regex import java.text.* int nb = 0 try { nb = NumberFormat.getInstance().parse('33.5') // '.5' will be ignored nb = NumberFormat.getInstance().parse('abc') } catch (ParseException ex) { assert ex.getMessage().contains('abc') } assert nb == 33 try { nb = Integer.parseInt('34') assert nb == 34 nb = new Integer('35') nb = Integer.parseInt('abc') } catch (NumberFormatException ex) { assert ex.getMessage().contains('abc') } assert nb == 35 integerPattern = /^[+-]?\d+$/ assert '-36' =~ integerPattern assert !('abc' =~ integerPattern) decimalPattern = /^-?(?:\d+(?:\.\d*)?|\.\d+)$/ assert '37.5' =~ decimalPattern //---------------------------------------------------------------------------------- // @@PLEAC@@_2.2 //---------------------------------------------------------------------------------- // Groovy defaults to BigDecimal if you don't use an explicit float or double wage = 5.36 week = 40 * wage assert "One week's wage is: \$$week" == /One week's wage is: $214.40/ // if you want to use explicit doubles and floats you can still use // printf in version 5, 6 or 7 JVMs // printf('%5.2f', week as double) // => 214.40 //---------------------------------------------------------------------------------- // @@PLEAC@@_2.3 //---------------------------------------------------------------------------------- a = 0.255 b = a.setScale(2, BigDecimal.ROUND_HALF_UP); assert a.toString() == '0.255' assert b.toString() == '0.26' a = [3.3 , 3.5 , 3.7, -3.3] as double[] // warning rint() rounds to nearest integer - slightly different to Perl's int() rintExpected = [3.0, 4.0, 4.0, -3.0] as double[] floorExpected = [3.0, 3.0, 3.0, -4.0] as double[] ceilExpected = [4.0, 4.0, 4.0, -3.0] as double[] a.eachWithIndex{ val, i -> assert Math.rint(val) == rintExpected[i] assert Math.floor(val) == floorExpected[i] assert Math.ceil(val) == ceilExpected[i] } //---------------------------------------------------------------------------------- // @@PLEAC@@_2.4 //---------------------------------------------------------------------------------- assert Integer.parseInt('0110110', 2) == 54 assert Integer.toString(54, 2) == '110110' // also works for other radix values, e.g. hex assert Integer.toString(60, 16) == '3c' //---------------------------------------------------------------------------------- // @@PLEAC@@_2.5 //---------------------------------------------------------------------------------- x = 3; y = 20 for (i in x..y) { //i is set to every integer from x to y, inclusive } (x.. years += age } assert years == [5, 6, 7, 8, 9, 10, 11, 12] //---------------------------------------------------------------------------------- // @@PLEAC@@_2.6 //---------------------------------------------------------------------------------- // We can add additional methods to the Integer class class IntegerCategory { static def romanMap = [1000:'M', 900:'CM', 500:'D', 400:'CD', 100:'C', 90:'XC', 50:'L', 40:'XL', 10:'X', 9:'IX', 5:'V', 4:'IV', 1:'I'] static getRoman(Integer self) { def remains = self def text = '' romanMap.keySet().sort().reverse().each{ key -> while (remains >= key) { remains -= key text += romanMap[key] } } return text } static int parseRoman(Object self, String input) { def ustr = input.toUpperCase() int sum = 0 romanMap.keySet().sort().reverse().each{ key -> while (ustr.startsWith(romanMap[key])) { sum += key ustr -= romanMap[key] } } return sum } } use(IntegerCategory) { int fifteen = 15 assert fifteen.roman == 'XV' assert parseRoman('XXVI') == 26 for (i in 1..3900) { assert i == parseRoman(i.roman) } } //---------------------------------------------------------------------------------- // @@PLEAC@@_2.7 //---------------------------------------------------------------------------------- random = new Random() 100.times{ next = random.nextInt(50) + 25 assert next > 24 assert next < 76 } chars = [] ['A'..'Z','a'..'z','0'..'9',('!@$%^&*' as String[]).toList()].each{chars += it} password = (1..8).collect{ chars[random.nextInt(chars.size())] }.join() assert password.size() == 8 //---------------------------------------------------------------------------------- // @@PLEAC@@_2.8 //---------------------------------------------------------------------------------- // By default Groovy uses Java's Random facilities which use the current time // as the initial seed. This always changes but does so slowly over time. // You are free to select a better seed if you want greater randomness or // use the same one each time if you need repeatability. long seed = System.currentTimeMillis() random1 = new Random(seed) random2 = new Random(seed) assert random1.nextInt() == random2.nextInt() //---------------------------------------------------------------------------------- // @@PLEAC@@_2.9 //---------------------------------------------------------------------------------- // java.util.Random which Groovy uses already uses a 48-bit seed // You can make use 64 not 48 bits (and make better use of the 48 bits) see here: // http://alife.co.uk/nonrandom/ // You can choose a better seed, e.g. Ant uses: seed = System.currentTimeMillis() + Runtime.runtime.freeMemory() // You can accept input from the user, e.g. // http://examples.oreilly.com/javacrypt/files/oreilly/jonathan/util/Seeder.java //---------------------------------------------------------------------------------- // @@PLEAC@@_2.10 //---------------------------------------------------------------------------------- // use Java's Random.nextGaussian() method random = new Random() mean = 25 sdev = 2 salary = random.nextGaussian() * sdev + mean // script: printf 'You have been hired at \$%.2f', salary // => You have been hired at $25.05 //---------------------------------------------------------------------------------- // @@PLEAC@@_2.11 //---------------------------------------------------------------------------------- // radians = Math.toRadians(degrees) assert Math.toRadians(90) == Math.PI / 2 // degrees = Math.toDegrees(radians) assert Math.toDegrees(Math.PI) == 180 //---------------------------------------------------------------------------------- // @@PLEAC@@_2.12 //---------------------------------------------------------------------------------- // use Java's trigonometry methods in java.lang.Math //---------------------------------------------------------------------------------- t = Math.tan(1.5) assert t > 14.1 && t < 14.11 ac = Math.acos(0.1) assert ac > 1.47 && ac < 1.48 //---------------------------------------------------------------------------------- // @@PLEAC@@_2.13 //---------------------------------------------------------------------------------- assert Math.log(Math.E) == 1 assert Math.log10(10000) == 4 def logn(base, val) { Math.log(val)/Math.log(base) } assert logn(2, 1024) == 10 //---------------------------------------------------------------------------------- // @@PLEAC@@_2.14 //---------------------------------------------------------------------------------- // there are several Java Matrix packages available, e.g. // http://math.nist.gov/javanumerics/jama import Jama.Matrix matrix1 = new Matrix([ [3, 2, 3], [5, 9, 8] ] as double[][]) matrix2 = new Matrix([ [4, 7], [9, 3], [8, 1] ] as double[][]) expectedArray = [[54.0, 30.0], [165.0, 70.0]] as double[][] productArray = matrix1.times(matrix2).array for (i in 0.. if (factors[x]) factors[x] += 1 else factors[x] = 1 } n = orig i = 2 sqi = 4 // square of i while (sqi <= n) { while (n.remainder(i) == 0) { n /= i addFactor i } // we take advantage of the fact that (i+1)**2 = i**2 + 2*i + 1 sqi += 2 * i + 1 i += 1 } if ((n != 1) && (n != orig)) addFactor n return factors } def pretty(factors) { if (!factors) return "PRIME" sb = new StringBuffer() factors.keySet().sort().each { key -> sb << key if (factors[key] > 1) sb << "**" + factors[key] sb << " " } return sb.toString().trim() } assert pretty(factorize(2178)) == '2 3**2 11**2' assert pretty(factorize(39887)) == 'PRIME' assert pretty(factorize(239322000000000000000000)) == '2**19 3 5**18 39887' //---------------------------------------------------------------------------------- // @@PLEAC@@_3.0 //---------------------------------------------------------------------------------- // 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) //---------------------------------------------------------------------------------- // @@PLEAC@@_3.1 //---------------------------------------------------------------------------------- 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 //---------------------------------------------------------------------------------- // @@PLEAC@@_3.2 //---------------------------------------------------------------------------------- // create a calendar with current time and time zone cal = Calendar.instance // set time zone using long or short timezone values cal.timeZone = TimeZone.getTimeZone("America/Los_Angeles") cal.timeZone = TimeZone.getTimeZone("UTC") // set date fields one at a time cal.set(Calendar.MONTH, Calendar.DECEMBER) // or several together //calendar.set(year, month - 1, day, hour, minute, second) // get time in seconds since EPOCH long time = cal.time.time / 1000 println time // => 1196522682 //---------------------------------------------------------------------------------- // @@PLEAC@@_3.3 //---------------------------------------------------------------------------------- // create a calendar with current time and time zone cal = Calendar.instance // set time cal.time = new Date(time * 1000) // get date fields println('Dateline: ' + cal.get(Calendar.HOUR_OF_DAY) + ':' + cal.get(Calendar.MINUTE) + ':' + cal.get(Calendar.SECOND) + '-' + cal.get(Calendar.YEAR) + '/' + (cal.get(Calendar.MONTH) + 1) + '/' + cal.get(Calendar.DATE)) // => Dateline: 7:33:16-2007/1/1 //---------------------------------------------------------------------------------- // @@PLEAC@@_3.4 //---------------------------------------------------------------------------------- import java.text.SimpleDateFormat long difference = 100 long after = time + difference long before = time - difference // any field of a calendar is incrementable via add() and roll() methods cal = Calendar.instance df = new SimpleDateFormat() printCal = {cal -> df.format(cal.time)} cal.set(2000, 0, 1, 00, 01, 0) assert printCal(cal) == '1/01/00 00:01' // roll minute back by 2 but don't adjust other fields cal.roll(Calendar.MINUTE, -2) assert printCal(cal) == '1/01/00 00:59' // adjust hour back 1 and adjust other fields if needed cal.add(Calendar.HOUR, -1) assert printCal(cal) == '31/12/99 23:59' // larger example cal.timeZone = TimeZone.getTimeZone("UTC") cal.set(1973, 0, 18, 3, 45, 50) cal.add(Calendar.DATE, 55) cal.add(Calendar.HOUR_OF_DAY, 2) cal.add(Calendar.MINUTE, 17) cal.add(Calendar.SECOND, 5) assert printCal(cal) == '14/03/73 16:02' // alternatively, work with epoch times long birthTime = 96176750359 // 18/Jan/1973, 3:45:50 am long interval = 5 + // 5 second 17 * 60 + // 17 minute 2 * 60 * 60 + // 2 hour 55 * 60 * 60 * 24 // and 55 day then = new Date(birthTime + interval * 1000) assert df.format(then) == '14/03/73 16:02' // Alternatively, the Google Data module has a category with DSL-like time support: // http://docs.codehaus.org/display/GROOVY/Google+Data+Support // which supports the following syntax // def interval = 5.seconds + 17.minutes + 2.hours + 55.days //---------------------------------------------------------------------------------- // @@PLEAC@@_3.5 //---------------------------------------------------------------------------------- bree = 361535725 // 16 Jun 1981, 4:35:25 nat = 96201950 // 18 Jan 1973, 3:45:50 difference = bree - nat println "There were $difference seconds between Nat and Bree" // => There were 265333775 seconds between Nat and Bree seconds = difference % 60 difference = (difference - seconds) / 60 minutes = difference % 60 difference = (difference - minutes) / 60 hours = difference % 24 difference = (difference - hours) / 24 days = difference % 7 weeks = (difference - days) / 7 println "($weeks weeks, $days days, $hours:$minutes:$seconds)" // => (438 weeks, 4 days, 23:49:35) //---------------------------------------------------------------------------------- cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")) cal.set(1981, 5, 16) // 16 Jun 1981 date1 = cal.time cal.set(1973, 0, 18) // 18 Jan 1973 date2 = cal.time difference = Math.abs(date2.time - date1.time) days = difference / (1000 * 60 * 60 * 24) assert days == 3071 //---------------------------------------------------------------------------------- // @@PLEAC@@_3.6 //---------------------------------------------------------------------------------- // create a calendar with current time and time zone cal = Calendar.instance cal.set(1981, 5, 16) yearDay = cal.get(Calendar.DAY_OF_YEAR); year = cal.get(Calendar.YEAR); yearWeek = cal.get(Calendar.WEEK_OF_YEAR); df1 = new SimpleDateFormat("dd/MMM/yy") df2 = new SimpleDateFormat("EEEE") print(df1.format(cal.time) + ' was a ' + df2.format(cal.time)) println " and was day number $yearDay and week number $yearWeek of $year" // => 16/Jun/81 was a Tuesday and was day number 167 and week number 25 of 1981 //---------------------------------------------------------------------------------- // @@PLEAC@@_3.7 //---------------------------------------------------------------------------------- input = "1998-06-03" df1 = new SimpleDateFormat("yyyy-MM-dd") date = df1.parse(input) df2 = new SimpleDateFormat("MMM/dd/yyyy") println 'Date was ' + df2.format(date) // => Date was Jun/03/1998 //---------------------------------------------------------------------------------- // @@PLEAC@@_3.8 //---------------------------------------------------------------------------------- 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 //---------------------------------------------------------------------------------- // @@PLEAC@@_3.9 //---------------------------------------------------------------------------------- // script: println 'Press return when ready' before = System.currentTimeMillis() input = new BufferedReader(new InputStreamReader(System.in)).readLine() after = System.currentTimeMillis() elapsed = (after - before) / 1000 println "You took $elapsed seconds." // => You took2.313 seconds. // take mean sorting time size = 500; number = 100; total = 0 for (i in 0.. On average, sorting 500 random numbers takes 0.32 milliseconds //---------------------------------------------------------------------------------- // @@PLEAC@@_3.10 //---------------------------------------------------------------------------------- delayMillis = 50 Thread.sleep(delayMillis) //---------------------------------------------------------------------------------- // @@PLEAC@@_3.11 //---------------------------------------------------------------------------------- // this could be done more simply using JavaMail's getAllHeaderLines() but is shown // in long hand for illustrative purposes sampleMessage = '''Delivered-To: alias-someone@somewhere.com.au Received: (qmail 27284 invoked from network); 30 Dec 2006 15:16:26 -0000 Received: from unknown (HELO lists-outbound.sourceforge.net) (66.35.250.225) by bne012m.server-web.com with SMTP; 30 Dec 2006 15:16:25 -0000 Received: from sc8-sf-list2-new.sourceforge.net (sc8-sf-list2-new-b.sourceforge.net [10.3.1.94]) by sc8-sf-spam2.sourceforge.net (Postfix) with ESMTP id D8CCBFDE3; Sat, 30 Dec 2006 07:16:24 -0800 (PST) Received: from sc8-sf-mx1-b.sourceforge.net ([10.3.1.91] helo=mail.sourceforge.net) by sc8-sf-list2-new.sourceforge.net with esmtp (Exim 4.43) id 1H0fwX-0003c0-GA for pleac-discuss@lists.sourceforge.net; Sat, 30 Dec 2006 07:16:20 -0800 Received: from omta05ps.mx.bigpond.com ([144.140.83.195]) by mail.sourceforge.net with esmtp (Exim 4.44) id 1H0fwY-0005D4-DD for pleac-discuss@lists.sourceforge.net; Sat, 30 Dec 2006 07:16:19 -0800 Received: from win2K001 ([138.130.127.127]) by omta05ps.mx.bigpond.com with SMTP id <20061230151611.XVWL19269.omta05ps.mx.bigpond.com@win2K001>; Sat, 30 Dec 2006 15:16:11 +0000 From: someone@somewhere.com To: Date: Sun, 31 Dec 2006 02:14:57 +1100 Subject: Re: [Pleac-discuss] C/Posix/GNU - @@pleac@@_10x Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Sender: pleac-discuss-bounces@lists.sourceforge.net Errors-To: pleac-discuss-bounces@lists.sourceforge.net ----- Original Message ----- From: someone@somewhere.com To: otherperson@somewhereelse.com Cc: Sent: Wednesday, December 27, 2006 9:18 AM Subject: Re: [Pleac-discuss] C/Posix/GNU - @@pleac@@_10x I really like that description of PLEAC. ''' expected = ''' Sender Recipient Time Delta somewhere.com 01:14:57 06/12/31 win2K001 omta05ps.mx.bigpond.com 01:14:57 06/12/31 1m 14s omta05ps.mx.bigpond.com mail.sourceforge.net 01:16:11 06/12/31 8s sc8-sf-mx1-b.sourceforge. sc8-sf-list2-new.sourcefo 01:16:19 06/12/31 1s sc8-sf-list2-new.sourcefo sc8-sf-spam2.sourceforge. 01:16:20 06/12/31 4s unknown bne012m.server-web.com 01:16:24 06/12/31 1s ''' class MailHopDelta { def headers, firstSender, firstDate, out MailHopDelta(mail) { extractHeaders(mail) out = new StringBuffer() def m = (mail =~ /(?m)^Date:\s+(.*)/) firstDate = parseDate(m[0][1]) firstSender = (mail =~ /(?m)^From.*\@([^\s>]*)/)[0][1] out('Sender Recipient Time Delta'.split(' ')) } def parseDate(date) { try { return new SimpleDateFormat('EEE, dd MMM yyyy hh:mm:ss Z').parse(date) } catch(java.text.ParseException ex) {} try { return new SimpleDateFormat('dd MMM yyyy hh:mm:ss Z').parse(date) } catch(java.text.ParseException ex) {} try { return DateFormat.getDateInstance(DateFormat.FULL).parse(date) } catch(java.text.ParseException ex) {} DateFormat.getDateInstance(DateFormat.LONG).parse(date) } def extractHeaders(mail) { headers = [] def isHeader = true def currentHeader = '' mail.split('\n').each{ line -> if (!isHeader) return switch(line) { case ~/^\s*$/: isHeader = false if (currentHeader) headers << currentHeader break case ~/^\s+.*/: currentHeader += line; break default: if (currentHeader) headers << currentHeader currentHeader = line } } } def out(line) { out << line[0][0..<[25,line[0].size()].min()].padRight(26) out << line[1][0..<[25,line[1].size()].min()].padRight(26) out << line[2].padRight(17) + ' ' out << line[3] + '\n' } def prettyDate(date) { new SimpleDateFormat('hh:mm:ss yy/MM/dd').format(date) } def process() { out(['', firstSender, prettyDate(firstDate), '']) def prevDate = firstDate headers.grep(~/^Received:\sfrom.*/).reverseEach{ hop -> def from = (hop =~ /from\s+(\S+)|\((.*?)\)/)[0][1] def by = (hop =~ /by\s+(\S+\.\S+)/)[0][1] def hopDate = parseDate(hop[hop.lastIndexOf(';')+2..-1]) out([from, by, prettyDate(prevDate), prettyDelta(hopDate.time - prevDate.time)]) prevDate = hopDate } return out.toString() } def prettyField(secs, sign, ch, multiplier, sb) { def whole = (int)(secs / multiplier) if (!whole) return 0 sb << '' + (sign * whole) + ch + ' ' return whole * multiplier } def prettyDelta(millis) { def sign = millis < 0 ? -1 : 1 def secs = (int)Math.abs(millis/1000) def sb = new StringBuffer() secs -= prettyField(secs, sign, 'd', 60 * 60 * 24, sb) secs -= prettyField(secs, sign, 'h', 60 * 60, sb) secs -= prettyField(secs, sign, 'm', 60, sb) prettyField(secs, sign, 's', 1, sb) return sb.toString().trim() } } assert '\n' + new MailHopDelta(sampleMessage).process() == expected //---------------------------------------------------------------------------------- // @@PLEAC@@_4.0 //---------------------------------------------------------------------------------- simple = [ "this", "that", "the", "other" ] nested = [ "this", "that", [ "the", "other" ] ] assert nested.size() == 3 assert nested[2].size() == 2 flattenNestedToSimple = [ "this", "that", [ "the", "other" ] ].flatten() assert flattenNestedToSimple.size() == 4 //---------------------------------------------------------------------------------- // @@PLEAC@@_4.1 //---------------------------------------------------------------------------------- a = [ "quick", "brown", "fox" ] assert a.size() == 3 a = 'Why are you teasing me?'.split(' ') assert a == ["Why", "are", "you", "teasing", "me?"] removeLeadingSpaces = { it.trim() } nonBlankLines = { it } lines = ''' The boy stood on the burning deck, It was as hot as glass. '''.split('\n').collect(removeLeadingSpaces).findAll(nonBlankLines) assert lines == ["The boy stood on the burning deck,", "It was as hot as glass."] // initialiseListFromFileScript: lines = new File('mydata.txt').readLines() // processFileScript: new File('mydata.txt').eachLine{ // dosomething } //---------------------------------------------------------------------------------- // @@PLEAC@@_4.2 //---------------------------------------------------------------------------------- marbleColors = ['red', 'green', 'yellow'] assert marbleColors.join(', ') == 'red, green, yellow' def commify(items) { if (!items) return items def sepchar = items.find{ it =~ /,/ } ? '; ' : ', ' switch (items.size()) { case 1: return items[0] case 2: return items.join(' and ') } items[0..-2].join(sepchar) + sepchar + 'and ' + items[-1] } assert commify(marbleColors) == 'red, green, and yellow' lists = [ [ 'just one thing' ], [ 'Mutt', 'Jeff' ], 'Peter Paul Mary'.split(' '), [ 'To our parents', 'Mother Theresa', 'God' ], [ 'pastrami', 'ham and cheese', 'peanut butter and jelly', 'tuna' ], [ 'recycle tired, old phrases', 'ponder big, happy thoughts' ], [ 'recycle tired, old phrases', 'ponder big, happy thoughts', 'sleep and dream peacefully' ], ] expected = ''' just one thing Mutt and Jeff Peter, Paul, and Mary To our parents, Mother Theresa, and God pastrami, ham and cheese, peanut butter and jelly, and tuna recycle tired, old phrases and ponder big, happy thoughts recycle tired, old phrases; ponder big, happy thoughts; and sleep and dream peacefully ''' assert expected == '\n' + lists.collect{commify(it)}.join('\n') + '\n' //---------------------------------------------------------------------------------- // @@PLEAC@@_4.3 //---------------------------------------------------------------------------------- // In Groovy, lists and arrays are more or less interchangeable // here is the example using lists people = ['Crosby', 'Stills', 'Nash'] assert people.size() == 3 people[3] = 'Young' assert people.size() == 4 assert people == ['Crosby', 'Stills', 'Nash', 'Young'] // to use arrays simply do 'people = peopleArray.toList()' at the start // and 'peopleArray = people as String[]' at the end // if you attempt to do extension on a Java array you will get an // ArrayIndexOutOfBoundsException - which is why Java has ArrayList et al //---------------------------------------------------------------------------------- // @@PLEAC@@_4.4 //---------------------------------------------------------------------------------- // list to process people == ['Crosby', 'Stills', 'Nash', 'Young'] // helper startsWithCapital = { word -> word[0] in 'A'..'Z' } // various styles are possible for processing lists // closure style people.each { person -> assert startsWithCapital(person) } // for loop style for (person in people) { assert startsWithCapital(person) } // unixScriptToFindAllUsersStartingWithLetterA: all = 'who'.execute().text.replaceAll('\r', '').split('\n') all.grep(~/^a.*/).each{ println it } // printFileWithWordsReversedScript: new File('Pleac/src/SlowCat.groovy').eachLine{ line -> line.split(' ').each{ print it.reverse() } } a = [0.5, 3]; b = [0, 1] assert [a, b].flatten().collect{ it * 7 } == [3.5, 21, 0, 7] // above doesn't modify original arrays // instead use a = a.collect{ ... } //---------------------------------------------------------------------------------- // @@PLEAC@@_4.5 //---------------------------------------------------------------------------------- // not relevant in Groovy since we have always references items = [] for (item in items) { // do something with item } //---------------------------------------------------------------------------------- // @@PLEAC@@_4.6 //---------------------------------------------------------------------------------- assert [ 1, 1, 2, 2, 3, 3, 3, 5 ].unique() == [ 1, 2, 3, 5 ] //---------------------------------------------------------------------------------- // @@PLEAC@@_4.7 //---------------------------------------------------------------------------------- assert [ 1, 1, 2, 2, 3, 3, 3, 4, 5 ] - [ 1, 2, 4 ] == [3, 3, 3, 5] assert [ 1, 1, 2, 2, 3, 3, 3, 4, 5 ].unique() - [ 1, 2, 4 ] == [3, 5] //---------------------------------------------------------------------------------- // @@PLEAC@@_4.8 //---------------------------------------------------------------------------------- a = [1, 3, 5, 6, 7, 8] b = [2, 3, 5, 7, 9] // intersection assert a.intersect(b) == [3, 5, 7] // union assert (a + b).unique().sort() == [1, 2, 3, 5, 6, 7, 8, 9] // difference assert (a - b) == [1, 6, 8] //---------------------------------------------------------------------------------- // @@PLEAC@@_4.9 //---------------------------------------------------------------------------------- members = [ "Time", "Flies" ] initiates = [ "An", "Arrow" ] members += initiates assert members == ["Time", "Flies", "An", "Arrow"] members.add(2, "Like") assert members == ["Time", "Flies", "Like", "An", "Arrow"] members[0] = "Fruit" members[3..4] = ["A", "Banana"] assert members == ["Fruit", "Flies", "Like", "A", "Banana"] //---------------------------------------------------------------------------------- // @@PLEAC@@_4.10 //---------------------------------------------------------------------------------- items = ["the", "quick", "brown", "fox"] assert items.reverse() == ["fox", "brown", "quick", "the"] firstLetters = [] items.reverseEach{ firstLetters += it[0] } assert firstLetters.join() == 'fbqt' descending = items.sort().reverse() assert descending == ["the", "quick", "fox", "brown"] descendingBySecondLastLetter = items.sort { a,b -> b[-2] <=> a[-2] } assert descendingBySecondLastLetter == ["brown", "fox", "the", "quick"] //---------------------------------------------------------------------------------- // @@PLEAC@@_4.11 //---------------------------------------------------------------------------------- // warning: not an exact equivalent, idiomatic use would return copies def shift2 = {one = friends[0]; two = friends[1]; 2.times{friends.remove(0)}} friends = 'Peter Paul Mary Jim Tim'.split(' ').toList() shift2() assert one == 'Peter' assert two == 'Paul' assert friends == ["Mary", "Jim", "Tim"] def pop2(items) { items[0..1] } beverages = 'Dew Jolt Cola Sprite Fresca'.split(' ').toList() pair = pop2(beverages) assert pair == ["Dew", "Jolt"] //---------------------------------------------------------------------------------- // @@PLEAC@@_4.12 //---------------------------------------------------------------------------------- class Employee { def name def position def salary } staff = [new Employee(name:'Jim',position:'Manager',salary:26000), new Employee(name:'Jill',position:'Engineer',salary:24000), new Employee(name:'Jack',position:'Engineer',salary:22000)] highestEngineer = staff.find { emp -> emp.position == 'Engineer' } assert highestEngineer.salary == 24000 //---------------------------------------------------------------------------------- // @@PLEAC@@_4.13 //---------------------------------------------------------------------------------- engineers = staff.findAll { e -> e.position == 'Engineer' } assert engineers.size() == 2 highPaid = staff.findAll { e -> e.salary > 23000 } assert highPaid*.name == ["Jim", "Jill"] //---------------------------------------------------------------------------------- // @@PLEAC@@_4.14 //---------------------------------------------------------------------------------- // sort works for numbers assert [100, 3, 20].sort() == [3, 20, 100] // strings representing numbers will be sorted alphabetically assert ['100', '3', '20'].sort() == ["100", "20", "3"] // closure style sorting allows arbitrary expressions for the comparison assert ['100', '3', '20'].sort{ a,b -> a.toLong() <=> b.toLong()} == ["3", "20", "100"] // obtain the following on unix systems using: 'ps ux'.execute().text processInput = ''' PID PPID PGID WINPID TTY UID STIME COMMAND 3868 1 3868 3868 con 1005 06:23:34 /usr/bin/bash 3456 3868 3456 3528 con 1005 06:23:39 /usr/bin/ps ''' nonEmptyLines = {it.trim()} lines = processInput.split("\n").findAll(nonEmptyLines)[1..-1] def col(n, s) { s.tokenize()[n] } commandIdx = 7 pidIdx = 0 ppidIdx = 1 linesByPid = lines.sort{ col(pidIdx,it).toLong() } assert col(commandIdx, linesByPid[0]) == '/usr/bin/ps' linesByPpid = lines.sort{ col(ppidIdx,it).toLong() } assert col(commandIdx, linesByPpid[0]) == '/usr/bin/bash' //---------------------------------------------------------------------------------- // @@PLEAC@@_4.15 //---------------------------------------------------------------------------------- // sort staff from 4.12 by name assert staff.sort { a,b -> a.name <=> b.name }*.name == ["Jack", "Jill", "Jim"] // sort by first two characters of name and if equal by descending salary assert staff.sort { a,b -> astart = a.name[0..1] bstart = b.name[0..1] if (astart == bstart) return b.salary <=> a.salary return astart <=> bstart }*.name == ["Jack", "Jim", "Jill"] //---------------------------------------------------------------------------------- // @@PLEAC@@_4.16 //---------------------------------------------------------------------------------- items = [1, 2, 3, 4, 5] processed = [] 10.times{ processed << items[0] items = items[1..-1] + items[0] } assert processed == [1, 2, 3, 4, 5, 1, 2, 3, 4, 5] //---------------------------------------------------------------------------------- // @@PLEAC@@_4.17 //---------------------------------------------------------------------------------- import java.text.DateFormatSymbols as Symbols items = new Symbols().shortWeekdays.toList()[1..7] assert items == ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] // not as random as you might expect println items.sort{ Math.random() } // => ["Sat", "Tue", "Sun", "Wed", "Mon", "Thu", "Fri"] // better to use the built-in method for this purpose Collections.shuffle(items) println items // => ["Wed", "Tue", "Fri", "Sun", "Sat", "Thu", "Mon"] //---------------------------------------------------------------------------------- // @@PLEAC@@_4.18 //---------------------------------------------------------------------------------- symbols = new Symbols() words = symbols.weekdays.toList()[1..7] + symbols.months.toList()[0..11] + symbols.eras.toList() + symbols.amPmStrings.toList() expected = // 'AD August February July May October September Tuesday \n' + 'AM BC Friday June Monday PM Sunday Wednesday \n' + 'April December January March November Saturday Thursday \n' class WordFormatter { def cols def process(list) { def sb = new StringBuffer() def colWidth = list.max{it.size()}.size() + 1 int columns = [cols/colWidth, 1].max() def numWords = list.size() int rows = (numWords + columns - 1) / columns for (row in 0.. 0) { result *= n n -= 1 } return result } expected = 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000 assert expected == factorial(100) // println factorial(10000) // => 284625... (greater than 35,000 digits) // simple version but less efficient def simplePermute(items, perms) { if (items.size() == 0) println perms.join(' ') else for (i in items) { newitems = items.clone() newperms = perms.clone() newperms.add(i) newitems.remove(i) simplePermute(newitems, newperms) } } simplePermute(['dog', 'bites', 'man'], []) // => //dog bites man //dog man bites //bites dog man //bites man dog //man dog bites //man bites dog // optimised version below expected = ''' man bites dog man dog bites bites man dog bites dog man dog man bites dog bites man ''' // n2pat(n, len): produce the N-th pattern of length len def n2pat(n, length) { def pat = [] int i = 1 while (i <= length) { pat << (n % i) n = n.intdiv(i) i += 1 } pat } // pat2perm(pat): turn pattern returned by n2pat() into // permutation of integers. def pat2perm(pat) { def source = (0 ..< pat.size()).collect{ it/*.toString()*/ } def perm = [] while (pat.size() > 0) { def next = pat.remove(pat.size()-1) perm << source[next] source.remove(next) } perm } def n2perm(n, len) { pat2perm(n2pat((int)n,len)) } data = ['man', 'bites', 'dog'] sb = new StringBuffer() numPermutations = fact(data.size()) for (j in 0.. data[k] } sb << permutation.join(' ') + '\n' } assert '\n' + sb.toString() == expected //---------------------------------------------------------------------------------- // @@PLEAC@@_5.0 //---------------------------------------------------------------------------------- // quotes are optional around the key age = [ Nat:24, Jules:25, Josh:17 ] assert age['Nat'] == 24 // alternate syntax assert age."Jules" == 25 foodColor = [ Apple: 'red', Banana: 'yellow', Lemon: 'yellow', Carrot: 'orange' ] assert foodColor.size() == 4 //---------------------------------------------------------------------------------- // @@PLEAC@@_5.1 //---------------------------------------------------------------------------------- foodColor['Lemon'] = 'green' assert foodColor.size() == 4 assert foodColor['Lemon'] == 'green' foodColor['Raspberry'] = 'pink' assert foodColor.size() == 5 //---------------------------------------------------------------------------------- // @@PLEAC@@_5.2 //---------------------------------------------------------------------------------- assert ['Banana', 'Martini'].collect{ foodColor.containsKey(it)?'food':'drink' } == [ 'food', 'drink' ] age = [Toddler:3, Unborn:0, Phantasm:null] ['Toddler', 'Unborn', 'Phantasm', 'Relic'].each{ key -> print "$key: " if (age.containsKey(key)) print 'has key ' if (age.containsKey(key) && age[key]!=null) print 'non-null ' if (age.containsKey(key) && age[key]) print 'true ' println '' } // => // Toddler: has key non-null true // Unborn: has key non-null // Phantasm: has key // Relic: //---------------------------------------------------------------------------------- // @@PLEAC@@_5.3 //---------------------------------------------------------------------------------- assert foodColor.size() == 5 foodColor.remove('Banana') assert foodColor.size() == 4 //---------------------------------------------------------------------------------- // @@PLEAC@@_5.4 //---------------------------------------------------------------------------------- hash = [:] hash.each { key, value -> // do something with key and value } hash.each { entry -> // do something with entry } hash.keySet().each { key -> // do something with key } sb = new StringBuffer() foodColor.each { food, color -> sb << "$food is $color\n" } assert '\n' + sb.toString() == ''' Lemon is green Carrot is orange Apple is red Raspberry is pink ''' foodColor.each { entry -> assert entry.key.size() > 4 && entry.value.size() > 2 } foodColorsSortedByFood = [] foodColor.keySet().sort().each { k -> foodColorsSortedByFood << foodColor[k] } assert foodColorsSortedByFood == ["red", "orange", "green", "pink"] fakedInput = ''' From: someone@somewhere.com From: someone@spam.com From: someone@somewhere.com ''' from = [:] fakedInput.split('\n').each{ matcher = (it =~ /^From:\s+([^\s>]*)/) if (matcher.matches()) { sender = matcher[0][1] if (from.containsKey(sender)) from[sender] += 1 else from[sender] = 1 } } // More useful to sort by number of received mail by person from.entrySet().sort { a,b -> b.value<=>a.value}.each { e-> println "${e.key}: ${e.value}" } // => // someone@somewhere.com: 2 // someone@spam.com: 1 //---------------------------------------------------------------------------------- // @@PLEAC@@_5.5 //---------------------------------------------------------------------------------- hash = [a:1, b:2, c:3] // Map#toString already produce a pretty decent output: println hash // => ["b":2, "a":1, "c":3] // Or do it by longhand for customised formatting hash.each { k,v -> println "$k => $v" } // => // b => 2 // a => 1 // c => 3 //---------------------------------------------------------------------------------- // @@PLEAC@@_5.6 //---------------------------------------------------------------------------------- // java.util.LinkedHashMap "maintains a doubly-linked list running through all of its entries. // This linked list defines the iteration ordering, which is normally the order in which keys // were inserted into the map (insertion-order)". foodColor = new LinkedHashMap() foodColor['Banana'] = 'Yellow' foodColor['Apple'] = 'Green' foodColor['Lemon'] = 'Yellow' foodColor.keySet().each{ key -> println key } // => // Banana // Apple // Lemon //---------------------------------------------------------------------------------- // @@PLEAC@@_5.7 //---------------------------------------------------------------------------------- foodsOfColor = [ Yellow:['Banana', 'Lemon'], Green:['Apple'] ] foodsOfColor['Green'] += 'Melon' assert foodsOfColor == ["Green":["Apple", "Melon"], "Yellow":["Banana", "Lemon"]] //---------------------------------------------------------------------------------- // @@PLEAC@@_5.8 //---------------------------------------------------------------------------------- surname = [Mickey: 'Mantle', Babe: 'Ruth'] assert surname.findAll{ it.value == 'Mantle' }.collect{ it.key } == ["Mickey"] firstname = [:] surname.each{ entry -> firstname[entry.value] = entry.key } assert firstname == ["Ruth":"Babe", "Mantle":"Mickey"] // foodfindScript: #!/usr/bin/groovy // usage: foodfind food_or_color" color = [Apple:'red', Banana:'yellow', Lemon:'yellow', Carrot:'orange'] given = args[0] if (color.containsKey(given)) println "$given is a food with color ${color[given]}." if (color.containsValue(given)) { // could use commify() here - see 4.2 foods = color.findAll{it.value == given}.collect{it.key} join = foods.size() == 1 ? 'is a food' : 'are foods' println "${foods.join(', ')} $join with color ${given}." } // foodfind red // => Apple is a food with color red. // foodfind yellow // => Lemon, Banana are foods with color yellow. // foodfind Carrot // => Carrot is a food with color orange. //---------------------------------------------------------------------------------- // @@PLEAC@@_5.9 //---------------------------------------------------------------------------------- foodColor = [Apple:'red', Carrot:'orange', Banana:'yellow', Cherry:'black'] // Sorted by keys assert foodColor.keySet().sort() == ["Apple", "Banana", "Carrot", "Cherry"] // you could now iterate through the hash with the sorted keys assert foodColor.values().sort() == ["black", "orange", "red", "yellow"] assert foodColor.values().sort{it.size()} == ["red", "black", "orange", "yellow"] //---------------------------------------------------------------------------------- // @@PLEAC@@_5.10 //---------------------------------------------------------------------------------- //merged = a.clone.update(b) # because Hash#update changes object in place drinkColor = [Galliano:'yellow', 'Mai Tai':'blue'] ingestedColor = [:] ingestedColor.putAll(drinkColor) // overrides any common keys ingestedColor.putAll(foodColor) totalColors = ingestedColor.values().sort().unique() assert totalColors == ["black", "blue", "orange", "red", "yellow"] //---------------------------------------------------------------------------------- // @@PLEAC@@_5.11 //---------------------------------------------------------------------------------- foodColor['Lemon']='yellow' citrusColor = [Lemon:'yellow', Orange:'orange', Lime:'green'] println foodColor println citrusColor common = foodColor.keySet().intersect(citrusColor.keySet()) assert common == ["Lemon"] foodButNotCitrus = foodColor.keySet().toList() - citrusColor.keySet().toList() assert foodButNotCitrus == ["Carrot", "Apple", "Banana", "Cherry"] //---------------------------------------------------------------------------------- // @@PLEAC@@_5.12 //---------------------------------------------------------------------------------- // no problem here, Groovy handles any kind of object for key-ing //---------------------------------------------------------------------------------- // @@PLEAC@@_5.13 //---------------------------------------------------------------------------------- // Groovy uses Java implementations for storing hashes and these // support setting an initial capacity and load factor (which determines // at what point the hash will be resized if needed) hash = [:] // Groovy shorthand gets defaults hash = new HashMap() // default capacity and load factor println hash.capacity() // => 16 ('A'..'Z').each{ hash[it] = it } println hash.capacity() // => 64 hash = new HashMap(100) // initial capacity of 100 and default load factor hash = new HashMap(100, 0.8f) // initial capacity of 100 and 0.8 load factor //---------------------------------------------------------------------------------- // @@PLEAC@@_5.14 //---------------------------------------------------------------------------------- count = [:] letters = [] foodColor.values().each{ letters.addAll((it as String[]).toList()) } letters.each{ if (count.containsKey(it)) count[it] += 1 else count[it] = 1 } assert count == ["o":3, "d":1, "k":1, "w":2, "r":2, "c":1, "l":5, "g":1, "b":1, "a":2, "y":2, "n":1, "e":4] //---------------------------------------------------------------------------------- // @@PLEAC@@_5.15 //---------------------------------------------------------------------------------- father = [ Cain:'Adam', Abel:'Adam', Seth:'Adam', Enoch:'Cain', Irad:'Enoch', Mehujael:'Irad', Methusael:'Mehujael', Lamech:'Methusael', Jabal:'Lamech', Jubal:'Lamech', Tubalcain:'Lamech', Enos:'Seth' ] def upline(person) { while (father.containsKey(person)) { print person + ' ' person = father[person] } println person } upline('Irad') // => Irad Enoch Cain Adam children = [:] father.each { k,v -> if (!children.containsKey(v)) children[v] = [] children[v] += k } def downline(person) { println "$person begat ${children.containsKey(person)?children[person].join(', '):'Nobody'}.\n" } downline('Tubalcain') // => Tubalcain begat Nobody. downline('Adam') // => Adam begat Abel, Seth, Cain. // This one doesn't recurse through subdirectories (as a simplification) // scriptToFindIncludeFilesWhichContainNoIncludesScript: dir = '' includes = [:] new File(dir).eachFile{ file -> if (file.directory) return file.eachLine{ line -> matcher = (line =~ '^\\s*#\\s*include\\s*<([^>]+)>') if (matcher.matches()) { if (!includes.containsKey(file.name)) includes[file.name] = [] includes[file.name] += matcher[0][1] } } } // find referenced files which have no includes; assumes all files // were processed and none are missing println includes.values().sort().flatten().unique() - includes.keySet() //---------------------------------------------------------------------------------- // @@PLEAC@@_5.16 //---------------------------------------------------------------------------------- // dutree - print sorted indented rendition of du output // obtaining this input is not shown, it is similar to other examples // on some unix systems it will be: duProcessFakedInput = "du options".process().text duProcessFakedInput = ''' 11732 groovysoap/lib 68 groovysoap/src/main/groovy/net/soap 71 groovysoap/src/main/groovy/net 74 groovysoap/src/main/groovy 77 groovysoap/src/main 9 groovysoap/src/examples 8 groovysoap/src/examples/groovy 102 groovysoap/src/test 202 groovysoap/src 11966 groovysoap ''' // The DuNode class collects all information about a directory, class DuNode { def name def size def kids = [] // support for sorting nodes with side def compareTo(node2) { size <=> node2.size } def getBasename() { name.replaceAll(/.*\//, '') } // returns substring before last "/", otherwise null def getParent() { def p = name.replaceAll(/\/[^\/]+$/,'') return (p == name) ? null : p } } // The DuTree does the actual work of // getting the input, parsing it, building up a tree // and formatting it for output class DuTree { def input def topdir def nodes = [:] def dirsizes = [:] def kids = [:] // get a node by name, create it if it does not exist yet def getOrCreateNode(name) { if (!nodes.containsKey(name)) nodes[name] = new DuNode(name:name) return nodes[name] } // figure out how much is taken in each directory // that isn't stored in the subdirectories. Add a new // fake kid called "." containing that much. def getDots(node) { def cursize = node.size for (kid in node.kids) { cursize -= kid.size getDots(kid) } if (node.size != cursize) { def newnode = getOrCreateNode(node.name + "/.") newnode.size = cursize node.kids += newnode } } def processInput() { def name = '' input.split('\n').findAll{it.trim()}.each{ line -> def tokens = line.tokenize() def size = tokens[0] name = tokens[1] def node = getOrCreateNode(name) node.size = size.toInteger() nodes[name] = node def parent = node.parent if (parent) getOrCreateNode(parent).kids << node } topdir = nodes[name] } // recursively output everything // passing padding and number width as well // on recursive calls def output(node, prefix='', width=0) { def line = node.size.toString().padRight(width) + ' ' + node.basename println (prefix + line) prefix += line.replaceAll(/\d /, '| ') prefix = prefix.replaceAll(/[^|]/, ' ') if (node.kids.size() > 0) { // not a bachelor node kids = node.kids kids.sort{ a,b -> b.compareTo(a) } width = kids[0].size.toString().size() for (kid in kids) output(kid, prefix, width) } } } tree = new DuTree(input:duProcessFakedInput) tree.processInput() tree.getDots(tree.topdir) tree.output(tree.topdir) // => // 11966 groovysoap // | 11732 lib // | 202 src // | | 102 test // | | 77 main // | | | 74 groovy // | | | | 71 net // | | | | | 68 soap // | | | | | 3 . // | | | | 3 . // | | | 3 . // | | 14 . // | | 9 examples // | | | 8 groovy // | | | 1 . // | 32 . //---------------------------------------------------------------------------------- // @@PLEAC@@_6.0 //---------------------------------------------------------------------------------- // Groovy has built-in language support for Regular Expressions: // * Strings quoted with '/' characters have special escaping // rules for backslashes and the like. // * ~string (regex pattern operator) // * m =~ /pattern/ (regex find operator) // * m ==~/pattern/ (regex match operator) // * patterns can be used in case expressions in a switch statement // * string.replaceAll can take a closure expression as the second argument // In addition, Groovy can make use of Java's Pattern, Matcher and Scanner classes // directly. (The sugar coating metnioed above sits on top of these anyway). // There are also additional open source Java regex libraries which can be used. meadow1 = 'cow grass butterflies Ovine' meadow2 = 'goat sheep flowers dog' // pattern strings can benefit from 'slashy' quotes partial = /sheep/ full = /.*sheep.*/ // find operator assert !(meadow1 =~ partial) assert meadow2 =~ partial finder = (meadow2 =~ partial) // underneath Groovy sugar coating is Java implementation assert finder instanceof java.util.regex.Matcher // match operator assert !(meadow1 ==~ full) assert meadow2 ==~ full matcher = (meadow2 ==~ full) // under the covers is just a boolean assert matcher instanceof Boolean assert meadow1 =~ /(?i)\bovines?\b/ // (?i) == case flag string = 'good food' println string.replaceFirst(/o*/, 'e') // => egood food println string.replaceAll(/o*/, 'e') // => egeede efeede (global) // beware this one is just textual replacement println string.replace(/o*/, 'e') // => good food println 'o*o*'.replace(/o*/, 'e') // => ee // groovy -e "m = args[0] =~ /(a|ba|b)+(a|ac)+/; if (m.matches()) println m[0][0]" ababacaca // => ababa digits = "123456789" nonlap = digits =~ /\d\d\d/ assert nonlap.count == 3 print 'Non-overlapping: ' (0.. dir.replaceFirst('bin', 'lib') } assert libdirs == expected //---------------------------------------------------------------------------------- // @@PLEAC@@_6.2 //---------------------------------------------------------------------------------- // Groovy uses Java regex (other Java regex packages would also be possible) // It doesn't support Locale-based settings but you can roll your own to some // extent, you can use any Unicode characters as per below and you can use // \p{Punct} Punctuation: One of !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ // or the other special character classes words = ''' silly façade coöperate niño Renée Moliçre hæmoglobin naïve tschüß random!stuff#here\u0948 ''' results = '' greekAlpha = '\u0391' special = 'çéüßöñàæï?' + greekAlpha // flag as either Y (alphabetic) or N (not) words.split('\n').findAll{it.trim()}.each{ results += it ==~ /^[\w/+special+/]+$/ ?'Y':'N' } assert results == 'YYYYYYYYYN' results = '' words.split('\n').findAll{it.trim()}.each{ results += it ==~ /^[^\p{Punct}]+$/ ?'Y':'N' } assert results == 'YYYYYYYYYN' //---------------------------------------------------------------------------------- // @@PLEAC@@_6.3 //---------------------------------------------------------------------------------- // as many non-whitespace bytes as possible finder = 'abczqz z' =~ /a\S+z/ assert finder[0] == 'abczqz' // as many letters, apostrophes, and hyphens finder = "aAzZ'z-z0z" =~ /a[A-Za-z'-]+z/ //' assert finder[0] == "aAzZ'z-z" // selecting words finder = '23rd Psalm' =~ /\b([A-Za-z]+)\b/ // usually best println finder[0][0] // => Psalm (23rd is not matched) finder = '23rd Psalm' =~ /\s([A-Za-z]+)\s/ // fails at ends or w/ punctuation println finder.matches() // => false (no whitespaces at ends) //---------------------------------------------------------------------------------- // @@PLEAC@@_6.4 //---------------------------------------------------------------------------------- str = 'groovy.codehaus.org and www.aboutgroovy.com' re = '''(?x) # to enable whitespace and comments ( # capture the hostname in $1 (?: # these parens for grouping only (?! [-_] ) # lookahead for neither underscore nor dash [\\w-] + # hostname component \\. # and the domain dot ) + # now repeat that whole thing a bunch of times [A-Za-z] # next must be a letter [\\w-] + # now trailing domain part ) # end of $1 capture ''' finder = str =~ re out = str (0.. groovy.codehaus.org [63.246.7.187] and www.aboutgroovy.com [63.246.7.76] // to match whitespace or #-characters in an extended re you need to escape them. foo = 42 str = 'blah #foo# blah' re = '''(?x) # to enable whitespace and comments \\# # a pound sign (\\w+) # the variable name \\# # another pound sign ''' finder = str =~ re found = finder[0] out = str.replaceAll(found[0], evaluate(found[1]).toString()) assert out == 'blah 42 blah' //---------------------------------------------------------------------------------- // @@PLEAC@@_6.5 //---------------------------------------------------------------------------------- fish = 'One fish two fish red fish blue fish' expected = 'The third fish is a red one.' thirdFish = /(?:\w+\s+fish\s+){2}(\w+)\s+fish.*/ assert expected == (fish.replaceAll(thirdFish, 'The third fish is a $1 one.')) anyFish = /(\w+)\s+fish\b/ finder = fish =~ anyFish // finder contains an array of matched groups // 2 = third one (index start at 0), 1 = matched word in group out = "The third fish is a ${finder[2][1]} one." assert out == expected evens = [] (0.. Even numbered fish are two blue. // one of several ways to do this pond = fish + ' in the pond' fishInPond = (/(\w+)(\s+fish\b\s*)/) * 4 + /(.*)/ found = (pond =~ fishInPond)[0] println ((found[1..6] + 'sushi' + found[8..9]).join()) // => One fish two fish red fish sushi fish in the pond // find last fish expected = 'Last fish is blue' pond = 'One fish two fish red fish blue fish swim here.' finder = (pond =~ anyFish) assert expected == "Last fish is ${finder[finder.count-1][1]}" // => Last fish is blue // greedy match version of above finder = (pond =~ /.*\b/ + anyFish) assert expected == "Last fish is ${finder[0][1]}" // last fish match version of above finder = (pond =~ /\b(\w+)\s+fish\b(?!.*\bfish\b)/) assert expected == "Last fish is ${finder[0][1]}" //---------------------------------------------------------------------------------- // @@PLEAC@@_6.6 //---------------------------------------------------------------------------------- // Html Stripper // get this using: fakedfile = new File('path_to_file.htm').text fakedFile = ''' Chapter 1 Title

Chapter 1: Some Heading

A paragraph. ''' stripExpectations = ''' Chapter 1 Title Chapter 1: Some Heading A paragraph. '''.trim() stripped = fakedFile.replaceAll(/(?m)<.*?>/,'').trim() assert stripExpectations == stripped pattern = '''(?x) ( # capture in $1 Chapter # text string \\s+ # mandatory whitespace \\d+ # decimal number \\s* # optional whitespace : # a real colon . * # anything not a newline till end of line ) ''' headerfyExpectations = ''' Chapter 1 Title

Chapter 1: Some Heading

A paragraph. '''.trim() headerfied = stripped.replaceAll(pattern, '

$1

') assert headerfyExpectations == headerfied // one liner equivalent which prints to stdout //% groovy -p -e "line.replaceAll(/^(Chapter\s+\d+\s*:.*)/,'

$1

')" // one liner equivalent which modifies file in place and creates *.bak original file //% groovy -pi .bak -e "line.replaceAll(/^(Chapter\s+\d+\s*:.*)/,'

$1

')" // use: realFileInput = new File(path_to_file).text fakeFileInput = ''' 0 START 1 2 END 3 4 5 START 6 END ''' chunkyPattern = /(?ms)^START(.*?)^END/ finder = fakeFileInput =~ chunkyPattern (0.. // Chunk #0 contains 2 lines. // Chunk #1 contains 1 lines. //---------------------------------------------------------------------------------- // @@PLEAC@@_6.7 //---------------------------------------------------------------------------------- // general pattern is: //file = new File("datafile").text.split(/pattern/) // .Ch, .Se and .Ss divide chunks of input text fakedFiletext = ''' .Ch abc .Se def .Ss ghi .Se jkl .Se mno .Ss pqr .Ch stu .Ch vwx .Se yz! ''' chunks = fakedFiletext.split(/(?m)^\.(Ch|Se|Ss)$/) println "I read ${chunks.size()} chunks." // => I read 10 chunks. //---------------------------------------------------------------------------------- // @@PLEAC@@_6.8 //---------------------------------------------------------------------------------- // Groovy doesn't support the ~/BEGIN/ .. ~/END/ notation // you have to emulate it as shown in the example below // The from line number to line number processing is supported // from the command line but not within a script, e.g. // command-line to print lines 15 through 17 inclusive (see below) // > groovy -p -e "if (count in 15..17) return line" datafile // Within a script itself, you emulate the count by keeping state htmlContent = '''

A Heading

Here is inline AAA. And the bigger Example 2: line BBB line CCC Done. '''.trim() examplePattern = /(?ms)(.*?)<\/XMP>/ finder = htmlContent =~ examplePattern (0..<finder.count).each { println "Example ${it+1}:" println finder[it][1] } // => // Example 1: // inline AAA // Example 2: // // line BBB // line CCC // htmlContent.split('\n').eachWithIndex{ line, count -> if (count in 4..5) println line } // => // line BBB // line CCC // You would probably use a mail Api for this in Groovy fakedMailInput = ''' From: A Person <someone@somewhere.com> To: <pleac-discuss@lists.sourceforge.net> Date: Sun, 31 Dec 2006 02:14:57 +1100 From: noone@nowhere.com To: <pleac-discuss@lists.sourceforge.net> Date: Sun, 31 Dec 2006 02:14:58 +1100 From: someone@somewhere.com To: <pleac-discuss@lists.sourceforge.net> Date: Sun, 31 Dec 2006 02:14:59 +1100 '''.trim()+'\n' seen = [:] fakedMailInput.split('\n').each{ line -> m = (line =~ /^From:?\s(.*)/) if (m) { addr = m[0][1] =~ /([^<>(),;\s]+\@[^<>(),;\s]+)/ x = addr[0][1] if (seen.containsKey(x)) seen[x] += 1 else seen[x] = 1 } } seen.each{ k,v -> println "Address $k seen $v time${v==1?'':'s'}." } // => // Address noone@nowhere.com seen 1 time. // Address someone@somewhere.com seen 2 times. //---------------------------------------------------------------------------------- // @@PLEAC@@_6.9 //---------------------------------------------------------------------------------- import java.util.regex.Pattern names = ''' myFile.txt oldFile.tex myPicture.jpg ''' def glob2pat(globstr) { def patmap = [ '*':'.*', '?':'.', '[':'[', ']':']' ] def result = '(?m)^' '^' + globstr.replaceAll(/(.)/) { all, c -> result += (patmap.containsKey(c) ? patmap[c] : Pattern.quote(c)) } result + '$' } def checkNumMatches(pat, count) { assert (names =~ glob2pat(pat)).count == count } checkNumMatches('*.*', 3) checkNumMatches('my*.*', 2) checkNumMatches('*.t*', 2) checkNumMatches('*File.*', 2) checkNumMatches('*Rabbit*.*', 0) //---------------------------------------------------------------------------------- // @@PLEAC@@_6.10 //---------------------------------------------------------------------------------- // version 1: simple obvious way states = 'CO ON MI WI MN'.split(' ').toList() def popgrep1(file) { file.eachLine{ line -> if (states.any{ line =~ /\b$it\b/ }) println line } } // popgrep1(new File('path_to_file')) // version 2: eval strings; fast but hard to quote (SLOW) def popgrep2(file) { def code = 'def found = false\n' states.each{ code += "if (!found && line =~ /\\b$it\\b/) found = true\n" } code += "if (found) println line\n" file.eachLine{ line = it; evaluate(code) } } // popgrep2(new File('path_to_file')) // version 2b: eval using switch/case (not in Perl cookbook) (SLOW) def popgrep2b(file) { def code = 'switch(line) {\n' states.each{ code += "case ~/.*\\b$it\\b.*/:\nprintln line;break\n" } code += "default:break\n}\n" file.eachLine{ line = it; evaluate(code) } } // popgrep2b(new File('path_to_file')) // version3: build a match_any function as a GString def popgrep3(file) { def code = states.collect{ "line =~ /\\b$it\\b/" }.join('||') file.eachLine{ line = it; if (evaluate(code)) println line } } // popgrep3(new File('path_to_file')) // version4: pretty fast, but simple: compile all re's first: patterns = states.collect{ ~/\b$it\b/ } def popgrep4(file) { file.eachLine{ line -> if (patterns.any{ it.matcher(line)}) println line } } // popgrep4(new File('path_to_file')) // version5: faster str = states.collect{ /\b$it\b/ }.join('|') def popgrep5(file) { file.eachLine{ line -> if (line =~ str) println line } } // popgrep5(new File('path_to_file')) // version5b: faster (like 5 but compiled outside loop) pattern = ~states.collect{ /\b$it\b/ }.join('|') def popgrep5b(file) { file.eachLine{ line -> if (pattern.matcher(line)) println line } } // popgrep5b(new File('path_to_file')) // speeds trials ON the current source file (~1200 lines) // popgrep1 => 0.39s // popgrep2 => 25.08s // popgrep2b => 23.86s // popgrep3 => 22.42s // popgrep4 => 0.12s // popgrep5 => 0.05s // popgrep5b => 0.05s // Groovy's built-in support is the way to go in terms of // both speed and simplicity of understanding. Avoid using // evaluate() unless you absolutely need it // generic matching functions input = ''' both cat and dog neither just a cat just a dog '''.split('\n').findAll{it.trim()} def matchAny(line, patterns) { patterns.any{ line =~ it } } def matchAll(line, patterns) { patterns.every{ line =~ it } } assert input.findAll{ matchAny(it, ['cat','dog']) }.size() == 3 assert input.findAll{ matchAny(it, ['cat$','^n.*']) }.size() == 2 assert input.findAll{ matchAll(it, ['cat','dog']) }.size() == 1 assert input.findAll{ matchAll(it, ['cat$','^n.*']) }.size() == 0 //---------------------------------------------------------------------------------- // @@PLEAC@@_6.11 //---------------------------------------------------------------------------------- // patternCheckingScript: prompt = '\n> ' print 'Enter patterns to check:' + prompt new BufferedReader(new InputStreamReader(System.in)).eachLine{ line -> try { Pattern.compile(line) print 'Valid' + prompt } catch (java.util.regex.PatternSyntaxException ex) { print 'Invalid pattern: ' + ex.message + prompt } } // => // Enter patterns to check: // > ab*.c // Valid // > ^\s+[^a-z]*$ // Valid // > ** // Invalid pattern: Dangling meta character '*' near index 0 // ** // ^ //---------------------------------------------------------------------------------- // @@PLEAC@@_6.12 //---------------------------------------------------------------------------------- src = 'dierk könig' // simplistic with locale issue dst = src ('a'..'z').each{ dst = dst.replaceAll(/(?<=[^a-zA-Z])/+it+/|\A/+it, it.toUpperCase()) } println dst // => Dierk KöNig // locale avoidance dst = src ('a'..'z').each{ dst = dst.replaceAll(/(?<=\A|\b)/+it, it.toUpperCase()) } println dst // => Dierk König //---------------------------------------------------------------------------------- // @@PLEAC@@_6.13 //---------------------------------------------------------------------------------- // Several libraries exist, e.g. // http://secondstring.sourceforge.net/ // http://sourceforge.net/projects/simmetrics/ // both support numerous algorithms. Using the second as an example: import uk.ac.shef.wit.simmetrics.similaritymetrics.* target = 'balast' candidates = ''' quick brown fox jumped over the lazy dog ballast ballasts balustrade balustrades blast blasted blaster blasters blasting blasts '''.split('\n').findAll{it.trim()} metrics = [new Levenshtein(), new MongeElkan(), new JaroWinkler(), new Soundex()] def out(name, results) { print name.padLeft(14) + ' '; results.each{print(it.padRight(16))}; println() } def outr(name, results){out(name, results.collect{''+((int)(it*100))/100})} out ('Word/Metric', metrics.collect{it.shortDescriptionString} ) candidates.each{ w -> outr(w, metrics.collect{ m -> m.getSimilarity(target, w)} )} // => // Word/Metric Levenshtein MongeElkan JaroWinkler Soundex // quick 0 0.11 0 0.66 // brown 0.16 0.23 0.5 0.73 // fox 0 0.2 0 0.66 // jumped 0 0.2 0 0.66 // over 0 0.44 0 0.55 // the 0 0.33 0 0.55 // lazy 0.33 0.5 0.44 0.66 // dog 0 0.2 0 0.66 // ballast 0.85 0.83 0.96 1 // ballasts 0.75 0.83 0.94 0.94 // balustrade 0.5 0.93 0.3 0.94 // balustrades 0.45 0.93 0.3 0.94 // blast 0.83 0.8 0.88 1 // blasted 0.57 0.66 0.8 0.94 // blaster 0.57 0.66 0.8 0.94 // blasters 0.5 0.66 0.77 0.94 // blasting 0.5 0.66 0.77 0.94 // blasts 0.66 0.66 0.84 0.94 // to implement the example, iterate through /usr/dict/words selecting words // where one or a combination of metrics are greater than some threshold //---------------------------------------------------------------------------------- // @@PLEAC@@_6.14 //---------------------------------------------------------------------------------- n = " 49 here" println n.replaceAll(/\G /,'0') // => 00049 here str = "3,4,5,9,120" print 'Found numbers:' str.eachMatch(/\G,?(\d+)/){ print ' ' + it[1] } println() // => Found numbers: 3 4 5 9 120 // Groovy doesn't have the String.pos or a /c re modifier like Perl // But it does have similar functionality. Matcher has start() and // end() for find the position and Matcher's usePattern() allows // you to swap patterns without changing the buffer position text = 'the year 1752 lost 10 days on the 3rd of September' p = ~/(?<=\D)(\d+)/ m = p.matcher(text) while (m.find()) { println 'Found ' + m.group() + ' starting at pos ' + m.start() + ' and ending at pos ' + m.end() } // now reset pos back to between 1st and 2nd numbers if (m.find(16)) { println 'Found ' + m.group() } // => // Found 1752 starting at pos 9 and ending at pos 13 // Found 10 starting at pos 19 and ending at pos 21 // Found 3 starting at pos 34 and ending at pos 35 // Found 10 // Alternatively you can use Scanner in Java 5-7+: p1 = ~/(?<=\D)(\d+)/ p2 = ~/\S+/ s = new Scanner(text) while ((f = s.findInLine(p1))) { println 'Found: ' + f } if ((f = s.findInLine(p2))) { println "Found $f after the last number." } // => // Found: 1752 // Found: 10 // Found: 3 // Found rd after the last number. //---------------------------------------------------------------------------------- // @@PLEAC@@_6.15 //---------------------------------------------------------------------------------- html = '<b><i>this</i> and <i>that</i> are important</b> Oh, <b><i>me too!</i></b>' greedyHtmlStripPattern = ~/(?m)<.*>/ // not good nonGreedyHtmlStripPattern = ~/(?m)<.*?>/ // not great simpleNested = ~/(?mx)<b><i>(.*?)<\/i><\/b>/ // match BEGIN, then not BEGIN, then END generalPattern = ~/BEGIN((?:(?!BEGIN).)*)END/ betterButInefficient1 = ~/(?mx)<b><i>( (?: (?!<\/b>|<\/i>). )* ) <\/i><\/b>/ betterButInefficient2 = ~/(?mx)<b><i>( (?: (?!<\/[ib]>). )* ) <\/i><\/b>/ efficientPattern = '''(?mx) <b><i> [^<]* # stuff not possibly bad, and not possibly the end. (?: # at this point, we can have '<' if not part of something bad (?! </?[ib]> ) # what we can't have < # okay, so match the '<' [^<]* # and continue with more safe stuff ) * </i></b> ''' //' //---------------------------------------------------------------------------------- // @@PLEAC@@_6.16 //---------------------------------------------------------------------------------- input = 'This is a test\nTest of the duplicate word finder.\n' dupWordPattern = '''(?ix) \\b # start at word boundary (\\S+) # find chunk of non-whitespace \\b # until a word boundary ( \\s+ # followed by whitespace \\1 # and that same chunk again \\b # and a word boundary ) + # one or more times ''' finder = input =~ dupWordPattern println 'Found duplicate word: ' + finder[0][1] // => Found duplicate word: test astr = 'nobody' bstr = 'bodysnatcher' m = "$astr $bstr" =~ /^(\w+)(\w+) \2(\w+)$/ actual = "${m[0][2]} overlaps in ${m[0][1]}-${m[0][2]}-${m[0][3]}" assert actual == 'body overlaps in no-body-snatcher' cap = 'o' * 180 while (m = (cap =~ /^(oo+?)\1+$/)) { p1 = m[0][1] print p1.size() + ' ' cap = cap.replaceAll(p1,'o') } println cap.size() // => 2 2 3 3 5 // diophantine // solve for 12x + 15y + 16z = 281, maximizing x if ((m = ('o' * 281) =~ /^(o*)\1{11}(o*)\2{14}(o*)\3{15}$/)) { x=m[0][1].size(); y=m[0][2].size(); z=m[0][3].size() println "One solution is: x=$x; y=$y; z=$z" } else println "No solution." // => One solution is: x=17; y=3; z=2 // using different quantifiers: // /^(o+)\1{11}(o+)\2{14}(o+)\3{15}$/ // => One solution is: x=17; y=3; z=2 // /^(o*?)\1{11}(o*)\2{14}(o*)\3{15}$/ // => One solution is: x=0; y=7; z=11 // /^(o+?)\1{11}(o*)\2{14}(o*)\3{15}$/ // => One solution is: x=1; y=3; z=14 //---------------------------------------------------------------------------------- // @@PLEAC@@_6.17 //---------------------------------------------------------------------------------- // Groovy doesn't currently support x!~y so you must use the !(x=~y) style // alpha OR beta assert 'alpha' ==~ /alpha|beta/ assert 'beta' ==~ /alpha|beta/ assert 'betalpha' =~ /alpha/ || 'betalpha' =~ /beta/ // alpha AND beta assert !('alpha' =~ /(?=.*alpha)(?=.*beta)/) assert 'alphabeta' =~ /(?=.*alpha)(?=.*beta)/ assert 'betalpha' =~ /(?=.*alpha)(?=.*beta)/ assert 'betalpha' =~ /alpha/ && 'betalpha' =~ /beta/ // alpha AND beta, no overlap assert 'alphabeta' =~ /alpha.*beta|beta.*alpha/ assert !('betalpha' =~ /alpha.*beta|beta.*alpha/) // NOT beta assert 'alpha gamma' =~ /^(?:(?!beta).)*$/ assert !('alpha beta gamma' =~ /^(?:(?!beta).)*$/) // NOT bad BUT good assert !('GOOD and BAD' =~ /(?=(?:(?!BAD).)*$)GOOD/) assert !('BAD' =~ /(?=(?:(?!BAD).)*$)GOOD/) assert !('WORSE' =~ /(?=(?:(?!BAD).)*$)GOOD/) assert 'GOOD' =~ /(?=(?:(?!BAD).)*$)GOOD/ // minigrep could be done as a one-liner as follows // groovy -p -e "if (line =~ /pat/) return line" datafile string = 'labelled' assert string =~ /^(?=.*bell)(?=.*lab)/ assert string =~ /bell/ && string =~ 'lab' fakeAddress = "blah bell blah " murrayHillRegex = '''(?x) ^ # start of string (?= # zero-width lookahead .* # any amount of intervening stuff bell # the desired bell string ) # rewind, since we were only looking (?= # and do the same thing .* # any amount of intervening stuff lab # and the lab part ) ''' assert string =~ murrayHillRegex assert !(fakeAddress =~ murrayHillRegex) // eliminate overlapping assert !(string =~ /(?:^.*bell.*lab)|(?:^.*lab.*bell)/) brandRegex = '''(?x) (?: # non-capturing grouper ^ .*? # any amount of stuff at the front bell # look for a bell .*? # followed by any amount of anything lab # look for a lab ) # end grouper | # otherwise, try the other direction (?: # non-capturing grouper ^ .*? # any amount of stuff at the front lab # look for a lab .*? # followed by any amount of anything bell # followed by a bell ) # end grouper ''' assert !(string =~ brandRegex) map = 'the great baldo' assert map =~ /^(?:(?!waldo).)*$/ noWaldoRegex = '''(?x) ^ # start of string (?: # non-capturing grouper (?! # look ahead negation waldo # is he ahead of us now? ) # is so, the negation failed . # any character (cuzza /s) ) * # repeat that grouping 0 or more $ # through the end of the string ''' assert map =~ noWaldoRegex // on unix systems use: realFakedInput = 'w'.process().text fakedInput = ''' 7:15am up 206 days, 13:30, 4 users, load average: 1.04, 1.07, 1.04 USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT tchrist tty1 5:16pm 36days 24:43 0.03s xinit tchrist tty2 5:19pm 6days 0.43s 0.43s -tcsh tchrist ttyp0 chthon 7:58am 3days 23.44s 0.44s -tcsh gnat ttyS4 coprolith 2:01pm 13:36m 0.30s 0.30s -tcsh '''.trim() + '\n' def miniGrepMethod(input) { input.split('\n').findAll{it =~ '^(?!.*ttyp).*tchrist'} } assert miniGrepMethod(fakedInput).size() == 2 findUserRegex = '''(?xm) ^ # anchored to the start (?! # zero-width look-ahead assertion .* # any amount of anything (faster than .*?) ttyp # the string you don't want to find ) # end look-ahead negation; rewind to start .* # any amount of anything (faster than .*?) tchrist # now try to find Tom ''' assert (fakedInput =~ findUserRegex).count == 2 //---------------------------------------------------------------------------------- // @@PLEAC@@_6.18 //---------------------------------------------------------------------------------- // Groovy uses Unicode character encoding // special care needs to be taken when using unicode because of the different // byte lengths, e.g. à can be encoded as two bytes \u0061\u0300 and is also // supported in legacy character sets by a single character \u00E0. To Match // this character, you can't use any of /./, /../, /a/, /\u00E0/, /\u0061/\u0300 // or /\pL/. The correct way is to use /X (not currently supported) or one // of /\pL/\pM*/ to ensure that it is a letter or /\PM\pM*/ when you just want // to combine multicharacter sequences and don't care whether it is a letter def checkUnicode(s) { println s + ' is of size ' + s.size() println 'Exactly matches /./ ' + (s ==~ /./) println 'Exactly matches /../ ' + (s ==~ /../) println 'Exactly matches /a/ ' + (s ==~ /a/) println 'Exactly matches /\\u00E0/ ' + (s ==~ /\u00E0/) println 'Exactly matches /\\u0061\\u0300/ ' + (s ==~ /\u0061\u0300/) println 'Exactly matches /\\pL/ ' + (s ==~ /\pL/) println 'Exactly matches /\\pL\\pM*/ ' + (s ==~ /\pL\pM*/) println 'Exactly matches /\\PM\\pM*/ ' + (s ==~ /\PM\pM*/) } checkUnicode('à') checkUnicode('\u0061\u0300') checkUnicode('\u00E0') // => // à is of size 1 // Exactly matches /./ true // Exactly matches /../ false // Exactly matches /a/ false // Exactly matches /\u00E0/ true // Exactly matches /\u0061\u0300/ false // Exactly matches /\pL/ true // Exactly matches /\pL\pM*/ true // Exactly matches /\PM\pM*/ true // a? is of size 2 // Exactly matches /./ false // Exactly matches /../ true // Exactly matches /a/ false // Exactly matches /\u00E0/ false // Exactly matches /\u0061\u0300/ true // Exactly matches /\pL/ false // Exactly matches /\pL\pM*/ true // Exactly matches /\PM\pM*/ true // à is of size 1 // Exactly matches /./ true // Exactly matches /../ false // Exactly matches /a/ false // Exactly matches /\u00E0/ true // Exactly matches /\u0061\u0300/ false // Exactly matches /\pL/ true // Exactly matches /\pL\pM*/ true // Exactly matches /\PM\pM*/ true //---------------------------------------------------------------------------------- // @@PLEAC@@_6.19 //---------------------------------------------------------------------------------- // The Perl Cookbook categorizes this as a hard problem ... mostly for // reasons not related to the actual regex - but with a 60-line regex // perhaps there are some issues with that too. Further details: // http://www.perl.com/CPAN/authors/Tom_Christiansen/scripts/ckaddr.gz simpleCommentStripper = /\([^()]*\)/ println 'Book Publishing <marketing@books.com> (We will spam you)'.replaceAll(simpleCommentStripper, '') // => Book Publishing <marketing@books.com> // inspired by the fact that domain names can contain any foreign character these days modern = /^.+@[^\.].*\.[a-z]{2,}>?$/ // .Net lenient = /\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/ // a little more checking strict = /^[_a-zA-Z0-9- <]+(\.[_a-zA-Z0-9- <]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\./ + /(([0-9]{1,3})|([a-zA-Z]{2,3})|(aero|coop|info|museum|name))>?$/ addresses = ['someuser@somehost.com', 'Book Publishing <marketing@books.com>'] addresses.each{ assert it =~ lenient assert it =~ strict assert it =~ modern } //---------------------------------------------------------------------------------- // @@PLEAC@@_6.20 //---------------------------------------------------------------------------------- def findAction(ans) { def re = '(?i)^' + Pattern.quote(ans) if ("SEND" =~ re) println "Action is send" else if ("STOP" =~ re) println "Action is stop" else if ("ABORT" =~ re) println "Action is abort" else if ("EDIT" =~ re) println "Action is edit" else println 'No Match' } findAction('edit something') // => No Match findAction('edit') // => Action is edit findAction('se') // => Action is send findAction('e') // => Action is edit def buildAbbrev(words) { def table = new TreeMap() words.each{ w -> (0..<w.size()).each { n -> if (!(words - w).any{ it.size() >= n+1 && it[0..n] == w[0..n] }) table[w[0..n]] = w } } table } println buildAbbrev('send stop abort edit'.split(' ').toList()) // => ["a":"abort", "ab":"abort", "abo":"abort", "abor":"abort", "abort":"abort", // "e":"edit", "ed":"edit", "edi":"edit", "edit":"edit", "se":"send", "sen":"send", // "send":"send", "st":"stop", "sto":"stop", "stop":"stop"] // miniShellScript: // dummy methods def invokeEditor() { println "invoking editor" } def deliverMessage() { println "delivering message at " + new Date() } actions = [ edit: this.&invokeEditor, send: this.&deliverMessage, list: { println Runtime.runtime.freeMemory() }, abort: { System.exit(0) }, unknown: { println "Unknown Command"} ] table = buildAbbrev(actions.keySet().toList()) prompt = '\n> ' print 'Enter Commands: edit send list abort' + prompt new BufferedReader(new InputStreamReader(System.in)).eachLine{ line -> def idx = (table.containsKey(line)) ? table[line] : 'unknown' actions[idx]() print prompt } //---------------------------------------------------------------------------------- // @@PLEAC@@_6.21 //---------------------------------------------------------------------------------- //% gunzip -c ~/mail/archive.gz | urlify > archive.urlified //% urlify ~/mail/*.inbox > ~/allmail.urlified urls = '(https?|telnet|gopher|file|wais|ftp|mail)' ltrs = /\w/ gunk = /\#\/~:.?+=&%@!\-/ punc = /.:?\-/ doll = /$/ all = /$ltrs$gunk$punc/ findUrls = """(?ix) \\b # start at word boundary ( # begin group 1 { $urls : # need resource and a colon [$all] +? # followed by on or more of any valid # character, but be conservative and # take only what you need to... ) # end group 1 } (?= # look-ahead non-consumptive assertion [$punc]* # either 0 or more punctuation [^$all] # followed by a non-url character | # or else $doll # then end of the string ) """ input = ''' If you find a typo on http://groovy.codehaus.org please send an email to mail:spelling.pedant@codehaus.org ''' println input.replaceAll(findUrls,'<a href="$1">$1</a>') // => // If you find a typo on <a href="http://groovy.codehaus.org">http://groovy.codehaus.org</a> please // send an email to <a href="mail:spelling.pedant@codehaus.org">mail:spelling.pedant@codehaus.org</a> // urlifyScript: #!/usr/bin/groovy // urlify - wrap HTML links around URL-like constructs // definitions from above args.each{ file -> new File(file).eachLine{ line -> println line.replaceAll(findUrls,'<a href="$1">$1</a>') } } //---------------------------------------------------------------------------------- // @@PLEAC@@_6.22 //---------------------------------------------------------------------------------- // @@INCOMPLETE@@ // @@INCOMPLETE@@ //---------------------------------------------------------------------------------- // @@PLEAC@@_6.23 //---------------------------------------------------------------------------------- romans = /(?i)^m*(d?c{0,3}|c[dm])(l?x{0,3}|x[lc])(v?i{0,3}|i[vx])$/ assert 'cmxvi' =~ romans // can't have tens before 1000s (M) or 100s (C) after 5s (V) assert !('xmvci' =~ romans) // swap first two words assert 'the words'.replaceAll(/(\S+)(\s+)(\S+)/, '$3$2$1') == 'words the' // extract keyword and value m = 'k=v' =~ /(\w+)\s*=\s*(.*)\s*$/ assert m.matches() assert m[0][1] == 'k' assert m[0][2] == 'v' hasAtLeastSize = { n -> /.{$n,}/ } assert 'abcdefghijklmnopqrstuvwxyz' =~ hasAtLeastSize(20) // MM/DD/YY HH:MM:SS (lenient - doesn't check HH > 23 etc) d = /\d+/ datetime = "($d)/($d)/($d) ($d):($d):($d)" assert '04/05/2006 10:26:59' =~ datetime orig = '/usr/bin/vi' expected = '/usr/local/bin/vi' orig.replaceAll('/usr/bin','/usr/local/bin') == expected escapeSequenceRegex = /%([0-9A-Fa-f][0-9A-Fa-f])/ convertEscapeToChar = { Object[] ch -> new Character((char)Integer.parseInt(ch[1],16)) } assert 'abc%3cdef'.replaceAll(escapeSequenceRegex, convertEscapeToChar) == 'abc<def' commentStripper = '''(?xms) /\\* # Match the opening delimiter .* # Match a minimal number of characters */ \\*/ # Match the closing delimiter ''' input = ''' a line /* some comment */ another line ''' expected = ''' a line another line ''' assert input.replaceAll(commentStripper,'') == expected // emulate s.trim() assert ' x y '.replaceAll(/^\s+/, '').replaceAll(/\s+$/, '') == 'x y' // convert \\n into \n assert (/a\nb/.replaceAll(/\\n/,"\n") == 'a\nb') // remove package symbol (Groovy/Java doesn't use this as package symbol) assert 'A::B'.replaceAll(/^.*::/, '') == 'B' // match IP Address (requires leading 0's) ipregex = /^([01]?\d\d|2[0-4]\d|25[0-5])\.([01]?\d\d|2[0-4]\d|25[0-5])\./ + /([01]?\d\d|2[0-4]\d|25[0-5])\.([01]?\d\d|2[0-4]\d|25[0-5])$/ assert !('123.456.789' =~ ipregex) assert '192.168.000.001' =~ ipregex // extract basename assert 'c:/usr/temp.txt'.replaceAll(/^.*\/{1}/, '') == 'temp.txt' termcap = ':co#80:li#24:' m = (termcap =~ /:co\#(\d+):/) assert m.count == 1 assert m[0][1] == '80' assert 'cmd c:/tmp/junk.txt'.replaceAll(/ \S+\/{1}/, ' ') == 'cmd junk.txt' os = System.getProperty('os.name') println 'Is Linux? ' + (os ==~ /(?i)linux.*/) println 'Is Windows? ' + (os ==~ /(?i)windows.*/) println 'Is Mac? ' + (os ==~ /(?i)mac.*/) // join multiline sting multi = ''' This is a test '''.trim() assert multi.replaceAll(/(?m)\n\s+/, ' ') == 'This is a test' // nums in string string = 'The 5th test was won today by 10 wickets after 10.5 overs' nums = string =~ /(\d+\.?\d*|\.\d+)/ assert (0..<nums.count).collect{ nums[it][1] }.join(' ') == '5 10 10.5' // capitalize words words = 'the Capital words ARE hiding' capwords = words =~ /(\b\p{Upper}+\b)/ assert (0..<capwords.count).collect{ capwords[it][1] }.join(' ') == 'ARE' lowords = words =~ /(\b\p{Lower}+\b)/ assert (0..<lowords.count).collect{ lowords[it][1] }.join(' ') == 'the words hiding' capWords = words =~ /(\b\p{Upper}\p{Lower}*\b)/ assert (0..<capWords.count).collect{ capWords[it][1] }.join(' ') == 'Capital' input = ''' If you find a typo on <a href="http://groovy.codehaus.org">http://groovy.codehaus.org</a> please send an email to <a href="mail:spelling.pedant@codehaus.org">mail:spelling.pedant@codehaus.org</a> ''' linkRegex = /(?im)<A[^>]+?HREF\s*=\s*["']?([^'" >]+?)[ '"]?>/ //' links = input =~ linkRegex (0..<links.count).each{ println links[it][1] } // => // http://groovy.codehaus.org // mail:spelling.pedant@codehaus.org // find middle initial if any m = 'Lee Harvey Oswald' =~ /^\S+\s+(\S)\S*\s+\S/ initial = m.count ? m[0][1] : "" assert initial == 'H' // inch marks to quotes println 'I said "Hello" to you.'.replaceAll(/"([^"]*)"/, /``$1''/) //" // => I said ``Hello'' to you. // extract sentences (2 spaces or newline after punctuation) input = ''' Is this a sentence? Yes! And so is this. And the fourth. ''' sentences = [] strip = input.replaceAll(/(\p{Punct})\n/, '$1 ').replaceAll(/\n/, ' ').replaceAll(/ {3,}/,' ') m = strip =~ /(\S.*?\p{Punct})(?= |\Z)/ (0..<m.count).each{ sentences += m[it][1] } assert sentences == ["Is this a sentence?", "Yes!", "And so is this.", "And the fourth."] // YYYY-MM-DD m = '2007-2-28' =~ /(\d{4})-(\d\d?)-(\d\d?)/ assert m.matches() assert ['2007', '2', '28'] == [m[0][1], m[0][2], m[0][3]] usPhoneRegex = /^[01]?[- .]?(\([2-9]\d{2}\)|[2-9]\d{2})[- .]?\d{3}[- .]?\d{4}$/ numbers = ''' (425) 555-0123 425-555-0123 425 555 0123 1-425-555-0123 '''.trim().split('\n').toList() assert numbers.every{ it ==~ usPhoneRegex } exclaimRegex = /(?i)\boh\s+my\s+gh?o(d(dess(es)?|s?)|odness|sh)\b/ assert 'Oh my Goodness!' =~ exclaimRegex assert !('Golly gosh' =~ exclaimRegex) input = 'line 1\rline 2\nline\r\nline 3\n\rline 4' m = input =~ /(?m)^([^\012\015]*)(\012\015?|\015\012?)/ assert m.count == 4 // @@PLEAC@@_6.22 // not an exact equivalent to original cookbook but has // a reasonable subset of mostly similar functionality // instead of -r recursion option, use Ant fileset wildcards // e.g. **/*.c. You can also specify an excludes pattern // e.g. **/*.* -X **/*.h will process all but header files // (currently not optimised and with minimal error checking) // uses jopt-simple (jopt-simple.sf.net) op = new joptsimple.OptionParser() NOCASE = 'i'; op.accepts( NOCASE, "case insensitive" ) WITHN = 'n'; op.accepts( WITHN, "display line/para with line/para number" ) WITHF = 'H'; op.accepts( WITHF, "display line/para with filename" ) NONAME = 'h'; op.accepts( NONAME, "hide filenames" ) COUNT = 'c'; op.accepts( COUNT, "give count of lines/paras matching" ) TCOUNT = 'C'; op.accepts( TCOUNT, "give count of total matches (multiple per line/para)" ) WORD = 'w'; op.accepts( WORD, "word boundaries only" ) EXACT = 'x'; op.accepts( EXACT, "exact matches only" ) INVERT = 'v'; op.accepts( INVERT, "invert search sense (lines that DON'T match)" ) EXCLUDE = 'X'; op.accepts( EXCLUDE, "exclude files matching pattern [default is '**/*.bak']" ). withRequiredArg().describedAs('path_pattern') MATCH = 'l'; op.accepts( MATCH, "list names of files with matches" ) NOMATCH = 'L'; op.accepts( NOMATCH, "list names of files with no match" ) PARA = 'p'; op.accepts( PARA, "para mode (.* matches newlines)" ). withOptionalArg().describedAs('para_pattern') EXPR = 'e'; op.accepts( EXPR, "expression (when pattern begins with '-')" ). withRequiredArg().describedAs('pattern') FILE = 'f'; op.accepts( FILE, "file containing pattern" ). withRequiredArg().describedAs('filename') HELP = 'help'; op.accepts( HELP, "display this message" ) options = op.parse(args) params = options.nonOptionArguments() if (options.wasDetected( HELP )) { op.printHelpOn( System.out ) } else if (params.size() == 0) { println "Usage: grep [OPTION]... PATTERN [FILE]...\nTry 'grep --$HELP' for more information." } else { modifiers = [] paraPattern = '' o_withn = options.wasDetected( WITHN ) o_withf = options.wasDetected( WITHF ) o_noname = options.wasDetected( NONAME ) o_count = options.wasDetected( COUNT ) o_tcount = options.wasDetected( TCOUNT ) o_invert = options.wasDetected( INVERT ) o_match = options.wasDetected( MATCH ) o_nomatch = options.wasDetected( NOMATCH ) if (options.wasDetected( EXPR )) { pattern = options.valueOf( EXPR ) } else if (options.wasDetected( FILE )) { pattern = new File(options.valueOf( FILE )).text.trim() } else { pattern = params[0] params = params[1..-1] } if (options.wasDetected( EXCLUDE )) excludes = options.valueOf( EXCLUDE ) else excludes = ['**/*.bak'] if (options.wasDetected( EXACT )) pattern = '^' + pattern + '$' else if (options.wasDetected( WORD )) pattern = /\b$pattern\b/ if (options.wasDetected( NOCASE )) modifiers += 'i' if (options.wasDetected( PARA )) { if (options.hasArgument( PARA )) paraPattern = options.valueOf( PARA ) else paraPattern = '^$' paraPattern = '(?sm)' + paraPattern modifiers += 'sm' } if (modifiers) pattern = "(?${modifiers.join()})" + pattern if (params.size() == 0) grepStream(System.in, '<stdin>') else { scanner = new AntBuilder().fileScanner { fileset(dir:'.', includes:params.join(','), excludes:excludes) } for (f in scanner) { grepStream(new FileInputStream(f), f) } } } def grepStream(s, name) { def count = 0 def tcount = 0 def pieces if (paraPattern) pieces = s.text.split(paraPattern) else pieces = s.readLines() def fileMode = o_match || o_nomatch || o_count || o_tcount pieces.eachWithIndex{line, index -> def m = line =~ pattern boolean found = m.count if (found != o_invert) { count++ tcount += m.count if (!fileMode) { linefields = [] if (o_withf) linefields += name if (o_withn) linefields += index + 1 linefields += line println linefields.join(':') } } } def display = true if ((o_match && count == 0) || (o_nomatch && count != 0)) display = false if (fileMode && display) { filefields = [] if (!o_noname) filefields += name if (o_tcount) filefields += tcount else if (o_count) filefields += count println filefields.join(':') } } //---------------------------------------------------------------------------------- // @@PLEAC@@_7.0 //---------------------------------------------------------------------------------- //testfile = new File('/usr/local/widgets/data') // unix testfile = new File('Pleac/data/blue.txt') // windows testfile.eachLine{ if (it =~ /blue/) println it } // Groovy (like Java) uses the File class as an abstraction for // the path representing a potential file system resource. // Channels and Streams (along with Reader adn Writer helper // classes) are used to read and write to files (and other // things). Files, channels, streams etc are all "normal" // objects; they can be passed around in your programs just // like other objects (though there are some restrictions // covered elsewhere - e.g. you can't expect to pass a File // object between JVMs on different machines running different // operating systems and expect them to maintain a meaningful // value across the different JVMs). In addition to Streams, // there is also support for random access to files. // Many operations are available on streams and channels. Some // return values to indicate success or failure, some can throw // exceptions, other times both styles of error reporting may be // available. // Streams at the lowest level are just a sequence of bytes though // there are various abstractions at higher levels to allow // interacting with streams at encoded character, data type or // object levels if desired. Standard streams include System.in, // System.out and System.err. Java and Groovy on top of that // provide facilities for buffering, filtering and processing // streams in various ways. // File channels provide more powerful operations than streams // for reading and writing files such as locks, buffering, // positioning, concurrent reading and writing, mapping to memory // etc. In the examples which follow, streams will be used for // simple cases, channels when more advanced features are // required. Groovy currently focusses on providing extra support // at the file and stream level rather than channel level. // This makes the simple things easy but lets you do more complex // things by just using the appropriate Java classes. All Java // classes are available within Groovy by default. // Groovy provides syntactic sugar over the top of Java's file // processing capabilities by providing meaning to shorthand // operators and by automatically handling scaffolding type // code such as opening, closing and handling exceptions behind // the scenes. It also provides many powerful closure operators, // e.g. file.eachLineMatch(pattern){ some_operation } will open // the file, process it line-by-line, finding all lines which // match the specified pattern and then invoke some operation // for the matching line(s) if any, before closing the file. // this example shows how to access the standard input stream // numericCheckingScript: prompt = '\n> ' print 'Enter text including a digit:' + prompt new BufferedReader(new InputStreamReader(System.in)).eachLine{ line -> // line is read from System.in if (line =~ '\\d') println "Read: $line" // normal output to System.out else System.err.println 'No digit found.' // this message to System.err } //---------------------------------------------------------------------------------- // @@PLEAC@@_7.1 //---------------------------------------------------------------------------------- // test values (change for your os and directories) inputPath='Pleac/src/pleac7.groovy'; outPath='Pleac/temp/junk.txt' // For input Java uses InputStreams (for byte-oriented processing) or Readers // (for character-oriented processing). These can throw FileNotFoundException. // There are also other stream variants: buffered, data, filters, objects, ... inputFile = new File(inputPath) inputStream = new FileInputStream(inputFile) reader = new FileReader(inputFile) inputChannel = inputStream.channel // Examples for random access to a file file = new RandomAccessFile(inputFile, "rw") // for read and write channel = file.channel // Groovy provides some sugar coating on top of Java println inputFile.text.size() // => 13496 // For output Java use OutputStreams or Writers. Can throw FileNotFound // or IO exceptions. There are also other flavours of stream: buffered, // data, filters, objects, ... outFile = new File(outPath) appendFlag = false outStream = new FileOutputStream(outFile, appendFlag) writer = new FileWriter(outFile, appendFlag) outChannel = outStream.channel // Also some Groovy sugar coating outFile << 'A Chinese sailing vessel' println outFile.text.size() // => 24 // @@PLEAC@@_7.2 //---------------------------------------------------------------------------------- // No problem with Groovy since the filename doesn't contain characters with // special meaning; like Perl's sysopen. Options are either additional parameters // or captured in different classes, e.g. Input vs Output, Buffered vs non etc. new FileReader(inputPath) //---------------------------------------------------------------------------------- // @@PLEAC@@_7.3 //---------------------------------------------------------------------------------- // '~' is a shell expansion feature rather than file system feature per se. // Because '~' is a valid filename character in some operating systems, and Java // attempts to be cross-platform, it doesn't automatically expand Tilde's. // Given that '~' expansion is commonly used however, Java puts the $HOME // environment variable (used by shells to do typical expansion) into the // "user.home" system property. This works across operating systems - though // the value inside differs from system to system so you shouldn't rely on its // content to be of a particular format. In most cases though you should be // able to write a regex that will work as expected. Also, Apple's // NSPathUtilities can expand and introduce Tildes on platforms it supports. path = '~paulk/.cvspass' name = System.getProperty('user.name') home = System.getProperty('user.home') println home + path.replaceAll("~$name(.*)", '$1') // => C:\Documents and Settings\Paul/.cvspass //---------------------------------------------------------------------------------- // @@PLEAC@@_7.4 //---------------------------------------------------------------------------------- // The exception raised in Groovy reports the filename try { new File('unknown_path/bad_file.ext').text } catch (Exception ex) { System.err.println(ex.message) } // => // unknown_path\bad_file.ext (The system cannot find the path specified) //---------------------------------------------------------------------------------- // @@PLEAC@@_7.5 //---------------------------------------------------------------------------------- try { temp = File.createTempFile("prefix", ".suffix") temp.deleteOnExit() } catch (IOException ex) { System.err.println("Temp file could not be created") } //---------------------------------------------------------------------------------- // @@PLEAC@@_7.6 //---------------------------------------------------------------------------------- // no special features are provided, here is a way to do it manually // DO NOT REMOVE THE FOLLOWING STRING DEFINITION. pleac_7_6_embeddedFileInfo = ''' Script size is 13731 Last script update: Wed Jan 10 19:05:58 EST 2007 ''' ls = System.getProperty('line.separator') file = new File('Pleac/src/pleac7.groovy') regex = /(?ms)(?<=^pleac_7_6_embeddedFileInfo = ''')(.*)(?=^''')/ def readEmbeddedInfo() { m = file.text =~ regex println 'Found:\n' + m[0][1] } def writeEmbeddedInfo() { lastMod = new Date(file.lastModified()) newInfo = "${ls}Script size is ${file.size()}${ls}Last script update: ${lastMod}${ls}" file.write(file.text.replaceAll(regex, newInfo)) } readEmbeddedInfo() // writeEmbeddedInfo() // uncomment to make script update itself // readEmbeddedInfo() // uncomment to redisplay the embedded info after the update // => (output when above two method call lines are uncommented) // Found: // // Script size is 13550 // Last script update: Wed Jan 10 18:56:03 EST 2007 // // Found: // // Script size is 13731 // Last script update: Wed Jan 10 19:05:58 EST 2007 //---------------------------------------------------------------------------------- // @@PLEAC@@_7.7 //---------------------------------------------------------------------------------- // general pattern for reading from System.in is: // System.in.readLines().each{ processLin