yax 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. data/yax-0.1/docs/classes/IO.html +251 -0
  2. data/yax-0.1/docs/classes/IO.src/M000003.html +46 -0
  3. data/yax-0.1/docs/classes/IO.src/M000004.html +27 -0
  4. data/yax-0.1/docs/classes/Module.html +156 -0
  5. data/yax-0.1/docs/classes/Module.src/M000002.html +23 -0
  6. data/yax-0.1/docs/classes/MultiIO.html +217 -0
  7. data/yax-0.1/docs/classes/MultiIO.src/M000006.html +18 -0
  8. data/yax-0.1/docs/classes/MultiIO.src/M000007.html +19 -0
  9. data/yax-0.1/docs/classes/MultiIO.src/M000008.html +20 -0
  10. data/yax-0.1/docs/classes/Numeric.html +155 -0
  11. data/yax-0.1/docs/classes/Numeric.src/M000005.html +16 -0
  12. data/yax-0.1/docs/classes/Regexp.html +153 -0
  13. data/yax-0.1/docs/classes/Regexp.src/M000001.html +16 -0
  14. data/yax-0.1/docs/classes/String.html +270 -0
  15. data/yax-0.1/docs/classes/String.src/M000009.html +18 -0
  16. data/yax-0.1/docs/classes/String.src/M000010.html +16 -0
  17. data/yax-0.1/docs/classes/String.src/M000011.html +22 -0
  18. data/yax-0.1/docs/classes/String.src/M000012.html +27 -0
  19. data/yax-0.1/docs/classes/String.src/M000013.html +21 -0
  20. data/yax-0.1/docs/classes/String.src/M000014.html +20 -0
  21. data/yax-0.1/docs/classes/Yax.html +256 -0
  22. data/yax-0.1/docs/classes/Yax.src/M000015.html +20 -0
  23. data/yax-0.1/docs/classes/Yax/Session.html +1661 -0
  24. data/yax-0.1/docs/classes/Yax/Session.src/M000016.html +20 -0
  25. data/yax-0.1/docs/classes/Yax/Session.src/M000017.html +18 -0
  26. data/yax-0.1/docs/classes/Yax/Session.src/M000018.html +19 -0
  27. data/yax-0.1/docs/classes/Yax/Session.src/M000019.html +19 -0
  28. data/yax-0.1/docs/classes/Yax/Session.src/M000020.html +19 -0
  29. data/yax-0.1/docs/classes/Yax/Session.src/M000021.html +46 -0
  30. data/yax-0.1/docs/classes/Yax/Session.src/M000022.html +19 -0
  31. data/yax-0.1/docs/classes/Yax/Session.src/M000023.html +18 -0
  32. data/yax-0.1/docs/classes/Yax/Session.src/M000024.html +20 -0
  33. data/yax-0.1/docs/classes/Yax/Session.src/M000025.html +18 -0
  34. data/yax-0.1/docs/classes/Yax/Session.src/M000026.html +19 -0
  35. data/yax-0.1/docs/classes/Yax/Session.src/M000027.html +20 -0
  36. data/yax-0.1/docs/classes/Yax/Session.src/M000028.html +18 -0
  37. data/yax-0.1/docs/classes/Yax/Session.src/M000029.html +18 -0
  38. data/yax-0.1/docs/classes/Yax/Session.src/M000030.html +19 -0
  39. data/yax-0.1/docs/classes/Yax/Session.src/M000031.html +25 -0
  40. data/yax-0.1/docs/classes/Yax/Session.src/M000032.html +18 -0
  41. data/yax-0.1/docs/classes/Yax/Session.src/M000033.html +36 -0
  42. data/yax-0.1/docs/classes/Yax/Session.src/M000034.html +34 -0
  43. data/yax-0.1/docs/classes/Yax/Session.src/M000035.html +23 -0
  44. data/yax-0.1/docs/classes/Yax/Session.src/M000036.html +20 -0
  45. data/yax-0.1/docs/classes/Yax/Session.src/M000037.html +32 -0
  46. data/yax-0.1/docs/classes/Yax/Session.src/M000038.html +20 -0
  47. data/yax-0.1/docs/classes/Yax/Session.src/M000042.html +18 -0
  48. data/yax-0.1/docs/classes/Yax/Session.src/M000043.html +18 -0
  49. data/yax-0.1/docs/classes/Yax/Session.src/M000044.html +20 -0
  50. data/yax-0.1/docs/classes/Yax/Session.src/M000046.html +37 -0
  51. data/yax-0.1/docs/classes/Yax/Session.src/M000047.html +29 -0
  52. data/yax-0.1/docs/classes/Yax/Session.src/M000048.html +29 -0
  53. data/yax-0.1/docs/classes/Yax/Session.src/M000049.html +30 -0
  54. data/yax-0.1/docs/classes/Yax/Session.src/M000050.html +22 -0
  55. data/yax-0.1/docs/classes/Yax/Session.src/M000051.html +20 -0
  56. data/yax-0.1/docs/classes/Yax/Session.src/M000052.html +20 -0
  57. data/yax-0.1/docs/classes/Yax/Session.src/M000053.html +19 -0
  58. data/yax-0.1/docs/classes/Yax/Session.src/M000054.html +28 -0
  59. data/yax-0.1/docs/classes/Yax/Session.src/M000055.html +21 -0
  60. data/yax-0.1/docs/classes/Yax/Session.src/M000056.html +27 -0
  61. data/yax-0.1/docs/classes/Yax/Session.src/M000057.html +20 -0
  62. data/yax-0.1/docs/classes/Yax/Session.src/M000058.html +22 -0
  63. data/yax-0.1/docs/classes/Yax/Session.src/M000059.html +21 -0
  64. data/yax-0.1/docs/classes/Yax/Session.src/M000060.html +21 -0
  65. data/yax-0.1/docs/classes/Yax/Session.src/M000061.html +20 -0
  66. data/yax-0.1/docs/classes/Yax/Session.src/M000062.html +21 -0
  67. data/yax-0.1/docs/classes/Yax/Session.src/M000063.html +20 -0
  68. data/yax-0.1/docs/classes/Yax/Session.src/M000064.html +21 -0
  69. data/yax-0.1/docs/classes/Yax/Session.src/M000065.html +26 -0
  70. data/yax-0.1/docs/classes/Yax/Session.src/M000066.html +25 -0
  71. data/yax-0.1/docs/classes/Yax/Session.src/M000067.html +29 -0
  72. data/yax-0.1/docs/classes/Yax/Session.src/M000068.html +26 -0
  73. data/yax-0.1/docs/classes/Yax/Session.src/M000069.html +19 -0
  74. data/yax-0.1/docs/classes/Yax/Session.src/M000070.html +31 -0
  75. data/yax-0.1/docs/classes/Yax/Session.src/M000071.html +25 -0
  76. data/yax-0.1/docs/created.rid +1 -0
  77. data/yax-0.1/docs/dot/f_0.dot +14 -0
  78. data/yax-0.1/docs/dot/f_0.png +0 -0
  79. data/yax-0.1/docs/dot/f_1.dot +14 -0
  80. data/yax-0.1/docs/dot/f_1.png +0 -0
  81. data/yax-0.1/docs/dot/f_2.dot +14 -0
  82. data/yax-0.1/docs/dot/f_2.png +0 -0
  83. data/yax-0.1/docs/dot/f_3.dot +93 -0
  84. data/yax-0.1/docs/dot/f_3.png +0 -0
  85. data/yax-0.1/docs/dot/m_3_0.dot +39 -0
  86. data/yax-0.1/docs/dot/m_3_0.png +0 -0
  87. data/yax-0.1/docs/files/License_txt.html +124 -0
  88. data/yax-0.1/docs/files/ReadMe_Amber_txt.html +489 -0
  89. data/yax-0.1/docs/files/ReadMe_txt.html +444 -0
  90. data/yax-0.1/docs/files/nist/yax_rb.html +138 -0
  91. data/yax-0.1/docs/fr_class_index.html +34 -0
  92. data/yax-0.1/docs/fr_file_index.html +30 -0
  93. data/yax-0.1/docs/fr_method_index.html +97 -0
  94. data/yax-0.1/docs/index.html +24 -0
  95. data/yax-0.1/docs/rdoc-style.css +208 -0
  96. data/yax-0.1/lib/nist/yax.rb +1367 -0
  97. data/yax-0.1/tests/test_yax.rb +190 -0
  98. metadata +195 -0
