Initial checkin. Base working version.
This commit is contained in:
@@ -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"""
|
||||
@@ -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()
|
||||
@@ -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']
|
||||
@@ -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")
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user