ymdp 0.0.7 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. data/VERSION +1 -1
  2. data/bin/ymdp +51 -0
  3. data/lib/new_application/.base +11 -0
  4. data/lib/new_application/Gemfile +12 -0
  5. data/lib/new_application/Rakefile +3 -0
  6. data/lib/new_application/app/.gitignore +1 -0
  7. data/lib/new_application/app/assets/images/lightbox/lightbox_bg.png +0 -0
  8. data/lib/new_application/app/assets/javascripts/OpenMailIntl.js +291 -0
  9. data/lib/new_application/app/assets/javascripts/controls.js +965 -0
  10. data/lib/new_application/app/assets/javascripts/date.js +104 -0
  11. data/lib/new_application/app/assets/javascripts/dragdrop.js +974 -0
  12. data/lib/new_application/app/assets/javascripts/effects.js +1123 -0
  13. data/lib/new_application/app/assets/javascripts/lowpro.js +320 -0
  14. data/lib/new_application/app/assets/javascripts/prototype.js +4874 -0
  15. data/lib/new_application/app/assets/javascripts/scriptaculous.js +68 -0
  16. data/lib/new_application/app/assets/yrb/en-US/application_en-US.pres +7 -0
  17. data/lib/new_application/app/helpers/application_helper.rb +3 -0
  18. data/lib/new_application/app/javascripts/application.js +580 -0
  19. data/lib/new_application/app/javascripts/debug.js +138 -0
  20. data/lib/new_application/app/javascripts/flash.js +96 -0
  21. data/lib/new_application/app/javascripts/header.js +13 -0
  22. data/lib/new_application/app/javascripts/help.js +76 -0
  23. data/lib/new_application/app/javascripts/i18n.js +235 -0
  24. data/lib/new_application/app/javascripts/launcher.js +159 -0
  25. data/lib/new_application/app/javascripts/logger.js +61 -0
  26. data/lib/new_application/app/javascripts/tag_helper.js +178 -0
  27. data/lib/new_application/app/stylesheets/application.css +0 -0
  28. data/lib/new_application/app/stylesheets/ie.css +0 -0
  29. data/lib/new_application/app/stylesheets/ie6.css +0 -0
  30. data/lib/new_application/app/stylesheets/ie7.css +0 -0
  31. data/lib/new_application/app/stylesheets/ie8.css +0 -0
  32. data/lib/new_application/app/stylesheets/lightbox.css +30 -0
  33. data/lib/new_application/app/stylesheets/non_ie.css +0 -0
  34. data/lib/new_application/app/views/layouts/application.html.haml +35 -0
  35. data/lib/new_application/app/views/shared/_error.html.haml +8 -0
  36. data/lib/new_application/app/views/shared/_flash.html.haml +2 -0
  37. data/lib/new_application/app/views/shared/_javascripts.html.haml +22 -0
  38. data/lib/new_application/app/views/shared/_loading.html.haml +13 -0
  39. data/lib/new_application/app/views/shared/_stylesheets.html.haml +23 -0
  40. data/lib/new_application/config/categories.yml +6 -0
  41. data/lib/new_application/config/config.yml.example +30 -0
  42. data/lib/new_application/config/constants.rb +54 -0
  43. data/lib/new_application/config/servers.yml.example +9 -0
  44. data/lib/new_application/lib/init.rb +13 -0
  45. data/lib/new_application/lib/tasks/environment.rake +4 -0
  46. data/lib/new_application/lib/tasks/keys.rake +235 -0
  47. data/lib/new_application/lib/tasks/setup.rake +13 -0
  48. data/lib/new_application/lib/tasks/ymdp.rake +718 -0
  49. data/lib/new_application/script/build +9 -0
  50. data/lib/new_application/script/config +13 -0
  51. data/lib/new_application/script/destroy +18 -0
  52. data/lib/new_application/script/generate +4 -0
  53. data/lib/new_application/script/gitrm +17 -0
  54. data/lib/new_application/script/growl +6 -0
  55. data/lib/new_application/script/images +48 -0
  56. data/lib/new_application/script/jslint.js +5072 -0
  57. data/lib/new_application/script/langs +31 -0
  58. data/lib/new_application/script/translate +5 -0
  59. data/lib/new_application/script/ymdt +1895 -0
  60. data/lib/new_application/script/ymdt.old +1890 -0
  61. data/lib/new_application/script/yuicompressor-2.4.2.jar +0 -0
  62. data/lib/new_application/ymdp +8 -0
  63. data/ymdp.gemspec +64 -1
  64. metadata +65 -4
