Development

/branches/1.4/lib/task/sfTask.class.php

You must first sign up to be able to contribute.

root/branches/1.4/lib/task/sfTask.class.php

Revision 33151, 18.7 kB (checked in by fabien, 2 years ago)

[1.4] fixed usage of mb_strlen in tasks (closes #9940)

  • Property svn:mime-type set to text/x-php
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
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$
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      if (!function_exists('mb_strlen')) {
655          return strlen($string);
656      }
657
658      if (false === $encoding = mb_detect_encoding($string)) {
659          return strlen($string);
660      }
661
662      return mb_strlen($string, $encoding);
663    }
664 }
665
Note: See TracBrowser for help on using the browser.