diff --git a/.github/ISSUE_TEMPLATE/delete.md b/.github/ISSUE_TEMPLATE/delete.md deleted file mode 100644 index 26c12ac..0000000 --- a/.github/ISSUE_TEMPLATE/delete.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -name: 🔴 Delete a package -about: Use this template for deleting an existing package of your PyPi index. -title: "🔴 Delete package :" ---- - -## 🔴 Package deletion form - -- **Package name :** diff --git a/.github/ISSUE_TEMPLATE/register.md b/.github/ISSUE_TEMPLATE/register.md deleted file mode 100644 index 9995e83..0000000 --- a/.github/ISSUE_TEMPLATE/register.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -name: 🟢 Register a new package -about: Use this template for registering a new package in your PyPi index. -title: "🟢 New package :" ---- - -## 🟢 New package registration form - -- **Package name :** -- **Version :** -- **Author :** -- **Short description :** -- **Long description :** -```html - -``` -- **Homepage :** -- **Link :** diff --git a/.github/ISSUE_TEMPLATE/update.md b/.github/ISSUE_TEMPLATE/update.md deleted file mode 100644 index 75f9c56..0000000 --- a/.github/ISSUE_TEMPLATE/update.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -name: 🔵 Update a package -about: Use this template for adding a new version of an existing package in your PyPi index. -title: "🔵 Update package :" ---- - -## 🔵 Package update form - -- **Package name :** -- **New version :** -- **Link for the new version :** diff --git a/.github/actions.py b/.github/actions.py index f07780d..4523c82 100644 --- a/.github/actions.py +++ b/.github/actions.py @@ -9,6 +9,7 @@ INDEX_FILE = "index.html" TEMPLATE_FILE = "pkg_template.html" +YAML_ACTION_FILES = [".github/workflows/delete.yml", ".github/workflows/update.yml"] def normalize(name): @@ -16,46 +17,6 @@ def normalize(name): return re.sub(r"[-_.]+", "-", name).lower() -def parse_issue(issue_ctx): - arguments = {} - - parts = issue_ctx['body'].split('- **')[1:] # Ignore the first one : it's the title - for text_arg in parts: - arg_name, arg_value = text_arg.split(':**') - arg_name, arg_value = arg_name.strip(), arg_value.strip() - - if arg_name == "Long description": - # Special case, where we have more than 1 line : it contain HTML code - arg_value = arg_value.split('```')[1] if '```' in arg_value else arg_value.split('`')[1] - - code_lang = arg_value.split('\n')[0].strip() - if code_lang != 'html': - raise ValueError("The {} argument should contain a HTML code section. But it contain {} code.".format(arg_name, code_lang)) - - arg_value = "\n".join(arg_value.split('\n')[1:]) - else: - if "\n" in arg_value: - raise ValueError("The {} argument should be a single line. Current value : {}".format(arg_name, arg_value)) - - arguments[arg_name.lower()] = arg_value - return arguments - - -def print_args(args): - print("\n--- Arguments detected from issue ---\n") - for arg_name, arg_value in args.items(): - print("\t{} : {}".format(arg_name, arg_value)) - print("\n") - - -def check_args(args, must_have): - for name in must_have: - if name not in args: - raise ValueError("Couldn't find argument {}".format(name)) - if args[name].strip() == "": - raise ValueError("Argument {} is empty. Please specify it".format(name)) - - def package_exists(soup, package_name): package_ref = package_name + "/" for anchor in soup.find_all('a'): @@ -64,25 +25,23 @@ def package_exists(soup, package_name): return False -def register(issue_ctx): - args = parse_issue(issue_ctx) - print_args(args) - check_args(args, ['package name', 'version', 'author', 'short description', 'long description', 'homepage', 'link']) +def register(pkg_name, version, author, short_desc, long_desc, homepage, link): + # Read our index first with open(INDEX_FILE) as html_file: soup = BeautifulSoup(html_file, "html.parser") - n_package_name = normalize(args['package name']) + norm_pkg_name = normalize(pkg_name) - if package_exists(soup, n_package_name): - raise ValueError("Package {} seems to already exists".format(n_package_name)) + if package_exists(soup, norm_pkg_name): + raise ValueError("Package {} seems to already exists".format(norm_pkg_name)) # Create a new anchor element for our new package last_anchor = soup.find_all('a')[-1] # Copy the last anchor element new_anchor = copy.copy(last_anchor) - new_anchor['href'] = "{}/".format(n_package_name) - new_anchor.contents[0].replace_with(args['package name']) + new_anchor['href'] = "{}/".format(norm_pkg_name) + new_anchor.contents[0].replace_with(pkg_name) spans = new_anchor.find_all('span') - spans[1].string = args['version'] # First span contain the version - spans[2].string = args['short description'] # Second span contain the short description + spans[1].string = version # First span contain the version + spans[2].string = short_desc # Second span contain the short description # Add it to our index and save it last_anchor.insert_after(new_anchor) @@ -93,93 +52,97 @@ def register(issue_ctx): with open(TEMPLATE_FILE) as temp_file: template = temp_file.read() - template = template.replace("_package_name", args['package name']) - template = template.replace("_version", args['version']) - template = template.replace("_link", "{}#egg={}-{}".format(args['link'], n_package_name, args['version'])) - template = template.replace("_homepage", args['homepage']) - template = template.replace("_author", args['author']) - template = template.replace("_long_description", args['long description']) + template = template.replace("_package_name", pkg_name) + template = template.replace("_version", version) + template = template.replace("_link", "{}#egg={}-{}".format(link, norm_pkg_name, version)) + template = template.replace("_homepage", homepage) + template = template.replace("_author", author) + template = template.replace("_long_description", long_desc) - os.mkdir(n_package_name) - package_index = os.path.join(n_package_name, INDEX_FILE) + os.mkdir(norm_pkg_name) + package_index = os.path.join(norm_pkg_name, INDEX_FILE) with open(package_index, "w") as f: f.write(template) -def update(issue_ctx): - args = parse_issue(issue_ctx) - print_args(args) - check_args(args, ['package name', 'new version', 'link for the new version']) +def update(pkg_name, version, link): + # Read our index first with open(INDEX_FILE) as html_file: soup = BeautifulSoup(html_file, "html.parser") - n_package_name = normalize(args['package name']) + norm_pkg_name = normalize(pkg_name) - if not package_exists(soup, n_package_name): - raise ValueError("Package {} seems to not exists".format(n_package_name)) + if not package_exists(soup, norm_pkg_name): + raise ValueError("Package {} seems to not exists".format(norm_pkg_name)) # Change the version in the main page - anchor = soup.find('a', attrs={"href": "{}/".format(n_package_name)}) + anchor = soup.find('a', attrs={"href": "{}/".format(norm_pkg_name)}) spans = anchor.find_all('span') - spans[1].string = args['new version'] + spans[1].string = version with open(INDEX_FILE, 'wb') as index: index.write(soup.prettify("utf-8")) # Change the package page - index_file = os.path.join(n_package_name, INDEX_FILE) + index_file = os.path.join(norm_pkg_name, INDEX_FILE) with open(index_file) as html_file: soup = BeautifulSoup(html_file, "html.parser") # Create a new anchor element for our new version last_anchor = soup.find_all('a')[-1] # Copy the last anchor element new_anchor = copy.copy(last_anchor) - new_anchor['href'] = "{}#egg={}-{}".format(args['link for the new version'], n_package_name, args['new version']) + new_anchor['href'] = "{}#egg={}-{}".format(link, norm_pkg_name, version) # Add it to our index last_anchor.insert_after(new_anchor) # Change the latest version - soup.html.body.div.section.find_all('span')[1].contents[0].replace_with(args['new version']) + soup.html.body.div.section.find_all('span')[1].contents[0].replace_with(version) # Save it with open(index_file, 'wb') as index: index.write(soup.prettify("utf-8")) -def delete(issue_ctx): - args = parse_issue(issue_ctx) - print_args(args) - check_args(args, ['package name']) +def delete(pkg_name): + # Read our index first with open(INDEX_FILE) as html_file: soup = BeautifulSoup(html_file, "html.parser") - n_package_name = normalize(args['package name']) + norm_pkg_name = normalize(pkg_name) - if not package_exists(soup, n_package_name): - raise ValueError("Package {} seems to not exists".format(n_package_name)) + if not package_exists(soup, norm_pkg_name): + raise ValueError("Package {} seems to not exists".format(norm_pkg_name)) # Remove the package directory - shutil.rmtree(n_package_name) + shutil.rmtree(norm_pkg_name) # Find and remove the anchor corresponding to our package - anchor = soup.find('a', attrs={"href": "{}/".format(n_package_name)}) + anchor = soup.find('a', attrs={"href": "{}/".format(norm_pkg_name)}) anchor.extract() with open(INDEX_FILE, 'wb') as index: index.write(soup.prettify("utf-8")) def main(): - # Get the context from the environment variable - context = json.loads(os.environ['GITHUB_CONTEXT']) - issue_ctx = context['event']['issue'] - title = issue_ctx['title'] - - if title.startswith("🟢"): - register(issue_ctx) - - if title.startswith("🔵"): - update(issue_ctx) - - if title.startswith("🔴"): - delete(issue_ctx) + # Call the right method, with the right arguments + action = os.environ["PKG_ACTION"] + + if action == "REGISTER": + register( + pkg_name=os.environ["PKG_NAME"], + version=os.environ["PKG_VERSION"], + author=os.environ["PKG_AUTHOR"], + short_desc=os.environ["PKG_SHORT_DESC"], + long_desc=os.environ["PKG_LONG_DESC"], + homepage=os.environ["PKG_HOMEPAGE"], + link=os.environ["PKG_LINK"], + ) + elif action == "DELETE": + delete(pkg_name=os.environ["PKG_NAME"]) + elif action == "UPDATE": + update( + pkg_name=os.environ["PKG_NAME"], + version=os.environ["PKG_VERSION"], + link=os.environ["PKG_LINK"], + ) if __name__ == "__main__": diff --git a/.github/workflows/delete.yml b/.github/workflows/delete.yml new file mode 100644 index 0000000..91d8b12 --- /dev/null +++ b/.github/workflows/delete.yml @@ -0,0 +1,39 @@ +name: delete + +on: + workflow_dispatch: + inputs: + package_name: + description: Package name + required: true + type: string + +jobs: + delete: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.6] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Run Action + env: + PKG_ACTION: DELETE + PKG_NAME: ${{ inputs.package_name }} + run: | + pip install beautifulsoup4 + python .github/actions.py + - name: Create Pull Request + uses: peter-evans/create-pull-request@v3 + with: + commit-message: ':package: [:robot:] Delete package from PyPi index' + title: '[🤖] Delete `${{ inputs.package_name }}` from PyPi index' + body: Automatically generated PR, deleting `${{ inputs.package_name }}` from + PyPi index. + branch-suffix: random + delete-branch: true diff --git a/.github/workflows/register.yml b/.github/workflows/register.yml new file mode 100644 index 0000000..abb17cb --- /dev/null +++ b/.github/workflows/register.yml @@ -0,0 +1,69 @@ +name: register + +on: + workflow_dispatch: + inputs: + package_name: + description: 'Package name' + required: true + type: string + version: + description: 'Version of the package' + required: true + type: string + author: + description: 'Author(s) of the package' + required: true + type: string + short_desc: + description: 'A short description of the package' + required: true + type: string + long_desc: + description: 'A longer description of the package (HTML)' + required: true + type: string + homepage: + description: 'Homepage of the package (link to the github repository)' + required: true + type: string + link: + description: 'Link used for `pip`. For example, for a github-hosted package refered by the tag `v3.0.2`, it would be : git+https://github.com/huggingface/transformers@v3.0.2' + required: true + type: string + +jobs: + update: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.6] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Run Action + env: + PKG_ACTION: REGISTER + PKG_NAME: ${{ inputs.package_name }} + PKG_VERSION: ${{ inputs.version }} + PKG_AUTHOR: ${{ inputs.author }} + PKG_SHORT_DESC: ${{ inputs.short_desc }} + PKG_LONG_DESC: ${{ inputs.long_desc }} + PKG_HOMEPAGE: ${{ inputs.homepage }} + PKG_LINK: ${{ inputs.link }} + run: | + pip install beautifulsoup4 + python .github/actions.py + - name: Create Pull Request + uses: peter-evans/create-pull-request@v3 + with: + commit-message: ':package: [:robot:] Register package in PyPi index' + title: '[🤖] Register `${{ inputs.package_name }}` in PyPi index' + body: Automatically generated PR, registering `${{ inputs.package_name }}` in PyPi + index. + branch-suffix: random + delete-branch: true diff --git a/.github/workflows/register_update.yml b/.github/workflows/register_update.yml deleted file mode 100644 index cee9688..0000000 --- a/.github/workflows/register_update.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: register_update - -on: - issues: - types: [opened, edited] - -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: [3.6] - - steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 - with: - python-version: ${{ matrix.python-version }} - - name: Run Action - env: - GITHUB_CONTEXT: ${{ toJson(github) }} - run: | - pip install beautifulsoup4 - python .github/actions.py - - name: Create Pull Request - uses: peter-evans/create-pull-request@v3 - with: - commit-message: ":package: [:robot:] Modify package in PyPi index" - title: "[🤖] ${{ github.event.issue.title }}" - body: "Fix #${{ github.event.issue.number }}" - branch: "pr/patch-${{ github.event.issue.number }}" diff --git a/.github/workflows/update.yml b/.github/workflows/update.yml new file mode 100644 index 0000000..ca66a54 --- /dev/null +++ b/.github/workflows/update.yml @@ -0,0 +1,50 @@ +name: update + +on: + workflow_dispatch: + inputs: + package_name: + description: Package name + required: true + type: string + version: + description: New version of the package + required: true + type: string + link: + description: 'Link used for `pip`. For example, for a github-hosted package + refered by the tag `v3.0.2`, it would be : git+https://github.com/huggingface/transformers@v3.0.2' + required: true + type: string + +jobs: + update: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.6] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Run Action + env: + PKG_ACTION: UPDATE + PKG_NAME: ${{ inputs.package_name }} + PKG_VERSION: ${{ inputs.version }} + PKG_LINK: ${{ inputs.link }} + run: | + pip install beautifulsoup4 + python .github/actions.py + - name: Create Pull Request + uses: peter-evans/create-pull-request@v3 + with: + commit-message: ':package: [:robot:] Update package in PyPi index' + title: '[🤖] Update `${{ inputs.package_name }}` in PyPi index' + body: Automatically generated PR, updating `${{ inputs.package_name }}` in PyPi + index. + branch-suffix: random + delete-branch: true diff --git a/README.md b/README.md index 5d8d268..dde670b 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,12 @@ _It will not work, because it's private and only me can access it !_ ## Get started -* Use this template and create your own repository : [![Generic badge](https://img.shields.io/badge/Use%20this%20template-blueviolet.svg)](https://github.com/astariul/github-hosted-pypi/generate) +* Use this template and create your own repository : + +

+ Use template +

+ * Go to `Settings` of your repository, and enable Github Page * Customize `index.html` and `pkg_template.html` to your liking * You're ready to go ! Visit `.github.io/` to see your PyPi index @@ -82,8 +87,9 @@ Now that your PyPi index is setup, you can register / update / delete packages i _Github actions are setup to do it automatically for you._ You just have to : -* Open an issue with the appropriate template -* Fill the information of the template (replace the comments) +* Go to the `Actions` tab of your repository +* Click the right workflow (`register` / `update` / `delete`) and trigger it manually +* Fill the form and start the workflow * Wait a bit * Check the new PR opened (ensure the code added correspond to what you want) * Merge the PR