Initial checkin. Base working version.

This commit is contained in:
Cassowary 2023-09-25 16:57:38 -07:00
commit 9ff59dd2f1
9 changed files with 962 additions and 0 deletions

226
.gitignore vendored Normal file
View File

@ -0,0 +1,226 @@
# Created by https://www.toptal.com/developers/gitignore/api/emacs,python
# Edit at https://www.toptal.com/developers/gitignore?templates=emacs,python
### Emacs ###
# -*- mode: gitignore; -*-
*~
\#*\#
/.emacs.desktop
/.emacs.desktop.lock
*.elc
auto-save-list
tramp
.\#*
# Org-mode
.org-id-locations
*_archive
# flymake-mode
*_flymake.*
# eshell files
/eshell/history
/eshell/lastdir
# elpa packages
/elpa/
# reftex files
*.rel
# AUCTeX auto folder
/auto/
# cask packages
.cask/
dist/
# Flycheck
flycheck_*.el
# server auth directory
/server/
# projectiles files
.projectile
# directory configuration
.dir-locals.el
# network security
/network-security.data
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.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
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
### Python Patch ###
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
poetry.toml
# ruff
.ruff_cache/
# LSP config files
pyrightconfig.json
# End of https://www.toptal.com/developers/gitignore/api/emacs,python

15
LICENSE Normal file
View File

@ -0,0 +1,15 @@
No Nazis
Otherwise:
Copyright (c) 2023
Aldercone Studio
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
THIS SOFTWARE IS PROVIDED BY Aldercone Studio “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL [Name of Organisation] BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

26
heckimp/__init__.py Normal file
View File

@ -0,0 +1,26 @@
#
#
#
"""
HECKIMP
A tool to rename and rebrand Gnu Image Manipulation Program from source.
Brought to you by Aldercone Studio
https://heckin.technology/AlderconeStudio/heckimp/
"""
__version__ = "0.1.0"
LOGO="""
aldercone
"""
ANNULUS = """M 116.63112 63.091239 C 87.273549 63.809372 39.29769 97.201192 38.448816 122.99818 C 37.404048 154.74832 76.218915 173.98333 120.81226 162.44331 C 152.15791 154.33157 154.42018 88.62447 133.17275 68.309009 C 129.18886 64.49986 123.40595 62.925517 116.63112 63.091239 z M 97.860177 76.918819 C 105.16897 76.821912 111.75814 79.563777 116.37998 85.410807 C 128.70488 101.0029 111.56781 150.92535 93.210331 149.80068 C 74.852841 148.67601 49.394526 116.36811 61.094979 99.831632 C 71.498378 85.128312 85.678846 77.080331 97.860177 76.918819 z"""

304
heckimp/__main__.py Normal file
View File

