Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
 
3
/*
4
 * This file is part of the symfony package.
5
 * (c) 2004-2006 Fabien Potencier <fabien.potencier@symfony-project.com>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
 
11
/**
12
 * Abstract class for all tasks.
13
 *
14
 * @package    symfony
15
 * @subpackage task
16
 * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
17
 * @version    SVN: $Id: sfTask.class.php 30773 2010-08-27 19:27:41Z Kris.Wallsmith $
18
 */
19
abstract class sfTask
20
{
21
  protected
22
    $namespace           = '',
23
    $name                = null,
24
    $aliases             = array(),
25
    $briefDescription    = '',
26
    $detailedDescription = '',
27
    $arguments           = array(),
28
    $options             = array(),
29
    $dispatcher          = null,
30
    $formatter           = null;
31
 
32
  /**
33
   * Constructor.
34
   *
35
   * @param sfEventDispatcher $dispatcher  An sfEventDispatcher instance
36
   * @param sfFormatter       $formatter   An sfFormatter instance
37
   */
38
  public function __construct(sfEventDispatcher $dispatcher, sfFormatter $formatter)
39
  {
40
    $this->initialize($dispatcher, $formatter);
41
 
42
    $this->configure();
43
  }
44
 
45
  /**
46
   * Initializes the sfTask instance.
47
   *
48
   * @param sfEventDispatcher $dispatcher  A sfEventDispatcher instance
49
   * @param sfFormatter       $formatter   A sfFormatter instance
50
   */
51
  public function initialize(sfEventDispatcher $dispatcher, sfFormatter $formatter)
52
  {
53
    $this->dispatcher = $dispatcher;
54
    $this->formatter  = $formatter;
55
  }
56
 
57
  /**
58
   * Configures the current task.
59
   */
60
  protected function configure()
61
  {
62
  }
63
 
64
  /**
65
   * Returns the formatter instance.
66
   *
67
   * @return sfFormatter The formatter instance
68
   */
69
  public function getFormatter()
70
  {
71
    return $this->formatter;
72
  }
73
 
74
  /**
75
   * Sets the formatter instance.
76
   *
77
   * @param sfFormatter The formatter instance
78
   */
79
  public function setFormatter(sfFormatter $formatter)
80
  {
81
    $this->formatter = $formatter;
82
  }
83
 
84
  /**
85
   * Runs the task from the CLI.
86
   *
87
   * @param sfCommandManager $commandManager  An sfCommandManager instance
88
   * @param mixed            $options         The command line options
89
   *
90
   * @return integer 0 if everything went fine, or an error code
91
   */
92
  public function runFromCLI(sfCommandManager $commandManager, $options = null)
93
  {
94
    $commandManager->getArgumentSet()->addArguments($this->getArguments());
95
    $commandManager->getOptionSet()->addOptions($this->getOptions());
96
 
97
    return $this->doRun($commandManager, $options);
98
  }
99
 
100
  /**
101
   * Runs the task.
102
   *
103
   * @param array|string $arguments  An array of arguments or a string representing the CLI arguments and options
104
   * @param array        $options    An array of options
105
   *
106
   * @return integer 0 if everything went fine, or an error code
107
   */
108
  public function run($arguments = array(), $options = array())
109
  {
110
    $commandManager = new sfCommandManager(new sfCommandArgumentSet($this->getArguments()), new sfCommandOptionSet($this->getOptions()));
111
 
112
    if (is_array($arguments) && is_string(key($arguments)))
113
    {
114
      // index arguments by name for ordering and reference
115
      $indexArguments = array();
116
      foreach ($this->arguments as $argument)
117
      {
118
        $indexArguments[$argument->getName()] = $argument;
119
      }
120
 
121
      foreach ($arguments as $name => $value)
122
      {
123
        if (false !== $pos = array_search($name, array_keys($indexArguments)))
124
        {
125
          if ($indexArguments[$name]->isArray())
126
          {
127
            $value = join(' ', (array) $value);
128
            $arguments[$pos] = isset($arguments[$pos]) ? $arguments[$pos].' '.$value : $value;
129
          }
130
          else
131
          {
132
            $arguments[$pos] = $value;
133
          }
134
 
135
          unset($arguments[$name]);
136
        }
137
      }
138
 
139
      ksort($arguments);
140
    }
141
 
142
    // index options by name for reference
143
    $indexedOptions = array();
144
    foreach ($this->options as $option)
145
    {
146
      $indexedOptions[$option->getName()] = $option;
147
    }
148
 
149
    foreach ($options as $name => $value)
150
    {
151
      if (is_string($name))
152
      {
153
        if (false === $value || null === $value || (isset($indexedOptions[$name]) && $indexedOptions[$name]->isArray() && !$value))
154
        {
155
          unset($options[$name]);
156
          continue;
157
        }
158
 
159
        // convert associative array
160
        $value = true === $value ? $name : sprintf('%s=%s', $name, isset($indexedOptions[$name]) && $indexedOptions[$name]->isArray() ? join(' --'.$name.'=', (array) $value) : $value);
161
      }
162
 
163
      // add -- before each option if needed
164
      if (0 !== strpos($value, '--'))
165
      {
166
        $value = '--'.$value;
167
      }
168
 
169
      $options[] = $value;
170
      unset($options[$name]);
171
    }
172
 
173
    return $this->doRun($commandManager, is_string($arguments) ? $arguments : implode(' ', array_merge($arguments, $options)));
174
  }
175
 
176
  /**
177
   * Returns the argument objects.
178
   *
179
   * @return sfCommandArgument An array of sfCommandArgument objects.
180
   */
181
  public function getArguments()
182
  {
183
    return $this->arguments;
184
  }
185
 
186
  /**
187
   * Adds an array of argument objects.
188
   *
189
   * @param array $arguments  An array of arguments
190
   */
191
  public function addArguments($arguments)
192
  {
193
    $this->arguments = array_merge($this->arguments, $arguments);
194
  }
195
 
196
  /**
197
   * Add an argument.
198
   *
199
   * This method always use the sfCommandArgument class to create an option.
200
   *
201
   * @see sfCommandArgument::__construct()
202
   */
203
  public function addArgument($name, $mode = null, $help = '', $default = null)
204
  {
205
    $this->arguments[] = new sfCommandArgument($name, $mode, $help, $default);
206
  }
207
 
208
  /**
209
   * Returns the options objects.
210
   *
211
   * @return sfCommandOption An array of sfCommandOption objects.
212
   */
213
  public function getOptions()
214
  {
215
    return $this->options;
216
  }
217
 
218
  /**
219
   * Adds an array of option objects.
220
   *
221
   * @param array $options    An array of options
222
   */
223
  public function addOptions($options)
224
  {
225
    $this->options = array_merge($this->options, $options);
226
  }
227
 
228
  /**
229
   * Add an option.
230
   *
231
   * This method always use the sfCommandOption class to create an option.
232
   *
233
   * @see sfCommandOption::__construct()
234
   */
235
  public function addOption($name, $shortcut = null, $mode = null, $help = '', $default = null)
236
  {
237
    $this->options[] = new sfCommandOption($name, $shortcut, $mode, $help, $default);
238
  }
239
 
240
  /**
241
   * Returns the task namespace.
242
   *
243
   * @return string The task namespace
244
   */
245
  public function getNamespace()
246
  {
247
    return $this->namespace;
248
  }
249
 
250
  /**
251
   * Returns the task name
252
   *
253
   * @return string The task name
254
   */
255
  public function getName()
256
  {
257
    if ($this->name)
258
    {
259
      return $this->name;
260
    }
261
 
262
    $name = get_class($this);
263
 
264
    if ('sf' == substr($name, 0, 2))
265
    {
266
      $name = substr($name, 2);
267
    }
268
 
269
    if ('Task' == substr($name, -4))
270
    {
271
      $name = substr($name, 0, -4);
272
    }
273
 
274
    return str_replace('_', '-', sfInflector::underscore($name));
275
  }
276
 
277
  /**
278
   * Returns the fully qualified task name.
279
   *
280
   * @return string The fully qualified task name
281
   */
282
  final function getFullName()
283
  {
284
    return $this->getNamespace() ? $this->getNamespace().':'.$this->getName() : $this->getName();
285
  }
286
 
287
  /**
288
   * Returns the brief description for the task.
289
   *
290
   * @return string The brief description for the task
291
   */
292
  public function getBriefDescription()
293
  {
294
    return $this->briefDescription;
295
  }
296
 
297
  /**
298
   * Returns the detailed description for the task.
299
   *
300
   * It also formats special string like [...|COMMENT]
301
   * depending on the current formatter.
302
   *
303
   * @return string The detailed description for the task
304
   */
305
  public function getDetailedDescription()
306
  {
307
    return preg_replace('/\[(.+?)\|(\w+)\]/se', '$this->formatter->format("$1", "$2")', $this->detailedDescription);
308
  }
309
 
310
  /**
311
   * Returns the aliases for the task.
312
   *
313
   * @return array An array of aliases for the task
314
   */
315
  public function getAliases()
316
  {
317
    return $this->aliases;
318
  }
319
 
320
  /**
321
   * Returns the synopsis for the task.
322
   *
323
   * @return string The synopsis
324
   */
325
  public function getSynopsis()
326
  {
327
    $options = array();
328
    foreach ($this->getOptions() as $option)
329
    {
330
      $shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : '';
331
      $options[] = sprintf('['.($option->isParameterRequired() ? '%s--%s="..."' : ($option->isParameterOptional() ? '%s--%s[="..."]' : '%s--%s')).']', $shortcut, $option->getName());
332
    }
333
 
334
    $arguments = array();
335
    foreach ($this->getArguments() as $argument)
336
    {
337
      $arguments[] = sprintf($argument->isRequired() ? '%s' : '[%s]', $argument->getName().($argument->isArray() ? '1' : ''));
338
 
339
      if ($argument->isArray())
340
      {
341
        $arguments[] = sprintf('... [%sN]', $argument->getName());
342
      }
343
    }
344
 
345
    return sprintf('%%s %s %s %s', $this->getFullName(), implode(' ', $options), implode(' ', $arguments));
346
  }
347
 
348
  protected function process(sfCommandManager $commandManager, $options)
349
  {
350
    $commandManager->process($options);
351
    if (!$commandManager->isValid())
352
    {
353
      throw new sfCommandArgumentsException(sprintf("The execution of task \"%s\" failed.\n- %s", $this->getFullName(), implode("\n- ", $commandManager->getErrors())));
354
    }
355
  }
356
 
357
  protected function doRun(sfCommandManager $commandManager, $options)
358
  {
359
    $event = $this->dispatcher->filter(new sfEvent($this, 'command.filter_options', array('command_manager' => $commandManager)), $options);
360
    $options = $event->getReturnValue();
361
 
362
    $this->process($commandManager, $options);
363
 
364
    $event = new sfEvent($this, 'command.pre_command', array('arguments' => $commandManager->getArgumentValues(), 'options' => $commandManager->getOptionValues()));
365
    $this->dispatcher->notifyUntil($event);
366
    if ($event->isProcessed())
367
    {
368
      return $event->getReturnValue();
369
    }
370
 
371
    $ret = $this->execute($commandManager->getArgumentValues(), $commandManager->getOptionValues());
372
 
373
    $this->dispatcher->notify(new sfEvent($this, 'command.post_command'));
374
 
375
    return $ret;
376
  }
377
 
378
  /**
379
   * Logs a message.
380
   *
381
   * @param mixed $messages  The message as an array of lines of a single string
382
   */
383
  public function log($messages)
384
  {
385
    if (!is_array($messages))
386
    {
387
      $messages = array($messages);
388
    }
389
 
390
    $this->dispatcher->notify(new sfEvent($this, 'command.log', $messages));
391
  }
392
 
393
  /**
394
   * Logs a message in a section.
395
   *
396
   * @param string  $section  The section name
397
   * @param string  $message  The message
398
   * @param int     $size     The maximum size of a line
399
   * @param string  $style    The color scheme to apply to the section string (INFO, ERROR, or COMMAND)
400
   */
401
  public function logSection($section, $message, $size = null, $style = 'INFO')
402
  {
403
    $this->dispatcher->notify(new sfEvent($this, 'command.log', array($this->formatter->formatSection($section, $message, $size, $style))));
404
  }
405
 
406
  /**
407
   * Logs a message as a block of text.
408
   *
409
   * @param string|array $messages The message to display in the block
410
   * @param string       $style    The style to use
411
   */
412
  public function logBlock($messages, $style)
413
  {
414
    if (!is_array($messages))
415
    {
416
      $messages = array($messages);
417
    }
418
 
419
    $style = str_replace('_LARGE', '', $style, $count);
420
    $large = (Boolean) $count;
421
 
422
    $len = 0;
423
    $lines = array();
424
    foreach ($messages as $message)
425
    {
426
      $lines[] = sprintf($large ? '  %s  ' : ' %s ', $message);
427
      $len = max($this->strlen($message) + ($large ? 4 : 2), $len);
428
    }
429
 
430
    $messages = $large ? array(str_repeat(' ', $len)) : array();
431
    foreach ($lines as $line)
432
    {
433
      $messages[] = $line.str_repeat(' ', $len - $this->strlen($line));
434
    }
435
    if ($large)
436
    {
437
      $messages[] = str_repeat(' ', $len);
438
    }
439
 
440
    foreach ($messages as $message)
441
    {
442
      $this->log($this->formatter->format($message, $style));
443
    }
444
  }
445
 
446
  /**
447
   * Asks a question to the user.
448
   *
449
   * @param string|array $question The question to ask
450
   * @param string       $style    The style to use (QUESTION by default)
451
   * @param string       $default  The default answer if none is given by the user
452
   *
453
   * @param string       The user answer
454
   */
455
  public function ask($question, $style = 'QUESTION', $default = null)
456
  {
457
    if (false === $style)
458
    {
459
      $this->log($question);
460
    }
461
    else
462
    {
463
      $this->logBlock($question, null === $style ? 'QUESTION' : $style);
464
    }
465
 
466
    $ret = trim(fgets(STDIN));
467
 
468
    return $ret ? $ret : $default;
469
  }
470
 
471
  /**
472
   * Asks a confirmation to the user.
473
   *
474
   * The question will be asked until the user answer by nothing, yes, or no.
475
   *
476
   * @param string|array $question The question to ask
477
   * @param string       $style    The style to use (QUESTION by default)
478
   * @param Boolean      $default  The default answer if the user enters nothing
479
   *
480
   * @param Boolean      true if the user has confirmed, false otherwise
481
   */
482
  public function askConfirmation($question, $style = 'QUESTION', $default = true)
483
  {
484
    $answer = 'z';
485
    while ($answer && !in_array(strtolower($answer[0]), array('y', 'n')))
486
    {
487
      $answer = $this->ask($question, $style);
488
    }
489
 
490
    if (false === $default)
491
    {
492
      return $answer && 'y' == strtolower($answer[0]);
493
    }
494
    else
495
    {
496
      return !$answer || 'y' == strtolower($answer[0]);
497
    }
498
  }
499
 
500
  /**
501
   * Asks for a value and validates the response.
502
   *
503
   * Available options:
504
   *
505
   *  * value:    A value to try against the validator before asking the user
506
   *  * attempts: Max number of times to ask before giving up (false by default, which means infinite)
507
   *  * style:    Style for question output (QUESTION by default)
508
   *
509
   * @param   string|array    $question
510
   * @param   sfValidatorBase $validator
511
   * @param   array           $options
512
   *
513
   * @return  mixed
514
   */
515
  public function askAndValidate($question, sfValidatorBase $validator, array $options = array())
516
  {
517
    if (!is_array($question))
518
    {
519
      $question = array($question);
520
    }
521
 
522
    $options = array_merge(array(
523
      'value'    => null,
524
      'attempts' => false,
525
      'style'    => 'QUESTION',
526
    ), $options);
527
 
528
    // does the provided value passes the validator?
529
    if ($options['value'])
530
    {
531
      try
532
      {
533
        return $validator->clean($options['value']);
534
      }
535
      catch (sfValidatorError $error)
536
      {
537
      }
538
    }
539
 
540
    // no, ask the user for a valid user
541
    $error = null;
542
    while (false === $options['attempts'] || $options['attempts']--)
543
    {
544
      if (null !== $error)
545
      {
546
        $this->logBlock($error->getMessage(), 'ERROR');
547
      }
548
 
549
      $value = $this->ask($question, $options['style'], null);
550
 
551
      try
552
      {
553
        return $validator->clean($value);
554
      }
555
      catch (sfValidatorError $error)
556
      {
557
      }
558
    }
559
 
560
    throw $error;
561
  }
562
 
563
  /**
564
   * Returns an XML representation of a task.
565
   *
566
   * @return string An XML string representing the task
567
   */
568
  public function asXml()
569
  {
570
    $dom = new DOMDocument('1.0', 'UTF-8');
571
    $dom->formatOutput = true;
572
    $dom->appendChild($taskXML = $dom->createElement('task'));
573
    $taskXML->setAttribute('id', $this->getFullName());
574
    $taskXML->setAttribute('namespace', $this->getNamespace() ? $this->getNamespace() : '_global');
575
    $taskXML->setAttribute('name', $this->getName());
576
 
577
    $taskXML->appendChild($usageXML = $dom->createElement('usage'));
578
    $usageXML->appendChild($dom->createTextNode(sprintf($this->getSynopsis(), '')));
579
 
580
    $taskXML->appendChild($descriptionXML = $dom->createElement('description'));
581
    $descriptionXML->appendChild($dom->createTextNode(implode("\n ", explode("\n", $this->getBriefDescription()))));
582
 
583
    $taskXML->appendChild($helpXML = $dom->createElement('help'));
584
    $help = $this->detailedDescription;
585
    $help = str_replace(array('|COMMENT', '|INFO'), array('|strong', '|em'), $help);
586
    $help = preg_replace('/\[(.+?)\|(\w+)\]/s', '<$2>$1</$2>', $help);
587
    $helpXML->appendChild($dom->createTextNode(implode("\n ", explode("\n", $help))));
588
 
589
    $taskXML->appendChild($aliasesXML = $dom->createElement('aliases'));
590
    foreach ($this->getAliases() as $alias)
591
    {
592
      $aliasesXML->appendChild($aliasXML = $dom->createElement('alias'));
593
      $aliasXML->appendChild($dom->createTextNode($alias));
594
    }
595
 
596
    $taskXML->appendChild($argumentsXML = $dom->createElement('arguments'));
597
    foreach ($this->getArguments() as $argument)
598
    {
599
      $argumentsXML->appendChild($argumentXML = $dom->createElement('argument'));
600
      $argumentXML->setAttribute('name', $argument->getName());
601
      $argumentXML->setAttribute('is_required', $argument->isRequired() ? 1 : 0);
602
      $argumentXML->setAttribute('is_array', $argument->isArray() ? 1 : 0);
603
      $argumentXML->appendChild($helpXML = $dom->createElement('description'));
604
      $helpXML->appendChild($dom->createTextNode($argument->getHelp()));
605
 
606
      $argumentXML->appendChild($defaultsXML = $dom->createElement('defaults'));
607
      $defaults = is_array($argument->getDefault()) ? $argument->getDefault() : ($argument->getDefault() ? array($argument->getDefault()) : array());
608
      foreach ($defaults as $default)
609
      {
610
        $defaultsXML->appendChild($defaultXML = $dom->createElement('default'));
611
        $defaultXML->appendChild($dom->createTextNode($default));
612
      }
613
    }
614
 
615
    $taskXML->appendChild($optionsXML = $dom->createElement('options'));
616
    foreach ($this->getOptions() as $option)
617
    {
618
      $optionsXML->appendChild($optionXML = $dom->createElement('option'));
619
      $optionXML->setAttribute('name', '--'.$option->getName());
620
      $optionXML->setAttribute('shortcut', $option->getShortcut() ? '-'.$option->getShortcut() : '');
621
      $optionXML->setAttribute('accept_parameter', $option->acceptParameter() ? 1 : 0);
622
      $optionXML->setAttribute('is_parameter_required', $option->isParameterRequired() ? 1 : 0);
623
      $optionXML->setAttribute('is_multiple', $option->isArray() ? 1 : 0);
624
      $optionXML->appendChild($helpXML = $dom->createElement('description'));
625
      $helpXML->appendChild($dom->createTextNode($option->getHelp()));
626
 
627
      if ($option->acceptParameter())
628
      {
629
        $optionXML->appendChild($defaultsXML = $dom->createElement('defaults'));
630
        $defaults = is_array($option->getDefault()) ? $option->getDefault() : ($option->getDefault() ? array($option->getDefault()) : array());
631
        foreach ($defaults as $default)
632
        {
633
          $defaultsXML->appendChild($defaultXML = $dom->createElement('default'));
634
          $defaultXML->appendChild($dom->createTextNode($default));
635
        }
636
      }
637
    }
638
 
639
    return $dom->saveXml();
640
  }
641
 
642
  /**
643
   * Executes the current task.
644
   *
645
   * @param array    $arguments  An array of arguments
646
   * @param array    $options    An array of options
647
   *
648
   * @return integer 0 if everything went fine, or an error code
649
   */
650
   abstract protected function execute($arguments = array(), $options = array());
651
 
652
   protected function strlen($string)
653
   {
654
     return function_exists('mb_strlen') ? mb_strlen($string) : strlen($string);
655
   }
656
}