yax 0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+