@ -0,0 +1,304 @@
"""
Heckweasel: Rename GnuImp
"""
import argparse
import datetime
import logging
import os
import pprint
import re
import shutil
import sys
import traceback
import yaml
import tqdm # type: ignore
from pathlib import Path
from typing import Sequence, Union
from PIL import Image
from . import (ANNULUS,
LOGO,
__version__)
from .gnuimpregexp import (CONTENTS_EXCEPTIONS,
CONTENTS_REGEXP,
DEBUG_REGEXP,
FILENAME_EXCEPTIONS,
FILENAME_REGEXP,
ICON_REGEXP,
LOGO_REGEXP,
MASCOT_REGEXP,
MASCOT_SCALABLE_REGEXP,
SCALABLE_REGEXP,
SKIP_PROCESSING,
SPLASH_REGEXP,
MAKE_DIRECTORIES)
from .misc import (TqdmLoggingHandler,
is_binary,
find_file,
reverse_domain,
PROBABLY_BINARY_EXTENSIONS
)
from .regexptools import (Replacement,
apply_regexps,
one_matches,
process_text_file)
logger = logging.getLogger(__name__)
####
# TODO
#
# ** wilber.ico and wilber.bmp in windows [we can make these from the input pngs and output them as mascot.bmp/ico]
# * README - need to ttalk about canvas shadow, formats and styles for icon.s etc.
####
# Later TODO
#
# * Add a 'additional files' config, like for installing brushes and such as part of the package. We'd have to patch
# the appropirate meson files to do it.
# ** we might want to allow color vs. symbolic icons
# * impliment versioning somehow ?
# * Fixing various urls, dialogs, etc. (also the text wiggle in about, basically sort of bury the original stuff)
# * We should fix the README, like make a README generated
# * We should fix help menu
# * We should fix references to URLs in the software itself in various places
# * It'd be cool to have a way to apply certain regex by filename instead of globally
# * probably disable check for updates too
# * fix script-fu, python plugins, etc. There's a ton of broken ones we'll have to fix. Something with the libarry not
# being installed (these are broken upstream too, or due to my python setup maybe?)
#
# We flatten
# icon
# image
# menu icons
# mascot
# into one set of files, but we could break these out a bit; we could also figure out how to autogenerate the
# canvas shadow curves from the icon svg but this might be a bit far afield.
#
def setup_logging(verbose:bool=False, quiet:bool=False, logfile:Union[Path, str, None]=None) -> None:
"""
Configure logging based on some flags.
"""
# Setup Tqdm handler
logger.setLevel(logging.DEBUG)
h = TqdmLoggingHandler()
if verbose:
f = logging.Formatter('%(asctime)s %(module)-12s %(levelname)-8s %(message)s')
h.setLevel(logging.DEBUG)
h.setFormatter(f)
elif quiet:
f = logging.Formatter('%(levelname)-8s %(message)s')
h.setLevel(logging.CRITICAL)
h.setFormatter(f)
else:
f = logging.Formatter('%(levelname)-8s %(message)s')
h.setLevel(logging.INFO)
h.setFormatter(f)
logger.addHandler(h)
# setup logfile if specified
if logfile:
lf = logging.FileHandler(logfile)
lf.setLevel(logging.DEBUG)
lf.setFormatter(logging.Formatter('%(asctime)s %(module)-12s %(levelname)-8s %(message)s'))
logger.addHandler(lf)
def parse_args(args: Sequence[str]) -> argparse.Namespace:
"""
Parse command-line arguments.
"""
parser = argparse.ArgumentParser(prog="heckimp",
description="Process clean GnuImp source tree into rebranded fork.")
parser.add_argument("-v", "--verbose", action="store_true", help="Show more verbosity but only a little.") # done
parser.add_argument("-q", "--quiet", action="store_true", help="Show less innformation like not every filename.") # done
parser.add_argument("--nogit", action="store_true", help="Don't do git stuff at the end.") # done
# Disabling these for now because it complicates filename transforms. We can work out better filename transform logic later and support this.
# For now you just need the checkout as 'gimp' and the output will be the filename.
# parser.add_argument("-i", "--input", type=Path, default="gimp", help="Force input tree, default is original codebase name.")
# parser.add_argument("-o", "--output", type=str, help="Force output tree. Default is the app's name.", default="")
parser.add_argument("-c", "--config", type=Path, default=Path("heckimp-pika/pika.yaml"), help="Specify configuration file.", required=True) # done
parser.add_argument("-p", "--paths", default=["."], nargs="+", help="Specify paths to look for images and stuff.") # done
parser.add_argument("--dump", action="store_true", help="Don't excute, just dump configuration and replacement strings (for debugging purposes).") # done
parser.add_argument("--log", type=str, default="", help="Write logs to specified file.") # done!
parser.add_argument("--no-progress", dest='no_progress', action="store_true", help="Don't show progress bar.") # done
# parser.add_argument("--simple", action="store_true", help="Simplify output (no progress bars and similar).") # fixme later [dependency loop]
# parser.add_argument("--build", action="store_true", help="Also execute the build process with Meson and Ninja.") #Fixme maybe
return parser.parse_args(args)
def heckimp(args: Sequence[str]):
print(LOGO)
pargs = parse_args(args[1:])
setup_logging(pargs.verbose, pargs.quiet, pargs.log)
inconfig = {}
with pargs.config.open() as conffile:
inconfig = yaml.safe_load(conffile)
searchpaths = set([Path(path) for path in pargs.paths])
searchpaths.add(pargs.config.parent)
outdir = Path(inconfig['name'].lower())
if 'shadow' in inconfig:
shadow = find_file(inconfig['shadow'], searchpaths).read_text().replace('\n', ' ')
else:
shadow = ANNULUS
derivedconfig={
# Derived variables from config
"longname": inconfig['name'].upper(),
"expandedname": inconfig['expansion'],
"symname": inconfig['name'].capitalize(),
"filename": inconfig['name'].lower(),
"ufilename": inconfig['name'].upper(),
"backdomain": reverse_domain(inconfig['url']),
"baseurl": inconfig['url'],
"copyright": inconfig['copyright'],
"version": inconfig['version'],
"resource_path": reverse_domain(inconfig['url']).replace('.', '/'),
# Paths..
"sourcetree": 'gimp',
"outtree": outdir,
"icon": inconfig['icon'],
"splash": inconfig['splash'],
"scalable": inconfig['scalable'],
# heckimp internal variables
"year": str(datetime.datetime.now().year),
"us": "heckimp",
"usversion": __version__,
"uscopy": "Aldercone Studio",
"shadow": shadow,
}
sourceroot = Path(derivedconfig["sourcetree"])
# precompute string replacements
for r in FILENAME_REGEXP:
r.replacement = r.replacement.format(**derivedconfig)
for r in CONTENTS_REGEXP:
r.replacement = r.replacement.format(**derivedconfig)
make_directories = [f.format(**derivedconfig) for f in MAKE_DIRECTORIES]
if pargs.dump:
# we can probably output regexes and stuff too but that seems a bit excessive right now.
print("Dumping configuration:")
print("## Input Configuration")
pprint.pprint(inconfig)
print("## Derived Configuration")
pprint.pprint(derivedconfig)
return 0
# visit each file in the source tree,
files = list(sourceroot.rglob('*'))
if pargs.no_progress:
loop = files
print("Processing codebase")
else:
loop = tqdm.tqdm(files, desc="Processing input codebase", unit="file", dynamic_ncols=True, leave=False)
for d in make_directories:
logger.info(f"making path ahead of time: {d}")
Path(d).mkdir(exist_ok=True, parents=True)
for path in loop:
if one_matches(FILENAME_EXCEPTIONS, str(path)):
logger.info(f"skipping excluded path {path}")
continue
# possibly rename file,
outpath = Path(apply_regexps(FILENAME_REGEXP, [], str(path)))
logger.info(f"processing {outpath}")
# write each file to the outtreea
if path.is_dir():
outpath.mkdir(exist_ok=True, parents=True)
logger.info(f"making output directory -> {outpath}")
else:
#
# fixme we should make a more functional way of doing all these special cases, like associating
# the special regexs with a symbol that we match on instead so we can treat several of them the same
# way
#
# do things to the contents
m = ICON_REGEXP.match(str(path))
n = MASCOT_REGEXP.match(str(path))
# FIXME we should separate this special processing stuff into some separate functions, especially
# the image resizing thing.
if SPLASH_REGEXP.match(str(path)):
logger.info(f"copying splash screen {derivedconfig['splash']}")
# fixme instead of breaking this should copy a default heckimp splash
splash = find_file(derivedconfig['splash'], searchpaths)
shutil.copy(str(splash), str(outpath))
elif SCALABLE_REGEXP.match(str(path)) or MASCOT_SCALABLE_REGEXP.match(str(path)):
scalable = find_file(derivedconfig['scalable'], searchpaths)
shutil.copy(str(scalable), str(outpath))
elif LOGO_REGEXP.match(str(path)):
iconpath = find_file(derivedconfig['icon'], searchpaths)
icon = Image.open(str(iconpath))
icon.thumbnail((256, 256), Image.ANTIALIAS)
icon.save(str(outpath), 'PNG')
elif m:
# size icon for size specified in the path
size = int(m.groups()[0])
logger.info(f"resizing icon {derivedconfig['icon']} to {size}")
iconpath = find_file(derivedconfig['icon'], searchpaths)
icon = Image.open(str(iconpath))
icon.thumbnail((size, size), Image.ANTIALIAS)
icon.save(str(outpath), 'PNG')
elif n:
# this is identical to the above so we should reuse the code
# size icon for size specified in the path
size = int(n.groups()[0])
logger.info(f"resizing icon {derivedconfig['icon']} to {size}")
iconpath = find_file(derivedconfig['icon'], searchpaths)
icon = Image.open(str(iconpath))
icon.thumbnail((size, size), Image.ANTIALIAS)
icon.save(str(outpath), 'PNG')
elif one_matches(SKIP_PROCESSING, str(path)) or (path.suffix in PROBABLY_BINARY_EXTENSIONS) or is_binary(path):
logger.info(f"skipping processing {outpath}")
shutil.copy(str(path), str(outpath))
else:
# process each line of file to produce output
process_text_file(CONTENTS_REGEXP, CONTENTS_EXCEPTIONS, path, outpath)
# re-sort library definition files=
if re.match(r".*\.def$", str(outpath)):
logger.info(f"sorting definition file {outpath}")
inf = outpath.read_text().split('\n')
outpath.write_text(inf[0]+('\n'.join(sorted(inf[1:], key=lambda d: d.strip()))))
if not pargs.nogit:
os.chdir(derivedconfig['outtree'])
os.system('git init .')
os.system('git add .')
os.system(f"""git commit -m "Initial checkin of {derivedconfig['symname']} from {derivedconfig["us"]}" """)
os.system(f"""git tag v{derivedconfig['version']}""")
print("Done.")
return 0
def main():
try:
sys.exit(heckimp(sys.argv))
except Exception as inst:
# fixme log exception
print(f"Exception! {inst}")
print(traceback.format_exc())
sys.exit(-1)
if __name__ == "__main__":
main()

