{
"info": {
"author": "aslape",
"author_email": "aslape@atlassian.com",
"bugtrack_url": null,
"classifiers": [
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3"
],
"description": "\n# CCExtender -- Cookiecutter with branching builds\n\nhttps://github.com/asecurityteam/ccextender\n\n\n\n- [CCExtender](#CCExtender)\n - [Overview](#overview)\n - [Commands](#commands)\n - [Installation](#installation)\n - [Usage](#usage)\n - [Configuration](#configuration)\n - [Setting up your templates](#settingupyourtemplates)\n - [Additive variable assignment](#additivevariableassignment)\n - [Hello World](#helloworld)\n - [Common Issues](#commonissues)\n - [Extending CCExtender](#extendingccextender)\n\n\n\n\n## Overview\n\nCCExtender, or CCX, is a repository construction application built on top of cookiecutter (a templating program). Its primary purpose is to provide a framework for logical, branching build paths that can create customized repositories based on user decisions.\n\n###### How CCExtender works, briefly\n1. You'll create various cookiecutter templates containing the building blocks of your typical repository setup. Using a few new techniques introduced by CCExtender, your templates will be ready to receive large and complicated packs of changes (change-packs).\n2. You'll write a configuration file where you define these so-called change-packs, along with the user prompts and build logic that will be used in constructing your future repos.\n3. From this point on, you'll simply run CCExtender against your configuration file whenever you want to create a repository. By answering the prompts written in the configuration file, you'll guide the build of each repository to fit your needs. CCExtender will save you time by making various configuration changes automatically, while also making sure your repository only has exactly what you need.\n\n\n###### What CCExtender is good for:\n- Developers that regularly create new repositories with similar structure and content\n- Anyone who wants to automate the creation of templatized files and folders\n\n###### What CCExtender is bad for:\n- One time repository creation. CCExtender is a way to front-load the work of creating many repositories, and won't bring much value to teams that only need one or two similar repos.\n\n\n## Commands\n\n```bash\nccextender\n help\n --ccx_config, -c \n --std_template, -s #(See #standards)\n --test_mode, -t # Activates test mode (which disables prompts for stdin)\n --outdir, -o \n```\n\n\n## Installation\n\n#### Prequisites:\n\nPython 3\n\noyaml - a python package allowing for yaml files to read as an ordered dictionary\n\ncookiecutter - a templating application\n\n\n```bash\npip3 install cookiecutter oyaml\n```\n\n```bash\npip3 install ccextender\n```\n\n\n## Usage\n\nCCExtender requires a yaml configuration file and at least one cookiecutter template directory to function.\n\nFor more information on how to set up cookiecutter templates, visit: https://cookiecutter.readthedocs.io/en/latest/first_steps.html\n\nFor how to setup a configuration file, read the #Configuration section of this doc.\n\n#### 1. Create a configuration file.\n\nYour configuration file will contain the logic for your interactive build, directions to the templates you plan to use, and what templatized changes should be associated with your decisions.\n\nBy default, ccextender will look for a file named ccextender.yaml in your current directory, but you can direct it to read any file through the --ccx_config flag:\n\n```bash\nccextender --ccx_config=/Users/me/Documents/myconfig.yaml\n```\n\nYour configuration file must follow the YAML format. To see how to write a CCExtender config file, see [Configuration](#Configuration).\n\n#### 2. Run CCExtender\n\nOnce the configuration file is created, CCExtender will do the rest. All that remains is for you to respond to the prompts established in your YAML file.\n\nExamples of use:\n\nTo read in a particular configuration file and write the output to a different directory:\n```bash\nccextender -c /Users/me/Documents/SecDev_Build.yaml -o /python/src/github.com/asecurityteam/\n```\n\nTo see available commands:\n```bash\nccextender help\n```\n\nTo use a specific template for the default values for your standards:\n```bash\nccextender -s template-standards\n```\n\nNOTE: This isn't a path or location of the template, but merely the template's directory name. You should add the templates location in the configuration file under the \"locations\" section. See [Configuration](#Configuration).\n\nTo run using the default variables:\n\n```bash\nccextender\n```\nThe default values for each command is:\n\n```bash\n--ccx_config, -c = ccextender.yaml\n--std_template, -s = template-standards\n--test_mode, -t = None\n--outdir, -o = .\n```\n\n## Configuration\n\n```YAML\nCCX_Version: X.X # Non-essential as of version 1.1\nstandard-context: # Standard variables that exist across most or all templates involved in build. This saves us the trouble of needing to re-type them for each template.\n project_name: \"Example default name\" # \"project_name\" corresponds to a variable in a cookiecutter.json file, and the right side string is the default value. Users will have the option to replace this default value for all standard-context variables.\n project_slug: \"example-default-slug\"\n project_namespace: \"example-team\"\ndecisions: # A series of decision blocks where the user responds to prompts with numerical choices, which then mark particular change-packs to be added into the repository.\n decision-block-one: # Decision blocks consist of a query block, followed by a list of choice blocks\n query: # query blocks contain the prompt given to the end-user and possibly some logic flags to indicate whether or not the decision-block should be run.\n prompt: \"Example question asking user to pick between [1] and [2]?\" # CCExtender reads YAML files as ordered dicts, so the order of each choice is preserved, thus \"choice-one\" corresponds to 1 and \"choice-two\" corresponds to 2. A response of 0 will skip the decision block, and a blank response defaults to 1 (as of version 1.11 of CCX)\n choice-one: # When a choice is selected, all change-packs under its block will be added to the new repository.\n - change-pack-A # tag corresponds to the change-pack of the same name in the change-packs block\n - change-pack-B\n choice-two:\n - change-pack-C\n decision-block-two:\n query:\n prompt: \"Example question asking user to pick between [1], [2], or [3]?\"\n exclude-if: # excludes this entire decision block if the user has picked choice-one in a previous decision block\n - choice-one\n 2nd-choice-one:\n - change-pack-D\n 2nd-choice-two:\n - change-pack-E\n 2nd-choice-three:\n - change-pack-D\n - change-pack-F\n decision-block-three:\n query:\n prompt: \"Would you like to pick [1]?\"\n include-if: # includes the decision block only if the user has selected both the of below choices\n - 2nd-choice-three\n - choice-two\n exclude-if:\n - choice-two\n 3rd-choice-one:\n - change-pack-G\n change-packs: # change packs define which templates should be added into the repository, and in some cases, what variables should be set to for those templates.\n change-pack-A:\n template-makefile: # Refers to the template-makefile listed in the locations block\n registry_link: \"REGISTRY := registry.hub.docker.com\" # corresponds to a variable called \"registry_link\" defined in the template-makefile's cookiecutter.json\n environment: \"ENVIRON := dev\"\n template-dockerfile:\n pip_install: \"pip install cookiecutter\"\n change-pack-B:\n template-dockerfile:\n pip_install: \"pip install setuptools\" #Since you can pick both change-pack-A and change-pack-B, which both set a different value for the same variable in template-dockerfile, how do we know which value is used? The answer is both. In the Dockerfile stored in template-dockerfile, the templatized variable {{cookiecutter.pip_install}} will be replaced with two lines of code (or more if other changepacks also set a value to pip_install). Essentially, when more than one value is set to a variable, it is appended to the current value as \"\\nnew value\". Thus, in this case, where change-packs A and B are both used, we end up with \"pip install cookiecutter\\npip install setuptools\" creating two lines in the place of the cookiecutter variable.\n change-pack-C:\n template-makefile:\n registry_link: \"REGISTRY := privateregistry.hub.docker.com\"\n environment: \"ENVIRON := prod\"\n change-pack-D:\n template-version:\n version: \"v2\"\n change-pack-E:\n template-version:\n version: \"v3\"\n change-pack-F:\n template-travis: # You don't need to set a variable to use a template. Here, this change pack will pull in the travis template without setting any cookiecutter variables. Note, if template-travis has variables in its cookiecutter.json that are not covered by the standard-context, you will need to include variable assignment here.\n change-pack-G:\n template-readme:\n template-license:\n template-dockerfile:\n pip_install: \"pip install pylint\" # adds \"\\npip install pylint\" to current value of pip_install\n locations:\n home:\n - https://github.com/yourtemplates/ # you can reference github template repos directly\n local_home:\n - /Users/you/your-templates/ # you can also reference local directories\n template-makefile:\n - $!home$ # To save time, you can create path shortcuts (like how we wrote home and local-home) and then add them in the ordered list here. CCExtender will then go through the list and append each section to the path string. In this case, we end up with https://github.com/yourtemplates/template-makefile\n - template-makefile\n template-dockerfile:\n - $!home$\n - template-dockerfile\n template-version:\n - $!home$\n - template-version\n template-travis:\n - $!local_home$\n - my-travis-template\n template-readme:\n - $!home$\n - template-readme\n template-license:\n - https://github.com/licensing-template\n template-standards:\n - $!home$\n - template-standards\n```\n\n\n## Setting up your templates\n\nCookiecutter templates are fairly straightforward.\n\nTheir structure is:\n\n```\ntemplate-example/\n cookiecutter.json\n \n```\n\nThe cookiecutter.json file is where you traditionally define your template variables, along with their default values. They might look something like this:\n\n```json\n{\n\"greeting\": \"Hello World\",\n\"recipient\": \"You\",\n\"license\": \"MIT License\"\n}\n```\n\nWhen you'd run cookiecutter, it would prompt you to enter values for each variable, and then replace all instances of {{cookiecutter.}} in every file within the template, before copying them into your target directory.\n\nCCExtender works differently, though. The only values you have to manually input are variables in your standards template (usually general stuff like project name, contact email, etc.) All the variables in the rest of your templates will be set by the change-packs you've configured in your configuration file.\n\nSo let's say you run CCExtender and make a decision that triggers change-pack-A:\n\n```YAML\nchange-pack-A:\n template-makefile: # Refers to the template-makefile listed in the locations block\n registry_link: \"REGISTRY := registry.hub.docker.com\" # corresponds to a variable called \"registry_link\" defined in the template-makefile's cookiecutter.json\n environment: \"ENVIRON := dev\"\n template-dockerfile:\n pip_install: \"RUN pip install cookiecutter\"\n```\n\nChange-pack-A assigns values to variables in our makefile template and our docker file template's cookiecutter.json files. Those files might look like:\n\ntemplate-makefile/cookiecutter.json\n```json\n{\n \"project_name\": \"My New Project\",\n \"registry_link\": \"\",\n \"environment\": \"ENVIRON := test\"\n}\n```\n\ntemplate-dockerfile/cookiecutter.json\n```json\n{\n \"project_name\": \"My New Project\",\n \"pip_install\": \"\"\n}\n```\n\nThen when CCExtender creates a new repository, it uses the variable assignments from change-pack-A for their corresponding cookiecutter variables. So you can think of the variable assignments in the change-pack blocks in our ccextender.yaml file to essentially take the place of your manually entering them in a traditionally cookiecutter session.\n\n\n#### Additive Variable assignment\n\nCCExtender lets you assign multiple values to the same variable. For instance, let's look at the cookiecutter.json for the dockerfile template from earlier:\n\ntemplate-dockerfile/cookiecutter.json\n```json\n{\n \"project_name\": \"My New Project\",\n \"pip_install\": \"\"\n}\n```\n\nThe pip_install variable corresponds to {{cookiecutter.pip_install}} in the Dockerfile:\n\nDockerfile\n```Dockerfile\n...\nCOPY . somefolder/\n\n{{cookiecutter.pip_install}}\n\nRUN apt-get blahblahblah\n...\n```\n\nNow let's say you have a configuration file with a changepack section like this:\n\nccextender.yaml\n```YAML\nchange-pack-A:\n template-dockerfile:\n pip_install: \"RUN pip install cookiecutter\"\nchange-pack-B:\n template-dockerfile:\n pip_install: \"RUN pip install setuptools\"\nchange-pack-C:\n template-dockerfile:\n pip_install: \"RUN pip install oyaml\"\n pip_install: \"RUN pip install pylint\"\n```\n\nNow let's say we run CCExtender and our decisions lead to change packs A, B, and C being used. That means that pip_install is being assigned a value 4 times. After the first assignemnt, CCExtender appends each subsequent assigned value to the end of the current value string, separated by a '\\n'. This results in CCExtender inserting multiple lines in the place a single value. For our example, the Dockerfile would now look like:\n\nDockerfile\n```Dockerfile\n...\nCOPY . somefolder/\n\nRUN pip install cookiecutter\nRUN pip install setuptools\nRUN pip install oyaml\nRUN pip install pylint\n\nRUN apt-get blahblahblah\n...\n```\n\n\n## Hello World\n\n#### 1. Create practice templates\n\n###### Create template directories:\n\n```bash\nmkdir templates\ncd templates\n\nmkdir -p template-project-info/{{cookiecutter.project_name}}\nmkdir -p template-hello/{{cookiecutter.project_name}}\nmkdir -p template-goodbye/{{cookiecutter.project_name}}\nmkdir -p template-standards/{{cookiecutter.project_name}}\n```\n\n###### Create README template:\n\nIn the template-project-info/ directory, add file cookiecutter.json with the following contents:\n\n```json\n{\n\"project_name\": \"HelloWorld\",\n\"owner\": \"New User\",\n\"contact_email\": \"you@default.com\"\n}\n```\n\nThen move into the lower directory:\n\n```bash\ncd {{cookiecutter.project_name}}\n```\n\nAdd file README.txt with the following contents:\n\n```text\n{{cookiecutter.project_name}} Project Documentation\n\nThis project was created by:\n{{cookiecutter.owner}}\n\nFor questions about this project, please email {{cookiecutter.contact_email}}.\n```\n\n###### Create Hello World template:\n\nIn the template-hello directory, add file cookiecutter.json with the following contents:\n\n```json\n{\n \"project_name\": \"HelloWorld\",\n \"contact_email\": \"you@default.com\",\n \"owner\": \"New User\",\n \"greeting\": \"Hello\"\n}\n```\n\nThen move into the lower directory:\n\n```bash\ncd {{cookiecutter.project_name}}\n```\n\nAdd file hello.py with the following contents:\n\n```python\nprint(\"{{cookiecutter.owner}} would like to say {{cookiecutter.greeting}}\")\n```\n\n###### Create Goodbye template:\n\nIn the template-goodbye directory, add a file cookiecutter.json with the following content:\n\n```json\n{\n \"project_name\": \"HelloWorld\",\n \"contact_email\": \"you@default.com\",\n \"owner\": \"New User\",\n \"greeting\": \"Goodbye\"\n}\n```\n\nThen move into the lower directory:\n\n```bash\ncd {{cookiecutter.project_name}}\n```\n\nAdd file hello.py with the following contents:\n\n```python\nprint(\"{{cookiecutter.owner}} would like to say {{cookiecutter.greeting}}\")\n```\n\n###### Create Standards Template:\n\nIn the template-standards directory, add file cookiecutter.json with the following contents:\n\n```json\n{\n\"project_name\": \"HelloWorld\",\n\"owner\": \"New User\",\n\"contact_email\": \"you@default.com\"\n}\n```\n\n#### 2. Create your configuration file:\n\nIn your templates folder, create a file ccextender.yaml with the following content:\n\n```yaml\nCCX_Version: 1.1\nstandard-context:\n project_name: \"HelloWorld\"\n owner: \"New User\"\n contact_email: \"you@default.com\"\ndecisions:\n readme:\n query:\n prompt: \"Would you like to [1] add project readme info?\"\n yes:\n - project-info\n greeting:\n query:\n prompt: \"Would you like to say [1] hello or [2] goodbye?\"\n include-if:\n - yes\n hello:\n - hello-pack\n goodbye:\n - goodbye-pack\nchange-packs:\n hello-pack:\n template-hello:\n greeting: \"Howdy\"\n goodbye-pack:\n template-goodbye:\n project-info:\n template-project-info:\nlocations:\n home:\n - \n template-hello:\n - $!home$\n - template-hello\n template-goodbye:\n - $!home$\n - template-goodbye\n template-project-info:\n - $!home$\n - template-project-info\n template-standards:\n - $!home$\n - template-standards\n```\n\n#### 3. Run CCExtender\n\nNow, navigate to the directory where you saved ccextender.yaml and then type:\n\n```bash\nccextender\n```\n\nAnswer the prompts. Once you are finished, you should see a new repository has been created in your current directory. Navigate into the repo and the run:\n\n```bash\npython hello.py\n```\n\nYou should see that your changes have been implemented. Try running the program a few times, and you\u2019ll see how the build changes.\n\n\n## Common Issues\n\n###### CCExtender can't find a cookiecutter.json for a template\n\nSo this either means that the path or url pointing to your template is broken or that one of your cookiecutter templates are missing the cookiecutter.json file. Verify that you have a cookiecutter.json in all template directories, including your standard template, and then verify that your links/paths work by manually calling them with:\n\n```bash\ncookiecutter \n```\n\n\n## Extending ccextender\n\n#### Adding new logic flags\n\nSo as of writing this, CCExtender only has two logical flags: include-if and exclude-if. But there is no limit on how many new flags can be added nor what they can do. To add a new flag, navigate to ccextender.py in pkg/ccextender and look at the class function prompt_user_decision. You should see a comment indicating the logic flag section. Right now, it looks something like this:\n\n```Python\n#Include-if flag logic\nif \"include-if\" in query_block.keys():\n for condition in query_block[\"include-if\"]:\n if condition not in self.past_decisions:\n print(str(condition) + \" NOT in \" + str(self.past_decisions))\n return list()\n#Exclude-if flag logic\nif \"exclude-if\" in query_block.keys():\n for condition in query_block[\"exclude-if\"]:\n if condition in self.past_decisions:\n print(str(condition) + \" in \" + str(self.past_decisions))\n return list()\n```\n\nThe only standard part your new code will need is the following:\n\n```Python\n#My-new-flag flag logic\nif \"my-new-flag\" in query_block.keys():\n #Your flag's code here\n```\n\nFor instance, let's say we want to let users configure the color of the user prompt:\n\n```Python\n#Color flag logic\nif \"color\" in query_block.keys():\n color_choice = query_block[color]\n if color_choice == \"blue\":\n prompt_string = '\\033[94m' + prompt_string + '\\033[0m'\n else if color_choice == \"green\":\n prompt_string = '\\033[92m' + prompt_string + '\\033[0m'\n```\n\nAnd then we'd just add the following line to any decision block we'd like to color:\n\n```YAML\ndecisions:\n readme:\n query:\n prompt: \"Would you like to [1] add project readme info?\"\n color: green\n yes:\n - project-info\n greeting:\n query:\n prompt: \"Would you like to say [1] hello or [2] goodbye?\"\n include-if:\n - yes\n color: blue\n hello:\n - hello-pack\n goodbye:\n - goodbye-pack\n```\n\n\n",
"description_content_type": "text/markdown",
"docs_url": null,
"download_url": "",
"downloads": {
"last_day": -1,
"last_month": -1,
"last_week": -1
},
"home_page": "https://github.com/aslape/ccextender",
"keywords": "",
"license": "",
"maintainer": "",
"maintainer_email": "",
"name": "ccextender",
"package_url": "https://pypi.org/project/ccextender/",
"platform": "",
"project_url": "https://pypi.org/project/ccextender/",
"project_urls": {
"Homepage": "https://github.com/aslape/ccextender"
},
"release_url": "https://pypi.org/project/ccextender/1.28/",
"requires_dist": null,
"requires_python": "",
"summary": "Cookiecutter Extended",
"version": "1.28"
},
"last_serial": 5579276,
"releases": {
"1": [
{
"comment_text": "",
"digests": {
"md5": "354f2e222753806d6d6d39be4128abae",
"sha256": "b2f97ca20b7dc375b3f359f129fa2e44749c324bf5a530a07f509e699dc0a147"
},
"downloads": -1,
"filename": "ccextender-1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "354f2e222753806d6d6d39be4128abae",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 9669,
"upload_time": "2019-07-12T18:24:38",
"url": "https://files.pythonhosted.org/packages/e9/1c/9a3611ac58c4bdbe264e26c404f3d1f836df1f374a1a263cd19bce34e31e/ccextender-1-py3-none-any.whl"
}
],
"1.1": [
{
"comment_text": "",
"digests": {
"md5": "f4aaad5e4d8650444e24847c248c9c8f",
"sha256": "881ea2e44dfb386d4a8d38a046614075d395e8357010595823e7ff55f013d3e5"
},
"downloads": -1,
"filename": "ccextender-1.1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "f4aaad5e4d8650444e24847c248c9c8f",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 9695,
"upload_time": "2019-07-12T18:39:03",
"url": "https://files.pythonhosted.org/packages/29/1f/f6668b63cc92da0b9c6d5f18f5e1db0bf26d2c09ee1e70647a20e3a8979d/ccextender-1.1-py3-none-any.whl"
}
],
"1.10": [
{
"comment_text": "",
"digests": {
"md5": "5943a80a2b79bbfbe61658224cfe0acf",
"sha256": "7a92cca58ba64ccf7dbd29c4c86ff1adf10530671d8dcdaed32da9b645fe6ec9"
},
"downloads": -1,
"filename": "ccextender-1.10-py3-none-any.whl",
"has_sig": false,
"md5_digest": "5943a80a2b79bbfbe61658224cfe0acf",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 9895,
"upload_time": "2019-07-15T18:31:16",
"url": "https://files.pythonhosted.org/packages/b6/33/23c7466461ef329c1e4a697647041515b09833356f6c22bffeeafb12878c/ccextender-1.10-py3-none-any.whl"
}
],
"1.11": [
{
"comment_text": "",
"digests": {
"md5": "7b819e307810f68e73634e2caf2bf1c8",
"sha256": "873dd51815220b9c1ab555199d49e3a74e6b8bca62b64036e0f8b9356a0cb7d8"
},
"downloads": -1,
"filename": "ccextender-1.11-py3-none-any.whl",
"has_sig": false,
"md5_digest": "7b819e307810f68e73634e2caf2bf1c8",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 9891,
"upload_time": "2019-07-15T19:05:22",
"url": "https://files.pythonhosted.org/packages/32/37/4a5b005b652aceb0964b88fe8e95bf75e961cee5049aa74ad95a985a0218/ccextender-1.11-py3-none-any.whl"
}
],
"1.12": [
{
"comment_text": "",
"digests": {
"md5": "f57f942c9a2a254108474903fcba3490",
"sha256": "7e6c0471668acee2e8730540b4cb1a50ce6e8ff429f7b33e6b40a68b1684f867"
},
"downloads": -1,
"filename": "ccextender-1.12-py3-none-any.whl",
"has_sig": false,
"md5_digest": "f57f942c9a2a254108474903fcba3490",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 7889,
"upload_time": "2019-07-15T19:36:04",
"url": "https://files.pythonhosted.org/packages/c1/ab/80526c5364af0366b8d0a714f670f0d3c99911fe50990c465c9909aec05b/ccextender-1.12-py3-none-any.whl"
}
],
"1.13": [
{
"comment_text": "",
"digests": {
"md5": "e9a627156c57d477280ce1b542fbf7c6",
"sha256": "4763dad6e18cb4d6b055759ab7997e12b448304b419af56f63b0afd6535d24c0"
},
"downloads": -1,
"filename": "ccextender-1.13-py3-none-any.whl",
"has_sig": false,
"md5_digest": "e9a627156c57d477280ce1b542fbf7c6",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 11907,
"upload_time": "2019-07-17T15:18:37",
"url": "https://files.pythonhosted.org/packages/b6/54/87e866404eba1921e9ce8afc82050b66ab48c564f59ca04f62e2ce1513c8/ccextender-1.13-py3-none-any.whl"
}
],
"1.14": [
{
"comment_text": "",
"digests": {
"md5": "b015c7cdf5ff019aa7564086ae581878",
"sha256": "3f2985c5652755d98d1cde834c50fddd747fbb85d557534493a1e3f0a429ed89"
},
"downloads": -1,
"filename": "ccextender-1.14-py3-none-any.whl",
"has_sig": false,
"md5_digest": "b015c7cdf5ff019aa7564086ae581878",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 12264,
"upload_time": "2019-07-18T13:50:16",
"url": "https://files.pythonhosted.org/packages/76/6b/a13770aab7f5f1ffc87b42e7a6e42f94080bbb47f11c583bd55b798f09dd/ccextender-1.14-py3-none-any.whl"
}
],
"1.15": [
{
"comment_text": "",
"digests": {
"md5": "6833398a8e2922eb486f959f5b95372c",
"sha256": "bcf4040e6653cc56e1ef80f40ef6d279190c50a0838f87edb6dd8ac4a56afb26"
},
"downloads": -1,
"filename": "ccextender-1.15-py3-none-any.whl",
"has_sig": false,
"md5_digest": "6833398a8e2922eb486f959f5b95372c",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 12250,
"upload_time": "2019-07-18T20:30:01",
"url": "https://files.pythonhosted.org/packages/3e/54/a7871679a5664a32159c21d5ce13c29903eacc72046782f25025d2289c2f/ccextender-1.15-py3-none-any.whl"
}
],
"1.17": [
{
"comment_text": "",
"digests": {
"md5": "743dbc5a39b45cfd7355b9030c71eb2e",
"sha256": "a6e0525e64705cc15af1248d0f67a405ba50c05ffe9cc3aaed13ed8147f70b01"
},
"downloads": -1,
"filename": "ccextender-1.17-py3-none-any.whl",
"has_sig": false,
"md5_digest": "743dbc5a39b45cfd7355b9030c71eb2e",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 12264,
"upload_time": "2019-07-24T17:37:21",
"url": "https://files.pythonhosted.org/packages/86/b5/87107b6e77a378a11d97394ca497419655e8920164b431159f70e43f983b/ccextender-1.17-py3-none-any.whl"
}
],
"1.18": [
{
"comment_text": "",
"digests": {
"md5": "a6ec2494dfb3052e190b399e542576fc",
"sha256": "a023264a3ff8e1f646c6b632d9d977d8dc6ff95696c75f4f1d23dc1049f763ba"
},
"downloads": -1,
"filename": "ccextender-1.18-py3-none-any.whl",
"has_sig": false,
"md5_digest": "a6ec2494dfb3052e190b399e542576fc",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 12435,
"upload_time": "2019-07-24T17:51:39",
"url": "https://files.pythonhosted.org/packages/ff/38/237de85a28719c040d9e3429b8266bd6109d72d7bc9059210f500e56b9dd/ccextender-1.18-py3-none-any.whl"
}
],
"1.19": [
{
"comment_text": "",
"digests": {
"md5": "59a86fe03b29000e834659fc2d205fb0",
"sha256": "4863c1526ee009b1c27b31477a14aae7129f213af9a84662dbc4202306ebc1d8"
},
"downloads": -1,
"filename": "ccextender-1.19-py3-none-any.whl",
"has_sig": false,
"md5_digest": "59a86fe03b29000e834659fc2d205fb0",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 12433,
"upload_time": "2019-07-24T17:53:55",
"url": "https://files.pythonhosted.org/packages/7a/72/c12d5c8105f10a2439e49fa6a23398a67d0e93d44c47c20dead8e7cc233d/ccextender-1.19-py3-none-any.whl"
}
],
"1.2": [
{
"comment_text": "",
"digests": {
"md5": "d2f4dd67a43484438a9a78f3c572894c",
"sha256": "41ca0a64069f555d5c2501c372b7cb583f6a571a9add7b11f4490560519ffe45"
},
"downloads": -1,
"filename": "ccextender-1.2-py3-none-any.whl",
"has_sig": false,
"md5_digest": "d2f4dd67a43484438a9a78f3c572894c",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 9847,
"upload_time": "2019-07-15T13:21:08",
"url": "https://files.pythonhosted.org/packages/42/84/73a6e37e1b303b55beb1d24d58569a6ea0ac2ade0f6bc59c85db825cb792/ccextender-1.2-py3-none-any.whl"
}
],
"1.20": [
{
"comment_text": "",
"digests": {
"md5": "fcf1074742bf44928b8e9576c20be9c3",
"sha256": "67fa65826c35e5e2f4f0114384cf4626445f9906f6d492591b43ec7afacaa565"
},
"downloads": -1,
"filename": "ccextender-1.20-py3-none-any.whl",
"has_sig": false,
"md5_digest": "fcf1074742bf44928b8e9576c20be9c3",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 12454,
"upload_time": "2019-07-24T17:57:45",
"url": "https://files.pythonhosted.org/packages/75/d1/0d0e37cc4f298fa92212a63781b7c76fda4a7459e13bd4161d53e0f84aa3/ccextender-1.20-py3-none-any.whl"
}
],
"1.21": [
{
"comment_text": "",
"digests": {
"md5": "be571ec0ba322d4c21575c14d96806a9",
"sha256": "a345ee65f04003ef41b6f38b120bf445c199ef67f946c9fa95ee0a91ec47a1ab"
},
"downloads": -1,
"filename": "ccextender-1.21-py3-none-any.whl",
"has_sig": false,
"md5_digest": "be571ec0ba322d4c21575c14d96806a9",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 12456,
"upload_time": "2019-07-24T17:59:57",
"url": "https://files.pythonhosted.org/packages/96/82/aa36262268b2fd143e4b47670b000620fb09db00bc1621de450612021cc1/ccextender-1.21-py3-none-any.whl"
}
],
"1.22": [
{
"comment_text": "",
"digests": {
"md5": "e4efbb9074c379297b0d06fc0dd47c7f",
"sha256": "bbb0c86c8ac008a114c4d56fbca663425f1e04444b2ecbfa25fb0421cb6f0eca"
},
"downloads": -1,
"filename": "ccextender-1.22-py3-none-any.whl",
"has_sig": false,
"md5_digest": "e4efbb9074c379297b0d06fc0dd47c7f",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 12457,
"upload_time": "2019-07-24T18:04:41",
"url": "https://files.pythonhosted.org/packages/b4/30/007c6229114f6f1ee6bdece0a228a53a257d8441ab9e2a1ba912af313ebd/ccextender-1.22-py3-none-any.whl"
}
],
"1.23": [
{
"comment_text": "",
"digests": {
"md5": "45af21b27b6abca8f1b44c747fb5da04",
"sha256": "adfeec54ad4af693db4309398e53a764115ed5314530a29e98ff71b2e0dc0ad0"
},
"downloads": -1,
"filename": "ccextender-1.23-py3-none-any.whl",
"has_sig": false,
"md5_digest": "45af21b27b6abca8f1b44c747fb5da04",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 12466,
"upload_time": "2019-07-24T18:05:56",
"url": "https://files.pythonhosted.org/packages/e9/36/49a0380a2ee0bd089b36828970b30e89a5ca8150123b7c671844af5ce166/ccextender-1.23-py3-none-any.whl"
}
],
"1.24": [
{
"comment_text": "",
"digests": {
"md5": "2fabd6c06a3177c0bf2107cb1155bd79",
"sha256": "ebe09fe474e076467a0ea6df0693f094cfbac734268469911e8d738cbe74a291"
},
"downloads": -1,
"filename": "ccextender-1.24-py3-none-any.whl",
"has_sig": false,
"md5_digest": "2fabd6c06a3177c0bf2107cb1155bd79",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 12945,
"upload_time": "2019-07-24T19:08:02",
"url": "https://files.pythonhosted.org/packages/fd/e6/688ddaac4ff3082a357d0f2bb2dcc8b909d26c83e038f3888efeb911d890/ccextender-1.24-py3-none-any.whl"
}
],
"1.25": [
{
"comment_text": "",
"digests": {
"md5": "a5422d9eaf21180a715816a99d0dc440",
"sha256": "bcca9274243487264c49f8172032aaf7228a0f05ae6d83acc2b610f7330e1cc5"
},
"downloads": -1,
"filename": "ccextender-1.25-py3-none-any.whl",
"has_sig": false,
"md5_digest": "a5422d9eaf21180a715816a99d0dc440",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 12930,
"upload_time": "2019-07-24T19:10:26",
"url": "https://files.pythonhosted.org/packages/a3/d8/bdaf58b1d1aadf1d6ac2711230ac97e2d6a843aa0dce1a7292e5041ae1b6/ccextender-1.25-py3-none-any.whl"
}
],
"1.26": [
{
"comment_text": "",
"digests": {
"md5": "3d04ea7f545991ed432a42edf8d54919",
"sha256": "6d7c57ee7f1e8f599c30b59602c89aa49edf6459c8236beb53e532e9fec539d9"
},
"downloads": -1,
"filename": "ccextender-1.26-py3-none-any.whl",
"has_sig": false,
"md5_digest": "3d04ea7f545991ed432a42edf8d54919",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 13054,
"upload_time": "2019-07-24T19:11:42",
"url": "https://files.pythonhosted.org/packages/f6/4c/54d1f116a1f30ea37cf7414d7914bc57aa68e83ec995414c5b6db4e1f6c6/ccextender-1.26-py3-none-any.whl"
}
],
"1.27": [
{
"comment_text": "",
"digests": {
"md5": "b8817a3129cc209c7412df82062cfd9e",
"sha256": "20f4c7f61250cc343fd7e1bc08f7fca233857360c13afdf00ad2ccf6c4e8d400"
},
"downloads": -1,
"filename": "ccextender-1.27-py3-none-any.whl",
"has_sig": false,
"md5_digest": "b8817a3129cc209c7412df82062cfd9e",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 13053,
"upload_time": "2019-07-24T19:12:27",
"url": "https://files.pythonhosted.org/packages/26/ca/03efaa7fc54a65d5149b41403beae23d90e7b9f0fabdf06c5fede1b0e744/ccextender-1.27-py3-none-any.whl"
}
],
"1.28": [
{
"comment_text": "",
"digests": {
"md5": "ee4b5aa9ddb677a2279bbb77de109880",
"sha256": "3f7478818297c179d2b5a17a55de475e4f8e71a1b983adcd11a4a7ec640338b6"
},
"downloads": -1,
"filename": "ccextender-1.28-py3-none-any.whl",
"has_sig": false,
"md5_digest": "ee4b5aa9ddb677a2279bbb77de109880",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 13055,
"upload_time": "2019-07-24T19:13:59",
"url": "https://files.pythonhosted.org/packages/fd/d0/a4049063868e5c7fa575d14c789bab0f8e9546396f911277aaf4aa1813b0/ccextender-1.28-py3-none-any.whl"
}
],
"1.3": [
{
"comment_text": "",
"digests": {
"md5": "de0837236372f1a41b85e63f3b7072ed",
"sha256": "7f22082735aa95afb894f9a097afc76110af6c880edb24d4ef4de6157d5fdaf8"
},
"downloads": -1,
"filename": "ccextender-1.3-py3-none-any.whl",
"has_sig": false,
"md5_digest": "de0837236372f1a41b85e63f3b7072ed",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 9839,
"upload_time": "2019-07-15T13:42:05",
"url": "https://files.pythonhosted.org/packages/73/ee/591bd895635ab5e4d4b7e90e5b365177b1184a6cf9b10fd304f40c1577b3/ccextender-1.3-py3-none-any.whl"
}
],
"1.4": [
{
"comment_text": "",
"digests": {
"md5": "4b139cecc91c7f2a4c6bdcc9630a9cbf",
"sha256": "d050f3fbf7544d3ae4447307450323727db01d6b350c7768b28e5676597cb985"
},
"downloads": -1,
"filename": "ccextender-1.4-py3-none-any.whl",
"has_sig": false,
"md5_digest": "4b139cecc91c7f2a4c6bdcc9630a9cbf",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 9870,
"upload_time": "2019-07-15T14:32:28",
"url": "https://files.pythonhosted.org/packages/78/a7/b8bf23eaeed7e9072102af90e0106cc743885d002af95b1ef838cfd8ce0e/ccextender-1.4-py3-none-any.whl"
}
],
"1.5": [
{
"comment_text": "",
"digests": {
"md5": "06d9d9857bf89e8fb4d6cd1db74efbb2",
"sha256": "79390565ef7c54dca87216774cc5a7474e0c302b85deb59833a578befff6b808"
},
"downloads": -1,
"filename": "ccextender-1.5-py3-none-any.whl",
"has_sig": false,
"md5_digest": "06d9d9857bf89e8fb4d6cd1db74efbb2",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 9870,
"upload_time": "2019-07-15T14:36:17",
"url": "https://files.pythonhosted.org/packages/52/bc/ab20e7f16ff32472b9790b2dc20635aa133d853b93bdff60d76a2b5b601f/ccextender-1.5-py3-none-any.whl"
}
],
"1.6": [
{
"comment_text": "",
"digests": {
"md5": "af88ac783d966a850bad0daf73dcea1b",
"sha256": "e0888a8fbedc4a08403f50d817bd5c336a72a770ad5e7bc54aeed86c7a1e2763"
},
"downloads": -1,
"filename": "ccextender-1.6-py3-none-any.whl",
"has_sig": false,
"md5_digest": "af88ac783d966a850bad0daf73dcea1b",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 9869,
"upload_time": "2019-07-15T14:39:44",
"url": "https://files.pythonhosted.org/packages/13/47/5d9a5d114ccc37e0ee8161c1fff3ca4812c4cf23420a762590ff7801ce73/ccextender-1.6-py3-none-any.whl"
}
],
"1.7": [
{
"comment_text": "",
"digests": {
"md5": "07297c243c5699fa11f22280fb2aa525",
"sha256": "71307bcae8405a7070730358981ea31e8e4a19ed0e7bea599dd3631728ce5f3e"
},
"downloads": -1,
"filename": "ccextender-1.7-py3-none-any.whl",
"has_sig": false,
"md5_digest": "07297c243c5699fa11f22280fb2aa525",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 9872,
"upload_time": "2019-07-15T14:45:02",
"url": "https://files.pythonhosted.org/packages/46/f3/c347518a2dfad0feb07e5734cf2e4849cf677fccf508a0175390f440a27d/ccextender-1.7-py3-none-any.whl"
}
],
"1.8": [
{
"comment_text": "",
"digests": {
"md5": "986bf3e092b6c143ac10986a932e0fa1",
"sha256": "fed6eac784a5d20a86b0e9d2b78e59f42592a242fcc207a3ff29bb24fdb9ed73"
},
"downloads": -1,
"filename": "ccextender-1.8-py3-none-any.whl",
"has_sig": false,
"md5_digest": "986bf3e092b6c143ac10986a932e0fa1",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 9872,
"upload_time": "2019-07-15T14:52:08",
"url": "https://files.pythonhosted.org/packages/b1/a4/3c7da50b5e10aeea4a9b0762ba9e2eb5bb19ebf4116c24d3136e54d09520/ccextender-1.8-py3-none-any.whl"
}
],
"1.9": [
{
"comment_text": "",
"digests": {
"md5": "beb41a38e6b912d28e37fda61504216f",
"sha256": "148762a38f8391c73dcff425e716d16e7084e25b86613829d1375f4ce17edc73"
},
"downloads": -1,
"filename": "ccextender-1.9-py3-none-any.whl",
"has_sig": false,
"md5_digest": "beb41a38e6b912d28e37fda61504216f",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 9867,
"upload_time": "2019-07-15T14:54:52",
"url": "https://files.pythonhosted.org/packages/eb/60/886f8aa902a41847f6467ef8eea011c64004c202dab3f70ae01cd73a0f6b/ccextender-1.9-py3-none-any.whl"
}
]
},
"urls": [
{
"comment_text": "",
"digests": {
"md5": "ee4b5aa9ddb677a2279bbb77de109880",
"sha256": "3f7478818297c179d2b5a17a55de475e4f8e71a1b983adcd11a4a7ec640338b6"
},
"downloads": -1,
"filename": "ccextender-1.28-py3-none-any.whl",
"has_sig": false,
"md5_digest": "ee4b5aa9ddb677a2279bbb77de109880",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 13055,
"upload_time": "2019-07-24T19:13:59",
"url": "https://files.pythonhosted.org/packages/fd/d0/a4049063868e5c7fa575d14c789bab0f8e9546396f911277aaf4aa1813b0/ccextender-1.28-py3-none-any.whl"
}
]
}