Repository: cookiecutter/cookiecutter

Issue:
Idea: have a way to specify context via command line
Something like repeat arguments:

```
cookiecutter mytemplate -Cname=my-project -Cgithub-user=ionelmc
```

Or maybe the whole json?

```
cookiecutter mytemplate --context='{"name": "my-project", "github-user": "ionelmc"}'
```

Or variable arguments?

```
cookiecutter mytemplate --context-name=my-project --context-github-user=ionelmc
```



Discussion:

Resolving patch:
diff --git a/cookiecutter/cli.py b/cookiecutter/cli.py
--- a/cookiecutter/cli.py
+++ b/cookiecutter/cli.py
@@ -38,9 +38,23 @@ def version_msg():
     return message.format(location, python_version)
 
 
+def validate_extra_context(ctx, param, value):
+    for s in value:
+        if '=' not in s:
+            raise click.BadParameter(
+                'EXTRA_CONTEXT should contain items of the form key=value; '
+                "'{}' doesn't match that form".format(s)
+            )
+
+    # Convert tuple -- e.g.: (u'program_name=foobar', u'startsecs=66')
+    # to dict -- e.g.: {'program_name': 'foobar', 'startsecs': '66'}
+    return dict(s.split('=', 1) for s in value) or None
+
+
 @click.command(context_settings=dict(help_option_names=[u'-h', u'--help']))
 @click.version_option(__version__, u'-V', u'--version', message=version_msg())
 @click.argument(u'template')
+@click.argument(u'extra_context', nargs=-1, callback=validate_extra_context)
 @click.option(
     u'--no-input', is_flag=True,
     help=u'Do not prompt for parameters and only use cookiecutter.json '
@@ -75,8 +89,8 @@ def version_msg():
     u'--default-config', is_flag=True,
     help=u'Do not load a config file. Use the defaults instead'
 )
-def main(template, no_input, checkout, verbose, replay, overwrite_if_exists,
-         output_dir, config_file, default_config):
+def main(template, extra_context, no_input, checkout, verbose, replay,
+         overwrite_if_exists, output_dir, config_file, default_config):
     """Create a project from a Cookiecutter project template (TEMPLATE)."""
     if verbose:
         logging.basicConfig(
@@ -101,6 +115,7 @@ def main(template, no_input, checkout, verbose, replay, overwrite_if_exists,
 
         cookiecutter(
             template, checkout, no_input,
+            extra_context=extra_context,
             replay=replay,
             overwrite_if_exists=overwrite_if_exists,
             output_dir=output_dir,


Test patch:
diff --git a/tests/fake-repo-pre/{{cookiecutter.repo_name}}/README.rst b/tests/fake-repo-pre/{{cookiecutter.repo_name}}/README.rst
--- a/tests/fake-repo-pre/{{cookiecutter.repo_name}}/README.rst
+++ b/tests/fake-repo-pre/{{cookiecutter.repo_name}}/README.rst
@@ -2,4 +2,6 @@
 Fake Project
 ============
 
+Project name: **{{ cookiecutter.project_name }}**
+
 Blah!!!!
diff --git a/tests/test_cli.py b/tests/test_cli.py
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -52,6 +52,8 @@ def test_cli():
     result = runner.invoke(main, ['tests/fake-repo-pre/', '--no-input'])
     assert result.exit_code == 0
     assert os.path.isdir('fake-project')
+    with open(os.path.join('fake-project', 'README.rst')) as f:
+        assert 'Project name: **Fake Project**' in f.read()
 
 
 @pytest.mark.usefixtures('remove_fake_project_dir')
@@ -59,6 +61,8 @@ def test_cli_verbose():
     result = runner.invoke(main, ['tests/fake-repo-pre/', '--no-input', '-v'])
     assert result.exit_code == 0
     assert os.path.isdir('fake-project')
+    with open(os.path.join('fake-project', 'README.rst')) as f:
+        assert 'Project name: **Fake Project**' in f.read()
 
 
 @pytest.mark.usefixtures('remove_fake_project_dir')
@@ -82,7 +86,8 @@ def test_cli_replay(mocker):
         replay=True,
         overwrite_if_exists=False,
         output_dir='.',
-        config_file=config.USER_CONFIG_PATH
+        config_file=config.USER_CONFIG_PATH,
+        extra_context=None
     )
 
 
@@ -117,7 +122,8 @@ def test_cli_exit_on_noinput_and_replay(mocker):
         replay=True,
         overwrite_if_exists=False,
         output_dir='.',
-        config_file=config.USER_CONFIG_PATH
+        config_file=config.USER_CONFIG_PATH,
+        extra_context=None
     )
 
 
@@ -151,7 +157,8 @@ def test_run_cookiecutter_on_overwrite_if_exists_and_replay(
         replay=True,
         overwrite_if_exists=True,
         output_dir='.',
-        config_file=config.USER_CONFIG_PATH
+        config_file=config.USER_CONFIG_PATH,
+        extra_context=None
     )
 
 
@@ -206,7 +213,8 @@ def test_cli_output_dir(mocker, output_dir_flag, output_dir):
         replay=False,
         overwrite_if_exists=False,
         output_dir=output_dir,
-        config_file=config.USER_CONFIG_PATH
+        config_file=config.USER_CONFIG_PATH,
+        extra_context=None
     )
 
 
@@ -246,7 +254,8 @@ def test_user_config(mocker, user_config_path):
         replay=False,
         overwrite_if_exists=False,
         output_dir='.',
-        config_file=user_config_path
+        config_file=user_config_path,
+        extra_context=None
     )
 
 
@@ -271,7 +280,8 @@ def test_default_user_config_overwrite(mocker, user_config_path):
         replay=False,
         overwrite_if_exists=False,
         output_dir='.',
-        config_file=None
+        config_file=None,
+        extra_context=None
     )
 
 
@@ -294,7 +304,8 @@ def test_default_user_config(mocker):
         replay=False,
         overwrite_if_exists=False,
         output_dir='.',
-        config_file=None
+        config_file=None,
+        extra_context=None
     )
 
 
@@ -343,3 +354,22 @@ def test_echo_unknown_extension_error(tmpdir):
     assert result.exit_code == 1
 
     assert 'Unable to load extension: ' in result.output
+
+
+@pytest.mark.usefixtures('remove_fake_project_dir')
+def test_cli_extra_context():
+    result = runner.invoke(main, ['tests/fake-repo-pre/', '--no-input', '-v',
+                                  'project_name=Awesomez'])
+    assert result.exit_code == 0
+    assert os.path.isdir('fake-project')
+    with open(os.path.join('fake-project', 'README.rst')) as f:
+        assert 'Project name: **Awesomez**' in f.read()
+
+
+@pytest.mark.usefixtures('remove_fake_project_dir')
+def test_cli_extra_context_invalid_format():
+    result = runner.invoke(main, ['tests/fake-repo-pre/', '--no-input', '-v',
+                                  'ExtraContextWithNoEqualsSoInvalid'])
+    assert result.exit_code == 2
+    assert 'Error: Invalid value for "extra_context"' in result.output
+    assert 'should contain items of the form key=value' in result.output