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.
- data/yax-0.1/docs/classes/IO.html +251 -0
- data/yax-0.1/docs/classes/IO.src/M000003.html +46 -0
- data/yax-0.1/docs/classes/IO.src/M000004.html +27 -0
- data/yax-0.1/docs/classes/Module.html +156 -0
- data/yax-0.1/docs/classes/Module.src/M000002.html +23 -0
- data/yax-0.1/docs/classes/MultiIO.html +217 -0
- data/yax-0.1/docs/classes/MultiIO.src/M000006.html +18 -0
- data/yax-0.1/docs/classes/MultiIO.src/M000007.html +19 -0
- data/yax-0.1/docs/classes/MultiIO.src/M000008.html +20 -0
- data/yax-0.1/docs/classes/Numeric.html +155 -0
- data/yax-0.1/docs/classes/Numeric.src/M000005.html +16 -0
- data/yax-0.1/docs/classes/Regexp.html +153 -0
- data/yax-0.1/docs/classes/Regexp.src/M000001.html +16 -0
- data/yax-0.1/docs/classes/String.html +270 -0
- data/yax-0.1/docs/classes/String.src/M000009.html +18 -0
- data/yax-0.1/docs/classes/String.src/M000010.html +16 -0
- data/yax-0.1/docs/classes/String.src/M000011.html +22 -0
- data/yax-0.1/docs/classes/String.src/M000012.html +27 -0
- data/yax-0.1/docs/classes/String.src/M000013.html +21 -0
- data/yax-0.1/docs/classes/String.src/M000014.html +20 -0
- data/yax-0.1/docs/classes/Yax.html +256 -0
- data/yax-0.1/docs/classes/Yax.src/M000015.html +20 -0
- data/yax-0.1/docs/classes/Yax/Session.html +1661 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000016.html +20 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000017.html +18 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000018.html +19 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000019.html +19 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000020.html +19 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000021.html +46 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000022.html +19 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000023.html +18 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000024.html +20 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000025.html +18 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000026.html +19 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000027.html +20 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000028.html +18 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000029.html +18 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000030.html +19 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000031.html +25 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000032.html +18 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000033.html +36 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000034.html +34 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000035.html +23 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000036.html +20 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000037.html +32 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000038.html +20 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000042.html +18 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000043.html +18 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000044.html +20 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000046.html +37 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000047.html +29 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000048.html +29 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000049.html +30 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000050.html +22 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000051.html +20 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000052.html +20 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000053.html +19 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000054.html +28 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000055.html +21 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000056.html +27 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000057.html +20 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000058.html +22 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000059.html +21 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000060.html +21 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000061.html +20 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000062.html +21 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000063.html +20 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000064.html +21 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000065.html +26 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000066.html +25 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000067.html +29 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000068.html +26 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000069.html +19 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000070.html +31 -0
- data/yax-0.1/docs/classes/Yax/Session.src/M000071.html +25 -0
- data/yax-0.1/docs/created.rid +1 -0
- data/yax-0.1/docs/dot/f_0.dot +14 -0
- data/yax-0.1/docs/dot/f_0.png +0 -0
- data/yax-0.1/docs/dot/f_1.dot +14 -0
- data/yax-0.1/docs/dot/f_1.png +0 -0
- data/yax-0.1/docs/dot/f_2.dot +14 -0
- data/yax-0.1/docs/dot/f_2.png +0 -0
- data/yax-0.1/docs/dot/f_3.dot +93 -0
- data/yax-0.1/docs/dot/f_3.png +0 -0
- data/yax-0.1/docs/dot/m_3_0.dot +39 -0
- data/yax-0.1/docs/dot/m_3_0.png +0 -0
- data/yax-0.1/docs/files/License_txt.html +124 -0
- data/yax-0.1/docs/files/ReadMe_Amber_txt.html +489 -0
- data/yax-0.1/docs/files/ReadMe_txt.html +444 -0
- data/yax-0.1/docs/files/nist/yax_rb.html +138 -0
- data/yax-0.1/docs/fr_class_index.html +34 -0
- data/yax-0.1/docs/fr_file_index.html +30 -0
- data/yax-0.1/docs/fr_method_index.html +97 -0
- data/yax-0.1/docs/index.html +24 -0
- data/yax-0.1/docs/rdoc-style.css +208 -0
- data/yax-0.1/lib/nist/yax.rb +1367 -0
- data/yax-0.1/tests/test_yax.rb +190 -0
- 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
|
+
|