Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Merge branch 'release/2.0.2'

  • Loading branch information...
commit 6f1fe958de26462b7c2b937b2bfc0e6c615e950f 2 parents ed6e002 + e6f041f
Stuart Herbert authored
4 build.properties
... ... @@ -1,13 +1,13 @@
1 1 project.name=CommandLineLib
2 2 project.majorVersion=2
3 3 project.minorVersion=0
4   -project.patchLevel=1
  4 +project.patchLevel=2
5 5 project.snapshot=false
6 6
7 7 checkstyle.standard=Zend
8 8
9 9 component.type=php-library
10   -component.version=10
  10 +component.version=11
11 11
12 12 project.channel=pear.phix-project.org
13 13 pear.local=/var/www/${project.channel}
103 build.xml
@@ -63,7 +63,7 @@
63 63 <property file="${project.distdir.lastBuilt}"/>
64 64 </then>
65 65 <else>
66   - <property name="project.lastBuiltTarfile" value="false"/>
  66 + <property name="project.lastBuiltTarfile" value="false"/>
67 67 </else>
68 68 </if>
69 69
@@ -114,7 +114,7 @@
114 114
115 115 <taskdef name="phingcallifexists" classname="Phix_Project.ComponentManager.Phing.PhingCallIfExistsTask" />
116 116 <import file="build.local.xml"/>
117   -
  117 +
118 118 <!-- Tell the user what this build file supports -->
119 119 <target name="help">
120 120 <echo message="${project.name} ${project.version}: build.xml targets:" />
@@ -139,6 +139,8 @@
139 139 <echo message=" Run code quality tests for PHP_CodeBrowser" />
140 140 <echo message=" phpcpd" />
141 141 <echo message=" Check for cut and paste problems" />
  142 + <echo message=" phploc" />
  143 + <echo message=" Calculate the size of your PHP project" />
142 144 <echo message=" phpdoc" />
143 145 <echo message=" Create the PHP docs from source code" />
144 146 <echo message="" />
@@ -205,7 +207,7 @@
205 207 <delete dir="${project.review.codecoveragedir}" />
206 208 <mkdir dir="${project.review.codecoveragedir}" />
207 209 <mkdir dir="${project.review.logsdir}" />
208   - <exec command="phpunit --configuration=phpunit.xml ${project.src.testunitdir}" checkreturn="true" logoutput="true"/>
  210 + <exec command="phpunit" checkreturn="true" logoutput="true"/>
209 211 <echo/>
210 212 <echo>The code coverage report is in file://${project.review.codecoveragedir}</echo>
211 213 <echo/>
@@ -217,7 +219,7 @@
217 219 </target>
218 220
219 221 <!-- Run the code review quality tests -->
220   - <target name="code-review" depends="run-unittests, code-browser, phpcpd, pdepend">
  222 + <target name="code-review" depends="run-unittests, code-browser, phpcpd, pdepend, phploc">
221 223 <phingcallifexists target="local.code-review"/>
222 224 </target>
223 225
@@ -263,6 +265,12 @@
263 265 <exec command="phpcb --log ${project.review.logsdir} --source ${project.src.phpdir} --output ${project.review.codebrowserdir}" logoutput="true" />
264 266 </target>
265 267
  268 + <!-- Work out the size of the project -->
  269 + <target name="phploc">
  270 + <mkdir dir="${project.review.logsdir}" />
  271 + <exec command="phploc --log-xml ${project.review.logsdir}/phploc.xml --log-csv ${project.review.logsdir}/phploc.csv ${project.src.phpdir}" logoutput="true" />
  272 + </target>
  273 +
266 274 <!-- Populate vendor with the dependencies for this component -->
267 275 <target name="build-vendor" depends="pear-package,setup-vendor">
268 276 <echo>Populating vendor/ with dependencies</echo>
@@ -298,13 +306,55 @@
298 306 <echo>Building release directory</echo>
299 307 <delete dir="${project.builddir}" />
300 308 <mkdir dir="${project.pkgdir}" />
  309 + <if>
  310 + <available file="${project.src.bindir}"/>
  311 + <then>
  312 + <copy todir="${project.pkgdir}">
  313 + <fileset refid="binfiles"/>
  314 + </copy>
  315 + </then>
  316 + </if>
  317 + <if>
  318 + <available file="${project.src.datadir}"/>
  319 + <then>
  320 + <copy todir="${project.pkgdir}">
  321 + <fileset refid="datafiles"/>
  322 + </copy>
  323 + </then>
  324 + </if>
  325 + <if>
  326 + <available file="${project.src.docdir}"/>
  327 + <then>
  328 + <copy todir="${project.pkgdir}">
  329 + <fileset refid="docfiles"/>
  330 + </copy>
  331 + </then>
  332 + </if>
  333 + <if>
  334 + <available file="${project.src.phpdir}"/>
  335 + <then>
  336 + <copy todir="${project.pkgdir}">
  337 + <fileset refid="phpfiles"/>
  338 + </copy>
  339 + </then>
  340 + </if>
  341 + <if>
  342 + <available file="${project.src.testunitdir}"/>
  343 + <then>
  344 + <copy todir="${project.pkgdir}">
  345 + <fileset refid="testfiles"/>
  346 + </copy>
  347 + </then>
  348 + </if>
  349 + <if>
  350 + <available file="${project.src.wwwdir}"/>
  351 + <then>
  352 + <copy todir="${project.pkgdir}">
  353 + <fileset refid="wwwfiles"/>
  354 + </copy>
  355 + </then>
  356 + </if>