@@ -0,0 +1,34 @@
1
+
2
+ <?xml version="1.0" encoding="iso-8859-1"?>
3
+ <!DOCTYPE html
4
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
5
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
6
+
7
+ <!--
8
+
9
+ Classes
10
+
11
+ -->
12
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
13
+ <head>
14
+ <title>Classes</title>
15
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
16
+ <link rel="stylesheet" href="rdoc-style.css" type="text/css" />
17
+ <base target="docwin" />
18
+ </head>
19
+ <body>
20
+ <div id="index">
21
+ <h1 class="section-bar">Classes</h1>
22
+ <div id="index-entries">
23
+ <a href="classes/IO.html">IO</a><br />
24
+ <a href="classes/Module.html">Module</a><br />
25
+ <a href="classes/MultiIO.html">MultiIO</a><br />
26
+ <a href="classes/Numeric.html">Numeric</a><br />
27
+ <a href="classes/Regexp.html">Regexp</a><br />
28
+ <a href="classes/String.html">String</a><br />
29
+ <a href="classes/Yax.html">Yax</a><br />
30
+ <a href="classes/Yax/Session.html">Yax::Session</a><br />
31
+ </div>
32
+ </div>
33
+ </body>
34
+ </html>
@@ -0,0 +1,30 @@
1
+
2
+ <?xml version="1.0" encoding="iso-8859-1"?>
3
+ <!DOCTYPE html
4
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
5
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
6
+
7
+ <!--
8
+
9
+ Files
10
+
11
+ -->
12
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
13
+ <head>
14
+ <title>Files</title>
15
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
16
+ <link rel="stylesheet" href="rdoc-style.css" type="text/css" />
17
+ <base target="docwin" />
18
+ </head>
19
+ <body>
20
+ <div id="index">
21
+ <h1 class="section-bar">Files</h1>
22
+ <div id="index-entries">
23
+ <a href="files/License_txt.html">License.txt</a><br />
24
+ <a href="files/ReadMe_txt.html">ReadMe.txt</a><br />
25
+ <a href="files/ReadMe_Amber_txt.html">ReadMe_Amber.txt</a><br />
26
+ <a href="files/nist/yax_rb.html">nist/yax.rb</a><br />
27
+ </div>
28
+ </div>
29
+ </body>
30
+ </html>
@@ -0,0 +1,97 @@
1
+
2
+ <?xml version="1.0" encoding="iso-8859-1"?>
3
+ <!DOCTYPE html
4
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
5
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
6
+
7
+ <!--
8
+
9
+ Methods
10
+
11
+ -->
12
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
13
+ <head>
14
+ <title>Methods</title>
15
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
16
+ <link rel="stylesheet" href="rdoc-style.css" type="text/css" />
17
+ <base target="docwin" />
18
+ </head>
19
+ <body>
20
+ <div id="index">
21
+ <h1 class="section-bar">Methods</h1>
22
+ <div id="index-entries">
23
+ <a href="classes/MultiIO.html#M000007"><< (MultiIO)</a><br />
24
+ <a href="classes/Yax/Session.html#M000039">answer (Yax::Session)</a><br />
25
+ <a href="classes/Yax/Session.html#M000054">archive_type (Yax::Session)</a><br />
26
+ <a href="classes/Yax/Session.html#M000066">banner (Yax::Session)</a><br />
27
+ <a href="classes/Yax/Session.html#M000025">build_default_dir_hash (Yax::Session)</a><br />
28
+ <a href="classes/Yax/Session.html#M000056">cd (Yax::Session)</a><br />
29
+ <a href="classes/Yax/Session.html#M000055">cd! (Yax::Session)</a><br />
30
+ <a href="classes/Yax/Session.html#M000057">chmod (Yax::Session)</a><br />
31
+ <a href="classes/Yax/Session.html#M000045">cmd (Yax::Session)</a><br />
32
+ <a href="classes/Yax/Session.html#M000064">cmp (Yax::Session)</a><br />
33
+ <a href="classes/Yax/Session.html#M000044">command (Yax::Session)</a><br />
34
+ <a href="classes/Yax/Session.html#M000058">cp (Yax::Session)</a><br />
35
+ <a href="classes/Yax/Session.html#M000069">curl (Yax::Session)</a><br />
36
+ <a href="classes/Yax/Session.html#M000052">d (Yax::Session)</a><br />
37
+ <a href="classes/Yax/Session.html#M000019">default_error_log_path (Yax::Session)</a><br />
38
+ <a href="classes/Yax/Session.html#M000020">default_exit_status_path (Yax::Session)</a><br />
39
+ <a href="classes/Yax/Session.html#M000022">default_prompt (Yax::Session)</a><br />
40
+ <a href="classes/Yax/Session.html#M000023">default_prompt (Yax::Session)</a><br />
41
+ <a href="classes/Yax/Session.html#M000018">default_time_log_path (Yax::Session)</a><br />
42
+ <a href="classes/Yax/Session.html#M000035">die (Yax::Session)</a><br />
43
+ <a href="classes/Yax/Session.html#M000050">dirName (Yax::Session)</a><br />
44
+ <a href="classes/Yax/Session.html#M000070">env (Yax::Session)</a><br />
45
+ <a href="classes/Yax/Session.html#M000071">env_path (Yax::Session)</a><br />
46
+ <a href="classes/Yax/Session.html#M000043">expect (Yax::Session)</a><br />
47
+ <a href="classes/Yax/Session.html#M000053">f (Yax::Session)</a><br />
48
+ <a href="classes/Yax/Session.html#M000051">fileName (Yax::Session)</a><br />
49
+ <a href="classes/Yax/Session.html#M000046">finish (Yax::Session)</a><br />
50
+ <a href="classes/MultiIO.html#M000008">flush (MultiIO)</a><br />
51
+ <a href="classes/String.html#M000013">from_seconds (String)</a><br />
52
+ <a href="classes/Yax/Session.html#M000027">get_password (Yax::Session)</a><br />
53
+ <a href="classes/Yax/Session.html#M000029">inverse_scaled_seconds (Yax::Session)</a><br />
54
+ <a href="classes/Yax/Session.html#M000036">kill (Yax::Session)</a><br />
55
+ <a href="classes/String.html#M000014">last_line (String)</a><br />
56
+ <a href="classes/Yax/Session.html#M000030">limit (Yax::Session)</a><br />
57
+ <a href="classes/Yax/Session.html#M000059">ln (Yax::Session)</a><br />
58
+ <a href="classes/Yax/Session.html#M000060">ln_sf (Yax::Session)</a><br />
59
+ <a href="classes/Yax/Session.html#M000033">log (Yax::Session)</a><br />
60
+ <a href="classes/Yax/Session.html#M000042">log_output (Yax::Session)</a><br />
61
+ <a href="classes/Yax/Session.html#M000031">log_time (Yax::Session)</a><br />
62
+ <a href="classes/Yax/Session.html#M000065">mangle (Yax::Session)</a><br />
63
+ <a href="classes/String.html#M000011">match_pattern_hash (String)</a><br />
64
+ <a href="classes/Yax/Session.html#M000061">mkdir (Yax::Session)</a><br />
65
+ <a href="classes/IO.html#M000004">multi_expect (IO)</a><br />
66
+ <a href="classes/Yax/Session.html#M000062">mv (Yax::Session)</a><br />
67
+ <a href="classes/Yax/Session.html#M000021">new (Yax::Session)</a><br />
68
+ <a href="classes/MultiIO.html#M000006">new (MultiIO)</a><br />
69
+ <a href="classes/Yax/Session.html#M000024">normalize_paths (Yax::Session)</a><br />
70
+ <a href="classes/Yax/Session.html#M000032">note (Yax::Session)</a><br />
71
+ <a href="classes/Yax/Session.html#M000017">phb_available (Yax::Session)</a><br />
72
+ <a href="classes/Yax/Session.html#M000041">reply (Yax::Session)</a><br />
73
+ <a href="classes/Yax/Session.html#M000048">resolve_dir (Yax::Session)</a><br />
74
+ <a href="classes/Yax/Session.html#M000049">resolve_file (Yax::Session)</a><br />
75
+ <a href="classes/Yax/Session.html#M000047">resolve_url (Yax::Session)</a><br />
76
+ <a href="classes/Yax/Session.html#M000038">respond (Yax::Session)</a><br />
77
+ <a href="classes/Yax/Session.html#M000063">rm_rf (Yax::Session)</a><br />
78
+ <a href="classes/Yax/Session.html#M000028">scaled_seconds (Yax::Session)</a><br />
79
+ <a href="classes/IO.html#M000003">single_expect (IO)</a><br />
80
+ <a href="classes/Yax/Session.html#M000040">snd (Yax::Session)</a><br />
81
+ <a href="classes/Yax/Session.html#M000034">spawn (Yax::Session)</a><br />
82
+ <a href="classes/Yax/Session.html#M000016">spawn (Yax::Session)</a><br />
83
+ <a href="classes/Yax/Session.html#M000068">tgz (Yax::Session)</a><br />
84
+ <a href="classes/String.html#M000010">to_regexp (String)</a><br />
85
+ <a href="classes/Regexp.html#M000001">to_regexp (Regexp)</a><br />
86
+ <a href="classes/Numeric.html#M000005">to_seconds (Numeric)</a><br />
87
+ <a href="classes/String.html#M000012">to_seconds (String)</a><br />
88
+ <a href="classes/Yax/Session.html#M000026">truncate_time_log (Yax::Session)</a><br />
89
+ <a href="classes/Yax/Session.html#M000067">untgz (Yax::Session)</a><br />
90
+ <a href="classes/String.html#M000009">url? (String)</a><br />
91
+ <a href="classes/Yax/Session.html#M000037">wait (Yax::Session)</a><br />
92
+ <a href="classes/Yax.html#M000015">yax (Yax)</a><br />
93
+ <a href="classes/Module.html#M000002">yaxify (Module)</a><br />
94
+ </div>
95
+ </div>
96
+ </body>
97
+ </html>
@@ -0,0 +1,24 @@
1
+ <?xml version="1.0" encoding="iso-8859-1"?>
2
+ <!DOCTYPE html
3
+ PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
4
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
5
+
6
+ <!--
7
+
8
+ yax
9
+
10
+ -->
11
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
12
+ <head>
13
+ <title>yax</title>
14
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
15
+ </head>
16
+ <frameset rows="20%, 80%">
17
+ <frameset cols="25%,35%,45%">
18
+ <frame src="fr_file_index.html" title="Files" name="Files" />
19
+ <frame src="fr_class_index.html" name="Classes" />
20
+ <frame src="fr_method_index.html" name="Methods" />
21
+ </frameset>
22
+ <frame src="files/ReadMe_txt.html" name="docwin" />
23
+ </frameset>
24
+ </html>
@@ -0,0 +1,208 @@
1
+
2
+ body {
3
+ font-family: Verdana,Arial,Helvetica,sans-serif;
4
+ font-size: 90%;
5
+ margin: 0;
6
+ margin-left: 40px;
7
+ padding: 0;
8
+ background: white;
9
+ }
10
+
11
+ h1,h2,h3,h4 { margin: 0; color: #efefef; background: transparent; }
12
+ h1 { font-size: 150%; }
13
+ h2,h3,h4 { margin-top: 1em; }
14
+
15
+ a { background: #eef; color: #039; text-decoration: none; }
16
+ a:hover { background: #039; color: #eef; }
17
+
18
+ /* Override the base stylesheet's Anchor inside a table cell */
19
+ td > a {
20
+ background: transparent;
21
+ color: #039;
22
+ text-decoration: none;
23
+ }
24
+
25
+ /* and inside a section title */
26
+ .section-title > a {
27
+ background: transparent;
28
+ color: #eee;
29
+ text-decoration: none;
30
+ }
31
+
32
+ /* === Structural elements =================================== */
33
+
34
+ div#index {
35
+ margin: 0;
36
+ margin-left: -40px;
37
+ padding: 0;
38
+ font-size: 90%;
39
+ }
40
+
41
+
42
+ div#index a {
43
+ margin-left: 0.7em;
44
+ }
45
+
46
+ div#index .section-bar {
47
+ margin-left: 0px;
48
+ padding-left: 0.7em;
49
+ background: #ccc;
50
+ font-size: small;
51
+ }
52
+
53
+
54
+ div#classHeader, div#fileHeader {
55
+ width: auto;
56
+ color: white;
57
+ padding: 0.5em 1.5em 0.5em 1.5em;
58
+ margin: 0;
59
+ margin-left: -40px;
60
+ border-bottom: 3px solid #006;
61
+ }
62
+
63
+ div#classHeader a, div#fileHeader a {
64
+ background: inherit;
65
+ color: white;
66
+ }
67
+
68
+ div#classHeader td, div#fileHeader td {
69
+ background: inherit;
70
+ color: white;
71
+ }
72
+
73
+
74
+ div#fileHeader {
75
+ background: #057;
76
+ }
77
+
78
+ div#classHeader {
79
+ background: #048;
80
+ }
81
+
82
+
83
+ .class-name-in-header {
84
+ font-size: 180%;
85
+ font-weight: bold;
86
+ }
87
+
88
+
89
+ div#bodyContent {
90
+ padding: 0 1.5em 0 1.5em;
91
+ }
92
+
93
+ div#description {
94
+ padding: 0.5em 1.5em;
95
+ background: #efefef;
96
+ border: 1px dotted #999;
97
+ }
98
+
99
+ div#description h1,h2,h3,h4,h5,h6 {
100
+ color: #125;;
101
+ background: transparent;
102
+ }
103
+
104
+ div#validator-badges {
105
+ text-align: center;
106
+ }
107
+ div#validator-badges img { border: 0; }
108
+
109
+ div#copyright {
110
+ color: #333;
111
+ background: #efefef;
112
+ font: 0.75em sans-serif;
113
+ margin-top: 5em;
114
+ margin-bottom: 0;
115
+ padding: 0.5em 2em;
116
+ }
117
+
118
+
119
+ /* === Classes =================================== */
120
+
121
+ table.header-table {
122
+ color: white;
123
+ font-size: small;
124
+ }
125
+
126
+ .type-note {
127
+ font-size: small;
128
+ color: #DEDEDE;
129
+ }
130
+
131
+ .xxsection-bar {
132
+ background: #eee;
133
+ color: #333;
134
+ padding: 3px;
135
+ }
136
+
137
+ .section-bar {
138
+ color: #333;
139
+ border-bottom: 1px solid #999;
140
+ margin-left: -20px;
141
+ }
142
+
143
+
144
+ .section-title {
145
+ background: #79a;
146
+ color: #eee;
147
+ padding: 3px;
148
+ margin-top: 2em;
149
+ margin-left: -30px;
150
+ border: 1px solid #999;
151
+ }
152
+
153
+ .top-aligned-row { vertical-align: top }
154
+ .bottom-aligned-row { vertical-align: bottom }
155
+
156
+ /* --- Context section classes ----------------------- */
157
+
158
+ .context-row { }
159
+ .context-item-name { font-family: monospace; font-weight: bold; color: black; }
160
+ .context-item-value { font-size: small; color: #448; }
161
+ .context-item-desc { color: #333; padding-left: 2em; }
162
+
163
+ /* --- Method classes -------------------------- */
164
+ .method-detail {
165
+ background: #efefef;
166
+ padding: 0;
167
+ margin-top: 0.5em;
168
+ margin-bottom: 1em;
169
+ border: 1px dotted #ccc;
170
+ }
171
+ .method-heading {
172
+ color: black;
173
+ background: #ccc;
174
+ border-bottom: 1px solid #666;
175
+ padding: 0.2em 0.5em 0 0.5em;
176
+ }
177
+ .method-signature { color: black; background: inherit; }
178
+ .method-name { font-weight: bold; }
179
+ .method-args { font-style: italic; }
180
+ .method-description { padding: 0 0.5em 0 0.5em; }
181
+
182
+ /* --- Source code sections -------------------- */
183
+
184
+ a.source-toggle { font-size: 90%; }
185
+ div.method-source-code {
186
+ background: #262626;
187
+ color: #ffdead;
188
+ margin: 1em;
189
+ padding: 0.5em;
190
+ border: 1px dashed #999;
191
+ overflow: hidden;
192
+ }
193
+
194
+ div.method-source-code pre { color: #ffdead; overflow: hidden; }
195
+
196
+ /* --- Ruby keyword styles --------------------- */
197
+
198
+ .standalone-code { background: #221111; color: #ffdead; overflow: hidden; }
199
+
200
+ .ruby-constant { color: #7fffd4; background: transparent; }
201
+ .ruby-keyword { color: #00ffff; background: transparent; }
202
+ .ruby-ivar { color: #eedd82; background: transparent; }
203
+ .ruby-operator { color: #00ffee; background: transparent; }
204
+ .ruby-identifier { color: #ffdead; background: transparent; }
205
+ .ruby-node { color: #ffa07a; background: transparent; }
206
+ .ruby-comment { color: #b22222; font-weight: bold; background: transparent; }
207
+ .ruby-regexp { color: #ffa07a; background: transparent; }
208
+ .ruby-value { color: #7fffd4; background: transparent; }
@@ -0,0 +1,1367 @@
1
+ # For an overview of use, features, and an example, see ReadMe.txt[link:files/ReadMe_txt.html].
2
+ #
3
+ # Author: A. Griesser
4
+
5
+ require 'pty'
6
+ require 'open3'
7
+ require 'stringio'
8
+ require 'timeout'
9
+ require 'fileutils'
10
+ require 'nist/common/sequentialHash'
11
+ require 'nist/common/singletonReflection'
12
+
13
+ class Regexp
14
+ # Lets us treat Strings and Regexp the same way.
15
+ def to_regexp; self; end
16
+ end # Regexp
17
+
18
+ class String
19
+ # Desribes the patterns that Yax recognizes as URLs.
20
+ URL_PAT = Regexp.new('^(http://|ftp://)')
21
+ # Returns true if we recognize the receiver as a URL.
22
+ def url?
23
+ URL_PAT.match(self)
24
+ end
25
+ # Lets us treat Strings and Regexp the same way.
26
+ def to_regexp; Regexp.new(Regexp.quote(self)); end
27
+ # Takes a hash whose keys are Regex instances.
28
+ # Returns nil if no match occured.
29
+ # If a match did occur, return an Array:
30
+ # result[0]=key
31
+ # result[1]=match result
32
+ def match_pattern_hash(hash)
33
+ hash.each {|key, pattern|
34
+ match = pattern.match(self)
35
+ return key, match if match
36
+ }
37
+ nil
38
+ end
39
+ # Converts a String in the form dd:hh:mm:ss into seconds.
40
+ #--
41
+ #I would rather use existing classes, but I find Ruby's Date, Time, and DateTime
42
+ # confusing. Also they don't seem to support time differences very well. Some
43
+ # day I will figure them out, and replace these.
44
+ #++
45
+ def to_seconds
46
+ parts = split(':')
47
+ raise "To convert a String to seconds, it needs to be expressed as dd:hh:mm:ss" if parts.size!=4
48
+ answer = parts[0].to_i
49
+ answer *= 24
50
+ answer += parts[1].to_i
51
+ answer *= 60
52
+ answer += parts[2].to_i
53
+ answer *= 60
54
+ answer += parts[3].to_i
55
+ answer
56
+ end
57
+ # Creates an instance in the form dd:hh:mm:ss from some number of seconds.
58
+ def self.from_seconds(seconds)
59
+ mm, ss = seconds.ceil.divmod(60)
60
+ hh, mm = mm.divmod(60)
61
+ dd, hh = hh.divmod(24)
62
+ self.new << dd.to_s << ':' << hh.to_s << ':' << mm.to_s << ':' << ss.to_s
63
+ end
64
+ # Returns the substring containing all the characters after the last \n.
65
+ # Returns nil if there is no last \n, or there are no characters after it.
66
+ def last_line
67
+ i=rindex("\n")
68
+ return nil unless i && size>(i+1)
69
+ self[(i+1)..-1]
70
+ end
71
+ end # String
72
+
73
+ class Numeric
74
+ # Lets us use String and Numeric expressions for time limits interchangeably.
75
+ def to_seconds; self; end
76
+ end # Numeric
77
+
78
+ class IO
79
+ # A modified version of expect that
80
+ # * can take an externally specified buffer.
81
+ # * checks to see if the buffer already matches the pattern
82
+ # * can work around an OS X idiosyncrasy.
83
+ # The buffer must be a String (although the caller can hold onto a StringIO
84
+ # wrapping this String).
85
+ # This method returns nil if a timeout occured, or the match
86
+ # results if a match occured. Can also take an optional one-argument
87
+ # result-processing block, in which case the block results are returned.
88
+ #
89
+ # When you set workAround=true,
90
+ # * \r\n is treated like \n
91
+ # * \r not immediately followed by \n deletes the preceeding character.
92
+ # This is an attempt to compensate for the odd "\r" character behavior which
93
+ # occurs for PDY stdout under OS X (and, for all I know, other platforms as well).
94
+ # Here is an example of about 5 extra \r characters:
95
+ # Yax::Session.expect timed out under current_command
96
+ # '"cd /Users/griesser/Documents/Code/DevRoot/Projects/guiStackOSX/temp/Build/TigerFix"'
97
+ # while waiting for /griesser[$#] /, buffer is:
98
+ # <<"cd / \rUsers/griesser/Documents/Code/DevRoot/Projects/guiStackOSX/temp/Build/TigerFix 2 \r>/Users/griesser/Documents/Code/DevRoot/Projects/guiStackOSX/temp/test_results/e \rrrorLog.txt\r\nSplendid:~/Documents/Code/DevRoot/Projects/guiStackOSX/temp/Build/TigerFix griess\rser$ ">>
99
+ def single_expect(pat, timeout, buf='', workAround=false)
100
+ e_pat = pat.to_regexp
101
+ result = buf.empty? ? nil : e_pat.match(buf)
102
+ return result if result
103
+ last_char_was_return = false
104
+ while true
105
+ break unless IO.select([self],nil,nil,timeout)
106
+ c = getc
107
+ return nil unless c # We hit the end of the file without a match.
108
+ if workAround
109
+ case c
110
+ when ?\r
111
+ # remember we enountered \r
112
+ last_char_was_return=true
113
+ # do not process the \r
114
+ next
115
+ when ?\n
116
+ # just ignore the last \r, process \n
117
+ else
118
+ # strip off the char before the \r
119
+ buf.chop! if last_char_was_return
120
+ end
121
+ last_char_was_return=false
122
+ end
123
+ buf << c.chr
124
+ break if result=e_pat.match(buf)
125
+ end
126
+ result = yield result if block_given?
127
+ # STDOUT.log_expect('single_expect', pat, buf, result)
128
+ result
129
+ end
130
+
131
+ # Similar to single_expect, but:
132
+ # * can not take a block
133
+ # * takes a Hash instead of a single pattern.
134
+ # * can limit the buffer size
135
+ # * can log the output
136
+ #
137
+ # The hash has:
138
+ # [keys] Identify the pattern (a Symbol is suggested)
139
+ # [values] Strings or patterns to be matched.
140
+ #
141
+ # If truncSize is nil, the buffer grows continuously. If truncSize
142
+ # is not nil, the buffer is allowed to grow up to maxSize, whereupon
143
+ # it is truncated to the most recent truncSize characters. Don't use
144
+ # this feature if the caller expects buf only to grow.
145
+ #
146
+ # If logger is non-nil, it must respond to log_output(string).
147
+ # This method is called for each line. It will be called with a
148
+ # nil argument if multi_expect receives a \n when there are no
149
+ # characters in the buffer after the last \n.
150
+ #
151
+ # Returns nil if no match occured, or an Array if there was a match:
152
+ # result[0]=key for pattern that matched
153
+ # result[1]=match result
154
+ def multi_expect(hash, maxSeconds, buf='', workAround=false, logger=nil, truncSize=1024, maxSize=1024*64)
155
+ result = nil
156
+ begin
157
+ Timeout::timeout(maxSeconds) {
158
+ result = _multi_expect(hash, 1, buf, workAround, logger, truncSize, maxSize)
159
+ }
160
+ rescue Timeout::Error
161
+ # silently return nil
162
+ end
163
+ # STDOUT.log_expect('multi_expect', hash, buf, result)
164
+ result
165
+ end
166
+ # timeout is not the time limit for the entire 'expect', it's the time limit for one
167
+ # 'IO.select'. This private method will continue forever unless a
168
+ # pattern matches
169
+ def _multi_expect(hash, timeout, buf='', workAround=false, logger=nil, truncSize=1024, maxSize=1024*64) # :nodoc:
170
+ patHash = Hash.new
171
+ hash.each {|key, value| patHash[key]=value.to_regexp }
172
+ result = buf.empty? ? nil : buf.match_pattern_hash(patHash)
173
+ return result if result
174
+ last_char_was_return = false
175
+ while true
176
+ next unless IO.select([self],nil,nil,timeout)
177
+ c = getc
178
+ return nil unless c # We hit the end of the file without a match.
179
+ if workAround
180
+ case c
181
+ when ?\r
182
+ # remember we enountered \r
183
+ last_char_was_return=true
184
+ # do not process the \r
185
+ next
186
+ when ?\n
187
+ # just ignore the last \r, process \n
188
+ else
189
+ # strip off the char before the \r
190
+ buf.chop! if last_char_was_return
191
+ end
192
+ last_char_was_return=false
193
+ end
194
+ logger.log_output(buf.last_line) if logger && c==?\n
195
+ buf << c.chr
196
+ result=buf.match_pattern_hash(patHash)
197
+ logger.log_output(buf.last_line) if logger && result && c!=?\n
198
+ break if result
199
+ buf[0..-(truncSize+1)]='' if truncSize && buf.size>maxSize
200
+
201
+ end
202
+ result
203
+ end
204
+
205
+ def log_expect(what, pat, buf, result) # :nodoc:
206
+ require 'nist/common/rubyToolsDay'
207
+ print "\n\n\n#################################"
208
+ print "\n# #{what}"
209
+ print "\n#################################"
210
+ print "\npat: "+pat.inspect
211
+ print "\ntimeout!" if !result
212
+ print "\nresult: "+result.to_a.inspect if result
213
+ print "\nbuf: "+buf.dump
214
+ show_stack
215
+ print "\n#################################\n\n\n"
216
+ flush
217
+ end
218
+ end # IO
219
+
220
+ # MultiIO lets you treat multiple IOs as one.
221
+ # Each element must respond to <<. If an IO
222
+ # responds to flush, that will be applied as well.
223
+ # Can as a Yax@transcript to log to both STDOUT and
224
+ # a file.
225
+ class MultiIO
226
+
227
+ # An Array of things that respond to << and (optionally) flush.
228
+ attr_accessor :ios
229
+
230
+ # Instantiate as: MultiIO.new( io_1, ... io_n )
231
+ def initialize(*ios)
232
+ self.ios = ios
233
+ end
234
+
235
+ # Append to all @ios.
236
+ def << (string)
237
+ ios.each {|io| io << string }
238
+ self
239
+ end
240
+
241
+ # Flush all @ios that understand flush.
242
+ def flush
243
+ ios.each {|io|
244
+ io.flush if io.respond_to?(:flush)
245
+ }
246
+ end
247
+
248
+ end # MultiIO
249
+
250
+
251
+ =begin rdoc
252
+ Most of the behavior in the Yax module is implemented in class Session.
253
+ The behaviors of a default instance of Session are reflected onto the module,
254
+ so if you "include Yax" you do not need to explicitly create an instance.
255
+ =end
256
+ module Yax
257
+
258
+ =begin rdoc
259
+ Represents an environment in which one can script an interaction with
260
+ a process (others should be possible, but only bash has been tested).
261
+
262
+ For an overview of use, features, and an example, see ReadMe.txt[link:files/ReadMe_txt.html].
263
+ =end
264
+ class Session
265
+
266
+ # Specifies how a new interactive session is spawned.
267
+ # currently choices are
268
+ # :spawn functions adequately
269
+ # :popen3 some code present, but problems with IO.expect (don't use it yet).
270
+ @@spawn_method = :spawn
271
+
272
+ # The @trascript records the session for debug purposes.
273
+ # When @transcript is not nil, it must respond to <<.
274
+ # If it also responds to #flush, each log entry will be flushed.
275
+ attr_accessor :transcript
276
+
277
+ # @time_log_path is optional. If present, a log of execution times will be kept.
278
+ attr_accessor :time_log_path
279
+
280
+ # If @time_log_reset is true, the previous time log (if any) will be removed
281
+ # when each session is spawned. Otherwise a single log will be used across sessions.
282
+ attr_accessor :time_log_reset
283
+
284
+ # @error_log_path contains a required absolute path to the error log file.
285
+ # Each command replaces this file: if you want the errors for the entire Session,
286
+ # consult the transcript.
287
+ attr_accessor :error_log_path
288
+
289
+ # @exit_status_path contains a required absolute path to a file that's used to record
290
+ # #command exit statuses.
291
+ attr_accessor :exit_status_path
292
+
293
+ # IO that the spawned process writes to.
294
+ attr_accessor :stdout
295
+
296
+ # IO that the spawned process takes input from.
297
+ attr_accessor :stdin
298
+
299
+ # The process ID of the spawned process
300
+ attr_accessor :pid
301
+
302
+ # Specifies the maximum time that expect and finish will wait, if the script does not supply a time.
303
+ attr_accessor :default_maxSeconds
304
+
305
+ # A String or Regexp describing the prompt issued by the spawned process.
306
+ # You may need to change this if you:
307
+ # * spawn a process other than bash
308
+ # * don't use OS X
309
+ # * customize your shell
310
+ # Do not confuse this attribute with @password_prompt.
311
+ attr_accessor :prompt
312
+
313
+ # A Hash used to specify the prefixes that identify lines in the transcript.
314
+ attr_accessor :prefixes
315
+
316
+ # The exit_status is an integer returned by most recently finished command: 0 is success.
317
+ attr_accessor :exit_status
318
+
319
+ # When set to true, Session attempts to work around some PTY idiosyncrasies
320
+ attr_accessor :pty_workaround
321
+
322
+ # A String or Regex used to decide when an administrative password
323
+ # is required (such as when a script or a command uses sudo).
324
+ # If this is nil, Session will not attempt to detect and answer
325
+ # administative passwords. If a password is actually demanded,
326
+ # your script will hang until the operation times out.
327
+ attr_accessor :password_prompt
328
+
329
+ # The automatic response to @password_prompt
330
+ attr_accessor :password
331
+
332
+ # If true, provides extra debug information at the price of
333
+ # excessive transcript verbosity.
334
+ attr_accessor :give_finishing_notice
335
+
336
+ # If true, prompts are logged like other output
337
+ attr_accessor :log_prompt
338
+
339
+ # @dir_hash lets you assign symbolic names to directories
340
+ # @dir_hash has:
341
+ # * key is a Symbol
342
+ # * value is an absolute path to a directory
343
+ attr_accessor :dir_hash
344
+
345
+ # @url_hash lets you assign symbolic names to URLs
346
+ # @url_hash has:
347
+ # * key is a Symbol
348
+ # * value is a URL
349
+ attr_accessor :url_hash
350
+
351
+ # @dir_overide lets you override the name of directory
352
+ # resulting from decompression of an archive.
353
+ # @dir_overide is a Hash with:
354
+ # * key is a string URL basename without extension
355
+ # * value is a directory name.
356
+ attr_accessor :dir_overide
357
+
358
+ # Session's file operations other than cd are thin wrappers on FileUtil,
359
+ # that make logging consistent, but which do not have time limits.
360
+ # #cd is different because it needs to invoke both "cd" using #command, and the FileUtil
361
+ # version (so that tests like File.exist? and File.directory? work).
362
+ # If we don't assign a time limit to "command cd", the subsequent operation
363
+ # will apply one of it's own. So we need to define a limit for #cd.
364
+ attr_accessor :cd_max_seconds
365
+
366
+ # To accomodate multiple possible outcomes, #expect can take a Hash argument.
367
+ # In many cases, however, a String or Regex good enough to specify the
368
+ # next response from the spawned process. In that case, @pattern_hash_class
369
+ # is instantiated, containing the expected String or Regex.
370
+ attr_accessor :pattern_hash_class
371
+
372
+ # Scripts should express time limits as measurements made under a particular
373
+ # set of conditions. You can then set the @timeout_multiplier to increase or decrease
374
+ # time limits according to the new hardware. The @timeout_offset is a minimal time added
375
+ # to every time limit (after multiplication by @timeout_multiplier)
376
+ attr_accessor :timeout_multiplier
377
+
378
+ # Used to scale timeouts: see @timeout_multiplier
379
+ attr_accessor :timeout_offset
380
+
381
+ # Turning off timestamps is nice to keep regression test results date independent.
382
+ attr_accessor :time_stamp_banners
383
+
384
+ # :stopdoc:
385
+
386
+ # IO that the spawned process writes errors to.
387
+ # Unfortunately, not supported by PTY: not currently used.
388
+ # @error_log_path is used instead
389
+ attr_accessor :stderr
390
+
391
+ # Internal state: a command that has not yet finished
392
+ attr_accessor :current_command
393
+
394
+ # Internal state: used when pty_workaround is true to ensure that a command's
395
+ # output has been flushed.
396
+ attr_accessor :current_command_num
397
+
398
+ # Internal state: if current_silence is true, non-zero exit_status does not raise an exception
399
+ attr_accessor :current_silence
400
+
401
+ # Used when pty_workaround is true
402
+ attr_accessor :sentinal_name
403
+
404
+ # Used when pty_workaround is true
405
+ attr_accessor :sentinal_value
406
+
407
+ # Internal state: used when pty_workaround is true
408
+ attr_accessor :most_recent_input
409
+
410
+ # Internal state: used by "cd '-'" to return to the directory you came from
411
+ attr_accessor :previous_directory
412
+
413
+ # :startdoc:
414
+
415
+ alias time_out default_maxSeconds
416
+ alias time_out= default_maxSeconds=
417
+
418
+ # Spawn a new process, executing the indicated program, and
419
+ # create a new instance that communicates with the process.
420
+ # Use #new instead if you want to adjust the attributes.
421
+ def self.spawn(program)
422
+ inst = self.new
423
+ inst.spawn(program)
424
+ inst
425
+ end
426
+
427
+ # PHB knows the directory structure of Amber projects, so
428
+ # when PHB is available, various temp files can be placed in
429
+ # more appropriate directories.
430
+ def phb_available
431
+ defined?(PHB) && PHB.get(:projectName)
432
+ end
433
+
434
+ # Guesses where to put the time log.
435
+ def default_time_log_path
436
+ return 'timeLog.txt' unless phb_available
437
+ PHB.int(:test_results, 'timeLog.txt')
438
+ end
439
+
440
+ # Guesses where to put the error log
441
+ def default_error_log_path
442
+ return 'errorLog.txt' unless phb_available
443
+ PHB.int(:test_results, 'errorLog.txt')
444
+ end
445
+
446
+ # Guesses where to put the error status collection file.
447
+ def default_exit_status_path
448
+ return 'exitStatus.txt' unless phb_available
449
+ PHB.int(:test_results, 'exitStatus.txt')
450
+ end
451
+
452
+ # #new does not make the instance usable: it needs to #spawn first.
453
+ # After you create an instance you can tweak its attributes before spawning.
454
+ def initialize
455
+ super
456
+ self.default_maxSeconds = 60
457
+ self.prompt=default_prompt
458
+ self.transcript = MultiIO.new(STDOUT)
459
+ self.password_prompt = 'Password:'
460
+ self.time_log_path = default_time_log_path
461
+ self.error_log_path = default_error_log_path
462
+ self.exit_status_path = default_exit_status_path
463
+ self.current_command_num = 0
464
+ self.sentinal_name = 'SENTINAL'
465
+ self.sentinal_value = 'Snark' # It doesn't matter what you use.
466
+ self.dir_hash = Hash.new
467
+ self.pty_workaround = true
468
+ self.give_finishing_notice = false
469
+ self.log_prompt = false
470
+ self.previous_directory = FileUtils.pwd
471
+ self.url_hash = Hash.new
472
+ self.dir_overide = Hash.new
473
+ self.cd_max_seconds = 60 * 3
474
+ self.pattern_hash_class = SequentialHash
475
+ self.timeout_multiplier = 1.25
476
+ self.timeout_offset = 60
477
+ self.time_log_reset = false
478
+ self.time_stamp_banners=true
479
+ # You can install a nil prefix to supress a given type of entry.
480
+ self.prefixes = { :command => "\nc> ", :expect => 'x> ', :respond => 'i> ',
481
+ :out => 'o> ', :note => 'n> ', :match => 'm> ', :error => 'e> ',
482
+ :dir => 'd> ', :missingKey => '?> ', :file_op => 'f> ',
483
+ :spawn => "\ns> "}
484
+ end
485
+
486
+ # Specifies the default value of the prompt attribute.
487
+ # This returns the default pattern that cmd will wait for.
488
+ # This is user dependent. Possible improvements:
489
+ # * include the host name
490
+ # * include the current working directory.
491
+ # Tested only under OS X, where the prompt for root ends with # instead of $
492
+ # The root prompt is used if the script is invoked with sudo
493
+ # TBD: there is probably a better way to get this from the OS, rather than guessing it.
494
+ def self.default_prompt
495
+ source = ENV['USER']+'[$#] '
496
+ Regexp.new(source)
497
+ end
498
+
499
+ # Instance side behavior of class method with same name.
500
+ def default_prompt
501
+ self.class.default_prompt
502
+ end
503
+
504
+ # Ensure that file paths are absolute. This is necessary so that
505
+ # these files can be found after a #cd.
506
+ def normalize_paths
507
+ self.error_log_path = File.expand_path(error_log_path)
508
+ self.exit_status_path = File.expand_path(exit_status_path)
509
+ self.time_log_path = File.expand_path(time_log_path) if time_log_path
510
+ end
511
+
512
+ # Constructs @dir_hash from @url_hash
513
+ def build_default_dir_hash
514
+ url_hash.each {|key, url| dir_hash[key] ||= File.expand_path(dirName(url, true)) }
515
+ end
516
+
517
+ # Empties the time log (if any)
518
+ def truncate_time_log
519
+ return unless time_log_path && time_log_reset
520
+ File.open(time_log_path, 'w') {}
521
+ end
522
+
523
+ # Prepares an instance for operation
524
+ def prepare # :nodoc:
525
+ normalize_paths
526
+ build_default_dir_hash
527
+ truncate_time_log
528
+ get_password if password_prompt && !password
529
+ # Print a session banner only on the time log
530
+ # so temporarily nil out the transcript.
531
+ temp, transcript = transcript, nil
532
+ banner('Starting new session')
533
+ transcript = temp
534
+ return unless pty_workaround
535
+ defineSentinal = "#{sentinal_name}=#{sentinal_value}"
536
+ stdin.puts(defineSentinal)
537
+ stdin.flush
538
+ _flush
539
+ end
540
+
541
+ # Prompts the user for password and saves it in the @passsword attribute,
542
+ # in order to provide it to 'sudo'.
543
+ def get_password
544
+ print "\n"+password_prompt
545
+ STDOUT.flush
546
+ self.password = STDIN.gets.chop
547
+ end
548
+
549
+ # Given an unscaled time limit, scale it to make it appropriate for the current hardware.
550
+ def scaled_seconds(limit)
551
+ timeout_multiplier * limit.to_seconds + timeout_offset
552
+ end
553
+
554
+ # Given a scaled time, return an unscaled time. This is the inverse of #scaled_seconds
555
+ def inverse_scaled_seconds(limit)
556
+ (limit.to_seconds - timeout_offset) / timeout_multiplier
557
+ end
558
+
559
+ # Execute block under a time limit (which gets scaled).
560
+ def limit(measuredSeconds, &block)
561
+ t = scaled_seconds(measuredSeconds)
562
+ Timeout::timeout(t, &block)
563
+ end
564
+
565
+ # Logs execution time of block, if @time_log_path is specified.
566
+ # Without @time_log_path, executes block without timing it. In either case,
567
+ # returns the block result.
568
+ def log_time(description, doLog=true)
569
+ return yield unless time_log_path && doLog
570
+ start = Time.now
571
+ answer = yield
572
+ delta = Time.now - start
573
+ File.open(time_log_path, 'a') {|timeLog|
574
+ timeLog << "\n" << String.from_seconds(delta) << "\t" << description
575
+ }
576
+ answer
577
+ end
578
+
579
+ # Write a notice to @transcript.
580
+ # The string can take optional arguments along the lines of printf.
581
+ def note(string, *args)
582
+ log( :note, string, *args)
583
+ end
584
+
585
+ # Write a string onto the @transcript.
586
+ # The string can take optional arguments along the lines of printf.
587
+ # A prefix that describes the type of string is applied.
588
+ def log(type, string, *args)
589
+ return unless transcript && string
590
+ return if pty_workaround && string.index('2>')
591
+ return if !log_prompt && prompt.to_regexp.match(string)
592
+ return transcript << prefixes[:missingKey] << "Unknown log entry type: #{type.inspect}" unless prefixes.has_key? type
593
+ prefix = prefixes[type]
594
+ return if !prefix # Don't log types with nil prefix
595
+ string = string.strip
596
+ string = string % args unless args.empty?
597
+ self.most_recent_input = string if pty_workaround && type==:respond
598
+ return if pty_workaround && type==:out && most_recent_input==string
599
+ # Do not expect string to have internal delimiters.
600
+ array = string.split("\n")
601
+ string = array.shift
602
+ transcript << prefix << string.dump << "\n" if string
603
+ # print the followup lines (after internal delimiters), indented
604
+ array.each {|more|
605
+ transcript << " " << prefix << more.dump << "\n" if more
606
+ }
607
+ transcript.flush if transcript.respond_to?(:flush)
608
+ end
609
+
610
+ # Spawn a new process, executing the indicated program.
611
+ # Once command has terminated, send, expect, and command will misbehave.
612
+ # You can, of course, spawn again.
613
+ # This has only been tested with bash!
614
+ def spawn(program='bash')
615
+ case @@spawn_method
616
+ when :spawn
617
+ log(:spawn, 'spawn '+program.inspect)
618
+ @stdout, @stdin, @pid = PTY.spawn(program)
619
+ stdin.sync=true
620
+ stdout.sync=true
621
+ log(:spawn, "pid is: #{pid.inspect}")
622
+ when :popen3
623
+ log(:spawn, 'popen3 '+program.inspect)
624
+ @stdin, @stdout, @stderr = Open3.popen3(program)
625
+ stdout.sync=true
626
+ stderr.sync=true
627
+ s = [stdin.sync, stdout.sync, stderr.sync]
628
+ p = [stdin.pid, stdout.pid, stderr.pid]
629
+ log(:spawn, "stdout is a #{stdout.class.name} syncs are: #{s.inspect} pids are: #{p.inspect}")
630
+ end
631
+ prepare
632
+ end
633
+
634
+ # Attempts to kill the pocess with SIGTERM.
635
+ # If the process has not terminated by maxSeconds,
636
+ # follows up with SIGKILL.
637
+ def die(maxSeconds=1)
638
+ # Don't scale any process termination timmeouts.
639
+ t = inverse_scaled_seconds(maxSeconds)
640
+ finish(t)
641
+ raise 'Unable to :die, no pid' if !pid
642
+ success = kill( 'SIGTERM', maxSeconds)
643
+ success || kill('SIGKILL', maxSeconds)
644
+ end
645
+
646
+ # Send a signal to the spawned process.
647
+ # 'SIGTERM' is polite, but not guaranteed to stop the process.
648
+ # 'SIGKILL' is guaranteed to stop the process
649
+ def kill( signal='SIGTERM', maxSeconds=1)
650
+ raise 'Unable to :kill, no pid' if !pid
651
+ Process.kill( signal, pid)
652
+ wait(maxSeconds)
653
+ end
654
+
655
+ # Wait for the spawned process to terminate on its own.
656
+ def wait(maxSeconds=1)
657
+ raise 'Unable to :wait, no pid' if !pid
658
+ # Don't scale any process termination timmeouts.
659
+ Timeout::timeout(maxSeconds.to_seconds) {
660
+ Process.waitpid(pid,0)
661
+ return true
662
+ }
663
+ rescue PTY::ChildExited
664
+ return true
665
+ rescue Errno::ECHILD
666
+ # Error message is 'No child process'
667
+ # I think this (rather than PTY::ChildExited)
668
+ # occurs if the child process died unexpectedly
669
+ return true
670
+ rescue Timeout::Error
671
+ return false
672
+ end
673
+
674
+ # Send a string to the spawned process
675
+ def respond(response)
676
+ log(:respond, response)
677
+ stdin.puts(response)
678
+ stdin.flush
679
+ end
680
+ alias answer respond
681
+ # 'alias send respond' conflicts with Object::send
682
+ alias snd respond
683
+ alias reply respond
684
+ # 'alias puts respond' conflicts with Kernel::puts
685
+
686
+ # Log some output from the spawned process.
687
+ def log_output(line)
688
+ log(:out, line)
689
+ end
690
+
691
+
692
+ # Untimed private method. Use this when the output should be logged.
693
+ # Use instead stdout.expect if the output is uninteresting.
694
+ # If log_output_only is true, the expectation and matches are not logged.
695
+ def _expect(pat, maxSeconds=nil, silent=false, log_output_only=false) # :nodoc:
696
+ pat = pattern_hash_class[ :expected, pat ] unless pat.kind_of?(Hash)
697
+ pat[:password_prompt]=password_prompt if password_prompt
698
+ maxSeconds ||= default_maxSeconds
699
+ log( :expect, pat.inspect) unless log_output_only
700
+ buffer=''
701
+ result = nil
702
+ begin
703
+ # We want to limit the entire expect, rather than start the clock over if we get a password prompt.
704
+ limit(maxSeconds) {
705
+ while true
706
+ buffer = String.new
707
+ result = stdout.multi_expect(pat, scaled_seconds(maxSeconds), buffer, pty_workaround, self)
708
+ log(:match, result[0].inspect+' '+result[1].to_a.inspect) if result && !log_output_only
709
+ # Want to show errors here as well.
710
+ break if !result || result[0]!=:password_prompt
711
+ # Ok, we've been asked for the password. Provide it, and try again.
712
+ # We could be asked multiple times (for example if the command runs
713
+ # runs a script that uses 'sudo' multiple times)
714
+ result = nil
715
+ raise "Password requested, but not available." if !password
716
+ stdin.puts(password+"\n")
717
+ stdin.flush
718
+ log(:note, "Password provided")
719
+ end
720
+ }
721
+ rescue Timeout::Error
722
+ # Handle it below, which also takes care of other cases
723
+ end
724
+ unless silent || ( result && result[0]!=:password_prompt)
725
+ errors = File.exist?(error_log_path) ? IO.read(error_log_path) : ''
726
+ message = "#{self.class.name}.expect timed out under current_command '#{current_command.inspect}' while waiting for #{pat.inspect}, buffer is: <<#{buffer.dump}>>, errors are: <<#{errors}>>"
727
+ raise(Timeout::Error, message)
728
+ end
729
+ result
730
+ end
731
+ private :_expect
732
+
733
+ # Expect the spawned process to produce some output.
734
+ # pat can be a String, a Regexp, or a Hash (keys identify patterns, values are Strings or Regexp).
735
+ # Raises an exception if no match occurs, unless silentlyContinueAfterTimeout is true.
736
+ # If a match does occur, returns an Array:
737
+ # result[0]=key for pattern that matched (:expected if pat is a String or Regexp)
738
+ # result[1]=match result
739
+ def expect(pat, maxSeconds=60, silentlyContinueAfterTimeout=false)
740
+ log_time("expect #{pat.inspect}") { _expect(pat, maxSeconds, silentlyContinueAfterTimeout) }
741
+ end
742
+
743
+
744
+ # Untimed private method. If simple is true,
745
+ # there is no error log, and finish executes immediately
746
+ def _command(command, maxSeconds=nil, silent=false, simple=false) # :nodoc:
747
+ get_password if password_prompt && !password
748
+ finish(10) # Previous command should be finished already
749
+ FileUtils::rm_rf(error_log_path) if File.exist?(error_log_path)
750
+ self.current_command=command
751
+ self.current_silence = silent
752
+ @current_command_num += 1
753
+ cmd = simple ? command : "#{command} 2>#{error_log_path}\n"
754
+ log(:command, command)
755
+ stdin.puts(cmd)
756
+ stdin.flush
757
+ return finish(10, false, false) if simple
758
+ finish(maxSeconds, true, false) if maxSeconds
759
+ end
760
+ private :_command
761
+
762
+ # Send a string to the spawned process.
763
+ # If maxSeconds is specified, the command will block until finished.
764
+ # If maxSedonds is not specified, or nil, you can :expect and :reply.
765
+ # Each invocation rewrites the error log (which is copied to
766
+ # transcript).
767
+ def command(command, maxSeconds=nil, silentlyIgnoreNonzeroExitStatus=false)
768
+ log_time("command #{command.inspect}") {
769
+ _command(command, maxSeconds, silentlyIgnoreNonzeroExitStatus, false)
770
+ }
771
+ end
772
+ alias cmd command
773
+
774
+ # Finish executing the most recent #command. If this has already been
775
+ # called since the most recent #command started, does nothing.
776
+ # Otherwise it blocks until the shell issues a #prompt. You usually
777
+ # don't need to explicitly call this, because it's done automatically.
778
+ # When a #command has maxSeconds, this is called automatically.
779
+ # When a #command does not have maxSeconds (so that #expect
780
+ # can be called), the subsequent #command will call this before
781
+ # issuing itself to the shell. If you ask the shell to die, it
782
+ # will also call this (in case the previous #command was called
783
+ # without maxSeconds).
784
+ def finish(maxSeconds=60, hasErrLog=true, doTimeLog=true)
785
+ log(:note, 'Nothing to finish ') if give_finishing_notice && !current_command
786
+ return unless current_command
787
+ comment = 'finish '+current_command
788
+ log(:note, comment) if give_finishing_notice
789
+ # _expect instead of stdout.single_expect because the command may
790
+ # have output we should display
791
+ log_time(comment, doTimeLog) { _expect(prompt, maxSeconds, false, true) }
792
+ get_exit_status
793
+ _flush
794
+ # Display any errors (expect already displayed any output)
795
+ File.open(error_log_path, 'r') {|errorLog|
796
+ log(:error, errorLog.gets) until errorLog.eof?
797
+ } if hasErrLog && File.exist?(error_log_path)
798
+ raise "Failure: command <<#{current_command}>> returned exit status #{exit_status.to_s}" unless current_silence || exit_status==0
799
+ self.current_command = nil
800
+ # The following line was intended to clean out
801
+ # the PTY.spawn stdout. Certainly it should not hang
802
+ # since IO::read does not block
803
+ # It hangs anyway.
804
+ #stdout.read unless stdout.eof?
805
+ end
806
+
807
+ # Stash the exit status in @exit_status
808
+ def get_exit_status # :nodoc:
809
+ req = "echo $? > #{exit_status_path}"
810
+ stdin.puts(req)
811
+ stdin.flush
812
+ xpct(req, 'Failed to get echo of status write request')
813
+ xpct(prompt, 'Failed to get prompt after status write request')
814
+ # sleep(1) until File.exist?(exit_status_path)
815
+ self.exit_status = IO.read(exit_status_path).to_i
816
+ FileUtils::rm_rf(exit_status_path)
817
+ end
818
+
819
+ # Flush any lingering output
820
+ def _flush # :nodoc:
821
+ return unless pty_workaround
822
+ num = current_command_num.to_s
823
+ req = "echo ${#{sentinal_name}}_#{num}"
824
+ resp = "#{sentinal_value}_#{num}"
825
+ stdin.puts(req)
826
+ stdin.flush
827
+ xpct(req, 'Flush failed to echo stdout')
828
+ xpct(resp, 'Flush failed to respond')
829
+ xpct(prompt, 'Failed to get prompt after flushing stdout')
830
+ end
831
+ private :_flush
832
+
833
+ def xpct(xpected, errStr) # :nodoc:
834
+ buffer = ''
835
+ result = stdout.single_expect(xpected, 10, buffer , pty_workaround)
836
+ raise "#{errStr}, buffer = #{buffer}" unless result
837
+ end
838
+
839
+
840
+ # ###########################
841
+ # Symbolic name resolution (basic)
842
+
843
+ # If input is:
844
+ # * A Symbol, return a URL by lookup in url_hash
845
+ # * A String, return the input
846
+ # * An Array, each element is recursively resolved
847
+ #
848
+ # Raises an exception if a symbolic
849
+ # name does not have a corresponding value, unless silent is true.
850
+ def resolve_url(input, silent=false)
851
+ finish
852
+ answer = nil
853
+ case input
854
+ when Array
855
+ answer = input.collect {|p| resolve_url(p) }
856
+ when Symbol
857
+ answer = url_hash[input]
858
+ raise "Yax::Session::resolve_url can not find a URL to #{input.inspect}." unless answer || silent
859
+ when String
860
+ answer = input
861
+ end
862
+ answer
863
+ end
864
+
865
+ # If input is:
866
+ # * A Symbol, a directory is looked up in dir_hash
867
+ # * A String, it is returned unchanged
868
+ # * An Array, each element is recursively resolved
869
+ #
870
+ # Raises an exception if a symbolic
871
+ # name does not have a corresponding value, unless silent is true.
872
+ def resolve_dir(input, silent=false)
873
+ finish
874
+ answer = nil
875
+ case input
876
+ when Array
877
+ answer = input.collect {|p| resolve_dir(p) }
878
+ when Symbol
879
+ answer = dir_hash[input]
880
+ raise "Yax::Session::resolve_dir can not find a directory corresponding to #{input.inspect}." unless answer || silent
881
+ when String
882
+ answer = input
883
+ end
884
+ answer
885
+ end
886
+
887
+ # If input is:
888
+ # * A Symbol, a file is computed from the corresponding value in url_hash
889
+ # * A String, it is returned unchanged
890
+ # * An Array, each element is recursively resolved
891
+ #
892
+ # Raises an exception if a symbolic
893
+ # name does not have a corresponding value, unless silent is true.
894
+ def resolve_file(input, silent=false)
895
+ finish
896
+ answer = nil
897
+ case input
898
+ when Array
899
+ answer = input.collect {|p| resolve_file(p) }
900
+ when Symbol
901
+ answer = url_hash[input]
902
+ answer = File.basename(answer) if answer
903
+ raise "Yax::Session::resolve_file can not find a directory corresponding to #{input.inspect}." unless answer || silent
904
+ when String
905
+ answer = input
906
+ end
907
+ answer
908
+ end
909
+
910
+ # ###########################
911
+ # Symbolic name resolution (url based)
912
+
913
+ # Given a symoblic name of an entry in url_hash,
914
+ # or an URL that points to a .tgz or .tar.gz file,
915
+ # return the relative directory expected to result from untgz,
916
+ # by removing the extension and the path leading up to the file name.
917
+ # Return this result, unless it is a key into dir_override, in which
918
+ # case the corresponding value is returned.
919
+ # If an overide is specified in dir_overide, that is used instead.
920
+ def dirName(url, silent=false)
921
+ url = resolve_url(url, silent)
922
+ return nil if silent && !url
923
+ ext = archive_type(url)[0]
924
+ f = ext ? File.basename(url, ext) : url
925
+ dir_overide[f] || f
926
+ end
927
+
928
+ # Given a symbolic name of an entry in url_hash, or
929
+ # an URL that refers to a file, return the
930
+ # relative name of the file, by removing the path leading
931
+ # up to the file name, but keeping the extension.
932
+ def fileName(url, silent=false)
933
+ url = resolve_url(url, silent)
934
+ return nil if silent && !url
935
+ File.basename(url)
936
+ end
937
+
938
+ # ###########################
939
+ # Symbolic name resolution (more)
940
+
941
+ # Similar to resolve_dir, but further processes String
942
+ # inputs to remove the extension and the leading path.
943
+ # Similar to dirName, but accepts Array inputs, and
944
+ # attempts to look up dir in dir_hash.
945
+ # Returns a local directory.
946
+ def d(input, silent=false)
947
+ answer = resolve_dir(input, silent)
948
+ return dirName(answer, silent) unless answer.kind_of?(Array)
949
+ answer.collect {|dir| dirName(dir, silent) }
950
+ end
951
+
952
+ # Similar to resolve_file, but further processes String
953
+ # inputs to remove the leading path.
954
+ # Similar to fileName, but accepts Array inputs.
955
+ # Returns a local directory.
956
+ def f(input, silent=false)
957
+ return fileName(input, silent) unless input.kind_of?(Array)
958
+ input.collect {|file| fileName(file, silent) }
959
+ end
960
+
961
+
962
+ # ###########################
963
+ # Symbolic name resolution (utility)
964
+
965
+ # Examines the extension of a file name or URL to determine the
966
+ # type of archive. Returns a two element array containing:
967
+ # result[0] = the extension
968
+ # result[1] = gnutar decompression option
969
+ def archive_type(fName)
970
+ case fName
971
+ when /\.tar\.gz$/
972
+ return '.tar.gz', 'z'
973
+ when /\.tgz$/
974
+ return '.tgz', 'z'
975
+ when /\.tar\.bz2$/
976
+ return '.tar.bz2', 'j'
977
+ when /\.tar$/
978
+ return '.tar', ''
979
+ end
980
+ [ nil, nil]
981
+ end
982
+
983
+ # ###########################
984
+ # File system utilities.
985
+ # These behaviors are a little smarter than the ones in FileUtils which they wrap.
986
+ # In general, file names and paths are resolved by resolve_dir if a Symbol is provided.
987
+ # In some cases the behavior depends on the data: for example cp will execute cp_r
988
+ # if appropriate. Also the logging is consistent with other Yax log entries.
989
+
990
+ # Associate a directory with nameSymbol, so that you can
991
+ # go there using 'cd nameSymbol'. Does not change the current directory.
992
+ # directory can be absolute or relative. If it is relative, it is made absolute
993
+ # based on the current working directory.
994
+ def cd!(nameSymbol, directory=FileUtils::pwd)
995
+ raise "cd! needs to have a Symbol as an argument" unless nameSymbol.instance_of?(Symbol)
996
+ directory = File.expand_path(directory)
997
+ # raise "cd! has been given an invalid directory: #{directory}" unless File.exist?(directory)
998
+ dir_hash[nameSymbol]=directory
999
+ end
1000
+
1001
+ def _cd(nameSymbol, *more) # :nodoc:
1002
+ goBack = '-'==nameSymbol
1003
+ path = goBack ? previous_directory : resolve_dir(nameSymbol)
1004
+ raise "Request to cd to unknown directory: #{nameSymbol.inspect}" unless path
1005
+ path = File.expand_path(path)
1006
+ path = File.join(path, *more) unless more.empty?
1007
+ raise "Invalid path specified: #{path}" unless File.exist? path
1008
+ self.previous_directory=FileUtils::pwd
1009
+ command( "cd #{path}", cd_max_seconds )
1010
+ FileUtils::cd path
1011
+ log(:dir, path)
1012
+ end
1013
+ private :_cd
1014
+
1015
+ # Set the working directory (both the shell's via :command, and Ruby via FileUtils::cd)
1016
+ # to a specified value. Several types of argument are acceptable:
1017
+ # * '-' which returns to the previous directory
1018
+ # * A String path (relative or absolute)
1019
+ # * A Symbol previously associated with a path in dir_hash
1020
+ #
1021
+ # dir_hash is setup by
1022
+ # * cd! which you invoke at your discretion
1023
+ # * build_default_dir_hash, which is called by :prepare during :spawn.
1024
+ # This method adds to dir_hash keys that are present in url_hash,
1025
+ # but missing from dir_hash. The values are derived by dirName.
1026
+ #
1027
+ # This method can be fed an optional block. The new directory is set
1028
+ # before the block executes, and the original directory is restored
1029
+ # after the block has finished. The previous_directory is also restored,
1030
+ # so that a subsequent "cd '-'" takes you to the directory before this
1031
+ # method executed (instead of the one specified here).
1032
+ #
1033
+ # Unlike the bash command, this takes any number of arguments which it
1034
+ # assembles with the appropriate separator into a single path. Also
1035
+ # unlike bash, it raises an exception if the argument does not
1036
+ # correspond to a reachable directory.
1037
+ def cd(nameSymbol, *more)
1038
+ return _cd(nameSymbol, *more) unless block_given?
1039
+ prev = previous_directory
1040
+ curr = FileUtils::pwd
1041
+ _cd(nameSymbol, *more)
1042
+ begin
1043
+ yield
1044
+ ensure
1045
+ _cd(curr)
1046
+ self.previous_directory=prev
1047
+ end
1048
+ end
1049
+
1050
+ # Change permissions
1051
+ def chmod(mode, list, *args)
1052
+ list = resolve_dir(list)
1053
+ log(:file_op, "chmod #{mode} #{list.inspect}")
1054
+ FileUtils::chmod(mode, list, *args)
1055
+ end
1056
+
1057
+ # Copies a file(s) src to dest. If dest is a directory, copies src to dest/src.
1058
+ # src can be a list or a directory: unlike bash, you don't need a -r for
1059
+ # directories. Also unlike bash (but like FileUtils::cp_r) you use '.' as a
1060
+ # wildcard instead of '*' when you want to copy all files in directory.
1061
+ def cp(src, dest, *args)
1062
+ src = resolve_dir(src)
1063
+ dest = resolve_dir(dest)
1064
+ log( :file_op, "cp #{src.inspect} #{dest.inspect}")
1065
+ #File.directory? ? FileUtils::cp_r(src, dest, *args) : FileUtils::cp(src, dest, *args)
1066
+ FileUtils::cp_r(src, dest, *args)
1067
+ end
1068
+
1069
+ # Creates a hard link new which points to old. If new already exists
1070
+ # and it is a directory, creates a symbolic link new/old
1071
+ def ln(old, new, *args)
1072
+ old = resolve_dir(old)
1073
+ new = resolve_dir(new)
1074
+ log( :file_op, "ln #{old.inspect} #{new.inspect}")
1075
+ FileUtils::ln(old, new, *args)
1076
+ end
1077
+
1078
+ # Creates a symbolic link new which points to old. If new already exists
1079
+ # and it is a directory, creates a symbolic link new/old. If new already
1080
+ # exists and is a link, replaces it.
1081
+ def ln_sf(old, new, *args)
1082
+ old = resolve_dir(old)
1083
+ new = resolve_dir(new)
1084
+ log( :file_op, "ln_s #{old.inspect} #{new.inspect}")
1085
+ FileUtils::ln_sf(old, new, *args)
1086
+ end
1087
+
1088
+ # Creates one or more directories (including any necessary parent directories)
1089
+ def mkdir(list, *args)
1090
+ list = resolve_dir(list)
1091
+ log( :file_op, "mkdir #{list.inspect}")
1092
+ FileUtils::mkdir_p(list, *args)
1093
+ end
1094
+
1095
+ # Moves file(s) src to dest. If file and dest exist on the different disk partition, the file is copied instead.
1096
+ # Unlike cp, the src argument does not accept '.' wilcards (or '*' for that matter).
1097
+ def mv(src, dest, *args)
1098
+ src = resolve_dir(src)
1099
+ dest = resolve_dir(dest)
1100
+ log( :file_op, "mv #{src.inspect} #{dest.inspect}")
1101
+ FileUtils::mv(src, dest, *args)
1102
+ end
1103
+
1104
+ # Remove files and directories named in list.
1105
+ def rm_rf(list, *args)
1106
+ list = resolve_dir(list)
1107
+ log( :file_op, "rm_rf #{list.inspect}")
1108
+ FileUtils::rm_rf(list, *args)
1109
+ end
1110
+
1111
+ # Return a Boolean, true if files have identical contents.
1112
+ def cmp(a, b, silent=true)
1113
+ a = resolve_file(a)
1114
+ a = resolve_file(b)
1115
+ log( :file_op, "cmp #{a.inspect} #{b.inspect}") unless silent
1116
+ FileUtils::cmp(a, b)
1117
+ end
1118
+
1119
+ # Load the input file into memory, make the specified changes, and
1120
+ # write the modified file to output. If output is nil, replace the
1121
+ # input file. Changes is a hash: keys are String or Regexp patterns to be globally
1122
+ # replaced, values are String replacement values.
1123
+ def mangle(input, changes, output=nil, silent=false)
1124
+ input = resolve_file(input)
1125
+ output = output ? resolve_file(output) : input
1126
+ log( :file_op, "mangling #{input} into #{output}")
1127
+ cache = IO.read( input )
1128
+ changes.each {|pat, replacement|
1129
+ modified = cache.gsub!(pat.to_regexp, replacement)
1130
+ raise "Failed to make change #{pat.inspect}" unless modified || silent
1131
+ }
1132
+ File.open( output, 'w') {|file| file << cache }
1133
+ end
1134
+
1135
+ # ###########################
1136
+ # Additional convenience methods
1137
+
1138
+ # Display a banner on both the console and the transcript.
1139
+ # The banner contains the specified text. It may also have
1140
+ # a timestamp (if @time_stamp_banners is true).
1141
+ def banner(what)
1142
+ finish
1143
+ div="\n# =========================================================\n"
1144
+ bnr = "\n\n" << div << "# " << what
1145
+ bnr << ' ' << Time.now.to_s if time_stamp_banners
1146
+ bnr << div
1147
+ transcript << bnr if transcript
1148
+ transcript.flush if transcript.respond_to? :flush
1149
+ File.open(time_log_path, 'a') {|timeLog| timeLog << bnr } if time_log_path
1150
+ end
1151
+
1152
+ # Decompress the archive specifed by the given URL or symbolic name.
1153
+ # Does not download the archive, just decompresses it.
1154
+ # Can handle: .tgz, .tar.gz, .tar.bz2
1155
+ def untgz(url, maxSeconds=60*5, silent=false)
1156
+ dir = d(url)
1157
+ log( :file_op, "untgz expects to create directory '#{dir}'")
1158
+ rm_rf(dir) if File.exist?(dir)
1159
+ raise "Failed to remove '#{dir}'." if File.exist?(dir)
1160
+ # Some packages did not install correctly when tar was used,
1161
+ # but did install correctly when gnutar was used.
1162
+ file = f(url)
1163
+ raise "utgz can not find archive: #{file}" unless File.exist?(file)
1164
+ # select a decompress option: z=gzip, j=bzip2
1165
+ opt = archive_type(file)[1]
1166
+ cmd( "gnutar x#{opt}f #{file}", maxSeconds )
1167
+ raise "utgz did not obtain the expected directory" unless File.exist?(dir) || silent
1168
+ end
1169
+
1170
+ # Compress the specified directory into an archive.
1171
+ # The extension determines the compression type, and is used to derive the name of the resulting file.
1172
+ # Instead of just specifying the extension, you can pass an
1173
+ # entire sample filename to extension, and this method will extract
1174
+ # the file name suffix and the compression type to use (throwing away the base name).
1175
+ def tgz(dir, extension='.tgz', maxSeconds=60*5)
1176
+ dir = d(dir)
1177
+ t = archive_type(extension)
1178
+ raise 'Invalid extension passed to tgz' unless t[0]
1179
+ file = dir + t[0]
1180
+ log( :file_op, "tgz directory '#{dir}'")
1181
+ rm_rf(file) if File.exist?(file)
1182
+ raise "Failed to remove '#{file}'." if File.exist?(file)
1183
+ cmd( "gnutar c#{t[1]}f #{file} #{dir}", maxSeconds )
1184
+ raise "tgz did not manage to create the expected archive" unless File.exist?(file)
1185
+ end
1186
+
1187
+ # Download the file specified by the given URL or symbolic name
1188
+ # (if it has not already been downloaded). You need to have
1189
+ # curl installed.
1190
+ def curl(url, maxSeconds=60*10)
1191
+ url=resolve_url(url)
1192
+ cmd("curl -O #{url}", maxSeconds) unless File.exist?fileName(url)
1193
+ end
1194
+
1195
+
1196
+ # Sets the named environmental variable for use during the session,
1197
+ # optionally warning the user to make a more permanent setting. If
1198
+ # warn is false, the user is not asked to make any changes. This
1199
+ # is useful for settings that are necessary only for compilation.
1200
+ #
1201
+ # If forceValue is false, any previously value is acceptable, so an
1202
+ # existing value is not changed. In this case the user is not asked to do
1203
+ # anything unless the value is undefined.
1204
+ #
1205
+ # If forceValue is true, only the provided value is acceptable. The
1206
+ # environmental variable will be changed if necessary. If it is changed,
1207
+ # and warning==true, the user is advised change the value.
1208
+ #
1209
+ # If value is nil, the environmental variable is unset.
1210
+ # In this case forceValue and warning have no effect.
1211
+ #
1212
+ # Returns the final value of the named environmental variable.
1213
+ # (an empty String if the value is unset).
1214
+ #
1215
+ # Note that OS X is smart enough to give you access to these
1216
+ # when you sudo. You can readily prove this by executing
1217
+ # in a bash shell 'export AnswerToEverything=42' and
1218
+ # then executing 'sudo echo $AnswerToEverything'
1219
+ def env(name, value, forceValue=false, warning=true)
1220
+ unless value
1221
+ cmd("unset #{name}", 10)
1222
+ return ''
1223
+ end
1224
+ change = forceValue && value!=ENV[name]
1225
+ if !ENV[name] || change
1226
+ defn = "export #{name}=#{value}"
1227
+ # We need to execute a command in the shell because
1228
+ # this doesn't affect the shell: # ENV[name]=value
1229
+ cmd(defn, 10)
1230
+ use = ENV[name] ? 'change' : 'define'
1231
+ warn "Need to #{use} environmental variable: 'export #{name}=#{value}'" if warning
1232
+ end
1233
+ ENV[name]
1234
+ end
1235
+
1236
+ # Sets the named environmental path variable to include value (for use
1237
+ # during the session), optionally warning the user to make a more
1238
+ # permanent setting.
1239
+ #
1240
+ # If the path is not defined at all, this will define it. If the path
1241
+ # is defined, and contains value, nothing happens. If the path is defined
1242
+ # but does not contain value,this prefixes value onto the path.
1243
+ def env_path(name, value, warning=true, sep=':')
1244
+ value = ENV[name]
1245
+ return env(name, value, true, warning) if !value
1246
+ pat = Regexp.new("(:|^)#{value}(:|$)")
1247
+ return value if pat.match(value)
1248
+ warn "\Need to add #{value} to $#{name}." if warning
1249
+ defn = "export #{name}=#{value}#{sep}#{name}"
1250
+ cmd(defn, 10)
1251
+ ENV[name]
1252
+ end
1253
+
1254
+ end # Session
1255
+
1256
+ end # Yax
1257
+
1258
+
1259
+
1260
+ class Module
1261
+ # Given an arbitrary number of arguments which are symbols corresponding
1262
+ # to Yax::Session instance method names, generates a module method that invokes the behavior
1263
+ # on an instance returned by the #yax module method.
1264
+ def yaxify(*method_name_symbols)
1265
+ forward('yax', Yax::Session, *method_name_symbols)
1266
+ #method_name_symbols.each { |method_sym|
1267
+ # raise "Method <#{method_sym}> must be defined before it is yaxified." unless Yax::Session.method_defined?(method_sym)
1268
+ # command = "def #{method_sym}(*args, &block); yax.#{method_sym}(*args, &block); end"
1269
+ # module_eval(command)
1270
+ # }
1271
+ end
1272
+ end # Module
1273
+
1274
+
1275
+ # #############################################################
1276
+ # Make most behaviors available as module messages
1277
+ # These methods invoke the behavior on a default :yax instance.
1278
+ # The default is not a singleton: you can make more instances
1279
+ # if it suits you.
1280
+ # #############################################################
1281
+
1282
+ =begin rdoc
1283
+ = Overview
1284
+
1285
+ In addition to providing a namespace for Yax::Session, the Yax Module
1286
+ offers singleton-like reflections of Session instance methods,
1287
+ implemented by Module#yaxify. If these methods don't conflict
1288
+ with others, you can "include Yax" to access them.
1289
+
1290
+ These methods are not shown below under "Public Instance methods"
1291
+ because they are constructed by meta-programming. They are not
1292
+ visible to rdoc. If you need some extra Session behaviors, you
1293
+ can easily add them using Module#yaxify.
1294
+
1295
+ == Reflected Methods
1296
+
1297
+ === Reflected Interactions
1298
+
1299
+ * Session#command, Session#cmd
1300
+ * Session#expect
1301
+ * Session#respond, Session#answer, Session#snd, Session#reply
1302
+ * Session#finish
1303
+ * Session#die
1304
+
1305
+ === Reflected Name Resolution & Management
1306
+
1307
+ * Session#resolve_url, Session#resolve_dir, Session#resolve_file
1308
+ * Session#dirName, Session#fileName
1309
+ * Session#d, Session#f
1310
+ * Session#cd!
1311
+
1312
+ === Reflected File Operations
1313
+
1314
+ * Session#mkdir, Session#chmod
1315
+ * Session#cd
1316
+ * Session#mv, Session#rm_rf, Session#cp
1317
+ * Session#ln, Session#ln_sf
1318
+ * Session#cmp
1319
+
1320
+ === Reflected Other Helpers
1321
+
1322
+ * Session#mangle
1323
+ * Session#curl
1324
+ * Session#untgz, Session#tgz
1325
+ * Session#env, Session#env_path
1326
+
1327
+ === Reflected Output Methods
1328
+
1329
+ * Session#note, Session#banner
1330
+
1331
+ =end
1332
+ module Yax
1333
+
1334
+
1335
+ # Returns a default instance, which is created on the first invocation.
1336
+ # This instance is stored in $YAX.
1337
+ def yax(program='bash', klass=Yax::Session)
1338
+ return $YAX if $YAX
1339
+ $YAX = program ? klass.spawn(program) : klass.new
1340
+ $YAX
1341
+ end
1342
+
1343
+ # #############################################################
1344
+ # Module versions of instance behaviors
1345
+
1346
+
1347
+ yaxify :command, :cmd
1348
+ yaxify :expect
1349
+ yaxify :respond, :answer, :snd, :reply
1350
+ yaxify :finish
1351
+ yaxify :die
1352
+
1353
+
1354
+ yaxify :truncate_time_log
1355
+ yaxify :note, :banner
1356
+
1357
+ yaxify :resolve_url, :resolve_dir, :resolve_file
1358
+ yaxify :dirName, :fileName, :d, :f
1359
+ yaxify :cd!, :cd
1360
+ yaxify :chmod, :cp, :ln, :ln_sf, :mkdir, :mv, :rm_rf, :cmp
1361
+
1362
+ yaxify :mangle
1363
+ yaxify :curl, :untgz, :tgz, :env, :env_path
1364
+
1365
+
1366
+ end # Yax
1367
+