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