diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml new file mode 100644 index 0000000..8652c48 --- /dev/null +++ b/.github/dependabot.yaml @@ -0,0 +1,19 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 + +updates: + - package-ecosystem: "github-actions" + # Workflow files stored in the + # default location of `.github/workflows` + directory: "/" + schedule: + interval: "daily" + + - package-ecosystem: "pip" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "daily" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..0e7e2ee --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,71 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ main ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ main ] + schedule: + - cron: '31 3 * * 5' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'python' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 0000000..6e99047 --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,19 @@ +name: 'Dependency Review' + +on: [ pull_request ] + +permissions: + contents: read + +jobs: + dependency-review: + runs-on: ubuntu-latest + + steps: + - name: 'Checkout Repository' + uses: actions/checkout@v3 + + - name: 'Dependency Review' + uses: actions/dependency-review-action@v3 + with: + config-file: darbiadev/.github/.github/dependency-review-config.yaml@main diff --git a/.github/workflows/github-pages-python-sphinx.yaml b/.github/workflows/github-pages-python-sphinx.yaml new file mode 100644 index 0000000..6981bd0 --- /dev/null +++ b/.github/workflows/github-pages-python-sphinx.yaml @@ -0,0 +1,10 @@ +name: GitHub Pages - Python Sphinx + +on: + push: + branches: + - main + +jobs: + docs: + uses: darbiadev/.github/.github/workflows/github-pages-python-sphinx.yaml@main diff --git a/.github/workflows/precommit-autoupdate.yaml b/.github/workflows/precommit-autoupdate.yaml new file mode 100644 index 0000000..b25dbec --- /dev/null +++ b/.github/workflows/precommit-autoupdate.yaml @@ -0,0 +1,9 @@ +name: Pre-Commit autoupdate + +on: + schedule: + - cron: "0 0 * * *" + +jobs: + pre-commit-autoupdate: + uses: darbiadev/.github/.github/workflows/generic-precommit-autoupdate.yaml@main diff --git a/.github/workflows/python-ci.yaml b/.github/workflows/python-ci.yaml new file mode 100644 index 0000000..21b2b85 --- /dev/null +++ b/.github/workflows/python-ci.yaml @@ -0,0 +1,27 @@ +name: Python CI + +on: + push: + branches: + - main + pull_request: + +jobs: + pre-commit: + uses: darbiadev/.github/.github/workflows/generic-precommit.yaml@main + + lint: + needs: pre-commit + uses: darbiadev/.github/.github/workflows/python-lint.yaml@main + + test: + needs: lint + strategy: + matrix: + os: [ ubuntu-latest, windows-latest, macos-latest ] + python-version: [ "3.7", "3.8", "3.9", "3.10", "3.11" ] + + uses: darbiadev/.github/.github/workflows/python-test.yaml@main + with: + os: ${{ matrix.os }} + python-version: ${{ matrix.python-version }} diff --git a/.github/workflows/python-publish-pypi.yaml b/.github/workflows/python-publish-pypi.yaml new file mode 100644 index 0000000..f9c5e32 --- /dev/null +++ b/.github/workflows/python-publish-pypi.yaml @@ -0,0 +1,10 @@ +name: Python - Publish to PyPI + +on: + release: + types: [released] + +jobs: + publish: + uses: darbiadev/.github/.github/workflows/python-pypi-publish.yaml@main + secrets: inherit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..98c9b5c --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +# JetBrains +.idea + +# Packaging +*.egg-info +dist + +# Cache +__pycache__ + +# Docs +docs/build diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..646b406 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,14 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: check-case-conflict + - id: check-merge-conflict + - id: check-toml + - id: check-yaml + - id: check-json + - id: trailing-whitespace + args: [ --markdown-linebreak-ext=md ] + - id: mixed-line-ending + args: [ --fix=lf ] + - id: end-of-file-fixer diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2c58912 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Let's build a ... + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3e6b095 --- /dev/null +++ b/Makefile @@ -0,0 +1,47 @@ +.DEFAULT_GOAL := install-dev + +.PHONY: init +init: + python -m pip install --upgrade pip wheel setuptools build + +.PHONY: install +install: + python -m pip install --upgrade . + +.PHONY: install-dev +install-dev: + python -m pip install --upgrade --editable .[dev,tests,docs] + +.PHONY: lint +lint: + python -m isort src/ + python -m black src/ + +.PHONY: pylint +pylint: + python -m pylint src/ + +.PHONY: test +test: + python -m pytest + +.PHONY: build-dist +build-dist: + python -m pip install --upgrade build + python -m build + +.PHONY: clean +clean: + find . -name '*.pyc' -exec rm -f {} + + find . -name '*.pyo' -exec rm -f {} + + find . -name '__pycache__' -exec rm -rf {} + + find . -name '.mypy_cache' -exec rm -rf {} + + rm -rf .tox + rm -f coverage.xml + rm -f coverage.json + rm -rf htmlcov + rm -rf .coverage + rm -rf .coverage.* + find . -name '.pytest_cache' -exec rm -rf {} + + rm -rf dist + rm -rf build diff --git a/README.md b/README.md index c509d25..499b79c 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ -# imsosorry +# I'm so sorry + Sometimes it can be necessary to call upon the ancient arts... diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..bed4efb --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + $(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..dc1312a --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst new file mode 100644 index 0000000..f5261b7 --- /dev/null +++ b/docs/source/changelog.rst @@ -0,0 +1,9 @@ +.. See docs for details on formatting your entries + https://releases.readthedocs.io/en/latest/concepts.html + +Changelog +========= + + +- :release:`1.0.0 <21th January 2023>` +- :feature:`1` Initialize package diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..1f890f3 --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,99 @@ +"""Configuration file for the Sphinx documentation builder. + +For the full list of built-in configuration values, see the documentation: +https://www.sphinx-doc.org/en/master/usage/configuration.html +""" + +from pathlib import Path +try: + from tomllib import loads as toml_loads +except ImportError: + from toml import loads as toml_loads + +project_config = toml_loads(Path("../../pyproject.toml").read_text()) +project: str = project_config["project"]["name"] +release: str = project_config["project"]["version"] +REPO_LINK: str = project_config["project"]["urls"]["repository"] +copyright: str = project_config["tool"]["sphinx"]["copyright"] # noqa: A001 +author: str = project_config["tool"]["sphinx"]["author"] + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named "sphinx.ext.*") or your custom +# ones. +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.linkcode", + "sphinx.ext.intersphinx", + "sphinx.ext.napoleon", + "autoapi.extension", + "releases", +] + +autoapi_type: str = "python" +autoapi_dirs: list[str] = ["../../src"] + +intersphinx_mapping = {"python": ("https://docs.python.org/3", None)} + +# Add any paths that contain templates here, relative to this directory. +templates_path: list[str] = ["_templates"] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns: list[str] = ["_build", "Thumbs.db", ".DS_Store"] + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme: str = "furo" + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path: list[str] = ["_static"] + +releases_github_path = REPO_LINK.removeprefix("https://github.com/") +releases_release_uri = f"{REPO_LINK}/releases/tag/v%s" + + +def linkcode_resolve(domain, info): + """linkcode_resolve""" + if domain != "py": + return None + if not info["module"]: + return None + + import importlib # pylint: disable=import-outside-toplevel + import inspect # pylint: disable=import-outside-toplevel + import types # pylint: disable=import-outside-toplevel + + mod = importlib.import_module(info["module"]) + + val = mod + for k in info["fullname"].split("."): + val = getattr(val, k, None) + if val is None: + break + + filename = info["module"].replace(".", "/") + ".py" + + if isinstance( + val, + ( + types.ModuleType, + types.MethodType, + types.FunctionType, + types.TracebackType, + types.FrameType, + types.CodeType, + ), + ): + try: + lines, first = inspect.getsourcelines(val) + last = first + len(lines) - 1 + filename += f"#L{first}-L{last}" + except (OSError, TypeError): + pass + + return f"{REPO_LINK}/blob/main/src/{filename}" diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..a5c8e9e --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,25 @@ +I'm so sorry +============ + +Sometimes it can be necessary to call upon the ancient arts... + +Module Index +------------ + +.. toctree:: + :maxdepth: 1 + + autoapi/index + +.. toctree:: + :caption: Other: + :hidden: + + changelog + +Extras +------ + +* :ref:`genindex` +* :ref:`search` +* :doc:`changelog` diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..0714720 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,55 @@ +[project] +name = "imsosorry" +version = "1.0.0" +description = "Sometimes it can be necessary to call upon the ancient arts..." +authors = [ + { name = "Bradley Reynolds", email = "bradley.reynolds@darbia.dev" }, +] +license = { text = "MIT" } +readme = "README.md" +requires-python = ">=3.7" +dependencies = [ + +] + +[project.urls] +repository = "https://github.com/letsbuilda/imsosorry/" +documentation = "https://projects.letsbuilda.dev/imsosorry/" + +[project.optional-dependencies] +dev = [ + "black", + "isort", + "pylint", +] +tests = [ + "pytest", +] +docs = [ + "sphinx", + "furo", + "sphinx-autoapi", + "releases", + "toml", +] + +[build-system] +requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta" + +[tool.sphinx] +copyright = "Let's build a ..." +author = "Bradley Reynolds" + +[tool.black] +target-version = ["py310"] +line-length = 120 + +[tool.isort] +profile = "black" + +[tool.pytest.ini_options] +addopts = "tests -r a -v --doctest-modules src" + +[tool.pylint.format] +max-line-length = 120 diff --git a/src/imsosorry/__init__.py b/src/imsosorry/__init__.py new file mode 100644 index 0000000..de1455c --- /dev/null +++ b/src/imsosorry/__init__.py @@ -0,0 +1,3 @@ +"""I'm so sorry +Sometimes it can be necessary to call upon the ancient arts... +""" diff --git a/src/imsosorry/py.typed b/src/imsosorry/py.typed new file mode 100644 index 0000000..1242d43 --- /dev/null +++ b/src/imsosorry/py.typed @@ -0,0 +1 @@ +# Marker file for PEP 561. diff --git a/src/imsosorry/uwuification.py b/src/imsosorry/uwuification.py new file mode 100644 index 0000000..28d1abc --- /dev/null +++ b/src/imsosorry/uwuification.py @@ -0,0 +1,121 @@ +"""The ancient arts of Uwuification""" + +from __future__ import annotations + +import random +import re +from functools import partial + +WORD_REPLACE = { + "small": "smol", + "cute": "kawaii~", + "fluff": "floof", + "love": "luv", + "stupid": "baka", + "idiot": "baka", + "what": "nani", + "meow": "nya~", + "roar": "rawrr~", +} + +EMOJIS = [ + "rawr x3", + "OwO", + "UwU", + "o.O", + "-.-", + ">w<", + "(⑅˘꒳˘)", + "(ꈍᴗꈍ)", + "(˘ω˘)", + "(U ᵕ U❁)", + "σωσ", + "òωó", + "(///ˬ///✿)", + "(U ﹏ U)", + "( ͡o ω ͡o )", + "ʘwʘ", + ":3", + ":3", # important enough to have twice + "XD", + "nyaa~~", + "mya", + ">_<", + "😳", + "🥺", + "😳😳😳", + "rawr", + "^^", + "^^;;", + "(ˆ ﻌ ˆ)♡", + "^•ﻌ•^", + "/(^•ω•^)", + "(✿oωo)", +] + +REGEX_WORD_REPLACE = re.compile(r"(?\g<2>-\g<2>" + +REGEX_NYA = re.compile(r"n([aeou][^aeiou])") +SUBSTITUTE_NYA = r"ny\1" + + +def word_replace(text: str) -> str: + """Replaces words that are keys in the word replacement hash to the values specified.""" + for word, replacement in WORD_REPLACE.items(): + text = text.replace(word, replacement) + return text + + +def char_replace(text: str) -> str: + """Replace certain characters with 'w'.""" + return REGEX_WORD_REPLACE.sub("w", text) + + +def stutter(text: str, strength: float) -> str: + """Adds stuttering to a string.""" + return REGEX_STUTTER.sub(partial(stutter_replace, strength=strength), text, 0) + + +def stutter_replace(match: re.Match, strength: float = 0.0) -> str: + """Replaces a single character with a stuttered character.""" + match_string = match.group() + if random.random() < strength: + return f"{match_string}-{match_string[-1]}" # Stutter the last character + return match_string + + +def nyaify(text: str) -> str: + """Nyaifies a string by adding a 'y' between an 'n' and a vowel.""" + return REGEX_NYA.sub(SUBSTITUTE_NYA, text, 0) + + +def emoji( + text: str, + strength: float, +) -> str: + """Replaces some punctuation with emoticons.""" + return REGEX_PUNCTUATION.sub(partial(emoji_replace, strength=strength), text, 0) + + +def emoji_replace(match: re.Match, strength: float = 0.0) -> str: + """Replaces a punctuation character with an emoticon.""" + match_string = match.group() + if random.random() < strength: + return f" {random.choice(EMOJIS)} " + return match_string + + +def uwuify(text: str, *, stutter_strength: float = 0.2, emoji_strength: float = 0.1) -> str: + """Takes a string and returns an uwuified version of it.""" + text = text.lower() + text = word_replace(text) + text = nyaify(text) + text = char_replace(text) + text = stutter(text, stutter_strength) + text = emoji(text, emoji_strength) + return text diff --git a/tests/test_uwuification.py b/tests/test_uwuification.py new file mode 100644 index 0000000..c49272c --- /dev/null +++ b/tests/test_uwuification.py @@ -0,0 +1,18 @@ +"""Test the arts of Uwuification""" + +from __future__ import annotations + +import pytest + +from imsosorry.uwuification import word_replace + + +@pytest.mark.parametrize( + "in_text,out_text", + [ + ("cats are small", "cats are smol"), + ("I love dogs", "I luv dogs"), + ], +) +def test_word_replace(in_text: str, out_text: str) -> None: + assert word_replace(in_text) == out_text