diff --git a/.github/workflows/wheel.yaml b/.github/workflows/wheel.yaml new file mode 100644 index 0000000..847727b --- /dev/null +++ b/.github/workflows/wheel.yaml @@ -0,0 +1,96 @@ +name: create wheels + +on: + workflow_dispatch: + pull_request: + push: + branches: + - main + tags: + - "v*" + +env: + FORCE_COLOR: 3 + +concurrency: + group: github.workflow−{{ github.workflow }}-{{ github.ref }} + cancel-in-progress: true + +jobs: + build_wheels: + name: Build universal wheels + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + submodules: false + + - uses: actions/setup-python@v4 + with: + python-version: "3.x" + + - name: Install build dependencies + run: | + python -m pip install --upgrade pip + pip install build + + - name: Build universal wheels + run: python -m build --wheel + + - name: Verify clean directory + run: git diff --exit-code + shell: bash + + - uses: actions/upload-artifact@v4 + with: + name: wheelhouse + path: dist/*.whl + overwrite: true + + + release: + name: Release Assets and Upload to PyPI + needs: [build_wheels] + runs-on: ubuntu-latest + + steps: + # Determine release type + - name: Determine release type + id: release_type + run: | + if [[ "${{ github.ref }}" =~ ^refs/tags/v.*$ ]]; then + echo "RELEASE_NAME=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV + echo "IS_TAG=true" >> $GITHUB_ENV + else + echo "RELEASE_NAME=__beta__" >> $GITHUB_ENV + echo "IS_TAG=false" >> $GITHUB_ENV + fi + shell: bash # Use bash for all platforms + + - uses: actions/download-artifact@v4 + with: + path: dist + merge-multiple: true + + + - name: List dist directory + run: tree --du -shaC . # Ensure the directory is not empty + + - name: Create or update release + id: create_release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ env.RELEASE_NAME }} + name: ${{ env.RELEASE_NAME }} + draft: false + prerelease: ${{ env.IS_TAG == 'false' }} + files: dist/* + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Publishing to PyPi + uses: pypa/gh-action-pypi-publish@release/v1 + if: startsWith(github.ref, 'refs/tags/') + with: + password: ${{ secrets.PYPI_PASSWORD }} + user: ${{ secrets.PYPI_USERNAME }} diff --git a/.gitignore b/.gitignore index 60db5bc..da89cca 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,68 @@ docs/html node_modules *.tgz + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# IDE +.vscode/ +.ropeproject + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Hatch +.hatch/ diff --git a/README.md b/README.md index 2110efb..b77855a 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,24 @@ Similarly, in the [xPack](https://xpack.github.io) ecosystem, this project can b as a development dependency to an [`xpm`](https://xpack.github.io/xpm/) managed project. +### Python + +For Python projects, you can install the theme as a Python package: + +```sh +# Install the package +pip install doxygen-awesome-css + +# Install files to your project directory +python -m doxygen_awesome_css --install . +# Install with verbose output +python -m doxygen_awesome_css --install ./docs --verbose +# Quiet mode (minimal output) +python -m doxygen_awesome_css --install ./docs --quiet +``` + +This method provides a clean way to manage the theme as a dependency and easily install the files where needed. + ### System-wide You can even install the theme system-wide by running `make install`. diff --git a/doxygen_awesome_css/__about__.py b/doxygen_awesome_css/__about__.py new file mode 100644 index 0000000..8d5d1df --- /dev/null +++ b/doxygen_awesome_css/__about__.py @@ -0,0 +1,4 @@ +"""Package metadata for Doxygen Awesome CSS.""" + +__version__ = "2.4.1.beta" +__license__ = "MIT" diff --git a/doxygen_awesome_css/__init__.py b/doxygen_awesome_css/__init__.py new file mode 100644 index 0000000..e55c4de --- /dev/null +++ b/doxygen_awesome_css/__init__.py @@ -0,0 +1,82 @@ +"""Doxygen Awesome CSS - A custom CSS theme for Doxygen HTML documentation.""" + +import logging +import shutil +import sys +from pathlib import Path +from typing import List + +from .__about__ import __version__, __license__ + + +class Installer: + """Handles installation of Doxygen Awesome CSS files.""" + + def __init__(self): + self.logger = logging.getLogger(__name__) + self.package_dir = Path(__file__).parent + self.files_to_copy = [ + "doxygen-awesome.css", + "doxygen-awesome-darkmode-toggle.js", + "doxygen-awesome-fragment-copy-button.js", + "doxygen-awesome-interactive-toc.js", + "doxygen-awesome-paragraph-link.js", + "doxygen-awesome-sidebar-only-darkmode-toggle.css", + "doxygen-awesome-sidebar-only.css", + "doxygen-awesome-tabs.js", + ] + + def get_package_files(self) -> List[Path]: + """Get list of package files that exist.""" + package_files = [] + for filename in self.files_to_copy: + file_path = self.package_dir / filename + if file_path.exists(): + package_files.append(file_path) + else: + self.logger.warning(f"{filename} not found in package") + return package_files + + def install(self, target_dir: Path) -> None: + """Install CSS/JS files to target directory.""" + if not target_dir.exists(): + try: + target_dir.mkdir(parents=True, exist_ok=True) + self.logger.info(f"Successfully created directory '{target_dir}'") + except Exception as e: + self.logger.error(f"Failed to create directory '{target_dir}': {e}") + sys.exit(1) + + if not target_dir.is_dir(): + self.logger.error(f"'{target_dir}' is not a directory") + sys.exit(1) + + package_files = self.get_package_files() + + if not package_files: + self.logger.error("No Doxygen Awesome CSS files found in package") + sys.exit(1) + + self.logger.info(f"Installing Doxygen Awesome CSS files to '{target_dir}'...") + + copied_count = 0 + for source_file in package_files: + target_file = target_dir / source_file.name + try: + shutil.copy2(source_file, target_file) + self.logger.debug(f"Copied: {source_file.name}") + copied_count += 1 + except Exception as e: + self.logger.error(f"Error copying {source_file.name}: {e}") + sys.exit(1) + + self.logger.info(f"Successfully installed {copied_count} files to '{target_dir}'") + + # Show installation summary + if logging.getLogger().getEffectiveLevel() == logging.DEBUG: + self.logger.debug("Installed files:") + for source_file in package_files: + self.logger.debug(f" - {source_file.name}") + + +__all__ = ["Installer", "__version__"] diff --git a/doxygen_awesome_css/__main__.py b/doxygen_awesome_css/__main__.py new file mode 100644 index 0000000..54bde06 --- /dev/null +++ b/doxygen_awesome_css/__main__.py @@ -0,0 +1,90 @@ +"""Main entry point for Doxygen Awesome CSS CLI.""" + +import argparse +import logging +from pathlib import Path + +from . import Installer +from .__about__ import __version__ + + +def setup_logging(verbose: bool = False, quiet: bool = False) -> None: + """Setup logging configuration based on verbosity.""" + if quiet: + level = logging.WARNING + elif verbose: + level = logging.DEBUG + else: + level = logging.INFO + + logging.basicConfig( + level=level, + format="%(message)s", + handlers=[logging.StreamHandler()] + ) + + +def create_parser() -> argparse.ArgumentParser: + """Create argument parser for CLI.""" + parser = argparse.ArgumentParser( + description="Doxygen Awesome CSS - A custom CSS theme for Doxygen HTML documentation", + epilog=""" +Examples: + # Install files to current directory + python -m doxygen_awesome_css --install . + + # Install files with verbose output + python -m doxygen_awesome_css --install ./docs --verbose + + # Show version + python -m doxygen_awesome_css --version + """, + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + + parser.add_argument( + "--install", + type=str, + metavar="DIR", + help="Install CSS/JS files to the specified directory", + ) + + parser.add_argument( + "--verbose", "-v", + action="store_true", + help="Enable verbose output (DEBUG level)", + ) + + parser.add_argument( + "--quiet", "-q", + action="store_true", + help="Enable quiet output (WARNING level)", + ) + + parser.add_argument( + "--version", + action="version", + version=f"doxygen-awesome-css {__version__}", + ) + + return parser + + +def main() -> None: + """Main entry point for CLI.""" + parser = create_parser() + args = parser.parse_args() + + setup_logging(verbose=args.verbose, quiet=args.quiet) + logger = logging.getLogger(__name__) + + if args.install: + installer = Installer() + installer.install(Path(args.install)) + else: + logger.info("Doxygen Awesome CSS - A custom CSS theme for Doxygen HTML documentation") + logger.info("Use --help for usage information") + + +if __name__ == "__main__": + main() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..e5e20d5 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,50 @@ +[build-system] +requires = ["setuptools>=63", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "doxygen-awesome-css" +dynamic = ["version"] +description = "Custom CSS theme for doxygen html-documentation with lots of customization parameters." +authors = [ + {name = "jothepro", email = "jothepro@github.com"}, + {name = "mohammadraziei", email = "mohammadraziei@github.com"} +] +license = {text = "MIT"} +readme = "README.md" +keywords = ["doxygen", "css", "theme", "awesome"] +dependencies = [] +requires-python = ">=3.8" + +[project.urls] +Homepage = "https://jothepro.github.io/doxygen-awesome-css/" +Repository = "https://github.com/jothepro/doxygen-awesome-css.git" + +[project.scripts] +doxygen-awesome-css = "doxygen_awesome_css.__main__:main" + +[tool.setuptools] +packages = ["doxygen_awesome_css"] + +[tool.setuptools.package-data] +doxygen_awesome_css = [ + "doxygen-awesome.css", + "doxygen-awesome-darkmode-toggle.js", + "doxygen-awesome-fragment-copy-button.js", + "doxygen-awesome-interactive-toc.js", + "doxygen-awesome-paragraph-link.js", + "doxygen-awesome-sidebar-only-darkmode-toggle.css", + "doxygen-awesome-sidebar-only.css", + "doxygen-awesome-tabs.js", +] + +[tool.setuptools.dynamic] +version = {attr = "doxygen_awesome_css.__about__.__version__"} + +[tool.black] +line-length = 88 +target-version = ['py38'] + +[tool.isort] +profile = "black" +multi_line_output = 3 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..03b624b --- /dev/null +++ b/setup.py @@ -0,0 +1,40 @@ +from pathlib import Path +import shutil + +from setuptools import setup +from setuptools.command.build_py import build_py as _build_py + + +ASSET_FILES = [ + "doxygen-awesome.css", + "doxygen-awesome-darkmode-toggle.js", + "doxygen-awesome-fragment-copy-button.js", + "doxygen-awesome-interactive-toc.js", + "doxygen-awesome-paragraph-link.js", + "doxygen-awesome-sidebar-only-darkmode-toggle.css", + "doxygen-awesome-sidebar-only.css", + "doxygen-awesome-tabs.js", +] + + +class build_py(_build_py): + """Custom build step that copies CSS/JS assets into the package.""" + + def run(self): + super().run() + self._copy_assets() + + def _copy_assets(self) -> None: + package_dir = Path(self.build_lib) / "doxygen_awesome_css" + source_root = Path(__file__).parent + package_dir.mkdir(parents=True, exist_ok=True) + + for filename in ASSET_FILES: + src = source_root / filename + dest = package_dir / filename + if not src.exists(): + raise FileNotFoundError(f"Asset missing: {src}") + shutil.copy2(src, dest) + + +setup(cmdclass={"build_py": build_py})