301 357 <copy todir="${project.pkgdir}">
302   - <fileset refid="binfiles"/>
303   - <fileset refid="datafiles"/>
304   - <fileset refid="phpfiles"/>
305   - <fileset refid="testfiles"/>
306   - <fileset refid="wwwfiles"/>
307   - <fileset refid="docfiles"/>
308 358 <fileset refid="topleveldocfiles"/>
309 359 </copy>
310 360 <copy todir="${project.builddir}">
@@ -325,8 +375,11 @@
325 375 </fileset>
326 376 </tar>
327 377
328   - <!-- write a message to say which file we built last -->
  378 + <!-- remember the tarball we have just build -->
  379 + <property name="project.lastBuiltTarfile" value="${project.tarfile}" override="true"/>
329 380 <echo file="${project.distdir.lastBuilt}" append="false">project.lastBuiltTarfile=${project.tarfile}</echo>
  381 +
  382 + <!-- write a message to say which file we built last -->
330 383 <echo>Your PEAR package is in ${project.tarfile}</echo>
331 384 <phingcallifexists target="local.pear-package"/>
332 385 </target>
@@ -338,18 +391,28 @@
338 391 <contains string="${project.lastBuiltTarfile}" substring="${project.name}"/>
339 392 </not>
340 393 <then>
341   - <echo>Please run 'phing pear-package' first, then try again.</echo>
  394 + <fail message="Please run 'phing pear-package' first, then try again."/>
  395 + </then>
  396 + </if>
  397 +
  398 + <if>
  399 + <not>
  400 + <available file="${project.vendordir"/>
  401 + </not>
  402 + <then>
  403 + <phingcall target="build-vendor" />
  404 + </then>
  405 + </if>
  406 +
  407 + <if>
  408 + <available file="${project.lastBuiltTarfile}"/>
  409 + <then>
  410 + <exec command="pear -c ${project.tmpdir}/pear-config install --alldeps -f ${project.lastBuiltTarfile}" logoutput="true" checkreturn="true"/>
  411 + <phingcallifexists target="local.install-vendor"/>
342 412 </then>
343   - <elseif>
344   - <available file="${project.lastBuiltTarfile}"/>
345   - <then>
346   - <exec command="pear -c ${project.tmpdir}/pear-config install --alldeps -f ${project.lastBuiltTarfile}" logoutput="true" checkreturn="true"/>
347   - <phingcallifexists target="local.install-vendor"/>
348   - </then>
349   - </elseif>
350 413 <else>
351 414 <echo>Cannot find PEAR package file ${project.lastBuiltTarfile}</echo>
352   - <echo>Run 'phing pear-package' to create a new PEAR package, then try again</echo>
  415 + <fail message="Run 'phing pear-package' to create a new PEAR package, then try again."/>
353 416 </else>
354 417 </if>
355 418 </target>
6 package.xml
@@ -46,6 +46,12 @@ ${contents}
46 46 <max>3.999.9999</max>
47 47 </package>
48 48 <package>
  49 + <name>ContractLib</name>
  50 + <channel>pear.phix-project.org</channel>
  51 + <min>2.0.0</min>
  52 + <max>2.999.9999</max>
  53 + </package>
  54 + <package>
