-
Notifications
You must be signed in to change notification settings - Fork 1
/
ch3-scripting.html
1070 lines (1048 loc) · 69.5 KB
/
ch3-scripting.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=edge"><![endif]-->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="generator" content="Asciidoctor 1.5.2">
<title>Doing odd jobs (scripting)</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400">
<style>
/* Asciidoctor default stylesheet | MIT License | http://asciidoctor.org */
/* Remove the comments around the @import statement below when using this as a custom stylesheet */
/*@import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400";*/
article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}
audio,canvas,video{display:inline-block}
audio:not([controls]){display:none;height:0}
[hidden],template{display:none}
script{display:none!important}
html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}
body{margin:0}
a{background:transparent}
a:focus{outline:thin dotted}
a:active,a:hover{outline:0}
h1{font-size:2em;margin:.67em 0}
abbr[title]{border-bottom:1px dotted}
b,strong{font-weight:bold}
dfn{font-style:italic}
hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}
mark{background:#ff0;color:#000}
code,kbd,pre,samp{font-family:monospace;font-size:1em}
pre{white-space:pre-wrap}
q{quotes:"\201C" "\201D" "\2018" "\2019"}
small{font-size:80%}
sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
sup{top:-.5em}
sub{bottom:-.25em}
img{border:0}
svg:not(:root){overflow:hidden}
figure{margin:0}
fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}
legend{border:0;padding:0}
button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}
button,input{line-height:normal}
button,select{text-transform:none}
button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}
button[disabled],html input[disabled]{cursor:default}
input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}
input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}
input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}
button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
textarea{overflow:auto;vertical-align:top}
table{border-collapse:collapse;border-spacing:0}
*,*:before,*:after{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}
html,body{font-size:100%}
body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;font-weight:400;font-style:normal;line-height:1;position:relative;cursor:auto}
a:hover{cursor:pointer}
img,object,embed{max-width:100%;height:auto}
object,embed{height:100%}
img{-ms-interpolation-mode:bicubic}
#map_canvas img,#map_canvas embed,#map_canvas object,.map_canvas img,.map_canvas embed,.map_canvas object{max-width:none!important}
.left{float:left!important}
.right{float:right!important}
.text-left{text-align:left!important}
.text-right{text-align:right!important}
.text-center{text-align:center!important}
.text-justify{text-align:justify!important}
.hide{display:none}
.antialiased,body{-webkit-font-smoothing:antialiased}
img{display:inline-block;vertical-align:middle}
textarea{height:auto;min-height:50px}
select{width:100%}
p.lead,.paragraph.lead>p,#preamble>.sectionbody>.paragraph:first-of-type p{font-size:1.21875em;line-height:1.6}
.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em}
div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0;direction:ltr}
a{color:#2156a5;text-decoration:underline;line-height:inherit}
a:hover,a:focus{color:#1d4b8f}
a img{border:none}
p{font-family:inherit;font-weight:400;font-size:1em;line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility}
p aside{font-size:.875em;line-height:1.35;font-style:italic}
h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em}
h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0}
h1{font-size:2.125em}
h2{font-size:1.6875em}
h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em}
h4,h5{font-size:1.125em}
h6{font-size:1em}
hr{border:solid #ddddd8;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em;height:0}
em,i{font-style:italic;line-height:inherit}
strong,b{font-weight:bold;line-height:inherit}
small{font-size:60%;line-height:inherit}
code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)}
ul,ol,dl{font-size:1em;line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit}
ul,ol,ul.no-bullet,ol.no-bullet{margin-left:1.5em}
ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0;font-size:1em}
ul.square li ul,ul.circle li ul,ul.disc li ul{list-style:inherit}
ul.square{list-style-type:square}
ul.circle{list-style-type:circle}
ul.disc{list-style-type:disc}
ul.no-bullet{list-style:none}
ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0}
dl dt{margin-bottom:.3125em;font-weight:bold}
dl dd{margin-bottom:1.25em}
abbr,acronym{text-transform:uppercase;font-size:90%;color:rgba(0,0,0,.8);border-bottom:1px dotted #ddd;cursor:help}
abbr{text-transform:none}
blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd}
blockquote cite{display:block;font-size:.9375em;color:rgba(0,0,0,.6)}
blockquote cite:before{content:"\2014 \0020"}
blockquote cite a,blockquote cite a:visited{color:rgba(0,0,0,.6)}
blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)}
@media only screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2}
h1{font-size:2.75em}
h2{font-size:2.3125em}
h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em}
h4{font-size:1.4375em}}table{background:#fff;margin-bottom:1.25em;border:solid 1px #dedede}
table thead,table tfoot{background:#f7f8f7;font-weight:bold}
table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left}
table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)}
table tr.even,table tr.alt,table tr:nth-of-type(even){background:#f8f8f7}
table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{display:table-cell;line-height:1.6}
h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em}
h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400}
.clearfix:before,.clearfix:after,.float-group:before,.float-group:after{content:" ";display:table}
.clearfix:after,.float-group:after{clear:both}
*:not(pre)>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background-color:#f7f7f8;-webkit-border-radius:4px;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed}
pre,pre>code{line-height:1.45;color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;text-rendering:optimizeSpeed}
.keyseq{color:rgba(51,51,51,.8)}
kbd{display:inline-block;color:rgba(0,0,0,.8);font-size:.75em;line-height:1.4;background-color:#f7f7f7;border:1px solid #ccc;-webkit-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em white inset;box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em #fff inset;margin:-.15em .15em 0 .15em;padding:.2em .6em .2em .5em;vertical-align:middle;white-space:nowrap}
.keyseq kbd:first-child{margin-left:0}
.keyseq kbd:last-child{margin-right:0}
.menuseq,.menu{color:rgba(0,0,0,.8)}
b.button:before,b.button:after{position:relative;top:-1px;font-weight:400}
b.button:before{content:"[";padding:0 3px 0 2px}
b.button:after{content:"]";padding:0 2px 0 3px}
p a>code:hover{color:rgba(0,0,0,.9)}
#header,#content,#footnotes,#footer{width:100%;margin-left:auto;margin-right:auto;margin-top:0;margin-bottom:0;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em}
#header:before,#header:after,#content:before,#content:after,#footnotes:before,#footnotes:after,#footer:before,#footer:after{content:" ";display:table}
#header:after,#content:after,#footnotes:after,#footer:after{clear:both}
#content{margin-top:1.25em}
#content:before{content:none}
#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0}
#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #ddddd8}
#header>h1:only-child,body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #ddddd8;padding-bottom:8px}
#header .details{border-bottom:1px solid #ddddd8;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:-ms-flexbox;display:-webkit-flex;display:flex;-ms-flex-flow:row wrap;-webkit-flex-flow:row wrap;flex-flow:row wrap}
#header .details span:first-child{margin-left:-.125em}
#header .details span.email a{color:rgba(0,0,0,.85)}
#header .details br{display:none}
#header .details br+span:before{content:"\00a0\2013\00a0"}
#header .details br+span.author:before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)}
#header .details br+span#revremark:before{content:"\00a0|\00a0"}
#header #revnumber{text-transform:capitalize}
#header #revnumber:after{content:"\00a0"}
#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #ddddd8;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem}
#toc{border-bottom:1px solid #efefed;padding-bottom:.5em}
#toc>ul{margin-left:.125em}
#toc ul.sectlevel0>li>a{font-style:italic}
#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0}
#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none}
#toc a{text-decoration:none}
#toc a:active{text-decoration:underline}
#toctitle{color:#7a2518;font-size:1.2em}
@media only screen and (min-width:768px){#toctitle{font-size:1.375em}
body.toc2{padding-left:15em;padding-right:0}
#toc.toc2{margin-top:0!important;background-color:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #efefed;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto}
#toc.toc2 #toctitle{margin-top:0;font-size:1.2em}
#toc.toc2>ul{font-size:.9em;margin-bottom:0}
#toc.toc2 ul ul{margin-left:0;padding-left:1em}
#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em}
body.toc2.toc-right{padding-left:0;padding-right:15em}
body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #efefed;left:auto;right:0}}@media only screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0}
#toc.toc2{width:20em}
#toc.toc2 #toctitle{font-size:1.375em}
#toc.toc2>ul{font-size:.95em}
#toc.toc2 ul ul{padding-left:1.25em}
body.toc2.toc-right{padding-left:0;padding-right:20em}}#content #toc{border-style:solid;border-width:1px;border-color:#e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;-webkit-border-radius:4px;border-radius:4px}
#content #toc>:first-child{margin-top:0}
#content #toc>:last-child{margin-bottom:0}
#footer{max-width:100%;background-color:rgba(0,0,0,.8);padding:1.25em}
#footer-text{color:rgba(255,255,255,.8);line-height:1.44}
.sect1{padding-bottom:.625em}
@media only screen and (min-width:768px){.sect1{padding-bottom:1.25em}}.sect1+.sect1{border-top:1px solid #efefed}
#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400}
#content h1>a.anchor:before,h2>a.anchor:before,h3>a.anchor:before,#toctitle>a.anchor:before,.sidebarblock>.content>.title>a.anchor:before,h4>a.anchor:before,h5>a.anchor:before,h6>a.anchor:before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em}
#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible}
#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none}
#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221}
.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em}
.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic}
table.tableblock>caption.title{white-space:nowrap;overflow:visible;max-width:0}
.paragraph.lead>p,#preamble>.sectionbody>.paragraph:first-of-type p{color:rgba(0,0,0,.85)}
table.tableblock #preamble>.sectionbody>.paragraph:first-of-type p{font-size:inherit}
.admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%}
.admonitionblock>table td.icon{text-align:center;width:80px}
.admonitionblock>table td.icon img{max-width:none}
.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase}
.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #ddddd8;color:rgba(0,0,0,.6)}
.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0}
.exampleblock>.content{border-style:solid;border-width:1px;border-color:#e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;-webkit-border-radius:4px;border-radius:4px}
.exampleblock>.content>:first-child{margin-top:0}
.exampleblock>.content>:last-child{margin-bottom:0}
.sidebarblock{border-style:solid;border-width:1px;border-color:#e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;-webkit-border-radius:4px;border-radius:4px}
.sidebarblock>:first-child{margin-top:0}
.sidebarblock>:last-child{margin-bottom:0}
.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center}
.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0}
.literalblock pre,.listingblock pre:not(.highlight),.listingblock pre[class="highlight"],.listingblock pre[class^="highlight "],.listingblock pre.CodeRay,.listingblock pre.prettyprint{background:#f7f7f8}
.sidebarblock .literalblock pre,.sidebarblock .listingblock pre:not(.highlight),.sidebarblock .listingblock pre[class="highlight"],.sidebarblock .listingblock pre[class^="highlight "],.sidebarblock .listingblock pre.CodeRay,.sidebarblock .listingblock pre.prettyprint{background:#f2f1f1}
.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{-webkit-border-radius:4px;border-radius:4px;word-wrap:break-word;padding:1em;font-size:.8125em}
.literalblock pre.nowrap,.literalblock pre[class].nowrap,.listingblock pre.nowrap,.listingblock pre[class].nowrap{overflow-x:auto;white-space:pre;word-wrap:normal}
@media only screen and (min-width:768px){.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{font-size:.90625em}}@media only screen and (min-width:1280px){.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{font-size:1em}}.literalblock.output pre{color:#f7f7f8;background-color:rgba(0,0,0,.9)}
.listingblock pre.highlightjs{padding:0}
.listingblock pre.highlightjs>code{padding:1em;-webkit-border-radius:4px;border-radius:4px}
.listingblock pre.prettyprint{border-width:0}
.listingblock>.content{position:relative}
.listingblock code[data-lang]:before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:#999}
.listingblock:hover code[data-lang]:before{display:block}
.listingblock.terminal pre .command:before{content:attr(data-prompt);padding-right:.5em;color:#999}
.listingblock.terminal pre .command:not([data-prompt]):before{content:"$"}
table.pyhltable{border-collapse:separate;border:0;margin-bottom:0;background:none}
table.pyhltable td{vertical-align:top;padding-top:0;padding-bottom:0}
table.pyhltable td.code{padding-left:.75em;padding-right:0}
pre.pygments .lineno,table.pyhltable td:not(.code){color:#999;padding-left:0;padding-right:.5em;border-right:1px solid #ddddd8}
pre.pygments .lineno{display:inline-block;margin-right:.25em}
table.pyhltable .linenodiv{background:none!important;padding-right:0!important}
.quoteblock{margin:0 1em 1.25em 1.5em;display:table}
.quoteblock>.title{margin-left:-1.5em;margin-bottom:.75em}
.quoteblock blockquote,.quoteblock blockquote p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify}
.quoteblock blockquote{margin:0;padding:0;border:0}
.quoteblock blockquote:before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)}
.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0}
.quoteblock .attribution{margin-top:.5em;margin-right:.5ex;text-align:right}
.quoteblock .quoteblock{margin-left:0;margin-right:0;padding:.5em 0;border-left:3px solid rgba(0,0,0,.6)}
.quoteblock .quoteblock blockquote{padding:0 0 0 .75em}
.quoteblock .quoteblock blockquote:before{display:none}
.verseblock{margin:0 1em 1.25em 1em}
.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility}
.verseblock pre strong{font-weight:400}
.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex}
.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic}
.quoteblock .attribution br,.verseblock .attribution br{display:none}
.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.05em;color:rgba(0,0,0,.6)}
.quoteblock.abstract{margin:0 0 1.25em 0;display:block}
.quoteblock.abstract blockquote,.quoteblock.abstract blockquote p{text-align:left;word-spacing:0}
.quoteblock.abstract blockquote:before,.quoteblock.abstract blockquote p:first-of-type:before{display:none}
table.tableblock{max-width:100%;border-collapse:separate}
table.tableblock td>.paragraph:last-child p>p:last-child,table.tableblock th>p:last-child,table.tableblock td>p:last-child{margin-bottom:0}
table.spread{width:100%}
table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede}
table.grid-all th.tableblock,table.grid-all td.tableblock{border-width:0 1px 1px 0}
table.grid-all tfoot>tr>th.tableblock,table.grid-all tfoot>tr>td.tableblock{border-width:1px 1px 0 0}
table.grid-cols th.tableblock,table.grid-cols td.tableblock{border-width:0 1px 0 0}
table.grid-all *>tr>.tableblock:last-child,table.grid-cols *>tr>.tableblock:last-child{border-right-width:0}
table.grid-rows th.tableblock,table.grid-rows td.tableblock{border-width:0 0 1px 0}
table.grid-all tbody>tr:last-child>th.tableblock,table.grid-all tbody>tr:last-child>td.tableblock,table.grid-all thead:last-child>tr>th.tableblock,table.grid-rows tbody>tr:last-child>th.tableblock,table.grid-rows tbody>tr:last-child>td.tableblock,table.grid-rows thead:last-child>tr>th.tableblock{border-bottom-width:0}
table.grid-rows tfoot>tr>th.tableblock,table.grid-rows tfoot>tr>td.tableblock{border-width:1px 0 0 0}
table.frame-all{border-width:1px}
table.frame-sides{border-width:0 1px}
table.frame-topbot{border-width:1px 0}
th.halign-left,td.halign-left{text-align:left}
th.halign-right,td.halign-right{text-align:right}
th.halign-center,td.halign-center{text-align:center}
th.valign-top,td.valign-top{vertical-align:top}
th.valign-bottom,td.valign-bottom{vertical-align:bottom}
th.valign-middle,td.valign-middle{vertical-align:middle}
table thead th,table tfoot th{font-weight:bold}
tbody tr th{display:table-cell;line-height:1.6;background:#f7f8f7}
tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold}
p.tableblock>code:only-child{background:none;padding:0}
p.tableblock{font-size:1em}
td>div.verse{white-space:pre}
ol{margin-left:1.75em}
ul li ol{margin-left:1.5em}
dl dd{margin-left:1.125em}
dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0}
ol>li p,ul>li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em}
ul.unstyled,ol.unnumbered,ul.checklist,ul.none{list-style-type:none}
ul.unstyled,ol.unnumbered,ul.checklist{margin-left:.625em}
ul.checklist li>p:first-child>.fa-square-o:first-child,ul.checklist li>p:first-child>.fa-check-square-o:first-child{width:1em;font-size:.85em}
ul.checklist li>p:first-child>input[type="checkbox"]:first-child{width:1em;position:relative;top:1px}
ul.inline{margin:0 auto .625em auto;margin-left:-1.375em;margin-right:0;padding:0;list-style:none;overflow:hidden}
ul.inline>li{list-style:none;float:left;margin-left:1.375em;display:block}
ul.inline>li>*{display:block}
.unstyled dl dt{font-weight:400;font-style:normal}
ol.arabic{list-style-type:decimal}
ol.decimal{list-style-type:decimal-leading-zero}
ol.loweralpha{list-style-type:lower-alpha}
ol.upperalpha{list-style-type:upper-alpha}
ol.lowerroman{list-style-type:lower-roman}
ol.upperroman{list-style-type:upper-roman}
ol.lowergreek{list-style-type:lower-greek}
.hdlist>table,.colist>table{border:0;background:none}
.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none}
td.hdlist1{padding-right:.75em;font-weight:bold}
td.hdlist1,td.hdlist2{vertical-align:top}
.literalblock+.colist,.listingblock+.colist{margin-top:-.5em}
.colist>table tr>td:first-of-type{padding:0 .75em;line-height:1}
.colist>table tr>td:last-of-type{padding:.25em 0}
.thumb,.th{line-height:0;display:inline-block;border:solid 4px #fff;-webkit-box-shadow:0 0 0 1px #ddd;box-shadow:0 0 0 1px #ddd}
.imageblock.left,.imageblock[style*="float: left"]{margin:.25em .625em 1.25em 0}
.imageblock.right,.imageblock[style*="float: right"]{margin:.25em 0 1.25em .625em}
.imageblock>.title{margin-bottom:0}
.imageblock.thumb,.imageblock.th{border-width:6px}
.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em}
.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0}
.image.left{margin-right:.625em}
.image.right{margin-left:.625em}
a.image{text-decoration:none}
span.footnote,span.footnoteref{vertical-align:super;font-size:.875em}
span.footnote a,span.footnoteref a{text-decoration:none}
span.footnote a:active,span.footnoteref a:active{text-decoration:underline}
#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em}
#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em 0;border-width:1px 0 0 0}
#footnotes .footnote{padding:0 .375em;line-height:1.3;font-size:.875em;margin-left:1.2em;text-indent:-1.2em;margin-bottom:.2em}
#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none}
#footnotes .footnote:last-of-type{margin-bottom:0}
#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0}
.gist .file-data>table{border:0;background:#fff;width:100%;margin-bottom:0}
.gist .file-data>table td.line-data{width:99%}
div.unbreakable{page-break-inside:avoid}
.big{font-size:larger}
.small{font-size:smaller}
.underline{text-decoration:underline}
.overline{text-decoration:overline}
.line-through{text-decoration:line-through}
.aqua{color:#00bfbf}
.aqua-background{background-color:#00fafa}
.black{color:#000}
.black-background{background-color:#000}
.blue{color:#0000bf}
.blue-background{background-color:#0000fa}
.fuchsia{color:#bf00bf}
.fuchsia-background{background-color:#fa00fa}
.gray{color:#606060}
.gray-background{background-color:#7d7d7d}
.green{color:#006000}
.green-background{background-color:#007d00}
.lime{color:#00bf00}
.lime-background{background-color:#00fa00}
.maroon{color:#600000}
.maroon-background{background-color:#7d0000}
.navy{color:#000060}
.navy-background{background-color:#00007d}
.olive{color:#606000}
.olive-background{background-color:#7d7d00}
.purple{color:#600060}
.purple-background{background-color:#7d007d}
.red{color:#bf0000}
.red-background{background-color:#fa0000}
.silver{color:#909090}
.silver-background{background-color:#bcbcbc}
.teal{color:#006060}
.teal-background{background-color:#007d7d}
.white{color:#bfbfbf}
.white-background{background-color:#fafafa}
.yellow{color:#bfbf00}
.yellow-background{background-color:#fafa00}
span.icon>.fa{cursor:default}
.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default}
.admonitionblock td.icon .icon-note:before{content:"\f05a";color:#19407c}
.admonitionblock td.icon .icon-tip:before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111}
.admonitionblock td.icon .icon-warning:before{content:"\f071";color:#bf6900}
.admonitionblock td.icon .icon-caution:before{content:"\f06d";color:#bf3400}
.admonitionblock td.icon .icon-important:before{content:"\f06a";color:#bf0000}
.conum[data-value]{display:inline-block;color:#fff!important;background-color:rgba(0,0,0,.8);-webkit-border-radius:100px;border-radius:100px;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold}
.conum[data-value] *{color:#fff!important}
.conum[data-value]+b{display:none}
.conum[data-value]:after{content:attr(data-value)}
pre .conum[data-value]{position:relative;top:-.125em}
b.conum *{color:inherit!important}
.conum:not([data-value]):empty{display:none}
h1,h2{letter-spacing:-.01em}
dt,th.tableblock,td.content{text-rendering:optimizeLegibility}
p,td.content{letter-spacing:-.01em}
p strong,td.content strong{letter-spacing:-.005em}
p,blockquote,dt,td.content{font-size:1.0625rem}
p{margin-bottom:1.25rem}
.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em}
.exampleblock>.content{background-color:#fffef7;border-color:#e0e0dc;-webkit-box-shadow:0 1px 4px #e0e0dc;box-shadow:0 1px 4px #e0e0dc}
.print-only{display:none!important}
@media print{@page{margin:1.25cm .75cm}
*{-webkit-box-shadow:none!important;box-shadow:none!important;text-shadow:none!important}
a{color:inherit!important;text-decoration:underline!important}
a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important}
a[href^="http:"]:not(.bare):after,a[href^="https:"]:not(.bare):after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em}
abbr[title]:after{content:" (" attr(title) ")"}
pre,blockquote,tr,img{page-break-inside:avoid}
thead{display:table-header-group}
img{max-width:100%!important}
p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3}
h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid}
#toc,.sidebarblock,.exampleblock>.content{background:none!important}
#toc{border-bottom:1px solid #ddddd8!important;padding-bottom:0!important}
.sect1{padding-bottom:0!important}
.sect1+.sect1{border:0!important}
#header>h1:first-child{margin-top:1.25rem}
body.book #header{text-align:center}
body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em 0}
body.book #header .details{border:0!important;display:block;padding:0!important}
body.book #header .details span:first-child{margin-left:0!important}
body.book #header .details br{display:block}
body.book #header .details br+span:before{content:none!important}
body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important}
body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always}
.listingblock code[data-lang]:before{display:block}
#footer{background:none!important;padding:0 .9375em}
#footer-text{color:rgba(0,0,0,.6)!important;font-size:.9em}
.hide-on-print{display:none!important}
.print-only{display:block!important}
.hide-for-print{display:none!important}
.show-for-print{display:inherit!important}}
</style>
<style>
/* Stylesheet for CodeRay to match GitHub theme | MIT License | http://foundation.zurb.com */
/*pre.CodeRay {background-color:#f7f7f8;}*/
.CodeRay .line-numbers{border-right:1px solid #d8d8d8;padding:0 0.5em 0 .25em}
.CodeRay span.line-numbers{display:inline-block;margin-right:.5em;color:rgba(0,0,0,.3)}
.CodeRay .line-numbers strong{font-weight: normal}
table.CodeRay{border-collapse:separate;border-spacing:0;margin-bottom:0;border:0;background:none}
table.CodeRay td{vertical-align: top}
table.CodeRay td.line-numbers{text-align:right}
table.CodeRay td.line-numbers>pre{padding:0;color:rgba(0,0,0,.3)}
table.CodeRay td.code{padding:0 0 0 .5em}
table.CodeRay td.code>pre{padding:0}
.CodeRay .debug{color:#fff !important;background:#000080 !important}
.CodeRay .annotation{color:#007}
.CodeRay .attribute-name{color:#000080}
.CodeRay .attribute-value{color:#700}
.CodeRay .binary{color:#509}
.CodeRay .comment{color:#998;font-style:italic}
.CodeRay .char{color:#04d}
.CodeRay .char .content{color:#04d}
.CodeRay .char .delimiter{color:#039}
.CodeRay .class{color:#458;font-weight:bold}
.CodeRay .complex{color:#a08}
.CodeRay .constant,.CodeRay .predefined-constant{color:#008080}
.CodeRay .color{color:#099}
.CodeRay .class-variable{color:#369}
.CodeRay .decorator{color:#b0b}
.CodeRay .definition{color:#099}
.CodeRay .delimiter{color:#000}
.CodeRay .doc{color:#970}
.CodeRay .doctype{color:#34b}
.CodeRay .doc-string{color:#d42}
.CodeRay .escape{color:#666}
.CodeRay .entity{color:#800}
.CodeRay .error{color:#808}
.CodeRay .exception{color:inherit}
.CodeRay .filename{color:#099}
.CodeRay .function{color:#900;font-weight:bold}
.CodeRay .global-variable{color:#008080}
.CodeRay .hex{color:#058}
.CodeRay .integer,.CodeRay .float{color:#099}
.CodeRay .include{color:#555}
.CodeRay .inline{color:#00}
.CodeRay .inline .inline{background:#ccc}
.CodeRay .inline .inline .inline{background:#bbb}
.CodeRay .inline .inline-delimiter{color:#d14}
.CodeRay .inline-delimiter{color:#d14}
.CodeRay .important{color:#555;font-weight:bold}
.CodeRay .interpreted{color:#b2b}
.CodeRay .instance-variable{color:#008080}
.CodeRay .label{color:#970}
.CodeRay .local-variable{color:#963}
.CodeRay .octal{color:#40e}
.CodeRay .predefined{color:#369}
.CodeRay .preprocessor{color:#579}
.CodeRay .pseudo-class{color:#555}
.CodeRay .directive{font-weight:bold}
.CodeRay .type{font-weight:bold}
.CodeRay .predefined-type{color:inherit}
.CodeRay .reserved,.CodeRay .keyword {color:#000;font-weight:bold}
.CodeRay .key{color:#808}
.CodeRay .key .delimiter{color:#606}
.CodeRay .key .char{color:#80f}
.CodeRay .value{color:#088}
.CodeRay .regexp .delimiter{color:#808}
.CodeRay .regexp .content{color:#808}
.CodeRay .regexp .modifier{color:#808}
.CodeRay .regexp .char{color:#d14}
.CodeRay .regexp .function{color:#404;font-weight:bold}
.CodeRay .string{color:#d20}
.CodeRay .string .string .string{background:#ffd0d0}
.CodeRay .string .content{color:#d14}
.CodeRay .string .char{color:#d14}
.CodeRay .string .delimiter{color:#d14}
.CodeRay .shell{color:#d14}
.CodeRay .shell .delimiter{color:#d14}
.CodeRay .symbol{color:#990073}
.CodeRay .symbol .content{color:#a60}
.CodeRay .symbol .delimiter{color:#630}
.CodeRay .tag{color:#008080}
.CodeRay .tag-special{color:#d70}
.CodeRay .variable{color:#036}
.CodeRay .insert{background:#afa}
.CodeRay .delete{background:#faa}
.CodeRay .change{color:#aaf;background:#007}
.CodeRay .head{color:#f8f;background:#505}
.CodeRay .insert .insert{color:#080}
.CodeRay .delete .delete{color:#800}
.CodeRay .change .change{color:#66f}
.CodeRay .head .head{color:#f4f}
</style>
</head>
<body class="book">
<div id="header">
<h1>Doing odd jobs (scripting)</h1>
</div>
<div id="content">
<div id="preamble">
<div class="sectionbody">
<div class="paragraph">
<p>As software developers, we typically build applications and systems. And yet we often find ourselves doing miscellaneous other tasks that involve processing text files, fixing databases, generating reports, etc. Those tasks may even need to be repeated in the future. Such tasks can be laborious and error prone when performed manually, so I like to write scripts to handle them.</p>
</div>
<div class="paragraph">
<p>In the past, I’d break out Sed and Awk for text processing and Ruby/Python for anything more involved. But these are not tools that I use day to day and I’d end up spending a lot of time trying to remember their syntaxes and (in the case of Ruby & Python) their class libraries. That’s not helpful when you’re trying to <em>save</em> yourself time.</p>
</div>
<div class="paragraph">
<p>Groovy’s an ideal scripting language for Java developers because it makes many of the things you need to do in scripts easy compared to Java, while using the same class library and a very similar syntax. You don’t have a big context switch going between Groovy and Java. Even if you’re an avid fan of a different scripting language, Groovy’s worth considering simply due to the JVM’s cross-platform support, which is handy in multi-platform environments.</p>
</div>
<div class="paragraph">
<p>In this chapter, I’ll show some common use cases for Groovy scripting that will also help you to become familiar with more of the standard API. I’ll also stop along the way to discuss any language structures that appear, with a particular focus on an important feature called closures.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_working_with_the_filesystem">Working with the filesystem</h2>
<div class="sectionbody">
<div class="paragraph">
<p>A lot of scripting needs involve files: finding them, parsing them, creating them, moving them around, and so on. Java’s API has been traditionally weak in this area, which is why you’ll find libraries like <a href="http://commons.apache.org/proper/commons-io/">Commons IO</a> that provide a lot of extra utility and convenience. That changed with Java 8, which introduced some significant enhancements to the file APIs. Not everyone will be able to use Java 8 though, so I will give examples for both Java 6 and Java 8. Even if you’re able to use the latter, you will certainly find it useful to learn the old API because so many Groovy projects use them and will probably continue to use them.</p>
</div>
<div class="paragraph">
<p>Let’s start with the basic mechanism of reading and writing files. Imagine you have a comma-separated values (CSV) file containing the first and last names of authors along with the country and date of birth. Your first job is to print out all the names of the authors sorted alphabetically by their surnames (or family names). Here’s a quick script to do that using Java 8, which you can run in the Groovy console:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="CodeRay highlight"><code>import java.nio.charset.Charset <b class="conum">(1)</b>
import java.nio.file.Files
import java.nio.file.FileSystems
def lines = Files.readAllLines(
FileSystems.default.getPath("authors.csv"), <b class="conum">(2)</b>
Charset.forName("UTF-8"))
def authorNames = []
for (l in lines) {
def authorDetails = l.split(/\s*,\s*/)
authorNames << "${authorDetails[1]}, ${authorDetails[0]}"
}
authorNames = authorNames.sort()
for (name in authorNames) {
println name
}</code></pre>
</div>
</div>
<div class="colist arabic">
<ol>
<li>
<p>Most classes need importing before you can reference them</p>
</li>
<li>
<p>Create a file path representing the location of the CSV file</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>There’s much to talk about here, starting with the core classes we’re using to work with the filesystem. The <code>FileSystems</code> class provides access to the default <code>FileSystem</code> of the current platform (Windows, Mac OS X, Linux, etc.), which in turn allows you to construct file paths represented by the <code>java.nio.file.Path</code> interface - a contract of behavior without an implementation. Those file paths are then used by the methods of the <code>Files</code> class to perform file operations, such as copying, reading, and listing directories. <code>Charset</code> is simply a representation of a character encoding such as ISO-8859-1 and UTF-8.</p>
</div>
<div class="paragraph">
<p>Why do we import these classes? It’s to help avoid name clashes. Classes have a name and a package, also known as a namespace in other languages. This means you can have classes with the same name as long as they are in different packages. The result is that you have to tell Groovy (and Java) which class you are referring to in your code, and that’s what the <code>import</code> statement does.</p>
</div>
<div class="paragraph">
<p>The <code>readAllLines()</code> method assumes that the target file is text with line breaks in it and produces a list of the lines of text. One interesting aspect of this particular method call is that I explicitly specify a character encoding. If you don’t provide one, the method assumes the text is encoded using the default platform encoding, which varies between Windows, Mac OS X and Linux. I like to use UTF-8 everywhere, but that may not be appropriate for you. It largely depends on who creates the files in the first place. Yes it’s a headache if you’re dealing with any non-ASCII characters, but you need to factor it in to your coding right from the start.</p>
</div>
<div class="paragraph">
<p>The rest of the script uses features I introduced in the previous chapter. It simply breaks each line of text into its component parts, removing the commas in the process. The first name and last name for each entry are then combined into a single author name string, with the last name first. That means we can perform a natural order sort on the resulting list to get the names in last name order before then printing them all.</p>
</div>
<div class="paragraph">
<p>Hopefully that’s all straightforward for you, even though the code for reading the file seems more verbose than is perhaps necessary. So what happens if you don’t have access to a Java 8 or newer JVM? In that case, you can use the old <code>java.io.File</code> class instead:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="CodeRay highlight"><code>def lines = new File("authors.csv").readLines("UTF-8")
def authorNames = []
...</code></pre>
</div>
</div>
<div class="paragraph">
<p>I’ve only shown the file reading code because that’s the only difference from the previous example. What’s striking is that I’ve somehow managed to replace 4 lines of code with just one. A big reason for this is the lack of imports, since I’m only using a single class this time, <code>File</code>, and Groovy doesn’t require you to import it. In fact, there are several core packages that don’t need to be imported because they’re so commonly used. Here’s a short list of those exceptions:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><code>java.lang.*</code></p>
</li>
<li>
<p><code>java.io.*</code></p>
</li>
<li>
<p><code>java.net.*</code></p>
</li>
<li>
<p><code>java.util.*</code></p>
</li>
<li>
<p><code>java.math.BigDecimal</code></p>
</li>
<li>
<p><code>java.math.BigInteger</code></p>
</li>
<li>
<p><code>groovy.lang.*</code></p>
</li>
<li>
<p><code>groovy.util.*</code></p>
</li>
</ul>
</div>
<div class="paragraph">
<p>These packages cover such classes as <code>File</code>, <code>String</code>, <code>List</code>, and <code>URL</code>, which means that your scripts become a lot cleaner without those <code>import</code> statements. One thing that newcomers may find confusing is how sub-packages are treated. Consider the classes associated with regular expressions, which I’ll talk more about later in this chapter. They reside in the package <code>java.util.regex</code>, which looks like a sub-package of <code>java.util</code>. In fact, packages aren’t hierarchical except in the way we developers interpret them. The concrete result is that you have to explicitly import a class like <code>java.util.regex.Pattern</code> because its not covered by the <code>java.util.*</code> auto import.</p>
</div>
<div class="paragraph">
<p>Another factor in the reduced amount of code is that <code>File</code> is a simpler API. And that’s one reason why you will continue to see it, even with code that exclusively runs on Java 8 or above. One thing to bear in mind is that in this case, the <code>readLines()</code> method is provided by the Groovy JDK extensions rather than Java. I recommend that you peruse the <a href="http://docs.groovy-lang.org/docs/next/html/groovy-jdk/?java/io/File.html">Groovy JDK documentation</a> for <code>File</code> because you’ll find plenty of useful methods to try out.</p>
</div>
<div class="paragraph">
<p>This all raises an obvious yet important question: Which API should you use? I would argue that you should move over to the new NIO classes as soon as you can. Although <code>File</code> is generally simpler and less verbose, which is fine for scripts with simple needs, the NIO API has several advantages. First, it’s easier to unit test as <code>Path</code> is an interface and you can implement your own mock <code>FileSystem</code> (or you can use an existing one such as <a href="https://github.com/google/jimfs">JIMFS</a>). Second, it integrates with Java 8 streams, allowing you to do things like lazily walk a directory tree.</p>
</div>
<div class="paragraph">
<p>One way to reduce the clutter in the Java 8 version of the CSV script is to make use of some special <code>import</code> features. You can both alias a class, giving it a shorter name, and explicitly import static methods, meaning you don’t have to qualify them with the class name. Here’s a modified version of the script using these techniques:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="CodeRay highlight"><code>import static java.nio.file.Files.readAllLines <b class="conum">(1)</b>
import java.nio.charset.Charset as Ch <b class="conum">(2)</b>
import java.nio.file.FileSystems as FS
def lines = readAllLines(FS.default.getPath("authors.csv"), Ch.forName("UTF-8"))
def authorNames = []
...</code></pre>
</div>
</div>
<div class="colist arabic">
<ol>
<li>
<p>Allows us to call <code>readAllLines()</code> without qualifying with the class name</p>
</li>
<li>
<p>Aliases the class name to something shorter</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>As you can see, the code for reading the lines of text from the file is now more readable, which is always good for aiding comprehension of your code. You’ll find static imports in Java as well, but the aliasing is specific to Groovy.</p>
</div>
<div class="paragraph">
<p>I now want to move away from the kinds of scripts that you can run in the Groovy console to something a bit more useful: command line scripts. You can still write them in the Groovy console, but you must first save them to a file before you can run them from the command line.</p>
</div>
<div class="paragraph">
<p>The first example harks back to the days of Apache Ant, a venerable build tool from the Java world. I often used its Filter task as a simple template engine, replacing occurrences of <code>@TOKEN@</code> with whatever parameter values I needed. It’s not hard to reproduce this behavior in a Groovy script, as you’ll see from the following example:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="CodeRay highlight"><code>import java.nio.file.FileSystems as FS
if (args.size() != 1) {
println "Incorrect number of arguments."
println ""
println "USAGE: processTemplates TMPL_PATH"
System.exit(1)
}
final tokens = [ <b class="conum">(1)</b>
NAME: "Whizzbang",
CURRENT_DATE: new Date()]
final templatePath = FS.default.getPath(args[0])
final sourceText = templatePath.getText("UTF-8")
def processedText = sourceText
for (entry in tokens) {
processedText = processedText.replace( <b class="conum">(2)</b>
"@${entry.key}@",
entry.value.toString())
}
final outFile = FS.default.getPath("processed.txt")
outFile.setText(processedText, "UTF-8") <b class="conum">(3)</b></code></pre>
</div>
</div>
<div class="colist arabic">
<ol>
<li>
<p>Hard code the replacement tokens</p>
</li>
<li>
<p>Perform the token substitution using a standard JDK method</p>
</li>
<li>
<p>Write the resulting text to a new (hard-coded) file</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>You should by now be familiar with most of the code in the script. The key new feature is the <code>setText()</code> method, which allows you to easily write text to a file. If the file doesn’t exist, it gets created. Otherwise, its content is overwritten by the string you provide. The method also closes the file automatically, so you don’t have to worry about leaving file handles open.</p>
</div>
<div class="paragraph">
<p>This works well for simple cases, but you often want to write to a file in chunks. Imagine you want to read records from a database table, perhaps process them, and then generate a CSV file containing one line per record. You could create the CSV in memory as a string and then write it out using <code>setText()</code>, but this is an inefficient use of memory and could take noticeably longer than writing each line to the file as you go. It depends on how many records you have.</p>
</div>
<div class="paragraph">
<p>Fortunately, Groovy provides several convenience methods for writing to a file on an as-you-go basis. I’m using <code>withPrintWriter()</code> in the next example to do exactly that, while also ensuring that the underlying file is closed regardless of whether my code throws an exception or not. The script takes a path to a directory and then generates a CSV file containing the filename, file size, and creation date for each file in that directory.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="CodeRay highlight"><code>import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME
import java.nio.file.Files
import java.nio.file.FileSystems as FS
import java.nio.file.Path
import java.time.ZoneId <b class="conum">(1)</b>
if (args.size() != 1) {
println "Incorrect number of arguments."
println ""
println "USAGE: filesReport DIR_PATH"
System.exit(1)
}
final dirPath = FS.default.getPath(args[0])
final files = Files.list(dirPath)
final outFile = FS.default.getPath("files.csv")
outFile.withPrintWriter("UTF-8") { writer -> <b class="conum">(2)</b>
for (path in files) {
writer.println("${path.fileName}, ${Files.size(path)}, ${getCreationDate(path)}")
}
}
println "Written report to ${outFile}"
String getCreationDate(Path path) {
final creationInstant = Files.getAttribute(path, "creationTime").toInstant()
return ISO_OFFSET_DATE_TIME.format(creationInstant.atZone(ZoneId.systemDefault()))
}</code></pre>
</div>
</div>
<div class="colist arabic">
<ol>
<li>
<p>Use the Java 8+ Date & Time API</p>
</li>
<li>
<p>Generate the CSV file, with one line per source file</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>This example doesn’t have many lines, but it does introduce two important features:</p>
</div>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>The Java 8+ Date and Time API</p>
</li>
<li>
<p>Closures</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>Let’s start with the new API.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_dates_and_time">Dates and time</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Before Java 8, developers either got by with the <code>java.util.Date</code> and <code>java.util.Calendar</code> classes, or they used a third-party library called Joda Time. The latter was often the only choice for even moderately complex date and time work because the core JDK classes were awkward to use and underpowered.</p>
</div>
<div class="paragraph">
<p>The Date and Time API brings coherence to working with dates in Java. Simple things are often easier to accomplish than with the old <code>Date</code> class, while you also gain greater control with more complex requirements. Still, the <code>Date</code> class should work just fine for the file creation dates in our example. So why use the <code>java.time.*</code> classes? For two reasons. First, the file NIO API gives us a <code>java.time.Instant</code> value rather than a <code>java.util.Date</code>. Second, I think you should prioritize the Date and Time API over <code>java.util.Date</code> for your own code because it’s a significant improvement over the older API. For example, the core classes are immutable and thread safe, unlike <code>Date</code> and <code>Calendar</code>.</p>
</div>
<div class="paragraph">
<p>Figure xxx shows you the most relevant classes of the Date and Time API for right now. The <code>Instant</code> class represents a fixed point in time, independent of calendars and dates. If people around the globe retrieved the current time as an <code>Instant</code> at the same time, they should all get the same value, Of course, this is unlikely as there will almost certainly be variations between the system clocks on their computers. But that’s the ideal.</p>
</div>
<div class="paragraph">
<p>The <code>java.time.LocalDateTime</code> class is different in that it represents a (Gregorian) calendar date and time, such as New Year’s Day (1st January for any given year). This is not a fixed point in time because it depends on the time zone you’re interested in. New Year’s Day hits Australia well before the west coast of America. To correlate a date and time to an <code>Instant</code>, you need to use either <code>java.time.ZonedDateTime</code> or <code>java.time.OffsetTimeZone</code>, both of which incorporate the time zone information.</p>
</div>
<div class="paragraph">
<p>It’s often hard work dealing with calendars and keeping track of time zones, but we’re fortunate that the API makes life easy for our simple use case. The idea is to store the file creation date in the CSV file as an ISO 8601 date rather than an instant. To do that, we want to format the <code>Instant</code> we get from the <code>Files</code> class. The date formatters require dates rather than instants, though, so we use the <code>Instant.atZone()</code> method to perform the conversion using the system’s current time zone. We could also perform the conversion using the UTC time zone if we wanted. It all depends on whether we’re interested in which time zone the file was created in. For server-side work, I’d recommend almost always using UTC.</p>
</div>
<div class="paragraph">
<p>The Date and Time API brings more to the table than just these classes. Durations and periods allow you to readily calculate deadlines and reminders among other things. Overall, it’s a richer API than we had previously in the JDK without it being laboriously hard to work with.</p>
</div>
<div class="paragraph">
<p>I now want to go onto the second feature the script introduces: closures.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_closures">Closures</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Developers start using Groovy for a variety of reasons. I started because it was the language of the Grails web framework. But if we could narrow down why people then stayed with Groovy to a single language feature, I’m convinced it would be closures. It’s fairly easy these days to explain the basics of them because most of the popular languages have comparable features:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>Ruby code blocks</p>
</li>
<li>
<p>Javascript functions</p>
</li>
<li>
<p>Java 8 lambdas</p>
</li>
<li>
<p>Python functions and lambdas</p>
</li>
<li>
<p>C# lambda expressions and anonymous functions</p>
</li>
<li>
<p>Scala functions</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>These aren’t all exactly the same feature, but they do allow for higher-order functions. If you’re not familiar with this term, it means that functions can themselves be arguments to other functions. Consider the example of filtering a list of words. You might want all the words that start with the letter 'p'. You might instead be interested words longer than 5 letters. Or perhaps you just want to remove all swear words using a dictionary. The only difference between each of these requirements is the filter constraint. Does the word start with a particular letter? Is it longer than a particular value? The algorithm for filtering the list is always the same, it’s only the constraint that changes.</p>
</div>
<div class="paragraph">
<p>That constraint can be implemented as a standalone predicate function, i.e. a function that returns <code>true</code> or <code>false</code>. Let’s see what this looks like in Groovy code:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="CodeRay highlight"><code>final swearWords = ["flabbergasted"] as Set
final words = ["orange", "sit", "profit", "test", "flabbergasted",
"honorific", "proposal"]
println words.findAll({ word -> word.startsWith("p") })
println words.findAll({ word -> word.size() > 5 })
println words.findAll({ word -> !swearWords.contains(word) })</code></pre>
</div>
</div>
<div class="paragraph">
<p>The <code>findAll()</code> method is a Groovy JDK extension that performs collection filtering. In each of the above calls, it effectively applies the given predicate function (in this case a closure) to each word in the list and only retains the word if the function returns <code>true</code>. You can run the example to see the result.</p>
</div>
<div class="paragraph">
<p>The basic form of a closure is the following:</p>
</div>
<div class="literalblock">
<div class="content">
<pre>{ <args> -> <function body> }</pre>
</div>
</div>
<div class="paragraph">
<p>So the curly braces delimit the closure literal, similar to the way square brackets delimit lists and maps. This can be a bit confusing because methods, loops, and conditions use curly braces as well. I generally ask myself this: are the curly braces part of an assignment or a method <em>call</em>? If so, then I have a closure. Assignments are easy to pick out:</p>
</div>
<div class="literalblock">
<div class="content">
<pre>def predicateFn = { word -> word.startsWith("p") }</pre>
</div>
</div>
<div class="paragraph">
<p>Method calls tend to be harder to identify because Groovy supports several syntaxes. I’ll explain shortly, but first, one question you may be asking yourself is where the <code>word</code> argument comes from. How does it work? It’s important to understand that a closure’s argument list behaves just the same as a method’s. In other words, <code>word</code> only exists within the context of the closure itself. It’s the responsibility of the <code>findAll()</code> method to pass each word as the argument when calling the closure.</p>
</div>
<div class="paragraph">
<p>To see what I mean, you can try invoking the closure yourself:</p>
</div>
<div class="literalblock">
<div class="content">
<pre>def predicateFn = { word -> word.startsWith("p") }
predicateFn.call("petals")</pre>
</div>
</div>
<div class="paragraph">
<p>Executing this in the Groovy console will show a result of <code>true</code>, because "petal" starts with a 'p'. What happens if you try to call the closure with more than one argument? The following results in a <code>MissingMethodException</code> for <code>call()</code> because the number of arguments don’t match:</p>
</div>
<div class="literalblock">
<div class="content">
<pre>def predicateFn = { word -> word.startsWith("p") }
predicateFn.call("petals", "badarg")</pre>
</div>
</div>
<div class="paragraph">
<p>If it’s easy to identify a closure that’s assigned to a variable, why is it harder when the closure is an argument of a method call? Let’s look at the first <code>findAll()</code> call from the list filtering example:</p>
</div>
<div class="literalblock">
<div class="content">
<pre>words.findAll({ word -> word.startsWith("p") })</pre>
</div>
</div>
<div class="paragraph">
<p>It’s clear that <code>findAll()</code> is a method call because of the parentheses and the closure is defined within the parentheses. This makes the closure definition relatively obvious. But this is an uncommon syntax. The following are all more common alternatives:</p>
</div>
<div class="literalblock">
<div class="content">
<pre>words.findAll() { word -> word.startsWith("p") }
words.findAll { word -> word.startsWith("p") }
words.findAll { word ->
word.startsWith("p")
}</pre>
</div>
</div>
<div class="paragraph">
<p>Of these, the second is probably the most common and it doesn’t even have any parentheses at all! What’s going on? This is simply syntax sugar for a very specific case. If the closure is the last (or only) argument of a method, it can be defined after the parentheses. If it’s the only argument, it’s common to dispense with the parentheses entirely.</p>
</div>
<div class="paragraph">
<p>Given this information about closures, can you identify the one in the earlier example where we created a CSV file from a directory listing? If you remember, the script writes the lines of text to the file using the <code>withPrintWriter()</code> method:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="CodeRay highlight"><code>...
final outFile = FS.default.getPath("files.csv")
outFile.withPrintWriter("UTF-8") { writer ->
for (path in files) {
writer.println("${path.fileName}, ${Files.size(path)}, ${getCreationDate(path)}")
}
}
...</code></pre>
</div>
</div>
<div class="paragraph">
<p>The closure begins with <code>{ writer → …​</code>. Under the hood, <code>withPrintWriter()</code> creates or opens the target file and creates a <code>java.io.PrintWriter</code> for it using the character encoding specified by the first argument. It then calls the second argument - the closure - passing the newly created writer as it’s argument. When the closure returns or throws an exception, <code>withPrintWriter()</code> closes the file. For Java developers, it’s worth noting that this is the Groovy alternative to Java’s <em>try-with-resources</em> statement, as that syntax isn’t supported by Groovy.</p>
</div>
<div class="paragraph">
<p>A good way to learn and understand something is through plenty of examples. Two (filtering and writing to a file) aren’t really sufficient. To rectify that, I’m going to revisit some examples from earlier chapters and try to rewrite them using closures where appropriate. This should give you an idea of an alternate approach to thinking about coding problems and how to solve them.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_revisiting_earlier_examples">Revisiting earlier examples</h2>
<div class="sectionbody">
<div class="paragraph">
<p>A lot of the examples you saw in the previous chapter have more concise and readable implementations once you start thinking in terms of higher order functions. Take the example for counting the number of vowels in a string. Instead of reaching immediately for a loop to solve the problem, think about what we want to achieve: counting characters. Which characters? Vowels. This is a similar problem to filtering a word list, except this time the algorithm is counting. The predicate simply determines whether a particular character should be counted or not. Is there a method available to do this?</p>
</div>
<div class="paragraph">
<p>If you check out the Groovy JDK documentation, you’ll find a <code>count()</code> method for <code>CharSequence</code>. The problem is that it only counts occurences of a specified string. What we need is the <code>Iterable.count(Closure)</code> method (the <code>groovy.lang.Closure</code> type is one way to identify a method that accepts closure arguments). Since <code>CharSequence</code> is not an <code>Iterable</code>, we first have to convert it to one using <code>iterator()</code>. We can then perform the count, as demonstrated here:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="CodeRay highlight"><code>def text = "Jack Rabbit Slims Twist Contest"
final vowels = "aeiou"
final vowelCount = text.iterator().count { vowels.contains(it.toLowerCase()) }
println "Number of vowels: " + vowelCount</code></pre>
</div>
</div>
<div class="paragraph">
<p>What was originally 5 lines of a loop is now a single line. The example is also easier to reason about because the code is no longer cluttered with the mechanics of how to count the vowels. The focus here is <em>what</em> we want to achieve, not <em>how</em>.</p>
</div>
<div class="sidebarblock">
<div class="content">
<div class="title">Implicit closure argument</div>
<div class="paragraph">
<p>One new element introduced by the previous example is the lack of an explicit argument for the <code>count()</code> closure. There’s no <code>→</code> that separates the argument list from the closure body. This is a special syntax that’s useful for very short closure implementations, typically one-liners. If you don’t specify an argument list, an implicit argument called <code>it</code> becomes available.</p>
</div>
<div class="paragraph">
<p>The implicit variable form only makes sense for methods that pass a single argument to the closure, such as <code>each()</code>, <code>collect()</code>, and <code>count()</code>. It also works with methods that pass no arguments at all, but then <code>it</code> will be <code>null</code>.</p>
</div>
</div>
</div>
<div class="paragraph">
<p>One thing you’ll quickly realise is how useful closures are in the world of collections. Consider the <a href="#wordStats">first example of section 3.2</a> [xcheck] calculates some statistics about a list of words using a loop. Although the implementation is efficient in terms of there only being a single loop, it’s not obvious at first glance what it’s trying to achieve.</p>
</div>
<div class="paragraph">
<p>A better approach in terms of code comprehension is to use some Groovy JDK extension methods for calculating the maximum, minimum and sum of values in a collection. The key point is that we’re not after the maximum word in this case - you could interpret this as the last word alphabetically perhaps - but the longest word. That’s where closures come in, as you can see from this alternate implementation of that example:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="CodeRay highlight"><code>final words = ["orange", "sit", "test", "flabbergasted", "honorific"]
println "Min word length: ${words.min { it.size() }.size()}" <b class="conum">(1)</b>
println "Max word length: ${words.max { it.size() }.size()}"
println "Avg word length: ${words.sum { it.size() } / words.size()}"</code></pre>
</div>
</div>
<div class="colist arabic">
<ol>
<li>
<p><code>min()</code> and <code>max()</code> return the relevant collection element, i.e. a word in this case, so we need to call <code>size()</code> again to get its length</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>In other words, we can specify exactly what property of the values in the collection we’re interested in by passing a closure to <code>min()</code>, <code>max()</code>, and <code>sum()</code> that extracts the relevant information. If we were interested in the number of vowels in the words instead, we could implement a <code>vowelCount()</code> method and use that from the closure:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="CodeRay highlight"><code>...
println words.sum { vowelCount(it) }</code></pre>
</div>
</div>
<div class="paragraph">
<p>The last example I want to revisit is the method for <a href="#powersOfTwo">calculating the power of twos</a>. I originally showed you an example based on a for loop, but let’s now take a different tack. Consider what result we want: a list of elements of the form 2<sup>x</sup>, where <code>x</code> is a range of numbers 0 to <code>n</code>. Given a sequence of numbers (<code>0..n</code>), applying the function 2<sup>x</sup> to each of the numbers in that sequence will generate a new sequence containing the required powers of two.</p>
</div>
<div class="paragraph">
<p>In code form, this looks like</p>