199
heckimp/gnuimpregexp.py Normal file
View File

@ -0,0 +1,199 @@
import re
from typing import List, Union
from .regexptools import Replacement
R = Replacement
"""
This module contains all of the specially crafted regexps for the GnuImp source code.
"""
#
# These regexps are applied to all files, and are the heart of our replacements.
#
CONTENTS_REGEXP: List[Replacement] = [
## Hyperspecific stuff that needs to override later stuff.
# Copyright notice update with maximal adherence.
R(
re.compile(r"/\* GIMP - The GNU Image Manipulation Program(.*)"),
(r"/* {longname} - {expandedname}\n"
r" * a rebranding of The GNU Image Manipulation Program (created with {us})\n"
r" * A derived work which may be trivial. However, any changes may be (C){year} by {copyright}\n *\n"
r" * Original copyright, applying to most contents (license remains unchanged): ")
),
R(
re.compile(r'(.*)Spencer Kimball, Peter Mattis and the GIMP Development Team(.*)'),
r"\1Based on work by Spencer Kimball, Peter Mattis and the GnuImp Development Team\2"
),
# Stuff that just doesn't munge right
R(
re.compile(r"#define GIMP\(obj\) \(G_TYPE_CHECK_INSTANCE_CAST \(\(obj\), GIMP_TYPE_GIMP, Gimp\)\)"),
r"#define {ufilename}(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), {ufilename}_TYPE_{ufilename}, {symname}))"
),
R(re.compile(r"typedef struct _Gimp Gimp;"),
r"typedef struct _{symname} {symname};"),
R(
re.compile(r'(.*)G_DEFINE_TYPE \(Gimp, gimp, GIMP_TYPE_OBJECT\)(.*)'),
r'\1G_DEFINE_TYPE ({symname}, {filename}, {ufilename}_TYPE_OBJECT)\2'
),
R(re.compile(r"(.*[\t ])_Gimp(.*)"), r"\1_{symname}\2"),
R(re.compile(r"(.*)mapGimp(.*)"), r'\1map{symname}\2'),
## general backdomain for eg. app names and such
R(re.compile(r"(.*)org.gimp(.*)"), r'\1{backdomain}\2'),
## canvas shadow crappe (we should constrain this but it's really specific)
# this one just whipes out all of the embdedded SVGs
R(re.compile(r"""^[ \t]*"M [0-9.]+.* z( )*";"""), ""),
# this one puts an empty path for eyes_path
R(re.compile(r"static const gchar eyes_path\[\] =.*"), r'static const gchar eyes_path[] = "M z"; // #heckimp skip'),
R(re.compile(r"static const gchar wilber_path\[\] =.*"), r'static const gchar mascot_path[] = "{shadow}";'),
## Resource paths
R(re.compile(r"(.*)/org/gimp/(.*)"), r'\1{resource_path}\2'),
## URLs and things # FIXME we need to respect *some* of the original URLs, we'll take the time to track down those
## and make exceptions later but for now we'll do a big replace. Stuff is mostly in the dialogs, just grep for
## the .org domain in there and we can do a great deal to fix it.
# we also need to make sure we're directing people to the original docs, but also making sure they don't bother
# their bug tracker, and also allow the user to replace the various kinds of urls (docs, subpaths like bugs, etc)
R(re.compile(r"(.*)https?://www.gimp.org(.*)"), r'\1{baseurl}\2'),
R(re.compile(r"(.*[ \t\"])(www.)?gimp.org(.*)"), r'\1{baseurl}\3'),
## general replacements
R(re.compile(r"(.*)gimp(.*)"), r'\1{filename}\2'),
R(re.compile(r"(.*)GIMP(.*)"), r'\1{ufilename}\2'),
R(re.compile(r"(.*)The GNU Image Manipulation Program(.*)"), r"\1{expandedname}\2"),
R(re.compile(r"(.*)GNU Image Manipulation Program(.*)"), r"\1{expandedname}\2"),
## symbol names
# various symbols and other things (fixme why don't these match ...something=somethingGimp...)
R(re.compile(r"(.*[ \t([{][/_%#*&$@:=+-]?)Gimp([:A-Za-z0-9._ ].*)"), r'\1{symname}\2'),
R(re.compile(r"^Gimp([A-Za-z0-9._ -].*)"), r'{symname}\1'),
R(re.compile(r"(.*[ \t([{][/_%#*&$@:=+-]?[A-Za-z][A-Za-z0-9]+)Gimp([:A-Za-z0-9._ [-].*)"), r'\1{symname}\2'),
R(re.compile(r"(.*=[ ]*[A-Za-z]*)Gimp(.*)"), r'\1{symname}\2'),
# some meson stuff
R(re.compile(r"(.*)LibGimp(.*)"), r'\1Lib{symname}\2'),
# documentation?
R(re.compile(r"(.*[#_/.])Gimp(.*)"), r'\1{symname}\2'),
R(re.compile(r"(.*)Gimp-tips(.*)"), r'\1{symname}-tips\2'),
# Strings and strinng like symbols
R(re.compile(r"""(.*['"`][A-Za-z0-9]*)Gimp([A-Za-z0-9.-]*.*)"""), r'\1{symname}\2'),
R(re.compile(r"""(.*)Gimp([A-Za-z0-9.-]*['"`].*)"""), r'\1{symname}\2'),
# special symbol converts (kind of hyperspecific, but we can worry about it later)
R(re.compile(r"(.*)import Gimp(.*)"), r'\1import {symname}\2'),
R(re.compile(r"(.*)gi.repository.Gimp(.*)"), r'\1gi.repository.{symname}\2'),
R(re.compile(r"(.*)Gimp-Python(.*)"), r'\1{symname}-Python\2'),
R(re.compile(r"(.*)Gimp-Print(.*)"), r'\1{symname}-Print\2'),
R(re.compile(r"(.*)Gimp@Type(.*)"), r'\1{symname}@Type\2'),
# Stuff in the pdb perl scripts (the above symbol convert should work but w/e)
R(re.compile(r"(.*s/)Gimp(/.*)"), r'\1{symname}\2'),
R(re.compile(r"(.*[$%&])Gimp(::.*)"), r'\1{symname}\2'),
# Mascot stuff
R(re.compile(r"(.*[_-])wilber(.*)"), r"\1mascot\2"),
R(re.compile(r"(.*)wilber([_-].*)"), r"\1mascot\2"),
R(re.compile(r"(.*[_-])WILBER(.*)"), r"\1MASCOT\2"),
# Chop out the wilber brush
R(re.compile(r"^[ \t]*'Wilber.gih',[ \t]*$"), ''),
]
#
# These regexps cause the replacement engine to skip the line once they match. This allows us to selectively skip some
# lines in the source code, and also cause a line to become skipped after some replacements.
#
CONTENTS_EXCEPTIONS: List[re.Pattern] = [
# skips generated by heckimp
re.compile(r".*#heckimp skip.*"),
# email addresses should not be replaced.
re.compile(r"(.*)@gimp\.org(.*)"),
# don't replace the expansion of GnuImp in the updated copyright notice
re.compile(r".*a rebranding of The GNU Image.*"),
# we want to preserve our copyright notice without it getting munged (its subtly different from the original)
re.compile(r".*Spencer Kimball, Peter Mattis and the GIMP Development Team, .*"),
# keep our xcfs compatible
re.compile(r'.*"gimp xcf.*'),
# Lets leave all of the weird urls alone for now until we let the user override them explicitly (or we rewrite
# the places they show up with our own versions of the strings) # FIXME the way it does skips breaks some
# replacements so we should fix this elsewise somehow.
#re.compile(r".*docs.gimp.org.*"),
#re.compile(r".*developer.gimp.org.*"),
#re.compile(r".wiki.gimp.org.*"),
re.compile(r".*irc.*gimp.org:6667.*"),
re.compile(r".*meetthegimp.org.*"), # wtf this domain
]
#
# This is the core filename renaming regexps.
#
FILENAME_REGEXP: List[Replacement] = [
# We'll stick all this historical cruft in its own directory.
R(re.compile(r"^gimp/README"), r'{filename}/upstream-documentation/README.upstream'),
R(re.compile(r"^gimp/(README.*)"), r'{filename}/upstream-documentation/\1'),
R(re.compile(r"^gimp/(Change[Ll]og.*)"), r'{filename}/upstream-documentation/\1'),
R(re.compile(r"^gimp/(NEWS.*)"), r'{filename}/upstream-documentation/\1'),
R(re.compile(r"^gimp/MAINTAINERS"), r'{filename}/upstream-documentation/MAINTAINERS.upstream'),
# other filename replacements
R(re.compile(r"(.*)org.gimp(.*)"), r'\1{backdomain}\2'),
R(re.compile(r"(.*)gimp(.*)"), r'\1{filename}\2'),
R(re.compile(r"(.*)GIMP(.*)"), r'\1{ufilename}\2'),
R(re.compile(r"(.*-)wilber(.*)"), r'\1mascot\2'),
]
#
# If a file matches one of these, it is skipped entirely (not copied or processed)
#
FILENAME_EXCEPTIONS: List[re.Pattern] = [
# We just leave the git out of it.
# If the fork should have a git repository it should probably not be connected to orignal.
re.compile(r"^gimp/\.git/.*"),
# re.compile(r"^gimp.icons.Legacy.*") # it'd be cool to chop this out but we'd have to patch icons/meson.build
# just chop out all the random Wilber images (including the brush)
re.compile(r".*Wilber*"),
]
#
# If a file matches one of these we skip processing but do copy it. We may have separate processes that also deal with
# these. FIXME we should proxbably process LICENSE, and copy NEWS and CHANGELOG etc to a subdirectory so they don't
# confuse the fork.
#
SKIP_PROCESSING: List[re.Pattern] = [
re.compile(r"^gimp/LICENSE"),
re.compile(r"^gimp/Change[Ll]og.*"),
re.compile(r"^gimp/NEWS.*"),
re.compile(r"^gimp/authors.*"),
re.compile(r"^gimp/AUTHORS"),
re.compile(r"^gimp/MAINTAINERS"),
]
#
# regexp that print extra debug info when a line matches them
#
DEBUG_REGEXP: List[re.Pattern] = [
# re.compile(r"^[ \t]+static const GimpEnum.*"),
]
# Regexes to match various specific files for special processing
SPLASH_REGEXP = re.compile(r".*gimp-splash.png")
ICON_REGEXP = re.compile(r".*desktop.([0-9]{2,3})x[0-9]{2,3}.gimp.png")
SCALABLE_REGEXP = re.compile(r".*desktop.scalable.gimp.svg")
LOGO_REGEXP = re.compile(r".*data.images.gimp-(devel-)?logo.png")
MASCOT_SCALABLE_REGEXP = re.compile(r"icons.(Symbolic|Color).scalable.gimp-wilber.*")
MASCOT_REGEXP = re.compile(r"icons.(Symbolic|Color|Lecgacy).([0-9]{2,3}).gimp-wilber.*")
MAKE_DIRECTORIES = ['{filename}/upstream-documentation']

