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