{"action":"created","issue":{"url":"https://api.github.com/repos/thephpleague/climate/issues/49","labels_url":"https://api.github.com/repos/thephpleague/climate/issues/49/labels{/name}","comments_url":"https://api.github.com/repos/thephpleague/climate/issues/49/comments","events_url":"https://api.github.com/repos/thephpleague/climate/issues/49/events","html_url":"https://github.com/thephpleague/climate/pull/49","id":54714192,"number":49,"title":"Add CLI argument support","user":{"login":"klaude","id":45157,"avatar_url":"https://avatars.githubusercontent.com/u/45157?v=3","gravatar_id":"","url":"https://api.github.com/users/klaude","html_url":"https://github.com/klaude","followers_url":"https://api.github.com/users/klaude/followers","following_url":"https://api.github.com/users/klaude/following{/other_user}","gists_url":"https://api.github.com/users/klaude/gists{/gist_id}","starred_url":"https://api.github.com/users/klaude/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/klaude/subscriptions","organizations_url":"https://api.github.com/users/klaude/orgs","repos_url":"https://api.github.com/users/klaude/repos","events_url":"https://api.github.com/users/klaude/events{/privacy}","received_events_url":"https://api.github.com/users/klaude/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":10,"created_at":"2015-01-18T23:14:24Z","updated_at":"2015-02-27T18:00:01Z","closed_at":null,"pull_request":{"url":"https://api.github.com/repos/thephpleague/climate/pulls/49","html_url":"https://github.com/thephpleague/climate/pull/49","diff_url":"https://github.com/thephpleague/climate/pull/49.diff","patch_url":"https://github.com/thephpleague/climate/pull/49.patch"},"body":"I've been using CLImate at work lately. I love it, but I hated having to use `getopt()` or `$argv` to talk to my command line arguments. I looked into various console libraries, but they also did output in addition to argument processing. I saw in #29 you said you weren't opposed to CLImate doing argument parsing, so I added it. I tried to follow the existing API and internal code conventions as much as I can.\r\n\r\nThis change adds an argument manager to the CLImate object. Users can define arguments against the argument manager, parse arguments on the command line into those defined arguments, retrieve CLI argument values, and generate a usage statement based on user defined arguments.\r\n\r\nAn argument is defined as something that's passed along to a script, either in a short form (\"-a foo\"), long form (\"--foo bar\"), both, or neither to process arguments after the prefixed argument list. Arguments can be:\r\n\r\n* marked as required\r\n* have default values\r\n* have their values cast to string, int, float, or boolean data types\r\n* set true if they're defined on the command line (for cases where a value isn't needed like \"-f\" to force or \"--help\" to show a usage statement).\r\n* have values set in \"-key value\" or \"-key=value\" form on the command line.\r\n\r\nArguments that are required but not defined on the command line generate an exception that's sent back to calling code.\r\n\r\nHere's a simple example. Assume you have a script that pushes a local directory to some SSH server. That script needs a username, a password, a host, maybe an option to print usage, one to overwrite existing files, maybe a verbosity option, and a path to push to my server. I may store the username and hostname internally, but let users enter their own if they like, but they have to provide a password. I'd probably call it by running:\r\n```\r\nphp ./pusher.php -u myusername -p secret -h hostname --force -v ./push_me\r\n```\r\nThat script would look like:\r\n```php\r\n<?php\r\n\r\nrequire __DIR__ . '/vendor/autoload.php';\r\n\r\nuse League\\CLImate\\CLImate;\r\n\r\n$climate = new CLImate;\r\n$climate->description('Awesome SCP Pusher v0.01');\r\n$climate->arguments->add([\r\n    'user' => [\r\n        'prefix' => 'u',\r\n        'longPrefix' => 'user',\r\n        'description' => 'SSH Username',\r\n        'defaultValue' => 'myuser',\r\n    ],\r\n    'password' => [\r\n        'prefix' => 'p',\r\n        'longPrefix' => 'password',\r\n        'description' => 'SSH Password',\r\n        'required' => true,\r\n    ],\r\n    'host' => [\r\n        'prefix' => 'H',\r\n        'longPrefix' => 'host',\r\n        'description' => 'SSH Hostname',\r\n        'defaultValue' => 'myhost.local'\r\n    ],\r\n    'force' => [\r\n        'prefix' => 'f',\r\n        'longPrefix' => 'force',\r\n        'description' => 'Overwrite files on copy',\r\n        'definedOnly' => true,\r\n    ],\r\n    'verbose' => [\r\n        'prefix' => 'v',\r\n        'longPrefix' => 'verbose',\r\n        'description' => 'Verbose output',\r\n        'definedOnly' => true,\r\n    ],\r\n    'help' => [\r\n        'prefix' => 'h',\r\n        'longPrefix' => 'help',\r\n        'description' => 'Print a usage statement',\r\n        'definedOnly' => true,\r\n    ],\r\n    'path' => [\r\n        'description' => 'The path to push',\r\n    ],\r\n]);\r\n\r\n// Print a usage statement on --help\r\nif ($climate->arguments->defined('help')) {\r\n    $climate->usage();\r\n    exit();\r\n}\r\n\r\n// Parse arguments.\r\ntry {\r\n    $climate->arguments->parse();\r\n} catch (Exception $e) {\r\n    // Let us know if arguments were missing.\r\n    $climate->error($e->getMessage());\r\n    $climate->br();\r\n    $climate->usage();\r\n    exit();\r\n}\r\n\r\n// Echo instead of push.\r\n$climate->out('Username: ' . $climate->arguments->get('user'));\r\n$climate->out('Hostname: ' . $climate->arguments->get('host'));\r\n\r\nif ($climate->arguments->get('force')) {\r\n    $climate->out(\"I'm going to overwrite on copy.\");\r\n}\r\n```\r\nHere's an execution that echo's the usage statement:\r\n```\r\n$ php ./pusher.php -h\r\nAwesome SCP Pusher v0.01\r\n\r\nUsage: ./pusher.php [-f, --force] [-H host, --host host (default: myhost.local)] [-h, --help] [-p password, --password password] [-u user, --user user (default: myuser)] [-v, --verbose] [path]\r\n\r\nRequired Arguments:\r\n\t-p password, --password password\r\n\t\tSSH Password\r\n\r\nOptional Arguments:\r\n\t-f, --force\r\n\t\tOverwrite files on copy\r\n\t-h, --help\r\n\t\tPrint a usage statement\r\n\t-H host, --host host (default: myhost.local)\r\n\t\tSSH Hostname\r\n\t-u user, --user user (default: myuser)\r\n\t\tSSH Username\r\n\t-v, --verbose\r\n\t\tVerbose output\r\n```\r\nAnd an execution that spits out those debug `out()`s:\r\n```\r\n$ php ./pusher.php -u bob -p secret -f ./my-dir\r\nUsername: bob\r\nHostname: myhost.local\r\nI'm going to overwrite on copy.\r\n```\r\nThe new changes pass all tests with 100% new code coverage, minus one line that needs to talk to the global scope to get `$argv`. Tests use a mocked `$argv`.\r\n\r\nLet me know if this looks okay to you. If it does and gets merged I can write public-facing documentation for this too. "},"comment":{"url":"https://api.github.com/repos/thephpleague/climate/issues/comments/76441159","html_url":"https://github.com/thephpleague/climate/pull/49#issuecomment-76441159","issue_url":"https://api.github.com/repos/thephpleague/climate/issues/49","id":76441159,"user":{"login":"klaude","id":45157,"avatar_url":"https://avatars.githubusercontent.com/u/45157?v=3","gravatar_id":"","url":"https://api.github.com/users/klaude","html_url":"https://github.com/klaude","followers_url":"https://api.github.com/users/klaude/followers","following_url":"https://api.github.com/users/klaude/following{/other_user}","gists_url":"https://api.github.com/users/klaude/gists{/gist_id}","starred_url":"https://api.github.com/users/klaude/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/klaude/subscriptions","organizations_url":"https://api.github.com/users/klaude/orgs","repos_url":"https://api.github.com/users/klaude/repos","events_url":"https://api.github.com/users/klaude/events{/privacy}","received_events_url":"https://api.github.com/users/klaude/received_events","type":"User","site_admin":false},"created_at":"2015-02-27T18:00:01Z","updated_at":"2015-02-27T18:00:01Z","body":"> I'm probably going to change defaultValue to just default, and it took me a little bit to figure out what definedOnly was. Is noValue a better name for that?\r\n\r\nI think that works, @joetannenbaum, and I dig your refactors. Let me know if there's anything you'd like me to do."}}