70
heckimp/misc.py Normal file
View File

@ -0,0 +1,70 @@
import logging
import urllib.parse
import tqdm # type: ignore
from typing import List, Set
from pathlib import Path
"""
Miscellaneous utilities and constants.
"""
PROBABLY_BINARY_EXTENSIONS: List[str] = [
".ico",
".ppm",
".pnm",
".png",
".gz",
".svg",
".pgm",
".gih",
".gbr",
]
class TqdmLoggingHandler(logging.Handler):
"""
A simple logging wrapper that won't clobber TQDM's progress bar.
"""
def __init__(self, level=logging.NOTSET):
super().__init__(level)
def emit(self, record):
try:
msg = self.format(record)
tqdm.tqdm.write(msg)
self.flush()
except Exception:
self.handleError(record)
## Utility Functions
def is_binary(path: Path, amount: int = 256) -> bool:
"""
A pretty sloppy way to determine if a file is binary or not. Takes a path and returns true/false. Bigger
amount means more accurate (more of the file read).
"""
try:
b = path.open().read(amount)
return False
except UnicodeDecodeError:
return True
def reverse_domain(url: str) -> str:
"""
Create a 'reverse domain' used for labeling objects and whatnot.
"""
p = urllib.parse.urlparse(url)
domain = p.netloc
return '.'.join(reversed(domain.split('.')))
def find_file(fname: str, searchpaths: Set[Path]) -> Path:
"""
Return a path object for the first file we find in the search paths with the filename or raise FileNotFound error.
"""
for path in searchpaths:
if (path / fname).exists():
return (path / fname)
raise FileNotFoundError(f"can't find {fname} in searchpaths")