@@ -0,0 +1,1895 @@
1
+ #!/usr/bin/env php
2
+ <?php
3
+
4
+
5
+
6
+
7
+ //YMDT::fixup helper
8
+ //Replaces all asset URLs in a file with the current versions.
9
+ class AssetURL_Fixer
10
+ {
11
+ function __construct($mapAssetURLsByPath)
12
+ {
13
+ $this->mapAssetURLsByPath = $mapAssetURLsByPath;
14
+ $this->changedFilePaths = array();
15
+
16
+ //horrible hack to reverse engineer the 'appid_version' part of
17
+ //asset URL for use when run() can't find full URL.
18
+ $this->urlPrefix = null;
19
+ foreach($this->mapAssetURLsByPath as $path=>$url){
20
+ $urlParts = explode('/', $url);
21
+ $this->urlPrefix = '/om/assets/' . $urlParts[3] . '/';
22
+ break;
23
+ }
24
+ }
25
+
26
+ function run($filepath)
27
+ {
28
+ $contents = file_get_contents($filepath);
29
+ verify($contents !== FALSE,
30
+ "Couldn't read " . $filepath);
31
+ echo 'Fixing up ' . $filepath . ":\n";
32
+ //Asset part of URL terminates in whitespace, ', ", ), ?, or &
33
+ $results = preg_replace_callback('|/om/assets/.+?_\d+?/([^\'\\s">\)&\?]+)|',
34
+ array($this, 'callback'),
35
+ $contents);
36
+ verify($results !== NULL, "Couldn't do fixup of " . $filepath);
37
+ if($results != $contents){
38
+ verify(file_put_contents($filepath, $results) !== FALSE);
39
+ $this->changedFilePaths[] = $filepath;
40
+ }
41
+ else
42
+ echo "\t(nothing to do)\n";
43
+ }
44
+
45
+ function callback($matches)
46
+ {
47
+ $fileURL = $matches[0];
48
+ $relativePath = $matches[1];
49
+ $key = "assets/$relativePath";
50
+
51
+ /// Until we're willing to restrict partners from using programatically-constructed
52
+ /// asset URLs (like pingg does for colored dot icons), can't make whole-URL validation
53
+ /// failures an error, just a warning. For these, we'll stick to just fixing up the URL
54
+ /// prefix and hope that it's referencing a legal asset.
55
+
56
+ if(array_key_exists($key, $this->mapAssetURLsByPath)){
57
+ $replacement = $this->mapAssetURLsByPath[$key];
58
+ }
59
+ else if($this->urlPrefix){
60
+ echo "\t*Warning* can't find correct URL for " . $fileURL . "\n" .
61
+ "\t\tMight not be a legal asset, will work off URL prefix\n";
62
+ verify(count($this->mapAssetURLsByPath),
63
+ "\t\tCan't even do that, app has no assets.");
64
+ $replacement = preg_replace('|/om/assets/.+?_.+?/|', $this->urlPrefix, $fileURL);
65
+ // echo "prefix is " . $this->urlPrefix . ",fileURL $fileURL , replacement $replacement\n";
66
+ }
67
+ else{
68
+ echo "\t*Warning* can't find correct URL for " . $fileURL . "\n" .
69
+ "\tThis app has no assets!";
70
+ $replacement = $fileURL;
71
+ }
72
+
73
+ if($replacement !== $fileURL){
74
+ echo "\treplacing " . $fileURL . "\n";
75
+ echo "\t---> $replacement \n";
76
+ return $replacement;
77
+ }
78
+ return $fileURL;
79
+ }
80
+ }
81
+
82
+
83
+
84
+ //File-system utility functions. Mostly for dealing with ymdt local app working
85
+ //directories.
86
+
87
+ class FileSys
88
+ {
89
+ const APPID_FNAME = '.appid';
90
+
91
+ //just like realpath, but no backslashes, not even on Windows
92
+ static function realpath($path)
93
+ {
94
+ return str_replace('\\', '/', realpath($path));
95
+ }
96
+
97
+ //$paths is an array of (possibly relative) paths / patterns.
98
+ //Expand directories to their file paths.
99
+ //Allows globs.
100
+ //All paths will be absolute.
101
+ //Ignores root readme.txt
102
+ static function expandPaths($paths)
103
+ {
104
+ $more_paths = array();
105
+ foreach($paths as $ndx => $path){
106
+ if(!is_dir($path))
107
+ continue;
108
+ unset($paths[$ndx]);
109
+ $expanded = glob(FileSys::realpath($path) . '/*', GLOB_MARK);
110
+ if(!empty($expanded))
111
+ $more_paths = array_merge($more_paths, $expanded);
112
+ }
113
+
114
+ if(empty($more_paths))
115
+ return array_map(array('FileSys', 'realpath'), $paths);
116
+
117
+ return self::expandPaths(array_merge($paths, $more_paths));
118
+ }
119
+
120
+ static function filterMetaFiles($paths, $basepath)
121
+ {
122
+ $newpaths = array();
123
+ $metas = array($basepath.'/'.self::APPID_FNAME,
124
+ $basepath.'/readme.txt');
125
+
126
+ foreach($paths as $p){
127
+ if(in_array($p, $metas))
128
+ continue;
129
+ if($p[0] == '.')
130
+ continue;
131
+ if($p[strlen($p)-1] == '~')
132
+ continue; //emacs backup files
133
+ $newpaths[] = $p;
134
+ }
135
+
136
+ return $newpaths;
137
+ }
138
+
139
+ static function filterNonTextPaths($paths)
140
+ {
141
+ $txt_extensions = array('htm', 'html', 'js', 'css', 'txt');
142
+ $results = array();
143
+ foreach($paths as $p){
144
+ if(in_array(pathinfo($p, PATHINFO_EXTENSION), $txt_extensions))
145
+ $results[] = $p;
146
+ }
147
+ return $results;
148
+ }
149
+
150
+ static function layout($appdir)
151
+ {
152
+ global $HELP_README;
153
+
154
+ $subdirs = array($appdir.'/views', $appdir.'/assets');
155
+ foreach($subdirs as $s){
156
+ verify(file_exists($s) || self::mkdir($s));
157
+ }
158
+
159
+ $readme = $appdir.'/readme.txt';
160
+ verify(file_put_contents($readme, $HELP_README),
161
+ "Couldn't write $readme");
162
+ }
163
+
164
+ static function mkdir($path)
165
+ {
166
+ if(is_dir($path))
167
+ return;
168
+ return mkdir($path, 0755, true);
169
+ }
170
+
171
+ static function modTime($path)
172
+ {
173
+ $stat = stat($path);
174
+ return $stat['mtime'];
175
+ }
176
+
177
+ static function nameFromConfFile($appdir)
178
+ {
179
+ $confFname = $appdir . '/config.xml';
180
+ $xml = simplexml_load_file($confFname);
181
+ verify(is_a($xml, 'SimpleXMLElement'),
182
+ "Couldn't read XML conf from $confFname");
183
+ return $xml->name;
184
+ }
185
+
186
+ //returns array(appid, basepath, subpath)
187
+ //basepath is absolute path to app root, i.e. the dir that contains .appid
188
+ //subpath is the path (file/dir/pattern) relative to basepath
189
+ //filename is the filename (or terminating glob / null if dir)
190
+ function parsePath($path)
191
+ {
192
+ $newpath = FileSys::realpath($path);
193
+ if(!$newpath){
194
+ $newpath = FileSys::realpath(dirname($path)) . '/' . basename($path);
195
+ //really only support globs in filenames, no higher up the path
196
+ verify($newpath, "unsupported glob in dirname $path ?");
197
+ }
198
+ $path = $newpath;
199
+
200
+ //Keep on going up the path until we find the magic .appid file
201
+ $base = $path;
202
+ if(!is_dir($base))
203
+ $base = dirname($base);
204
+
205
+ while(true){
206
+ $appid_path = $base . '/' . self::APPID_FNAME;
207
+
208
+ if(file_exists($appid_path)){
209
+ $appid = file_get_contents($appid_path);
210
+ verify($appid != '', 'Empty .appid file?');
211
+ $subpath = substr($path, strlen($base)+1);
212
+
213
+ return array($appid, $base, $subpath);
214
+ }
215
+ if($base == '.' || $base == '/' || $base == '')
216
+ break;
217
+ $base = dirname($base);
218
+ }
219
+ verify(false, "Couldn't find .appid along $path");
220
+ }
221
+ };
222
+
223
+
224
+
225
+ /* vim: set expandtab tabstop=4 shiftwidth=4: */
226
+ // +----------------------------------------------------------------------+
227
+ // | PHP Version 5 |
228
+ // +----------------------------------------------------------------------+
229
+ // | Copyright (c) 1997-2004 The PHP Group |
230
+ // +----------------------------------------------------------------------+
231
+ // | This source file is subject to version 3.0 of the PHP license, |
232
+ // | that is bundled with this package in the file LICENSE, and is |
233
+ // | available through the world-wide-web at the following url: |
234
+ // | http://www.php.net/license/3_0.txt. |
235
+ // | If you did not receive a copy of the PHP license and are unable to |
236
+ // | obtain it through the world-wide-web, please send a note to |
237
+ // | license@php.net so we can mail you a copy immediately. |
238
+ // +----------------------------------------------------------------------+
239
+ // | Author: Andrei Zmievski <andrei@php.net> |
240
+ // +----------------------------------------------------------------------+
241
+ //
242
+ // $Id: Getopt.php,v 1.4 2007/06/12 14:58:56 cellog Exp $
243
+
244
+
245
+ /**
246
+ * Command-line options parsing class.
247
+ *
248
+ * @author Andrei Zmievski <andrei@php.net>
249
+ *
250
+ */
251
+ class Console_Getopt {
252
+ /**
253
+ * Parses the command-line options.
254
+ *
255
+ * The first parameter to this function should be the list of command-line
256
+ * arguments without the leading reference to the running program.
257
+ *
258
+ * The second parameter is a string of allowed short options. Each of the
259
+ * option letters can be followed by a colon ':' to specify that the option
260
+ * requires an argument, or a double colon '::' to specify that the option
261
+ * takes an optional argument.
262
+ *
263
+ * The third argument is an optional array of allowed long options. The
264
+ * leading '--' should not be included in the option name. Options that
265
+ * require an argument should be followed by '=', and options that take an
266
+ * option argument should be followed by '=='.
267
+ *
268
+ * The return value is an array of two elements: the list of parsed
269
+ * options and the list of non-option command-line arguments. Each entry in
270
+ * the list of parsed options is a pair of elements - the first one
271
+ * specifies the option, and the second one specifies the option argument,
272
+ * if there was one.
273
+ *
274
+ * Long and short options can be mixed.
275
+ *
276
+ * Most of the semantics of this function are based on GNU getopt_long().
277
+ *
278
+ * @param array $args an array of command-line arguments
279
+ * @param string $short_options specifies the list of allowed short options
280
+ * @param array $long_options specifies the list of allowed long options
281
+ *
282
+ * @return array two-element array containing the list of parsed options and
283
+ * the non-option arguments
284
+ *
285
+ * @access public
286
+ *
287
+ */
288
+ function getopt2($args, $short_options, $long_options = null)
289
+ {
290
+ return Console_Getopt::doGetopt(2, $args, $short_options, $long_options);
291
+ }
292
+
293
+ /**
294
+ * This function expects $args to start with the script name (POSIX-style).
295
+ * Preserved for backwards compatibility.
296
+ * @see getopt2()
297
+ */
298
+ function getopt($args, $short_options, $long_options = null)
299
+ {
300
+ return Console_Getopt::doGetopt(1, $args, $short_options, $long_options);
301
+ }
302
+
303
+ /**
304
+ * The actual implementation of the argument parsing code.
305
+ */
306
+ function doGetopt($version, $args, $short_options, $long_options = null)
307
+ {
308
+ // in case you pass directly readPHPArgv() as the first arg
309
+ // if (PEAR::isError($args)) {
310
+ // return $args;
311
+ // }
312
+ if (empty($args)) {
313
+ return array(array(), array());
314
+ }
315
+ $opts = array();
316
+ $non_opts = array();
317
+
318
+ settype($args, 'array');
319
+
320
+ if ($long_options) {
321
+ sort($long_options);
322
+ }
323
+
324
+ /*
325
+ * Preserve backwards compatibility with callers that relied on
326
+ * erroneous POSIX fix.
327
+ */
328
+ if ($version < 2) {
329
+ if (isset($args[0]{0}) && $args[0]{0} != '-') {
330
+ array_shift($args);
331
+ }
332
+ }
333
+
334
+ reset($args);
335
+ while (list($i, $arg) = each($args)) {
336
+
337
+ /* The special element '--' means explicit end of
338
+ options. Treat the rest of the arguments as non-options
339
+ and end the loop. */
340
+ if ($arg == '--') {
341
+ $non_opts = array_merge($non_opts, array_slice($args, $i + 1));
342
+ break;
343
+ }
344
+
345
+ if ($arg{0} != '-' || (strlen($arg) > 1 && $arg{1} == '-' && !$long_options)) {
346
+ $non_opts[] = $arg;
347
+ // = array_merge($non_opts, array_slice($args, $i));
348
+ // break;
349
+ } elseif (strlen($arg) > 1 && $arg{1} == '-') {
350
+ $error = Console_Getopt::_parseLongOption(substr($arg, 2), $long_options, $opts, $args);
351
+ // if (PEAR::isError($error))
352
+ // return $error;
353
+ } elseif ($arg == '-') {
354
+ // - is stdin
355
+ $non_opts = array_merge($non_opts, array_slice($args, $i));
356
+ break;
357
+ } else {
358
+ $error = Console_Getopt::_parseShortOption(substr($arg, 1), $short_options, $opts, $args);
359
+ // if (PEAR::isError($error))
360
+ // return $error;
361
+ }
362
+ }
363
+
364
+ return array($opts, $non_opts);
365
+ }
366
+
367
+ /**
368
+ * @access private
369
+ *
370
+ */
371
+ function _parseShortOption($arg, $short_options, &$opts, &$args)
372
+ {
373
+ for ($i = 0; $i < strlen($arg); $i++) {
374
+ $opt = $arg{$i};
375
+ $opt_arg = null;
376
+
377
+ /* Try to find the short option in the specifier string. */
378
+ if (($spec = strstr($short_options, $opt)) === false || $arg{$i} == ':')
379
+ {
380
+ return verify(false, "Console_Getopt: unrecognized option -- $opt");
381
+ }
382
+
383
+ if (strlen($spec) > 1 && $spec{1} == ':') {
384
+ if (strlen($spec) > 2 && $spec{2} == ':') {
385
+ if ($i + 1 < strlen($arg)) {
386
+ /* Option takes an optional argument. Use the remainder of
387
+ the arg string if there is anything left. */
388
+ //$opts[] = array($opt, substr($arg, $i + 1));
389
+ $opts[$opt] = substr($arg, $i + 1);
390
+ break;
391
+ }
392
+ } else {
393
+ /* Option requires an argument. Use the remainder of the arg
394
+ string if there is anything left. */
395
+ if ($i + 1 < strlen($arg)) {
396
+ // $opts[] = array($opt, substr($arg, $i + 1));
397
+ $opts[$opt] = substr($arg, $i + 1);
398
+ break;
399
+ } else if (list(, $opt_arg) = each($args)) {
400
+ /* Else use the next argument. */;
401
+ if (Console_Getopt::_isShortOpt($opt_arg) || Console_Getopt::_isLongOpt($opt_arg)) {
402
+ verify(false, "Console_Getopt: option requires an argument -- $opt");
403
+ }
404
+ } else {
405
+ return verify(false, "Console_Getopt: option requires an argument -- $opt");
406
+ }
407
+ }
408
+ }
409
+
410
+ // $opts[] = array($opt, $opt_arg);
411
+ $opts[$opt] = $opt_arg;
412
+ }
413
+ }
414
+
415
+ /**
416
+ * @access private
417
+ *
418
+ */
419
+ function _isShortOpt($arg)
420
+ {
421
+ return strlen($arg) == 2 && $arg[0] == '-' && preg_match('/[a-zA-Z]/', $arg[1]);
422
+ }
423
+
424
+ /**
425
+ * @access private
426
+ *
427
+ */
428
+ function _isLongOpt($arg)
429
+ {
430
+ return strlen($arg) > 2 && $arg[0] == '-' && $arg[1] == '-' &&
431
+ preg_match('/[a-zA-Z]+$/', substr($arg, 2));
432
+ }
433
+
434
+ /**
435
+ * @access private
436
+ *
437
+ */
438
+ function _parseLongOption($arg, $long_options, &$opts, &$args)
439
+ {
440
+ @list($opt, $opt_arg) = explode('=', $arg, 2);
441
+ $opt_len = strlen($opt);
442
+
443
+ for ($i = 0; $i < count($long_options); $i++) {
444
+ $long_opt = $long_options[$i];
445
+ $opt_start = substr($long_opt, 0, $opt_len);
446
+ $long_opt_name = str_replace('=', '', $long_opt);
447
+
448
+ /* Option doesn't match. Go on to the next one. */
449
+ if ($long_opt_name != $opt) {
450
+ continue;
451
+ }
452
+
453
+ $opt_rest = substr($long_opt, $opt_len);
454
+
455
+ /* Check that the options uniquely matches one of the allowed
456
+ options. */
457
+ if ($i + 1 < count($long_options)) {
458
+ $next_option_rest = substr($long_options[$i + 1], $opt_len);
459
+ } else {
460
+ $next_option_rest = '';
461
+ }
462
+ if ($opt_rest != '' && $opt{0} != '=' &&
463
+ $i + 1 < count($long_options) &&
464
+ $opt == substr($long_options[$i+1], 0, $opt_len) &&
465
+ $next_option_rest != '' &&
466
+ $next_option_rest{0} != '=') {
467
+ return verify(false, "Console_Getopt: option --$opt is ambiguous");
468
+ }
469
+
470
+ if (substr($long_opt, -1) == '=') {
471
+ if (substr($long_opt, -2) != '==') {
472
+ /* Long option requires an argument.
473
+ Take the next argument if one wasn't specified. */;
474
+ if (!strlen($opt_arg) && !(list(, $opt_arg) = each($args))) {
475
+ return verify(false, "Console_Getopt: option --$opt requires an argument");
476
+ }
477
+ if (Console_Getopt::_isShortOpt($opt_arg) || Console_Getopt::_isLongOpt($opt_arg)) {
478
+ return verify(false, "Console_Getopt: option requires an argument --$opt");
479
+ }
480
+ }
481
+ } else if ($opt_arg) {
482
+ return verify(false, "Console_Getopt: option --$opt doesn't allow an argument");
483
+ }
484
+
485
+ $opts[] = array('--' . $opt, $opt_arg);
486
+ return;
487
+ }
488
+
489
+ return verify(false, "Console_Getopt: unrecognized option --$opt");
490
+ }
491
+
492
+ /**
493
+ * Safely read the $argv PHP array across different PHP configurations.
494
+ * Will take care on register_globals and register_argc_argv ini directives
495
+ *
496
+ * @access public
497
+ * @return mixed the $argv PHP array or PEAR error if not registered
498
+ */
499
+ function readPHPArgv()
500
+ {
501
+ global $argv;
502
+ if (!is_array($argv)) {
503
+ if (!@is_array($_SERVER['argv'])) {
504
+ if (!@is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])) {
505
+ return verify(false,
506
+ "Console_Getopt: Could not read cmd args (register_argc_argv=Off?)");
507
+ }
508
+ return $GLOBALS['HTTP_SERVER_VARS']['argv'];
509
+ }
510
+ return $_SERVER['argv'];
511
+ }
512
+ return $argv;
513
+ }
514
+
515
+ }
516
+
517
+
518
+
519
+
520
+
521
+ $HELP_README = <<<EOT
522
+ This dir contains files to make a Yahoo! Mail Application.
523
+
524
+ Run ymdt to get an overview on how to exchange these files with a Yahoo!
525
+ Mail development server.
526
+
527
+ What's here to start with, for a minimal app?
528
+
529
+ config.xml: app's config XML
530
+
531
+ auth.xml: app's authentication XML
532
+
533
+ readme.txt: this file, ignored by Yahoo! tools
534
+
535
+ .appid: metadata for use by Yahoo! tools. Do not modify.
536
+
537
+ views/: subdirectory where you may add view html files
538
+
539
+ assets/: subdirectory where you may add non-view assets
540
+ it may contain other subdirectories
541
+
542
+ Portable Network Graphics files you could add at this directory's top-level:
543
+
544
+ icon.png: a 16x16 pixel image for use as an icon
545
+
546
+ thumbnail.png: a 64x64 pixel image for use in an app gallery
547
+
548
+ full.png: a 300x250 pixel image for preview / help display purposes
549
+
550
+ EOT;
551
+
552
+ $HELP_USAGE = <<<EOT
553
+ Usage:
554
+
555
+ ymdt <command> [flags] [command arguments]
556
+
557
+ Type 'ymdt help <command>' for help on a specific command.
558
+
559
+ Available commands:
560
+ apps lists all of the user's apps
561
+ create creates a new app
562
+ destroy delete the entire app
563
+ del deletes one of an app's files
564
+ dev develop-o-matic mode
565
+ fixup rewrite app's views and text assets so that asset URLs are correct
566
+ get get latest of an app's file(s) from dev server
567
+ help get help on a specific command
568
+ ls list all of an app's file(s) and their URLs
569
+ publish publish an app
570
+ put upload apps file(s) to dev server
571
+ tester list, invite, or delete testers
572
+ EOT;
573
+
574
+ $HELP_COMMON_OPTIONS = <<<EOT
575
+ Global Options:
576
+
577
+ -h<host>
578
+ Override default Yahoo! Mail developer hostname.
579
+ (Default is YMDT_HOST environment variable or 'developer.mail.yahoo.com')
580
+
581
+ -u<user>
582
+ Specify user ID. If this option is omitted, the user ID is either
583
+ figured from cookies or by prompting the user.
584
+
585
+ After authentication, a token is normally cached in a cookie file in your
586
+ home directory. It does not contain your password. This time-sensitive
587
+ token will be reused on subsequent invocations to avoid logging on. Use
588
+ the -d flag to avoid writing that file and force login on every invocation.
589
+
590
+ -d
591
+ Don't save auth token to file.
592
+
593
+ -p<password>
594
+ You're better off not using this option and letting the program offer you a
595
+ masked prompt for password entry.
596
+
597
+ -k
598
+ Disables SSL host verification, use if the host you're connecting to is
599
+ using self-signed SSL certs.
600
+
601
+ EOT;
602
+
603
+ $HELP_APPS = <<<EOT
604
+ usage: apps
605
+
606
+ Lists all of the logged-on user's apps.
607
+
608
+ Each app is reported as a line beginning with the appid, followed by a tab and
609
+ then the app name.
610
+
611
+ EOT;
612
+
613
+ $HELP_CREATE = <<<EOT
614
+ usage: create <path>
615
+
616
+ Create an app on the development server, with a local working copy kept
617
+ in <path>.
618
+
619
+ If <path> doesn't already contain an app, a minimal app will be created.
620
+
621
+ If <path> already contains an app, a new app will be created based on
622
+ the app files in <path>, and <path> will be converted to a valid working
623
+ directory for the newly-created app.
624
+
625
+ In either case, the app name will be set to '<path>', but you can modify it
626
+ to be different from '<path>' by editing the app's config.xml.
627
+
628
+ For a description of the files comprising an app, see <path>/readme.txt.
629
+
630
+ Options:
631
+ -A<pub_appid>:<priv_appid>
632
+ -A<pub_appid>:
633
+ -A:<priv_appid>
634
+ Usually, the server allocates a globally-unique appid for both the public
635
+ and private (development) versions of the app. This option allows admins
636
+ to explicitly specify a public appid, private appid, or both. Fails if
637
+ any of the specified appids already exist or if the logged-on user doesn't
638
+ have admin rights. Note the magic ':', it must be there even if you're
639
+ not specifying both appids.
640
+
641
+ Examples:
642
+ create a new app in directory ~/apps/llama:
643
+ ymdt create ~/apps/llama
644
+
645
+ create a new app in directory ~/apps/lindy with pub appid 123 and
646
+ private appid 456:
647
+ ymdt -A123:456 create ~/apps/lindy
648
+
649
+ create a new app in directory ~/apps/lindy with autogenerated
650
+ public appid and private appid 456:
651
+ ymdt -A:456 create ~/apps/lindy
652
+
653
+ create a new app in directory ~/apps/lindy with public appid 123 and
654
+ an autogenerated private appid:
655
+ ymdt -A123: create ~/apps/lindy
656
+
657
+ EOT;
658
+
659
+ $HELP_DEL = <<<EOT
660
+ usage: del <path>
661
+
662
+ Delete one of an app's files.
663
+
664
+ <path> must be to a single file in an app's local working directory.
665
+
666
+ Examples:
667
+ delete asset 'lark.jpg' for app in directory ./birds:
668
+ ymdt del ./birds/assets/lark.jpg
669
+
670
+ EOT;
671
+
672
+ $HELP_DEV = <<<EOT
673
+ usage: dev <path>
674
+
675
+ Enter dev-o-matic mode. <path> must be to the root of an app's local working
676
+ directory. Will do a get of app, then continuously watch for local changes and
677
+ update the server.
678
+
679
+ EOT;
680
+
681
+ $HELP_DESTROY = <<<EOT
682
+ usage: destroy [<path>]
683
+
684
+ Destroy an application. Must specify the path or the -a flag.
685
+
686
+ -a<appid>
687
+ <appid> is the application's private appid.
688
+
689
+ -z
690
+ Admin option. Really, really delete it from the development server.
691
+
692
+ EOT;
693
+
694
+ $HELP_FIXUP = <<<EOT
695
+ usage: fixup <path>
696
+
697
+ When your app is published, the in-development version of your app is copied to
698
+ the public version of your app, which is visible to the world. The first time
699
+ after publication that you upload (put) assets to the development server, the
700
+ asset URLs change. Use this command to fixup up all of the asset URLs in your
701
+ view HTML and your text assets.
702
+
703
+ Recommended best practice: after your app is published (by an admin), run fixup,
704
+ but backup your personal source first. (Your app is in source control, right?)
705
+
706
+ Options:
707
+ -z
708
+ Suppress autoput. Normally, fixup executes these steps:
709
+ 1) Uploads all of your app's files to the development server.
710
+ 2) Fetches the latest asset URLs from the server.
711
+ 3) Rewrites views and text type assets to use the latest asset URLs.
712
+ 4) Uploads any files modified in step 3.
713
+ Using the -z option suppresses steps 1 and 4. This is handy if you want
714
+ to avoid step 1 when you know the assets have already been uploaded once
715
+ since the last publish or if you want to manually review the results of 3
716
+ before uploading files to the development server.
717
+
718
+ -n
719
+ Use the application name from the app's conf.xml to lookup the app's
720
+ private appid on the server and modify the .appid file in your local
721
+ working copy. See: put -n.
722
+
723
+ -s
724
+ Sync. Same as for put.
725
+ EOT;
726
+
727
+ $HELP_GET = <<<EOT
728
+ usage: get <path>
729
+
730
+ Get latest of an app's file(s) from dev server. Normally, <path> is to the root
731
+ or some subpath of the app's local working copy. Wildcards (?*) allowed below
732
+ app root, but only if <path> is quoted.
733
+
734
+ Options:
735
+ -a<appid>
736
+ Get an app that exists on the server, but for which you don't yet have
737
+ a working copy. In this case, <path> must be to an empty directory.
738
+
739
+ -s
740
+ Sync: Do the get, but also delete any local files below <path> that
741
+ aren't present on the server.
742
+
743
+ -y
744
+ Autoyes: don't prompt user to confirm local deletes, use with care!
745
+
746
+ Examples:
747
+ get latest of all files for app 045fa65e3, using directory duck as the
748
+ local working directory:
749
+ ymdt -a045fa65e3 get duck
750
+
751
+ get latest of all files for app in directory ./scrooge:
752
+ ymdt get ./scrooge
753
+
754
+ get latest of all assets for app in directory ./mcduck:
755
+ ymdt get ./mcduck/assets
756
+
757
+ get latest of config for app in directory ./ham:
758
+ ymdt get ./ham/config.xml
759
+
760
+ get latest of .js files in ./ham/assets
761
+ ymdt get './ham/assets/*.js'
762
+
763
+ EOT;
764
+
765
+ $HELP_HELP = <<<EOT
766
+ usage: help <command>
767
+
768
+ Give help for a particular command.
769
+
770
+ For general usage, run ymdt without any arguments.
771
+
772
+ EOT;
773
+
774
+ $HELP_LS = <<<EOT
775
+ usage: ls <path>
776
+
777
+ List server-side files and their URLs. Handy for figuring out how to reference
778
+ your assets from views or other assets.
779
+
780
+ <path> must minimally be an app's working directory, in which case all of the
781
+ files comprising the app will be listed. <path> may also specify a particular
782
+ subdir or file inside the app's working directory. Wildcards (*?) allowed below
783
+ app's working directory root.
784
+
785
+ Examples:
786
+ list all assets for app in directory ~/apps/pigeon:
787
+ ymdt ls ~/apps/pigeon/assets
788
+
789
+ list all .jpg assets for app in directory ~/apps/zebra:
790
+ ymdt ls '~/apps/zebra/assets/*.jpg'
791
+ # note quotes to avoid shell expansion
792
+
793
+ EOT;
794
+
795
+ $HELP_PUBLISH = <<<EOT
796
+ usage: publish [<path>]
797
+
798
+ Update the publically-visible version of your app on the development server to
799
+ be the same as the latest uploaded private (in-development) version of an app.
800
+ <path> is the root of the private version's local working copy. It is only used
801
+ to figure out the right appid. The public app update is peformed only using the
802
+ private app files already uploaded to the server.
803
+
804
+ Requires admin rights.
805
+
806
+ You must use the -a option if you omit <path>.
807
+
808
+ Options:
809
+ -a<appid>
810
+ Specify the private appid of the app you'd like to publish.
811
+
812
+ -z
813
+ Suppress asset URL validation.
814
+
815
+ EOT;
816
+
817
+ $HELP_PUT = <<<EOT
818
+ usage: put <path>
819
+
820
+ Upload files from local app working copy to the server. <path> is the root or
821
+ some subpath of the app's local working copy. Wildcards (?*) allowed below app
822
+ root, but only if <path> is quoted.
823
+
824
+ Options:
825
+ -a<appid>
826
+ Explicitly specify the appid of the destination app on the server.
827
+ Normally the .appid in the root directory of the app's local working
828
+ copy determines what app is modified on the server. Since you may
829
+ only modify the private (in-development) version of your app, <appid>
830
+ must be a private appid. Changes .appid in your app's working dir
831
+ as well.
832
+
833
+ -n
834
+ Use the application name from the app's conf.xml to lookup the app's
835
+ private appid on the server and modify the .appid file in your local
836
+ working copy. For situations where you created the local working copy
837
+ by doing a get -a with a public appid or if you've done a get -h from
838
+ a different server. Revises the .appid so that future puts will apply
839
+ to the private app on the correct server.
840
+
841
+ -s
842
+ Sync: Do the put, but also delete any files on the server that aren't
843
+ present in the local working copy.
844
+
845
+ -y
846
+ Autoyes: don't prompt user to confirm server deletes, use with care!
847
+
848
+ Examples:
849
+ upload all files for app in directory ./mcduck:
850
+ ymdt put ./mcduck
851
+
852
+ upload all assets for app in directory ./phish:
853
+ ymdt put ./phish/assets
854
+
855
+ upload latest of .js files in ./ham/assets
856
+ ymdt put './ham/assets/*.js'
857
+
858
+ EOT;
859
+
860
+ $HELP_TESTER = <<<EOT
861
+ usage: tester <ls|invite|del> [tester email address or yid]
862
+
863
+ tester ls will display your tester list.
864
+
865
+ invite <tester email address> will invite someone to be a tester.
866
+
867
+ del <tester Yahoo! ID> will delete an existing tester.
868
+
869
+ EOT;
870
+
871
+ $HELP_UPGRADE = <<<EOT
872
+ usage: upgrade
873
+
874
+ Download the latest version of ymdt and replace the running script with it.
875
+
876
+ EOT;
877
+
878
+
879
+
880
+
881
+ //Functions for logging onto Bouncer and Yahoo!
882
+ class Login
883
+ {
884
+ static function isWindows()
885
+ {
886
+ //better way to do this?
887
+ if(getenv('HOME'))
888
+ return false;
889
+ return true;
890
+ }
891
+
892
+ static function getHomeDir()
893
+ {
894
+ $home = getenv('HOME');
895
+ return $home ? $home : getenv('HOMEPATH'); //windows
896
+ }
897
+
898
+ static function promptUserInput($prompt = 'username:', $echo = true)
899
+ {
900
+ printf($prompt);
901
+ $stty = '';
902
+ if(!$echo){
903
+ $stty = `stty -g`;
904
+ system("stty -echo");
905
+ }
906
+ $input = trim(fgets(STDIN));
907
+ if(!$echo)
908
+ system("stty $stty");
909
+
910
+ return $input;
911
+ }
912
+
913
+ static function promptCredentials($userPrompt, $passPrompt)
914
+ {
915
+ return array(self::promptUserInput($userPrompt),
916
+ self::promptUserInput($passPrompt, false));
917
+ }
918
+
919
+ //on failure: returns false
920
+ //on success: returns cookie string appropriate for curl_setopt(,CURLOPT_COOKIE,)
921
+ static function authBouncer($bid, $password, $cookie_file_name, $use_guesthouse)
922
+ {
923
+ $url = "https://bouncer.by.corp.yahoo.com/login/";
924
+ if($use_guesthouse)
925
+ $url = "https://bouncer.gh.corp.yahoo.com/login/";
926
+
927
+ $ch = curl_init();
928
+ $timeout = 0;
929
+
930
+ curl_setopt($ch, CURLOPT_POST, TRUE);
931
+ curl_setopt($ch, CURLOPT_POSTFIELDS,
932
+ array('action' => 'login',
933
+ 'id' => $bid,
934
+ 'pass_word' => $password));
935
+
936
+ curl_setopt($ch, CURLOPT_URL, $url);
937
+ curl_setopt($ch, CURLOPT_HEADER, TRUE);
938
+ if($cookie_file_name){
939
+ curl_setopt($ch, CURLOPT_COOKIEJAR, $cookie_file_name);
940
+ curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie_file_name);
941
+ }
942
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
943
+ curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
944
+ if(self::isWindows()){
945
+ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
946
+ curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
947
+ }
948
+
949
+ $result = curl_exec($ch);
950
+ curl_close($ch);
951
+ if($cookie_file_name)
952
+ chmod($cookie_file_name, 0600);
953
+ if($result === false || preg_match("/YCorp=/", $result) != 1){
954
+ return false;
955
+ }
956
+
957
+ preg_match_all('|Set-Cookie: (.*);|U', $result, $matches);
958
+ return implode(';', $matches[1]);
959
+ // return true;
960
+ }
961
+
962
+ static function authYahoo($yid, $passwd, $cookie_file_name)
963
+ {
964
+ $agent = 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US;'.
965
+ 'rv:1.9.0.5) Gecko/2008120121 Firefox/3.0.5';
966
+ $ch = curl_init();
967
+ curl_setopt($ch, CURLOPT_USERAGENT, $agent);
968
+ curl_setopt($ch, CURLOPT_COOKIEJAR, $cookie_file_name);
969
+ curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie_file_name);
970
+ $postFields = "&login=$yid&passwd=$passwd";
971
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields);
972
+ curl_setopt($ch, CURLOPT_URL, 'http://login.yahoo.com');
973
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
974
+ $result = curl_exec($ch);
975
+ curl_close($ch);
976
+
977
+ return self::extractCookieString($cookie_file_name) != null;
978
+ }
979
+
980
+ static function extractCookieString($cookie_file_name)
981
+ {
982
+ if (!file_exists($cookie_file_name))
983
+ return null;
984
+ $file = fopen($cookie_file_name, 'r');
985
+ if (!$file)
986
+ return null;
987
+
988
+ $regex = '/^\.yahoo\.com\tTRUE\t\/\tFALSE\t\d+\t(\w+)\t(.*)$/';
989
+ while ($line = fgets($file)) {
990
+ if (!preg_match($regex, $line, $matches))
991
+ continue;
992
+
993
+ $key = $matches[1];
994
+ $val = $matches[2];
995
+ $cookies[] = "$key=$val";
996
+ }
997
+ fclose($file);
998
+ if(!count($cookies))
999
+ return null;
1000
+ $cstr = join('; ', $cookies);
1001
+ return $cstr;
1002
+ }
1003
+
1004
+ static function extractBID_FromCookie($cookie_file_name)
1005
+ {
1006
+ if(!file_exists($cookie_file_name))
1007
+ return null;
1008
+
1009
+ static $regex = '/YBY\sid%3D[0-9]+%26userid%3D([^%]+)%26/';
1010
+ $matches = array();
1011
+ $contents = file_get_contents($cookie_file_name);
1012
+ $count = preg_match($regex, $contents, $matches);
1013
+ if($count != 1){
1014
+ return null;
1015
+ }
1016
+ return $matches[1];
1017
+ }
1018
+
1019
+ }
1020
+
1021
+
1022
+
1023
+ //Wraps calls to the development server.
1024
+ class WebServiceClient
1025
+ {
1026
+ function __construct($hostname, $cookieFname, $lamessl, $uname, $passwd)
1027
+ {
1028
+
1029
+ $this->hostname = $hostname ? $hostname : ( getenv('YMDT_HOST') ? getenv('YMDT_HOST') : 'developer.mail.yahoo.com');
1030
+ echo "Developer server: " . $this->hostname . "\n";
1031
+ $this->cookieFname = $cookieFname;
1032
+ $this->lamessl = $lamessl;
1033
+ $this->uname = $uname;
1034
+ $this->passwd = $passwd;
1035
+ $this->wssid = null;
1036
+ }
1037
+
1038
+ function call($op, &$msg, $fields = array(), $format = 'json',
1039
+ $checkStatus = true, $needLogin = true)
1040
+ {
1041
+ if(!$this->wssid && $needLogin)
1042
+ $this->login();
1043
+
1044
+ $url = 'https://' . $this->hostname;
1045
+ if(strpos($op, 'admin.') !== false)
1046
+ $url = 'http://' . $this->hostname . ':9999';
1047
+
1048
+ $url .= '/om/api/1.0/openmail.' . $op;
1049
+
1050
+ if($this->wssid){
1051
+ $url .= '?bycrumb='.$this->wssid;
1052
+ }
1053
+
1054
+ $ch = curl_init();
1055
+ $timeout = 0;
1056
+ curl_setopt($ch, CURLOPT_POST, TRUE);
1057
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $fields);
1058
+ curl_setopt($ch, CURLOPT_URL, $url);
1059
+ curl_setopt($ch, CURLOPT_HEADER, TRUE);
1060
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
1061
+ curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
1062
+ if($this->cookieFname)
1063
+ curl_setopt($ch, CURLOPT_COOKIEFILE, $this->cookieFname);
1064
+ else
1065
+ curl_setopt($ch, CURLOPT_COOKIE, $this->cookies);
1066
+ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
1067
+ if($this->lamessl)
1068
+ curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
1069
+
1070
+ $result = curl_exec($ch);
1071
+ if(curl_errno($ch)){
1072
+ $msg = 'Curl failure: ' . curl_error($ch);
1073
+ }
1074
+
1075
+ $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
1076
+ $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
1077
+
1078
+ curl_close($ch);
1079
+ if(file_exists($this->cookieFname))
1080
+ chmod($this->cookieFname, 0600);
1081
+
1082
+ $content = substr($result, $header_size);
1083
+
1084
+ $rval = null;
1085
+ if($format){
1086
+ if($format == 'json')
1087
+ $rval = json_decode($content);
1088
+ elseif($format == 'xml')
1089
+ $rval = simplexml_load_string(trim($content));
1090
+ if(!get_class($rval) && !is_array($rval))
1091
+ $rval = null;
1092
+ }
1093
+
1094
+ if($http_code != '200'){
1095
+ $msg = "$op invoke failed (HTTP $http_code).";
1096
+ if($rval && property_exists($rval, 'message'))
1097
+ $msg .= "\nServer says: {$rval->message}";
1098
+ verify(false, $msg);
1099
+ }
1100
+
1101
+ if(!$format)
1102
+ return $content;
1103
+
1104
+ verify($rval !== null,
1105
+ "Unexpected webservice result from $op: \n$format:\n\t[$content]");
1106
+
1107
+ //Would be nice if ws results were a little more standardized, a la JSON-RPC.
1108
+ //But, they're not. . .
1109
+ if($checkStatus && $rval->status != 200){
1110
+ $msg = property_exists($rval, 'message') ? $rval->message : '';
1111
+ $msg = $msg . " (status: {$rval->status})";
1112
+ verify(false, $msg);
1113
+ }
1114
+
1115
+ return $rval;
1116
+ }
1117
+
1118
+ function getWSSID()
1119
+ {
1120
+ //This call, unlike all the others, returns in XML (since JSON is
1121
+ //security no-no for wssid.)
1122
+ $result = $this->call('dev.file.init', $msg, array(), 'xml', true,
1123
+ false);
1124
+
1125
+ verify($result && property_exists($result, 'wssid'),
1126
+ 'unable to retrieve wssid');
1127
+
1128
+ $this->wssid = $result->wssid;
1129
+ if(property_exists($result, 'version') &&
1130
+ YMDT_Version < $result->version){
1131
+ $latest = $result->version;
1132
+ echo "***You are running an outdated version of ymdt ".
1133
+ "(latest is $latest, you have " . YMDT_Version . ".).\n".
1134
+ "***Please run: ymdt upgrade\n\n";
1135
+ }
1136
+ }
1137
+
1138
+ function login()
1139
+ {
1140
+ $uname = $this->uname;
1141
+ $passwd = $this->passwd;
1142
+
1143
+ $use_guesthouse = strpos($this->hostname, 'corp.yahoo.com') === false;
1144
+
1145
+ //Username changed? Blow away cookie file.
1146
+ $prevUsername = Login::extractBID_FromCookie($this->cookieFname);
1147
+ if($uname && $prevUsername != $uname){
1148
+ verify(!file_exists($this->cookieFname) ||
1149
+ unlink($this->cookieFname),
1150
+ "login fought {$this->cookieFname} and lost");
1151
+ }
1152
+
1153
+ //If we can't get a crumb, try logging into backyard to refresh cookie,
1154
+ //then try getting crumb again.
1155
+ try {
1156
+ $this->getWSSID();
1157
+ if($this->wssid)
1158
+ return;
1159
+ } catch (Exception $e) {}
1160
+
1161
+ if(!$uname){
1162
+ $prompt = 'Backyard ID: ';
1163
+ if($use_guesthouse)
1164
+ $prompt = 'Guesthouse ID: ';
1165
+ list($uname, $passwd) = Login::promptCredentials($prompt,
1166
+ 'Password: ');
1167
+ }
1168
+ elseif(!$passwd){
1169
+ $passwd = Login::promptUserInput('Password:', false);
1170
+ }
1171
+
1172
+ $this->cookies = Login::authBouncer($uname, $passwd, $this->cookieFname, $use_guesthouse);
1173
+ verify($this->cookies, 'login failed');
1174
+ echo "\nlogin successful\n";
1175
+ if($this->cookieFname){
1176
+ echo "\nAn authentication token has been stored in " .
1177
+ $this->cookieFname . "\nUntil its expiration,".
1178
+ "it will be used by future ymdt invocations to avoid having to" .
1179
+ " relogin.\nTo disable this behavior, use the -d option.\n\n";
1180
+ }
1181
+ $this->getWSSID();
1182
+ }
1183
+
1184
+ function appList()
1185
+ {
1186
+ $msg = null;
1187
+ $fields = array('ignorePublicationStatus' => 'true');
1188
+ verify(($result = $this->call('dev.app.list', $msg, $fields, 'json',
1189
+ false)) !== null,
1190
+ "No webservice result.");
1191
+ return $result;
1192
+ }
1193
+
1194
+ function ls($appid, $subpath = null)
1195
+ {
1196
+ $fields = array('app' => $appid);
1197
+ if($subpath){
1198
+ $fields['path'] = $subpath;
1199
+ }
1200
+
1201
+ $result = $this->call('dev.file.ls', $msg, $fields);
1202
+ return get_object_vars($result->data);
1203
+ }
1204
+
1205
+ //upgrade helper, not really a ws call
1206
+ function fetchLatestScript()
1207
+ {
1208
+ $ch = curl_init();
1209
+ $timeout = 0;
1210
+ $url = 'https://' . $this->hostname . '/openmail/assets/ymdt';
1211
+ curl_setopt($ch, CURLOPT_URL, $url);
1212
+ curl_setopt($ch, CURLOPT_HEADER, TRUE);
1213
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
1214
+ curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
1215
+ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
1216
+ if($this->lamessl)
1217
+ curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
1218
+ $result = curl_exec($ch);
1219
+ $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
1220
+ $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
1221
+ curl_close($ch);
1222
+ $content = substr($result, $header_size);
1223
+ verify($http_code == '200' && !empty($content),
1224
+ "Upgrade fetch failed (HTTP $http_code).");
1225
+ return $content;
1226
+ }
1227
+
1228
+ };
1229
+
1230
+
1231
+ define('YMDT_Version', '0.46');
1232
+
1233
+
1234
+
1235
+ $HOMEDIR = Login::getHomeDir();
1236
+
1237
+ ini_set('open_basedir', "");
1238
+ ini_set('error_log', './error.log');
1239
+ ini_set('display_errors', 'On');
1240
+ ini_set('error_reporting', E_ERROR);
1241
+
1242
+ $DEBUG_MODE = true;
1243
+
1244
+ function verify($cond, $msg)
1245
+ {
1246
+ if($cond)
1247
+ return;
1248
+ throw new Exception($msg);
1249
+ }
1250
+
1251
+ class YMDT
1252
+ {
1253
+ private $ws;
1254
+
1255
+ const OPTS___CONSTRUCT = 'h:ku:p:d';
1256
+ public function __construct($cookieFname, $hostname = null, $lamessl = false,
1257
+ $username = null, $password = null,
1258
+ $nocookies = null)
1259
+ {
1260
+ if($nocookies){
1261
+ verify( !file_exists($cookieFname) || unlink($cookieFname),
1262
+ "Couldn't delete " . $cookieFname . '.');
1263
+ $cookieFname = null;
1264
+ }
1265
+
1266
+ $lamessl_hosts = array('om0001.mail.mud.yahoo.com',
1267
+ 'om0002.mail.mud.yahoo.com');
1268
+ if(in_array($hostname, $lamessl_hosts))
1269
+ $lamessl = true;
1270
+
1271
+ $this->ws = new WebServiceClient($hostname, $cookieFname,
1272
+ $lamessl, $username,
1273
+ $password);
1274
+ }
1275
+
1276
+ private function membersFromCmdLineOptions($optString, $memberNames, $opts)
1277
+ {
1278
+ $opts = str_replace(':', '', $opts);
1279
+ foreach($opts as $ndx=>$char){
1280
+ $memberName = $memberNames[$ndx];
1281
+
1282
+ if(array_key_exists($char, $opts))
1283
+ $this->args->$memberName = $opts[$char];
1284
+ else
1285
+ $this->args->$memberName = null;
1286
+ }
1287
+ }
1288
+
1289
+ public function apps()
1290
+ {
1291
+ $result = $this->ws->appList();
1292
+
1293
+ printf("\n%-16s %-16s %.100s", 'private appid', 'public appid', 'name');
1294
+ printf("\n%-16s %-16s %.100s", '--------------', '--------------',
1295
+ '---------------------------------------------');
1296
+ foreach ($result as $r) {
1297
+ printf("\n%-16s %-16s %.100s", $r->app, $r->published_to,
1298
+ $r->name);
1299
+ }
1300
+ echo "\n";
1301
+ }
1302
+
1303
+ const OPTS_CREATE = 'A:';
1304
+ public function create($appdir, $appids = null)
1305
+ {
1306
+ verify(file_exists($appdir) || FileSys::mkdir($appdir),
1307
+ "Couldn't create dir $appdir");
1308
+
1309
+ $appidFname = $appdir.'/'.FileSys::APPID_FNAME;
1310
+ $confFname = $appdir.'/'. 'config.xml';
1311
+
1312
+ //If it's not a virgin app, we have a local working copy already
1313
+ //(i.e. that we've unzipped from someone else), just nothing on the
1314
+ //server yet.
1315
+ $isVirginApp = !file_exists($confFname);
1316
+
1317
+ $fields = array('name' => basename($appdir));
1318
+ if($appids){
1319
+ $appids = explode(':', $appids);
1320
+ verify(count($appids) == 2, "Expected -A<pub appid>:<priv appid>");
1321
+ if($appids[0])
1322
+ $fields['pub_appid'] = $appids[0];
1323
+ if($appids[0])
1324
+ $fields['priv_appid'] = $appids[1];
1325
+ }
1326
+
1327
+ verify(($result = $this->ws->call('dev.app.create', $msg, $fields)),
1328
+ "No webservice result.");
1329
+
1330
+ verify(file_put_contents($appidFname, $result->id) !== false,
1331
+ "Couldn't write $appidFname for {$result->id}");
1332
+
1333
+ echo($result->message . "\n");
1334
+
1335
+ if(!$isVirginApp)
1336
+ return self::fixup($appdir);
1337
+
1338
+ FileSys::layout(FileSys::realpath($appdir));
1339
+ return self::get($appdir);
1340
+ }
1341
+
1342
+ function del($path)
1343
+ {
1344
+ list($appid, $basepath, $subpath) = FileSys::parsePath($path);
1345
+ verify($subpath, 'del must reference file in working app dir');
1346
+
1347
+ if(file_exists($path))
1348
+ verify(!is_dir($path), 'del requires a path to a single file');
1349
+
1350
+ $fields = array('app' => $appid);
1351
+ $fields['path'] = $subpath;
1352
+
1353
+ $json = $this->ws->call('dev.file.del', $msg, $fields);
1354
+ echo "\tdeleted $path from server\n";
1355
+ return;
1356
+ }
1357
+
1358
+ function dev($path)
1359
+ {
1360
+ //todo: offer choice of starting with sync from server to local dir or
1361
+ // vice versa. Make sure this is an app dir, or you have an appid,
1362
+ // etc.
1363
+ self::get($path);
1364
+ echo "\nMonitoring $path for changes. Hit Ctrl-C to exit\n\n";
1365
+
1366
+
1367
+ $prevFiles = FileSys::expandPaths(array($path));
1368
+ $tmLastMod = max(array_map(array('FileSys', 'modTime'), $prevFiles));
1369
+
1370
+ while(true){
1371
+ $files = FileSys::expandPaths(array($path));
1372
+ $deletes = array_diff($prevFiles, $files);
1373
+ $updates = array();
1374
+ $tmMaxMod = $tmLastMod;
1375
+ foreach($files as $f){
1376
+ $tmMod = stat($f); $tmMod = $tmMod['mtime'];
1377
+ if($tmMod <= $tmLastMod)
1378
+ continue;
1379
+ $updates[] = $f;
1380
+ $tmMaxMod = max($tmMaxMod, $tmMod);
1381
+ }
1382
+ if(!empty($updates) || !empty($deletes)){
1383
+ echo "\nNoticed some changes in $path, syncing. . .\n";
1384
+ array_walk($updates, array($this, 'putOne'));
1385
+ array_walk($deletes, array($this, 'del'));
1386
+ echo "Sync done.\n";
1387
+ }
1388
+ $prevFiles = $files;
1389
+ $tmLastMod = $tmMaxMod;
1390
+ usleep(1000 * 100);
1391
+ }
1392
+ }
1393
+
1394
+ const OPTS_DESTROY = 'a:z';
1395
+ function destroy($appdir = null, $appid = null, $reallyReally = false)
1396
+ {
1397
+ verify($appdir || $appid, "Must specify either appdir or -a.");
1398
+ if(!$appid){
1399
+ list($appid, $basepath, $subpath) = FileSys::parsePath($appdir);
1400
+ }
1401
+
1402
+ $fields = array('app' => $appid);
1403
+ $call = 'dev.app.delete';
1404
+ if($reallyReally){
1405
+ $call = 'admin.app.destroy';
1406
+ }
1407
+ verify(($result = $this->ws->call($call, $msg, $fields)),
1408
+ "No webservice result.");
1409
+
1410
+ echo($result->message . "\n");
1411
+ }
1412
+
1413
+ //fixup all asset URLs for view/assets files in $approot
1414
+ const OPTS_FIXUP = 'nzs';
1415
+ function fixup($approot, $appidFromName = false, $suppressAutoput = false,
1416
+ $sync = false)
1417
+ {
1418
+ list($appid, $basepath, $subpath) = FileSys::parsePath($approot);
1419
+ verify(!strlen($subpath),
1420
+ "fixup requires app's root directory as an argument.");
1421
+
1422
+ if($appidFromName){
1423
+ $appid = $this->changePrivateAppid($basepath,
1424
+ FileSys::nameFromConfFile($basepath));
1425
+ }
1426
+
1427
+ if(!$suppressAutoput){
1428
+ echo "Uploading all files:\n";
1429
+ self::put($approot, null, false, $sync);
1430
+ }
1431
+
1432
+ //Grab only text assets (by file extension). Assume everything in view
1433
+ //dir is text.
1434
+ $paths = FileSys::expandPaths(array($basepath . '/assets'));
1435
+ $paths = FileSys::filterNonTextPaths($paths);
1436
+ $paths = array_merge($paths, FileSys::expandPaths(array($basepath . '/views')));
1437
+ $paths = FileSys::filterMetaFiles($paths);
1438
+ $fixer = new AssetURL_Fixer($this->ws->ls($appid, 'assets'));
1439
+ array_walk($paths, array($fixer, 'run'));
1440
+ $numChanged = count($fixer->changedFilePaths);
1441
+ if(!$suppressAutoput && $numChanged){
1442
+ echo "Uploading changed files:\n";
1443
+ array_walk($fixer->changedFilePaths, array($this, 'putOne'));
1444
+ }
1445
+ }
1446
+
1447
+ const OPTS_GET = 'a:sy';
1448
+ function get($path, $appid = null, $sync = false, $autoYes = false)
1449
+ {
1450
+ if($appid){
1451
+ list($subpath, $basepath) = array(null, $path);
1452
+ $fname = $basepath.'/'.FileSys::APPID_FNAME;
1453
+ FileSys::mkdir($basepath);
1454
+ verify(file_put_contents($fname, $appid), "Couldn't write $fname.");
1455
+ FileSys::layout($basepath);
1456
+ }
1457
+ else{
1458
+ list($appid, $basepath, $subpath) = FileSys::parsePath($path);
1459
+ }
1460
+ $fetchedPaths = self::lsAndGet($appid, $subpath, $basepath);
1461
+ if(!$sync)
1462
+ return;
1463
+
1464
+ $fetchedPaths = array_map(array('FileSys', 'realpath'), $fetchedPaths);
1465
+ $localPaths = FileSys::expandPaths(array($path));
1466
+ // echo 'localPaths: ' . print_r($localPaths, true) . "\n";
1467
+ $localPaths = FileSys::filterMetaFiles($localPaths, FileSys::realpath($basepath));
1468
+ // echo 'filtered: ' . print_r($localPaths, true) . "\n";
1469
+ //echo 'fetched: ' . print_r($fetchedPaths, true) . "\n";
1470
+
1471
+ $deletes = array_diff($localPaths, $fetchedPaths);
1472
+ if(!$autoYes &&
1473
+ !self::confirmDeletes($deletes,
1474
+ "\nThe following files aren't present on the server:\n",
1475
+ "\nWould you like to delete the local copies? "))
1476
+ return;
1477
+
1478
+ foreach($deletes as $d){
1479
+ verify(unlink($d) , "Couldn't delete $d");
1480
+ echo "\tdeleted $d\n";
1481
+ }
1482
+ }
1483
+
1484
+ function help($command)
1485
+ {
1486
+ if(!$command)
1487
+ show_usage();
1488
+
1489
+ $txt_var = 'HELP_' . strtoupper($command);
1490
+ if(!array_key_exists($txt_var, $GLOBALS)){
1491
+ echo 'Command ' . $command . ' unknown.' . "\n\n";
1492
+ return;
1493
+ }
1494
+
1495
+ global $$txt_var, $HELP_COMMON_OPTIONS;
1496
+ echo $$txt_var . "\n$HELP_COMMON_OPTIONS\n\n";;
1497
+ }
1498
+
1499
+ function ls($path)
1500
+ {
1501
+ list($appid, $basepath, $subpath) = FileSys::parsePath($path);
1502
+ $results = $this->ws->ls($appid, $subpath);
1503
+
1504
+ foreach($results as $fname => $url)
1505
+ echo "\t" . $fname . "\t" . $url . "\n";
1506
+ }
1507
+
1508
+ const OPTS_PUBLISH = 'a:z';
1509
+ function publish($appdir = null, $appidOverride = null, $suppressValidation = false)
1510
+ {
1511
+ $appid = null;
1512
+ if(strlen($appdir)){
1513
+ list($appid, $basepath, $subpath) = FileSys::parsePath($appdir);
1514
+ verify(!strlen($subpath),
1515
+ "publish requires path to be app's root directory.");
1516
+ }
1517
+
1518
+ if($appidOverride)
1519
+ $appid = $appidOverride;
1520
+
1521
+ verify($appid, 'publish requires -a option or a <path>');
1522
+
1523
+ $fields = array('app' => $appid);
1524
+ if(!$suppressValidation)
1525
+ $fields['validate_assets'] = 'true';
1526
+
1527
+ $this->ws->call('dev.app.publish', $msg, $fields);
1528
+ echo "\tdone publication\n";
1529
+ }
1530
+
1531
+ const OPTS_PUT = 'a:nsy';
1532
+ function put($pattern, $appidOverride = null, $appidFromName = false,
1533
+ $sync = false, $autoYes = false)
1534
+ {
1535
+ list($appid, $basepath, $subpath) = FileSys::parsePath($pattern);
1536
+ if($appidOverride){
1537
+ $appid = $appidOverride;
1538
+ }
1539
+ if($appidFromName){
1540
+ verify(!$appidOverride, "put can't accept both -n and -a");
1541
+ $appid = $this->changePrivateAppid($basepath,
1542
+ FileSys::nameFromConfFile($basepath));
1543
+ }
1544
+
1545
+ $paths = FileSys::expandPaths(glob($pattern, GLOB_MARK));
1546
+ $paths = FileSys::filterMetaFiles($paths, $basepath);
1547
+ array_walk($paths, array($this, 'putOne'));
1548
+
1549
+ if(count($paths) > 1)
1550
+ echo "Done all puts for $pattern.\n";
1551
+
1552
+ if(!$sync)
1553
+ return;
1554
+
1555
+ echo "\tappid now $appid\n";
1556
+ $remote_paths = $this->ws->ls($appid, $subpath);
1557
+ $remote_paths = array_keys($remote_paths);
1558
+
1559
+ //Need relative (to app root) paths
1560
+ $root = FileSys::realpath($basepath);
1561
+ $newpaths = array();
1562
+ foreach($paths as $p){
1563
+ $newpaths[] = substr($p, strlen($root)+1);
1564
+ }
1565
+ $paths = $newpaths;
1566
+
1567
+ $deletes = array();
1568
+
1569
+ //echo 'remote paths: ' . print_r($remote_paths, true) . "\n\n";
1570
+ //echo 'paths: ' . print_r($paths, true) . "\n\n";
1571
+
1572
+ foreach($remote_paths as $p){
1573
+ if(in_array($p, $paths) || is_dir($p))
1574
+ continue;
1575
+ $deletes[] = $p;
1576
+ }
1577
+
1578
+ if(!$autoYes &&
1579
+ !self::confirmDeletes($deletes,
1580
+ "\nThe following files aren't present locally:\n",
1581
+ "\nWould you like to delete them from the server? "));
1582
+ return;
1583
+
1584
+ foreach($deletes as $path){
1585
+ $fields = array('app' => $appid);
1586
+ $fields['path'] = $path;
1587
+ $json = $this->ws->call('dev.file.del', $msg, $fields);
1588
+ echo "\tdeleted $path from server\n";
1589
+ }
1590
+ }
1591
+
1592
+ function tester($subcmd = null, $emailOrYid = null)
1593
+ {
1594
+ $legalSubs = array('ls', 'del', 'invite');
1595
+ verify($subcmd && in_array($subcmd, $legalSubs),
1596
+ "tester command requires a subcommand, one of: " .
1597
+ implode(', ', $legalSubs) . ".");
1598
+
1599
+ if($subcmd == 'ls')
1600
+ return $this->testerLs();
1601
+
1602
+ $msg = '';
1603
+ if($subcmd == 'del'){
1604
+ verify($emailOrYid, "tester del requires the tester's Yahoo! ID.");
1605
+ $fields = array('email' => $emailOrYid);
1606
+ $result = $this->ws->call('dev.yid.remove', $msg, $fields);
1607
+ echo "Tester $emailOrYid deleted.\n\n";
1608
+ return;
1609
+ }
1610
+
1611
+ verify($emailOrYid, "tester del requires the tester's Yahoo email address.");
1612
+ $fields = array('email' => $emailOrYid);
1613
+ $result = $this->ws->call('dev.yid.add', $msg, $fields);
1614
+ echo "Tester $emailOrYid invited.\n\n";
1615
+ }
1616
+
1617
+ function testerLs()
1618
+ {
1619
+ $fields = array();
1620
+ $arr = $this->ws->call('dev.yid.list', $fields, $msg, 'json', false);
1621
+ verify(is_array($arr), "Unexpected ws result for dev.yid.list: "
1622
+ . print_r($arr, true));
1623
+ printf("%-32s %-16s\n", 'Yahoo! ID', "Pending?");
1624
+ printf("-----------------------------------------\n");
1625
+ foreach($arr as $tester){
1626
+ printf("%-32s %-16s\n", $tester->yid,
1627
+ $tester->pending ? 'yes' : '');
1628
+ }
1629
+ }
1630
+
1631
+ function confirmDeletes($deletes, $list_header, $question)
1632
+ {
1633
+ if(!empty($deletes)){
1634
+ shell_exec("./script/growl");
1635
+ echo $list_header;
1636
+ foreach($deletes as $d){
1637
+ echo "\t$d\n";
1638
+ }
1639
+ $response = Login::promptUserInput($question);
1640
+ if(strtolower($response[0]) === 'n')
1641
+ return false;
1642
+ }
1643
+ return true;
1644
+ }
1645
+
1646
+ function lsAndGet($appid, $src_subpath, $dest_basepath)
1647
+ {
1648
+ //Do an ls first in case they're using a dir or glob for path.
1649
+ $results = $this->ws->ls($appid, $src_subpath);
1650
+ $count = 0;
1651
+ //Get each file returned by ls
1652
+ $local_file_paths = array();
1653
+ foreach($results as $fname => $url){
1654
+ $fields = array('app' => $appid,
1655
+ 'name' => $fname);
1656
+ $file_path = $dest_basepath . '/' . $fname;
1657
+ echo "\tdownloading $file_path. . .\n";
1658
+ $result = $this->ws->call('dev.file.get', $msg, $fields, null);
1659
+
1660
+ FileSys::mkdir(dirname($file_path));
1661
+
1662
+ verify(file_put_contents($file_path, $result) !== false,
1663
+ "Couldn't write $file_path");
1664
+ $local_file_paths[] = $file_path;
1665
+ $count ++;
1666
+ }
1667
+ if($count > 1)
1668
+ echo "Done all gets for $dest_basepath.\n";
1669
+ return $local_file_paths;
1670
+ }
1671
+
1672
+ private function changePrivateAppidFromName($appdir, $name)
1673
+ {
1674
+ $list = $this->ws->appList();
1675
+ $privAppid = null;
1676
+ foreach($list as $app){
1677
+ if($name != $app->name)
1678
+ continue;
1679
+ verify(!$privAppid,
1680
+ "More than one app named '$name' for logged on developer.");
1681
+ $privAppid = $app->app;
1682
+ }
1683
+ verify($privAppid, "No app named '$name' for logged-on developer.");
1684
+ return $this->changeWorkingAppid($appdir, $privAppid);
1685
+ }
1686
+
1687
+ private function changeWorkingAppid($appdir, $appid)
1688
+ {
1689
+ verify(file_put_contents($appdir . '/' . FileSys::APPID_FNAME, $appid)
1690
+ !== FALSE);
1691
+ echo "\tchanged appid to $appid. \n";
1692
+ return $appid;
1693
+ }
1694
+
1695
+ function putOne($path)
1696
+ {
1697
+ echo "\tuploading $path. . .\n";
1698
+
1699
+ list($appid, $basepath, $subpath) = FileSys::parsePath($path);
1700
+
1701
+ $contents = file_get_contents($path);
1702
+ verify($contents !== false, "Couldn't read contents of $path.");
1703
+
1704
+ $fields = array('app' => $appid,
1705
+ 'name' => $subpath,
1706
+ 'file' => '@' . $path);
1707
+
1708
+ $result = $this->ws->call('dev.file.put', $msg, $fields);
1709
+
1710
+ return;
1711
+ }
1712
+
1713
+ function upgrade()
1714
+ {
1715
+ $newYmdt = $this->ws->fetchLatestScript();
1716
+
1717
+ verify(!strpos(__FILE__, '.php'),
1718
+ "Can't upgrade source file, only built version.");
1719
+ $archive = __FILE__ . '.old';
1720
+ verify(copy(__FILE__, $archive),
1721
+ "Upgrade failed, couldn't archive current version to $archive.");
1722
+ verify(file_put_contents(__FILE__, $newYmdt) !== false,
1723
+ "Upgrade failed, couldn't write " . __FILE__);
1724
+ echo "Done upgrade, previous version archived to $archive.\n";
1725
+ return 0;
1726
+ }
1727
+ }
1728
+
1729
+ function show_usage()
1730
+ {
1731
+ global $HELP_USAGE;
1732
+
1733
+ echo $HELP_USAGE . "\n\n";
1734
+ exit(-1);
1735
+ }
1736
+
1737
+ //Given an options string as taken by getopt(), return options array and reindex
1738
+ //global argv to omit the options.
1739
+ function getopts_and_reindex($opts)
1740
+ {
1741
+ list($options, $rest) = Console_Getopt::getopt($GLOBALS['argv'], $opts);
1742
+ $GLOBALS['argv'] = array_merge(array('ymdt'), $rest);
1743
+ // echo "OPTS: $opts\n";
1744
+ //echo "OPTIONS:\n" . print_r($options, true);
1745
+ //echo "ARGV:\n" . print_r($GLOBALS['argv'], true);
1746
+ return $options;
1747
+ }
1748
+
1749
+ function getMethodMetadata($className, $methodName)
1750
+ {
1751
+ $class = new ReflectionClass($className);
1752
+ $optString = $class->getConstant(strtoupper('OPTS_' . $methodName));
1753
+
1754
+ $method = new ReflectionMethod($className, $methodName);
1755
+ $params = $method->getParameters();
1756
+ $optionalParamNames = array();
1757
+ $requiredParamCount = $method->getNumberOfRequiredParameters();
1758
+ foreach($params as $ndx=>$p){
1759
+ if($ndx < $requiredParamCount)
1760
+ continue;
1761
+ $optionalParamNames[] = $p->getName();
1762
+ }
1763
+
1764
+ //Optional parameters come either from -xYYY command-line or just plain argv args
1765
+ $optChars = str_replace(':', '', $optString);
1766
+ verify(strlen($optChars) <= count($optionalParamNames),
1767
+ "Internal error: $methodName optstring wrong len.");
1768
+ return array($method, $optString, $requiredParamCount);
1769
+ }
1770
+
1771
+ function getMethodOptionalParamVals($method, $optString, $options)
1772
+ {
1773
+ $params = $method->getParameters();
1774
+ $requiredParamCount = $method->getNumberOfRequiredParameters();
1775
+ $optionalParamCount = $method->getNumberOfParameters() - $requiredParamCount;
1776
+
1777
+ $optChars = str_replace(':', '', $optString);
1778
+ $paramVals = array();
1779
+
1780
+ //Switchless as in specified w/o a command-line switch, i.e. for:
1781
+ //ymdt destroy myappdir #myappdir is a switchless optional param
1782
+ //ymdt destroy -a myappid #myappid is a command-line switch optional param
1783
+ $switchlessOptionalParamCount = $optionalParamCount - strlen($optChars);
1784
+
1785
+ //Grab the switchless optional params that came from argv, unspecified ones
1786
+ //will get default vals. +2 excludes 'ymdt' and command
1787
+ $paramVals = array_slice($GLOBALS['argv'], $requiredParamCount + 2, $switchlessOptionalParamCount);
1788
+ $defaultSwitchlessOptionalParamCount = $switchlessOptionalParamCount - count($paramVals);
1789
+ $defaultSwitchlessOptionalParamCount = max(0, $defaultSwitchlessOptionalParamCount);
1790
+ // echo "sOPC $switchlessOptionalParamCount, dSOPC
1791
+ // $defaultSwitchlessOptionalParamCount rPC $requiredParamCount\n";
1792
+ // echo "paramVals " . print_r($paramVals, true);
1793
+
1794
+ for($i = 0; $i < $defaultSwitchlessOptionalParamCount; $i++){
1795
+ $paramNdx = $requiredParamCount + $i;
1796
+ $paramVals[] = $params[$paramNdx]->getDefaultValue();
1797
+ }
1798
+
1799
+ //Grab the optional params that are specified by a command-line switch
1800
+ $switchOptionalParamCount = strlen($optChars);
1801
+ for($i = 0; $i < $switchOptionalParamCount; $i++){
1802
+ $paramNdx = $requiredParamCount + $defaultSwitchlessOptionalParamCount + $i;
1803
+ $optChar = $optChars[$i];
1804
+ if(array_key_exists($optChar, $options))
1805
+ $paramVals[] = strlen($options[$optChar]) ? $options[$optChar] : true;
1806
+ else
1807
+ $paramVals[] = $params[$paramNdx]->getDefaultValue();
1808
+ }
1809
+
1810
+ return $paramVals;
1811
+ }
1812
+
1813
+ function main()
1814
+ {
1815
+ echo "Yahoo! Mail Development Tool Version " . YMDT_Version . "\n";
1816
+
1817
+ static $legal_cmds = array('apps', 'create', 'ls', 'fixup', 'get', 'help',
1818
+ 'put', 'del', 'dev', 'upgrade', 'publish',
1819
+ 'destroy', 'tester');
1820
+
1821
+ if(count($GLOBALS['argv']) < 2){
1822
+ echo "\nFirst argument must be a command\n\n";
1823
+ show_usage();
1824
+ }
1825
+ $cmd = $GLOBALS['argv'][1];
1826
+ if(!in_array($cmd, $legal_cmds)){
1827
+ echo "\nFirst argument must be a command.\n"
1828
+ ."'$cmd' is not a legal command.\n\n";
1829
+ show_usage();
1830
+ }
1831
+
1832
+ //get constructor optstring, opt params
1833
+ //get command optstring, opt params, required params
1834
+
1835
+ list($ctor, $ctorOptString, $ctorRequiredParamCount) =
1836
+ getMethodMetadata('YMDT', '__construct');
1837
+
1838
+ list($method, $cmdOptString, $cmdRequiredParamCount) =
1839
+ getMethodMetadata('YMDT', $cmd);
1840
+
1841
+ $ctorOptChars = str_replace(':', '', $ctorOptString);
1842
+ verify(!strpbrk($ctorOptChars, $cmdOptString),
1843
+ "Internal error: $cmd optstring clashes with common optstring.");
1844
+
1845
+ //array of option char ==> value
1846
+ $options = getopts_and_reindex($cmdOptString . $ctorOptString);
1847
+
1848
+ //Invoke constructor with cookieFname, <optional parameters. . .>
1849
+ //All optional take defaults unless overridden at command line.
1850
+ global $HOMEDIR;
1851
+
1852
+ $paramVals = array("$HOMEDIR/.ymdtcookie");
1853
+ $paramVals = array_merge($paramVals,
1854
+ getMethodOptionalParamVals($ctor, $ctorOptString,
1855
+ $options));
1856
+ $paramVals = array_slice($paramVals, 0, -1);
1857
+
1858
+ //create it
1859
+ // echo "params: " . print_r($paramVals, true) . "\n\n";
1860
+ $class = new ReflectionClass('YMDT');
1861
+ $ymdt = $class->newInstanceArgs($paramVals);
1862
+
1863
+ $argCount = count($GLOBALS['argv']) - 2;
1864
+ if($argCount < $cmdRequiredParamCount){
1865
+ echo "\n$cmd expected at least $cmdRequiredParamCount argument(s), " .
1866
+ "got $argCount\n" .
1867
+ "run ymdt help $cmd for more information.\n\n";
1868
+ exit(-1);
1869
+
1870
+ }
1871
+
1872
+ $paramVals = array_slice($GLOBALS['argv'], 2, $cmdRequiredParamCount);
1873
+ $paramVals = array_merge($paramVals,
1874
+ getMethodOptionalParamVals($method, $cmdOptString,
1875
+ $options));
1876
+ // echo print_r($paramVals, true);
1877
+ //invoke the command
1878
+ echo "$cmd:\n";
1879
+ // implode(' ', array_slice($paramVals, 0, $cmdRequiredParamCount)). "\n\n";
1880
+
1881
+ try {
1882
+ $method->invokeArgs($ymdt, $paramVals);
1883
+ }
1884
+ catch(Exception $e){
1885
+ global $DEBUG_MODE;
1886
+ echo $e->getMessage() . "\n";
1887
+ if($DEBUG_MODE)
1888
+ throw $e;
1889
+ }
1890
+
1891
+ }
1892
+
1893
+ main();
1894
+
1895
+ ?>