ymdp 0.0.7 → 0.0.8

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