80
heckimp/regexptools.py Normal file
View File

@ -0,0 +1,80 @@
"""
Various functios to process files and work with regexps and Replacement objects
"""
import re
import logging
from collections.abc import Iterable
from pathlib import Path
from typing import cast, Sequence, Union, Optional
logger = logging.getLogger(__name__)
# This is a generic type that's a regexp with its replacement (it needs to be mutatable but we should
# add a lot of the below functionality to the class itself, for example we could have __call__ call the
# regex.match, we could make a function that configures the replacement based on a dictionary, we could
# make a function that executes the replacement on a string, etc. It would probably make the below code
# a bit less type dependent).
class Replacement:
def __init__(self, regexp, replacement):
self.regexp = regexp
self.replacement = replacement
def one_matches(relist: Sequence[Union[re.Pattern, Replacement, Sequence]], s: str) -> Union[None, re.Pattern, Replacement, Sequence]:
"""
Return True if one of the regular expressions, patterns, or 0th elements in Sequence of lists matches.
"""
for reg in relist:
if isinstance(reg, Replacement):
r = reg.regexp
elif isinstance(reg, re.Pattern):
r = reg
elif isinstance(reg, Iterable):
r = reg[0]
else:
raise Exception("got a weird thing")
if r.match(s):
return reg
return None
def apply_regexps(relist: Sequence[Replacement], exceptlist: Optional[Sequence[re.Pattern]]=None, s: str = "", debuglist: Optional[Sequence[re.Pattern]]=None) -> str:
"""
Return the string after applying each Replacement in the list passed as relist.
"""
if debuglist is None:
debuglist = []
if exceptlist is None:
exceptlist = []
res = s
debug = False
if one_matches(debuglist, s):
debug = True
logger.debug(f"-- regexp start {s}")
while True:
m: Replacement = cast(Replacement, one_matches(relist, res))
if debug and m is not None:
logger.debug(m)
if m is None:
break;
if one_matches(exceptlist, res):
if debug:
logger.debug("exceptlist: "+str(one_matches(exceptlist, res)))
break
res = m.regexp.sub(m.replacement, res)
if debug:
logger.debug(f"-- regexp done {res}")
return res
def process_text_file(relist: Sequence[Replacement], exceptlist: Sequence[re.Pattern], infile: Path, outfile: Path, debug_regexps: Sequence[re.Pattern] = None):
if debug_regexps is None:
debug_regexps = []
with infile.open("r") as inf, outfile.open("w") as outf:
fixedlines = [apply_regexps(relist, exceptlist, line, debug_regexps) for line in inf.readlines()]
outf.writelines(fixedlines)

40
setup.cfg Normal file
View File

@ -0,0 +1,40 @@
[metadata]
name = heckimp
version = 0.1.1
description = Rename and rebrand Gnu Image Manipulation Program
author = Aldercone Studio
author_email = alderconestudio@gmail.com
keywords = text, graphics, rebranding, code processing
long_description = file: README.md
long_description_content_type = text/markdown
license_file = LICENSE
url=https://heckin.technology/AlderconeStudio/heckimp
license = No Nazis (otherwise BSD 1 clause)
platform = any
classifiers =
Development Status :: 3 - Alpha
Intended Audience :: Developers
License :: OSI Approved :: BSD License
Operating System :: OS Independent
Programming Language :: Python
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11
Topic :: Artistic Software
Topic :: Text Processing
[options]
packages =
heckimp
zip_safe = true
install_requires =
tqdm
pillow
[options.entry_points]
console_scripts =
heckimp = heckimp.__main__:main

2
setup.py Normal file
View File

@ -0,0 +1,2 @@
import setuptools
setuptools.setup()