49 55 <name>ValidationLib</name>
50 56 <channel>pear.phix-project.org</channel>
51 57 <min>3.0.0</min>
0  phpunit.xml → phpunit.xml.dist
File renamed without changes
68 src/README.txt
... ... @@ -0,0 +1,68 @@
  1 +Your src/ folder
  2 +================
  3 +
  4 +This src/ folder is where you put all of your code for release. There's
  5 +a folder for each type of file that the PEAR Installer supports. You can
  6 +find out more about these file types online at:
  7 +
  8 +http://blog.stuartherbert.com/php/2011/04/04/explaining-file-roles/
  9 +
  10 + * bin/
  11 +
  12 + If you're creating any command-line tools, this is where you'd put
  13 + them. Files in here get installed into /usr/bin on Linux et al.
  14 +
  15 + There is more information available here: http://blog.stuartherbert.com/php/2011/04/06/php-components-shipping-a-command-line-program/
  16 +
  17 + You can find an example here: https://github.com/stuartherbert/phix/tree/master/src/bin
  18 +
  19 + * data/
  20 +
  21 + If you have any data files (any files that aren't PHP code, and which
  22 + don't belong in the www/ folder), this is the folder to put them in.
  23 +
  24 + There is more information available here: http://blog.stuartherbert.com/php/2011/04/11/php-components-shipping-data-files-with-your-components/
  25 +
  26 + You can find an example here: https://github.com/stuartherbert/ComponentManagerPhpLibrary/tree/master/src/data
  27 +
  28 + * php/
  29 +
  30 + This is where your component's PHP code belongs. Everything that goes
  31 + into this folder must be PSR0-compliant, so that it works with the
  32 + supplied autoloader.
  33 +
  34 + There is more information available here: http://blog.stuartherbert.com/php/2011/04/05/php-components-shipping-reusable-php-code/
  35 +
  36 + You can find an example here: https://github.com/stuartherbert/ContractLib/tree/master/src/php
  37 +
  38 + * tests/functional-tests/
  39 +
  40 + Right now, this folder is just a placeholder for future functionality.
  41 + You're welcome to make use of it yourself.
  42 +
  43 + * tests/integration-tests/
  44 +
  45 + Right now, this folder is just a placeholder for future functionality.
  46 + You're welcome to make use of it yourself.
  47 +
  48 + * tests/unit-tests/
  49 +
  50 + This is where all of your PHPUnit tests go.
  51 +
  52 + It needs to contain _exactly_ the same folder structure as the src/php/
  53 + folder. For each of your PHP classes in src/php/, there should be a
  54 + corresponding test file in test/unit-tests.
  55 +
  56 + There is more information available here: http://blog.stuartherbert.com/php/2011/08/15/php-components-shipping-unit-tests-with-your-component/
  57 +
  58 + You can find an example here: https://github.com/stuartherbert/ContractLib/tree/master/test/unit-tests
  59 +
  60 + * www/
  61 +
  62 + This folder is for any files that should be published in a web server's
  63 + DocRoot folder.
  64 +
  65 + It's quite unusual for components to put anything in this folder, but
  66 + it is there just in case.
  67 +
  68 + There is more information available here: http://blog.stuartherbert.com/php/2011/08/16/php-components-shipping-web-pages-with-your-components/
192 src/php/Phix_Project/CommandLineLib/CommandLineParser.php
@@ -46,16 +46,48 @@
46 46
47 47 namespace Phix_Project\CommandLineLib;
48 48
  49 +use Phix_Project\ContractLib\Contract;
  50 +
  51 +/**
  52 + * Main entry point for parsing a command line, looking for any expected
  53 + * switches and their (possibly optional) arguments
  54 + */
49 55 class CommandLineParser
50 56 {
  57 + /**
  58 + * Parse an array of command-line arguments, looking for command-
  59 + * line switches
  60 + *
  61 + * @param array $args
  62 + * The array of command-line arguments (normally $argv)
  63 + * @param int $argIndex
  64 + * The current index inside $args to search from
  65 + * @param DefinedSwitches $expectedOptions
  66 + * The list of command-line switches that we support
  67 + * @return array(ParsedSwitches, int)
  68 + * The set of parsed command line switches, plus the new value
  69 + * for $argIndex
  70 + */
51 71 public function parseSwitches($args, $argIndex, DefinedSwitches $expectedOptions)
52 72 {
  73 + // catch programming errors
  74 + Contract::Preconditions(function() use ($args, $argIndex, $expectedOptions)
  75 + {
  76 + Contract::RequiresValue($args, is_array($args), '$args must be array');
  77 + Contract::RequiresValue($args, count($args) > 0, '$args cannot be an empty array');
  78 +
  79 + Contract::RequiresValue($argIndex, is_integer($argIndex), '$argIndex must be an integer');
  80 + Contract::RequiresValue($argIndex, count($args) >= $argIndex, '$argIndex cannot be more than +1 beyond the end of $args');
  81 +
  82 + Contract::RequiresValue($expectedOptions, count($expectedOptions->getSwitches()) > 0, '$expectedOptions must have some switches defined');
  83 + });
  84 +
53 85 // create our return values
54   - $ParsedSwitches = new ParsedSwitches($expectedOptions);
  86 + $parsedSwitches = new ParsedSwitches($expectedOptions);
55 87 $argCount = count($args);
56 88
57 89 // var_dump($args);
58   -
  90 +
59 91 // let's work through the args from left to right
60 92 $done = false;
61 93 while ($argIndex < $argCount && !$done)
@@ -68,7 +100,7 @@ public function parseSwitches($args, $argIndex, DefinedSwitches $expectedOptions
68 100 // skip over it
69 101 $argIndex++;
70 102 $done = true;
71   - }
  103 + }
72 104 else if ($args[$argIndex]{0} !== '-')
73 105 {
74 106 // var_dump('Not a switch');
@@ -82,34 +114,83 @@ public function parseSwitches($args, $argIndex, DefinedSwitches $expectedOptions
82 114 // var_dump('Parsing short switch');
83 115 // var_dump('$argIndex is: ' . $argIndex);
84 116 // it is a short switch
85   - $argIndex = $this->parseShortSwitch($args, $argIndex, $ParsedSwitches, $expectedOptions);
  117 + $argIndex = $this->parseShortSwitch($args, $argIndex, $parsedSwitches, $expectedOptions);
86 118 // var_dump('$argIndex is now: ' . $argIndex);
87 119 }
88 120 else
89 121 {
90 122 // var_dump('Parsing long switch');
91 123 // it is a long switch
92   - $argIndex = $this->parseLongSwitch($args, $argIndex, $ParsedSwitches, $expectedOptions);
  124 + $argIndex = $this->parseLongSwitch($args, $argIndex, $parsedSwitches, $expectedOptions);
93 125 }
94 126 }
95 127
96 128 // now, we need to merge in the default values for any
97 129 // arguments that have not been specified by the user
98 130 $defaultValues = $expectedOptions->getDefaultValues();
99   -
  131 +
100 132 foreach ($defaultValues as $name => $value)
101 133 {
102 134 if ($value !== null && $expectedOptions->getSwitchByName($name)->testHasArgument())
103 135 {
104   - $ParsedSwitches->addDefaultValue($expectedOptions, $name, $value);
  136 + $parsedSwitches->addSwitchWithDefaultValueIfUnseen($expectedOptions, $name, $value);
105 137 }
106 138 }
107   -
108   - return array($ParsedSwitches, $argIndex);
  139 +
  140 + return array($parsedSwitches, $argIndex);
109 141 }
110 142
111   - protected function parseShortSwitch($args, $argIndex, ParsedSwitches $ParsedSwitches, DefinedSwitches $expectedOptions)
  143 + /**
  144 + * Take a short switch, and see if it is one that we understand
  145 + *
  146 + * This parser supports short switches of the form:
  147 + *
  148 + * * -x
  149 + * where 'x' is a single switch
  150 + * * -xfred
  151 + * where 'x' is a single switch, and 'fred' is its argument
  152 + * * -x fred
  153 + * where 'x' is a single switch, and 'fred' is its argument
  154 + * * -xyz
  155 + * where 'x', 'y' and 'z' are all short switches
  156 + * * -xyz fred
  157 + * where 'x', 'y' and 'z' are all short switches, and 'fred'
  158 + * is the argument to switch 'z'
  159 + *
  160 + * These are all of the common short switch types traditionally
  161 + * supported by UNIX-like systems
  162 + *
  163 + * All successfully parsed short switches are added to the
  164 + * $parsedSwitches object.
  165 + *
  166 + * Any unexpected switches, or if there are any switches which are
  167 + * missing their required argument, will trigger an exception
  168 + *
  169 + * @param array $args
  170 + * The array of command-line arguments (normally $argv)
  171 + * @param int $argIndex
  172 + * The current index inside $args to search from
  173 + * @param ParsedSwitches $parsedSwitches
  174 + * The list of switches we have already parsed
  175 + * @param DefinedSwitches $expectedOptions
  176 + * The list of command-line switches that we support
  177 + * @return int
  178 + * The new value for $argIndex
  179 + */
  180 + protected function parseShortSwitch($args, $argIndex, ParsedSwitches $parsedSwitches, DefinedSwitches $expectedOptions)
112 181 {
  182 + // catch programming errors
  183 + Contract::Preconditions(function() use ($args, $argIndex, $expectedOptions)
  184 + {
  185 + Contract::RequiresValue($args, is_array($args), '$args must be array');
  186 + Contract::RequiresValue($args, count($args) > 0, '$args cannot be an empty array');
  187 +
  188 + Contract::RequiresValue($argIndex, is_integer($argIndex), '$argIndex must be an integer');
  189 + Contract::RequiresValue($argIndex, count($args) > $argIndex, '$argIndex cannot be beyond the end of $args');
  190 +
  191 + Contract::RequiresValue($expectedOptions, count($expectedOptions->getSwitches()) > 0, '$expectedOptions must have some switches defined');
  192 + });
  193 +
113 194 // $args[$argIndex] contains one or more short switches,
114 195 // which we expect to be defined in $expectedOptions
115 196
@@ -131,7 +212,7 @@ protected function parseShortSwitch($args, $argIndex, ParsedSwitches $ParsedSwit
131 212
132 213 // should it have an argument?
133 214 if ($switch->testHasArgument())
134   - {
  215 + {
135 216 // yes, but it may be optional
136 217 // are we the first switch in this string?
137 218 if ($j == 1)
@@ -151,7 +232,7 @@ protected function parseShortSwitch($args, $argIndex, ParsedSwitches $ParsedSwit
151 232 }
152 233 else
153 234 {
154   - // are we at the end of the list of switches?
  235 + // are we at the end of the list of switches?
155 236 if ($j != $switchStringLength - 1)
156 237 {
157 238 // no, we are not
@@ -164,7 +245,7 @@ protected function parseShortSwitch($args, $argIndex, ParsedSwitches $ParsedSwit
164 245 }
165 246
166 247 // var_dump("Adding switch " . $switch->name);
167   - $ParsedSwitches->addSwitch($expectedOptions, $switch->name, $arg);
  248 + $parsedSwitches->addSwitch($expectedOptions, $switch->name, $arg);
168 249 }
169 250
170 251 // increment our counter through the args
@@ -174,8 +255,49 @@ protected function parseShortSwitch($args, $argIndex, ParsedSwitches $ParsedSwit
174 255 return $argIndex;
175 256 }
176 257
177   - protected function parseLongSwitch($args, $argIndex, ParsedSwitches $ParsedSwitches, DefinedSwitches $expectedOptions)
  258 + /**
  259 + * Take a long switch, and see if it is one that we are expecting
  260 + *
  261 + * This parser supports the following long switch formats:
  262 + *
  263 + * * --switch
  264 + * * --switch=<argument>
  265 + * * --switch <argument>
  266 + *
  267 + * These are all of the common long switch formats traditionally
  268 + * supported on UNIX-like systems
  269 + *
  270 + * All successfully parsed long switches are added to the
  271 + * $parsedSwitches object
  272 + *
  273 + * Any unexpected long switches, or any long switches that are
  274 + * missing their argument, will trigger an exception
  275 + *
  276 + * @param array $args
  277 + * The array of command-line arguments (normally $argv)
  278 + * @param int $argIndex
  279 + * The current index inside $args to search from
  280 + * @param ParsedSwitches $parsedSwitches
  281 + * The list of switches that we have already parsed
  282 + * @param DefinedSwitches $expectedOptions
  283 + * The list of command-line switches that we support
  284 + * @return int
  285 + * The new value of $argIndex
  286 + */
  287 + protected function parseLongSwitch($args, $argIndex, ParsedSwitches $parsedSwitches, DefinedSwitches $expectedOptions)
178 288 {
  289 + // catch programming errors
  290 + Contract::Preconditions(function() use ($args, $argIndex, $expectedOptions)
  291 + {
  292 + Contract::RequiresValue($args, is_array($args), '$args must be array');
  293 + Contract::RequiresValue($args, count($args) > 0, '$args cannot be an empty array');
  294 +
  295 + Contract::RequiresValue($argIndex, is_integer($argIndex), '$argIndex must be an integer');
  296 + Contract::RequiresValue($argIndex, count($args) > $argIndex, '$argIndex cannot be beyond the end of $args');
  297 +
  298 + Contract::RequiresValue($expectedOptions, count($expectedOptions->getSwitches()) > 0, '$expectedOptions must have some switches defined');
  299 + });
  300 +
179 301 // $args[i] contains a long switch, and might contain
180 302 // a parameter too
181 303 $equalsPos = strpos($args[$argIndex], '=');
@@ -217,14 +339,54 @@ protected function parseLongSwitch($args, $argIndex, ParsedSwitches $ParsedSwitc
217 339 // increment to the next item in the list
218 340 $argIndex++;
219 341
220   - $ParsedSwitches->addSwitch($expectedOptions, $switch->name, $arg);
  342 + $parsedSwitches->addSwitch($expectedOptions, $switch->name, $arg);
221 343
222 344 // all done
223 345 return $argIndex;
224 346 }
225 347
  348 + /**
  349 + * Examine the command line for a (possibly optional) argument
  350 + * for a switch that we have just found on that command line
  351 + *
  352 + * @param array $args
  353 + * The array of command-line arguments (normally $argv)
  354 + * @param int $argIndex
  355 + * The current index inside $args to search from.
  356 + * This may be just beyond the end of the command-line args
  357 + * if the last command-line argument is a switch.
  358 + * @param int $startFrom
  359 + * The offset inside $args[$argIndex] where the argument string
  360 + * starts
  361 + * @param DefinedSwitch $switch
  362 + * The command-line switch that may need an argument
  363 + * @param string $switchSeen
  364 + * The actual switch we found on the command-line
  365 + * @return array(string, int)
  366 + * The argument we have parsed, plus the new value of $argIndex
  367 + */
226 368 protected function parseArgument($args, $argIndex, $startFrom, DefinedSwitch $switch, $switchSeen)
227 369 {
  370 + // catch programming errors
  371 + Contract::Preconditions(function() use ($args, $argIndex, $startFrom, $switchSeen)
  372 + {
  373 + Contract::RequiresValue($args, is_array($args), '$args must be array');
  374 + Contract::RequiresValue($args, count($args) > 0, '$args cannot be an empty array');
  375 +
  376 + Contract::RequiresValue($argIndex, is_integer($argIndex), '$argIndex must be an integer');
  377 + Contract::RequiresValue($argIndex, count($args) >= $argIndex, '$argIndex cannot be more than +1 beyond the end of $args');
  378 +
  379 + // this is a conditional test because it is legal
  380 + // for $args[$argindex] to be unset()
  381 + if (isset($args[$argIndex]))
  382 + {
  383 + Contract::RequiresValue($startFrom, $startFrom <= strlen($args[$argIndex]), '$startFrom cannot be more than +1 beyond the end of $args[$argIndex]');
  384 + }
  385 +
  386 + Contract::RequiresValue($switchSeen, is_string($switchSeen), '$switchSeen must be a string');
  387 + Contract::RequiresValue($switchSeen, strlen($switchSeen) > 0, '$switchSeen cannot be an empty string');
  388 + });
  389 +
228 390 // initialise the return value
229 391 $arg = null;
230 392
47 src/php/Phix_Project/CommandLineLib/DefinedArg.php
@@ -46,15 +46,49 @@
46 46
47 47 namespace Phix_Project\CommandLineLib;
48 48
  49 +use Phix_Project\ContractLib\Contract;
49 50 use Phix_Project\ValidationLib\Validator;
50 51
  52 +/**
  53 + * Represents the definition of a single argument for a single switch
  54 + */
51 55 class DefinedArg
52 56 {
  57 + /**
  58 + * The argument's name
  59 + *
  60 + * @var string
  61 + */
53 62 public $name;
  63 +
  64 + /**
  65 + * The argument's description
  66 + *
  67 + * @var string
  68 + */
54 69 public $desc;
  70 +
  71 + /**
  72 + * The default value of this argument, used if this argument isn't
  73 + * found when the command-line is parsed
  74 + *
  75 + * @var string
  76 + */
55 77 public $defaultValue = null;
  78 +
  79 + /**
  80 + * Is this argument mandatory?
  81 + *
  82 + * @var boolean
  83 + */
56 84 public $isRequired = false;
57 85
  86 + /**
  87 + * How do we validate this argument before the calling app is
  88 + * allowed to see it?
  89 + *
  90 + * @var array(Validator)
  91 + */
58 92 protected $validators = array();
59 93
60 94 /**
@@ -99,7 +133,7 @@ public function setValidator(Validator $validator)
99 133
100 134 /**
101 135 * Is this argument optional?
102   - *
  136 + *
103 137 * @return boolean
104 138 */
105 139 public function testIsOptional()
@@ -123,12 +157,19 @@ public function testIsRequired()
123 157
124 158 /**
125 159 * Does this arg have a specific validator defined?
126   - *
  160 + *
127 161 * @param string $validatorName
128 162 * @return boolean
129 163 */
130 164 public function testMustValidateWith($validatorName)
131 165 {
  166 + // catch programming errors
  167 + Contract::Preconditions(function() use($validatorName)
  168 + {
  169 + Contract::RequiresValue($validatorName, is_string($validatorName), '$validatorName must be a string');
  170 + Contract::RequiresValue($validatorName, strlen($validatorName) > 0, '$validatorName cannot be an empty string');
  171 + });
  172 +
132 173 foreach ($this->validators as $validator)
133 174 {
134 175 if (get_class($validator) == $validatorName)
@@ -167,7 +208,7 @@ public function testIsValid($value)
167 208
168 209 /**
169 210 * Remember the default value for this arg
170   - *
  211 + *
171 212 * @param mixed $value
172 213 * @return DefinedArg $this
173 214 */
159 src/php/Phix_Project/CommandLineLib/DefinedSwitch.php
@@ -47,24 +47,103 @@
47 47 namespace Phix_Project\CommandLineLib;
48 48
49 49 use Phix_Project\ValidationLib\Validator;
  50 +use Phix_Project\ContractLib\Contract;
50 51
  52 +/**
  53 + * Represents a single definition of a single command-line switch
  54 + *
  55 + * We only need one DefinedSwitch to represent all of the valid forms of
  56 + * a single switch
  57 + */
51 58 class DefinedSwitch
52 59 {
  60 + /**
  61 + * The name of the switch
  62 + *
  63 + * @var string
  64 + */
53 65 public $name;
  66 +
  67 + /**
  68 + * The switch's short description
  69 + *
  70 + * This can be used by the calling app, when outputing help to
  71 + * the user
  72 + *
  73 + * @var string
  74 + */
54 75 public $desc;
  76 +
  77 + /**
  78 + * The switch's long description
  79 + *
  80 + * This can be used by the calling app, when outputing help to
  81 + * the user
  82 + *
  83 + * @var string
  84 + */
55 85 public $longdesc;
  86 +
  87 + /**
  88 + * A list of the different short switches
  89 + *
  90 + * Any one switch can have multiple short switches. The classic
  91 + * example is '-?' and '-h', both of which do the same thing in
  92 + * well-behaved command-line applications
  93 + *
  94 + * @var array(string)
  95 + */
56 96 public $shortSwitches = array();
  97 +
  98 + /**
  99 + * A list of the different long switches
  100 + *
  101 + * Any one switch can have multiple long switches. The classic
  102 + * example is '--?' and '--help', both of which do the same thing
  103 + * in well-behaved command-line applications
  104 + *
  105 + * @var array(string)
  106 + */
57 107 public $longSwitches = array();
58 108
59 109 /**
  110 + * The argument (if any) that this switch expects
  111 + *
60 112 * @var DefinedArg
61 113 */
62 114 public $arg = null;
  115 +
  116 + /**
  117 + * A bitset of flags that affect how this switch is parsed by
  118 + * the command-line parser
  119 + *
  120 + * @var int
  121 + */
63 122 public $flags = null;
64 123
  124 + /**
  125 + * The default behaviour flag
  126 + */
65 127 const FLAG_NONE = 0;
  128 +
  129 + /**
  130 + * The behaviour flag for switches that can be repeated on the
  131 + * command-line
  132 + *
  133 + * The classic example of such a switch is '-vvv', where additional
  134 + * repeats make the app more and more verbose
  135 + */
66 136 const FLAG_REPEATABLE = 1;
67 137
  138 + /**
  139 + * Constructor
  140 + *
  141 + * @param string $name
  142 + * The switch's name, used as it's ID everywhere in the
  143 + * command-line parser
  144 + * @param string $desc
  145 + * The switch's short description
  146 + */
68 147 public function __construct($name, $desc)
69 148 {
70 149 $this->name = $name;
@@ -74,7 +153,7 @@ public function __construct($name, $desc)
74 153 /**
75 154 * Set it so that this switch is allowed to be repeated on
76 155 * the command-line by the user
77   - *
  156 + *
78 157 * @return DefinedSwitch
79 158 */
80 159 public function setSwitchIsRepeatable()
@@ -92,6 +171,13 @@ public function setSwitchIsRepeatable()
92 171 */
93 172 public function setWithShortSwitch($switch)
94 173 {
  174 + // catch programming errors
  175 + Contract::PreConditions(function() use ($switch)
  176 + {
  177 + Contract::RequiresValue($switch, is_string($switch), '$switch must be a string');
  178 + Contract::RequiresValue($switch, strlen($switch) > 0, '$switch cannot be an empty string');
  179 + });
  180 +
95 181 // make sure the switch does not start with a '-'!!
96 182 if ($switch{0} == '-')
97 183 {
@@ -112,6 +198,13 @@ public function setWithShortSwitch($switch)
112 198 */
113 199 public function setWithLongSwitch($switch)
114 200 {
  201 + // catch programming errors
  202 + Contract::PreConditions(function() use ($switch)
  203 + {
  204 + Contract::RequiresValue($switch, is_string($switch), '$switch must be a string');
  205 + Contract::RequiresValue($switch, strlen($switch) > 0, '$switch cannot be an empty string');
  206 + });
  207 +
115 208 // make sure the switch does not start with a '-'!!
116 209 if ($switch{0} == '-')
117 210 {
@@ -132,6 +225,16 @@ public function setWithLongSwitch($switch)
132 225 */
133 226 public function setWithOptionalArg($argName, $argDesc)
134 227 {
  228 + // catch programming errors
  229 + Contract::PreConditions(function() use ($argName, $argDesc)
  230 + {
  231 + Contract::RequiresValue($argName, is_string($argName), '$argName must be a string');
  232 + Contract::RequiresValue($argName, strlen($argName) > 0, '$argName cannot be an empty string');
  233 +
  234 + Contract::RequiresValue($argDesc, is_string($argDesc), '$argDesc must be a string');
  235 + Contract::RequiresValue($argDesc, strlen($argDesc) > 0, '$argDesc cannot be an empty string');
  236 + });
  237 +
135 238 $this->arg = new DefinedArg($argName, $argDesc);
136 239 $this->arg->setIsOptional();
137 240 return $this;
@@ -139,25 +242,62 @@ public function setWithOptionalArg($argName, $argDesc)
139 242
140 243 /**
141 244 * Add an argument that this switch requires
142   - *
  245 + *
143 246 * @param string $argName the name of the argument
144 247 * @param string $argDesc the argument's description
145 248 * @return DefinedSwitch
146 249 */
147 250 public function setWithRequiredArg($argName, $argDesc)
148 251 {
  252 + // catch programming errors
  253 + Contract::PreConditions(function() use ($argName, $argDesc)
  254 + {
  255 + Contract::RequiresValue($argName, is_string($argName), '$argName must be a string');
  256 + Contract::RequiresValue($argName, strlen($argName) > 0, '$argName cannot be an empty string');
  257 +
  258 + Contract::RequiresValue($argDesc, is_string($argDesc), '$argDesc must be a string');
  259 + Contract::RequiresValue($argDesc, strlen($argDesc) > 0, '$argDesc cannot be an empty string');
  260 + });
  261 +
149 262 $this->arg = new DefinedArg($argName, $argDesc);
150 263 $this->arg->setIsRequired();
151 264 return $this;
152 265 }
153 266
  267 + /**
  268 + * Set the default value for the argument that this switch
  269 + * expects
  270 + *
  271 + * This only makes sense if the argument is optional
  272 + *
  273 + * @param string $value the default value for the argument
  274 + * @return DefinedSwitch
  275 + */
154 276 public function setArgHasDefaultValueOf($value)
155 277 {
  278 + // catch programming errors
  279 + Contract::PreConditions(function() use ($value)
  280 + {
  281 + Contract::RequiresValue($value, is_string($value), '$value must be a string');
  282 + Contract::RequiresValue($value, strlen($value) > 0, '$value cannot be an empty string');
  283 + });
  284 +
156 285 $this->requireValidArg();
157 286 $this->arg->setDefaultValue($value);
158 287 return $this;
159 288 }
160 289
  290 + /**
  291 + * Add Validator object to the switch's argument
  292 + *
  293 + * The Validators are run, in the order that they've been added,
  294 + * when the command-line parser finds the argument for this switch.
  295 + * They are used to cover the very basics, but sophisticated
  296 + * Validators could be created and added too.
  297 + *
  298 + * @param Validator $validator
  299 + * @return DefinedSwitch
  300 + */
161 301 public function setArgValidator(Validator $validator)
162 302 {
163 303 $this->requireValidArg();
@@ -168,12 +308,19 @@ public function setArgValidator(Validator $validator)
168 308 /**
169 309 * Provide a longer description of this switch, to be shown during
170 310 * the output of extended help information
171   - *
  311 + *
172 312 * @param string $desc
173 313 * @return DefinedSwitch $this
174 314 */
175 315 public function setLongDesc($desc)
176 316 {
  317 + // catch programming errors
  318 + Contract::PreConditions(function() use ($desc)
  319 + {
  320 + Contract::RequiresValue($desc, is_string($desc), '$desc must be a string');
  321 + Contract::RequiresValue($desc, strlen($desc) > 0, '$desc cannot be an empty string');
  322 + });
  323 +
177 324 $this->longdesc = $desc;
178 325 return $this;
179 326 }
@@ -286,6 +433,12 @@ public function testIsRepeatable()
286 433 return false;
287 434 }
288 435
  436 + /**
  437 + * Return a list of the different forms of this switch, in a form
  438 + * that is suitable for building up human-readable help messages
  439 + *
  440 + * @return array
  441 + */
289 442 public function getHumanReadableSwitchList()
290 443 {
291 444 $return = array();
100 src/php/Phix_Project/CommandLineLib/DefinedSwitches.php
@@ -46,14 +46,35 @@
46 46
47 47 namespace Phix_Project\CommandLineLib;
48 48
  49 +use Phix_Project\ContractLib\Contract;
  50 +
  51 +/**
  52 + * Container for all of the DefinedSwitches that we expect to find in the
  53 + * same place when parsing a command-line
  54 + */
49 55 class DefinedSwitches
50 56 {
51   - const FLAG_NONE = 0;
52   - const FLAG_HASPARAM = 1;
53   - const FLAG_CANBEDUPLICATED = 2;
54   -
  57 + /**
  58 + * A dynamic cache of all of the short switches that have been
  59 + * defined
  60 + *
  61 + * @var array(DefinedSwitch)
  62 + */
55 63 public $shortSwitches = array();
  64 +
  65 + /**
  66 + * A dynamic cache of all of the long switches that have been
  67 + * defined
  68 + *
  69 + * @var array(DefinedSwitch)
  70 + */
56 71 public $longSwitches = array();
  72 +
  73 + /**
  74 + * A list of all of the switches that have been defined
  75 + *
  76 + * @var array(DefinedSwitch)
  77 + */
57 78 public $switches = array();
58 79
59 80 /**
@@ -64,13 +85,22 @@ class DefinedSwitches
64 85
65 86 /**
66 87 * Add a switch to the list of allowed switches
67   - *
  88 + *
68 89 * @param string $name
69 90 * @param string $desc
70   - * @return DefinedSwitch
  91 + * @return DefinedSwitch
71 92 */
72 93 public function addSwitch($name, $desc)
73 94 {
  95 + Contract::PreConditions(function() use ($name, $desc)
  96 + {
  97 + Contract::RequiresValue($name, is_string($name), '$name must be a string');
  98 + Contract::RequiresValue($name, strlen($name) > 0, '$name cannot be an empty string');
  99 +
  100 + Contract::RequiresValue($desc, is_string($desc), '$desc must be a string');
  101 + Contract::RequiresValue($desc, strlen($desc) > 0, '$desc cannot be an empty string');
  102 + });
  103 +
74 104 // note that our cache of introspected switches
75 105 // is now invalid
76 106 $this->allSwitchesLoaded = false;
@@ -83,6 +113,12 @@ public function addSwitch($name, $desc)
83 113 return $this->switches[$name];
84 114 }
85 115
  116 + /**
  117 + * Do we have the definition of a specific switch?
  118 + *
  119 + * @param string $switchName
  120 + * @return boolean
  121 + */
86 122 public function testHasSwitchByName($switchName)
87 123 {
88 124 if (isset($this->switches[$switchName]))
@@ -92,7 +128,13 @@ public function testHasSwitchByName($switchName)
92 128
93 129 return false;
94 130 }
95   -
  131 +
  132 + /**
  133 + * Do we have a given short switch?
  134 + *
  135 + * @param string $switch
  136 + * @return boolean
  137 + */
96 138 public function testHasShortSwitch($switch)
97 139 {
98 140 // make sure the cache is complete
@@ -106,6 +148,12 @@ public function testHasShortSwitch($switch)
106 148 return false;
107 149 }
108 150
  151 + /**
  152 + * Get the definition of a given short switch
  153 + *
  154 + * @param string $switchName
  155 + * @return DefinedSwitch
  156 + */
109 157 public function getShortSwitch($switchName)
110 158 {
111 159 // make sure the cache is complete
@@ -119,6 +167,12 @@ public function getShortSwitch($switchName)
119 167 throw new \Exception("unknown switch $switch");
120 168 }
121 169
  170 + /**
  171 + * Do we have a definition for a given long switch?
  172 + *
  173 + * @param string $switch
  174 + * @return boolean
  175 + */
122 176 public function testHasLongSwitch($switch)
123 177 {
124 178 // make sure the cache is complete
@@ -132,6 +186,12 @@ public function testHasLongSwitch($switch)
132 186 return false;
133 187 }
134 188
  189 + /**
  190 + * Get the definition for a long switch
  191 + *
  192 + * @param string $switch
  193 + * @return DefinedSwitch
  194 + */
135 195 public function getLongSwitch($switch)
136 196 {
137 197 // make sure the cache is complete
@@ -145,6 +205,12 @@ public function getLongSwitch($switch)
145 205 throw new \Exception("unknown switch $switch");
146 206 }
147 207
  208 + /**
  209 + * Get the definition of a switch, by its name
  210 + *
  211 + * @param string $name
  212 + * @return DefinedSwitch
  213 + */
148 214 public function getSwitchByName($name)
149 215 {
150 216 if (isset($this->switches[$name]))
@@ -155,11 +221,22 @@ public function getSwitchByName($name)
155 221 throw new \Exception("unknown switch $switch");
156 222 }
157 223
  224 + /**
  225 + * Get the complete list of defined switches
  226 + *
  227 + * @return array(DefinedSwitch)
  228 + */
158 229 public function getSwitches()
159 230 {
160 231 return $this->switches;
161 232 }
162 233
  234 + /**
  235 + * Get a complete list of default values for all switches that
  236 + * take arguments
  237 + *
  238 + * @return array(string)
  239 + */
163 240 public function getDefaultValues()
164 241 {
165 242 $return = array();
@@ -179,6 +256,15 @@ public function getDefaultValues()
179 256 return $return;
180 257 }
181 258
  259 + /**
  260 + * Get an array of all of the switches in this definition, sorted
  261 + * into the right order to display in a help message
  262 + *
  263 + * This is for apps like phix, which display a help message that