Skip to content

Added all open source licenses (Fixes #2941) #3072

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/workflows/update-licenses.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Update Licenses

on:
# Every day at 2am
schedule:
- cron: "0 2 * * *"
Comment on lines +4 to +6
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might want to run this at another time that the chnagelog update.

# Manual trigger
workflow_dispatch:

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2

- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Update list
run: python scripts/update_licenses.py
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v4.9.1
with:
commit_message: Update Licenses
file_pattern: cookiecutter.json {{cookiecutter.project_slug}}/licenses/*.txt
8 changes: 1 addition & 7 deletions cookiecutter.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,7 @@
"domain_name": "example.com",
"email": "{{ cookiecutter.author_name.lower()|replace(' ', '-') }}@example.com",
"version": "0.1.0",
"open_source_license": [
"MIT",
"BSD",
"GPLv3",
"Apache Software License 2.0",
"Not open source"
],
"open_source_license": ["Not open source", "MIT License", "BSD 3-Clause \"New\" or \"Revised\" License", "GNU General Public License v3.0", "Apache License 2.0", "\"Do What The F*ck You Want To Public License\"", "Academic Free License v3.0", "Artistic License 2.0", "BSD 2-Clause \"Simplified\" License", "BSD 3-Clause Clear License", "BSD 4-Clause \"Original\" or \"Old\" License", "BSD Zero Clause License", "Boost Software License 1.0", "CeCILL Free Software License Agreement v2.1", "Creative Commons Attribution 4.0 International", "Creative Commons Attribution Share Alike 4.0 International", "Creative Commons Zero v1.0 Universal", "Eclipse Public License 1.0", "Eclipse Public License 2.0", "Educational Community License v2.0", "European Union Public License 1.1", "European Union Public License 1.2", "GNU Affero General Public License v3.0", "GNU General Public License v2.0", "GNU Lesser General Public License v2.1", "GNU Lesser General Public License v3.0", "ISC License", "LaTeX Project Public License v1.3c", "Microsoft Public License", "Microsoft Reciprocal License", "Mozilla Public License 2.0", "ODC Open Database License v1.0", "Open Software License 3.0", "PostgreSQL License", "SIL Open Font License 1.1", "The Unlicense", "Universal Permissive License v1.0", "University of Illinois/NCSA Open Source License", "Vim License", "zlib License"],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see the script has now indenting this properly, although this is not yet reflected here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm slightly confused what you mean here? Are you saying that the GitHub workflow may not get the indentation correctly? Hopefully when the argument "indent=2" or 4 (can't remember) from json.dump works properly (preserving ordering as well). But I can't remember during my test run if all the arrays became... well aren't vertically spaced out like postgresql_version

Copy link
Member

@browniebroke browniebroke Mar 2, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With indent=2, I would expect this part to wrap vertically, a bit like it used to, but it doesn't. I was trying to ask if it's because you generated this with an earlier version where indent was missing or if my expectation is just wrong.

(sorry looking back, my initial comment was poorly worded)

"timezone": "UTC",
"windows": "n",
"use_pycharm": "n",
Expand Down
45 changes: 29 additions & 16 deletions hooks/post_gen_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,6 @@
DEBUG_VALUE = "debug"


def remove_open_source_files():
file_names = ["CONTRIBUTORS.txt", "LICENSE"]
for file_name in file_names:
os.remove(file_name)


def remove_gplv3_files():
file_names = ["COPYING"]
for file_name in file_names:
os.remove(file_name)


def remove_pycharm_files():
idea_dir_path = ".idea"
if os.path.exists(idea_dir_path):
Expand Down Expand Up @@ -324,6 +312,34 @@ def remove_storages_module():
os.remove(os.path.join("{{cookiecutter.project_slug}}", "utils", "storages.py"))


def handle_licenses():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this to remove the Jekyll front matter? I'm wondering if wouldn't make more sense to do this work in the update_licenses.py scripts maybe?

We can afford to use more modern Python there, we control the Python version better.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorta, we need the Jekyll formatting at first. Each file's name is not the same as the title. So in order to find the correct file, we loop through each file until we find one that has the Jekyll title of the exact license name from cookiecutter.json.

We could improve on this inefficient method though by having update_licenses.py update this Python file to have a dictionary that maps titles (the dict keys) to their files (the dict values). That way we can remove the need for the Jekyll headers.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see...

special_license_files = {
"European Union Public License 1.1": "COPYING",
"GNU General Public License v3.0": "COPYING",
"GNU Lesser General Public License v3.0": "COPYING.LESSER",
"The Unlicense": "UNLICENSE",
}
with open(os.path.join("licenses", "-temporary-placeholder.txt")) as f:
selected_title = f.readline()
for filename in os.listdir("licenses"):
if filename == "-temporary-placeholder.txt":
continue
# You'll always see: '---\n' marking beginning + end of Jekyll format
with open(os.path.join("licenses", filename)) as f:
contents = f.readlines()
title = contents[1].replace("title: ", "").rstrip()
if title != selected_title:
continue
with open(special_license_files.get(title, "LICENSE"), "w") as f:
# +2 to get rid of the --- and and an extra new line
f.writelines(contents[contents.index("---\n", 1) + 2 :])
break

if selected_title == "Not open source":
os.remove("CONTRIBUTORS.txt")
shutil.rmtree("licenses")


def main():
debug = "{{ cookiecutter.debug }}".lower() == "y"

Expand All @@ -334,10 +350,7 @@ def main():
)
set_flags_in_settings_files()

if "{{ cookiecutter.open_source_license }}" == "Not open source":
remove_open_source_files()
if "{{ cookiecutter.open_source_license}}" != "GPLv3":
remove_gplv3_files()
handle_licenses()

if "{{ cookiecutter.use_pycharm }}".lower() == "n":
remove_pycharm_files()
Expand Down
59 changes: 59 additions & 0 deletions scripts/update_licenses.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import json
import os
import re
from pathlib import Path
from github import Github

CURRENT_FILE = Path(__file__)
ROOT = CURRENT_FILE.parents[1]
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN", None)


def main() -> None:
"""
Script entry point.
"""
repo = Github(login_or_token=GITHUB_TOKEN).get_repo("github/choosealicense.com")
license_dir = ROOT / "{{cookiecutter.project_slug}}" / "licenses"
titles = []
for file in repo.get_contents("_licenses", "gh-pages"):
content = file.decoded_content.decode(file.encoding)
titles.append(content.split("\n", maxsplit=2)[1].replace("title: ", ""))
path = license_dir / file.name
if not path.is_file():
(license_dir / file.name).write_text(replace_content_options(content))
# Put "Not open source" at front so people know it's an option
front_options = [
"Not open source",
"MIT License",
'BSD 3-Clause "New" or "Revised" License',
"GNU General Public License v3.0",
"Apache License 2.0",
]
titles = [x for x in sorted(titles) if x not in front_options]
update_cookiecutter(front_options + titles)


year = (re.compile(r"\[year]"), "{% now 'utc', '%Y' %}")
email = (re.compile(r"\[email]"), "{{ cookiecutter.email }}")
fullname = (re.compile(r"\[fullname]"), "{{ cookiecutter.author_name }}")
project = (re.compile(r"\[project]"), "{{ cookiecutter.project_name }}")
projecturl = (re.compile(r"\[projecturl]"), "{{ cookiecutter.domain_name }}")
Comment on lines +37 to +41
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think we could add a test to make sure we don't miss any new placeholder? Seems a bit tricky to make the difference between a placeholder and a genuine [...] though. 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's why I wanted the pull request initially... In the Jekyll headers for all these files, there are directions that specify what to replace though! The directions say something like "replace [year] with etc." if I'm not mistaken. So after replacing everything, if we still match a regex of \[.*?\] (which matches to anything with square brackets including if there are multiple square brackets like [asdf] [asf] will be two matches).

Side note: I think we don't really need to care about Appendix directions (like that found in Apache 2.0 where they say to add the boilerplate code with year and name. With us putting it in already, I think the developer will understand. Additionally, license years don't have to be updated so there's that...)



def replace_content_options(content) -> str:
for compiled, replace in (year, email, fullname, project, projecturl):
content = compiled.sub(replace, content)
return content


def update_cookiecutter(titles: list):
with open("cookiecutter.json") as f:
data = json.load(f)
data["open_source_license"] = titles
with open("cookiecutter.json", "wt") as f:
json.dump(data, f, indent=2)
Comment on lines +51 to +55
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can do away without the context manager with Pathlib's read_text() and write_text() APIs. Not a big deal, I find them a bit higher level.



if __name__ == "__main__":
main()
16 changes: 12 additions & 4 deletions tests/test_cookiecutter_generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,19 @@ def context():
}


def generate_license_file_titles():
directory = os.path.join("{{cookiecutter.project_slug}}", "licenses")
titles = []
for file in os.listdir(directory):
if file == "-temporary-placeholder.txt":
continue
with open(os.path.join(directory, file)) as f:
titles.append(f.readlines()[1].replace("title: ", "").replace("\n", ""))
return titles


SUPPORTED_COMBINATIONS = [
{"open_source_license": "MIT"},
{"open_source_license": "BSD"},
{"open_source_license": "GPLv3"},
{"open_source_license": "Apache Software License 2.0"},
*[{"open_source_license": x} for x in generate_license_file_titles()],
{"open_source_license": "Not open source"},
{"windows": "y"},
{"windows": "n"},
Expand Down
7 changes: 7 additions & 0 deletions {{cookiecutter.project_slug}}/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,10 @@ Bootstrap's javascript as well as its dependencies is concatenated into a single
.. _Bootstrap docs: https://getbootstrap.com/docs/4.1/getting-started/theming/

{% endif %}

{% if cookiecutter.open_source_license != "Not open source" %}
License
^^^^^^

Licensed under the {{cookiecutter.open_source_license}}
{% endif %}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{ cookiecutter.open_source_license }}
39 changes: 39 additions & 0 deletions {{cookiecutter.project_slug}}/licenses/0bsd.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
title: BSD Zero Clause License
spdx-id: 0BSD

description: The BSD Zero Clause license goes further than the BSD 2-Clause license to allow you unlimited freedom with the software without requirements to include the copyright notice, license text, or disclaimer in either source or binary forms.

how: Create a text file (typically named LICENSE or LICENSE.txt) in the root of your source code and copy the text of the license into the file. Replace [year] with the current year and [fullname] with the name (or names) of the copyright holders. You may take the additional step of removing the copyright notice.

using:
PickMeUp: https://github.com/nazar-pc/PickMeUp/blob/master/copying.md
smoltcp: https://github.com/m-labs/smoltcp/blob/master/LICENSE-0BSD.txt
Toybox: https://github.com/landley/toybox/blob/master/LICENSE

permissions:
- commercial-use
- distribution
- modifications
- private-use

conditions: []

limitations:
- liability
- warranty

---

Copyright (c) {% now 'utc', '%Y' %} {{ cookiecutter.author_name }}

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
Loading