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,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
+ ?>