From 7c5aba8d4a2eb08ade851aac4a6d1779397db2d6 Mon Sep 17 00:00:00 2001 From: Al Stone Date: Sat, 24 May 2025 16:09:30 -0600 Subject: [PATCH 1/3] Add a basic help command Typing ./make.py (with no arguments) used to just result in error messages about missing values in sys.argv. Added a little bit of parameter checking, and a usage() function so that providing no arguments, or by using any of {-h|--help|help} as parameters will produce a message describing available commands and options. Signed-off-by: Al Stone --- make.py | 110 +++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 97 insertions(+), 13 deletions(-) diff --git a/make.py b/make.py index 560b18d95..88092fb37 100755 --- a/make.py +++ b/make.py @@ -59,18 +59,6 @@ from xml.etree import ElementTree from subprocess import call, Popen, PIPE -if "GRAMPSPATH" in os.environ: - GRAMPSPATH = os.environ["GRAMPSPATH"] -else: - GRAMPSPATH = "../../.." - -gramps_version = sys.argv[1] - -command = sys.argv[2] -if len(sys.argv) >= 4: - addon = sys.argv[3] - - def system(scmd, **kwargs): """ Replace and call system with scmd. @@ -340,6 +328,100 @@ def extract_po(addon): args = ["git", "restore", po] call(args) +def usage(): + """ + what are my options? + """ + print(f"usage: {sys.argv[0]} []") + print("where:") + print(" => Gramps maintenance branch name: one of") + print(" {gramps42 | gramps50 } gramps51 | gramps52 | gramps60}" ) + print(" => one of the following, with their :") + print() + print(" as-needed:") + print(" build packages (tgz files) for only those addons that have") + print(" changed, then rebuild listings files, and do some cleanup.") + print() + print(" aggregate-pot:") + print(" aggregates all 'template.pot' files into a single 'po/addons.pot'") + print(" file. Strings already in 'gramps.pot' are excluded.") + print() + print(" build { | all}:") + print(" build a specific adddon package if named and put it in") + print(" addons/Addon/Addon.addon.tgz; or, if 'all' used, build") + print(" a package for all existing addons.") + print() + print(" clean []:") + print(" remove unneeded local files, typically used before") + print(" making commits; if is used, clean that specific") + print(" addon, otherwise clean all addons.") + print() + print(" compile { | all}:") + print(" compile AddOn/po/*-local.po files for the named addon") + print(" and put the output .mo file in AddOn/locale/*/LC_MESSAGES/addon.mo;") + print(" or, if 'all' is used, do the same thing for all addons.") + print() + print(" extract-po:") + print(" extract strings from the aggregated 'po/.po' files") + print(" into the '-local.po' files for each addon.") + print() + print(" {-h | --help | help}:") + print(" print out this help message") + print() + print(" init []:") + print(" create a new addon working directory (use a name in") + print(" in camel case); if is given, create an empty") + print(" translation file (e.g., for 'fr', AddOn/po/fr-local.po)") + print() + print(" listing { | all}:") + print(" create or update the /listings/*.json files for") + print(" the languages supported by the named addonput; or, if 'all'") + print(" is used, do the same thing for all addons.") + print() + print(" manifest-check:") + print(" verify that the manifest files for all addons are correct.") + print() + print(" unlist :") + print(" remove the named addon from all language listings files.") + print() + print(" update :") + print(" update the adddon with the latest translations for that") + print(" language (e.g., for 'fr', AddOn/po/fr-local.po)") + print(" in camel case); if is given, create an empty") + print(" files that are required.") + + +#--- main ------------------------------------------------------------ +if "GRAMPSPATH" in os.environ: + GRAMPSPATH = os.environ["GRAMPSPATH"] +else: + GRAMPSPATH = "../../.." + +#-- should probably convert to parseargs some day .... +if len(sys.argv) < 2: # no parameters provided + usage() + exit(0) +elif len(sys.argv) == 2 and \ + (sys.argv[1] == "help" or sys.argv[1] == "-h" or sys.argv[1] == "--help"): + usage() + exit(0) + +KNOWN_GRAMPS_VERSIONS = [ + "gramps42", + "gramps50", + "gramps51", + "gramps52", + "gramps60", +] + +gramps_version = sys.argv[1] +if gramps_version not in KNOWN_GRAMPS_VERSIONS: + print(f"? {gramps_version} is not a supported version of gramps") + sys.exit(1) + +command = sys.argv[2] +if len(sys.argv) >= 4: + addon = sys.argv[3] if command == "clean": if len(sys.argv) == 3: @@ -1039,4 +1121,6 @@ def register(ptype, **kwargs): extract_po(addon) else: - raise AttributeError("unknown command") + print(f"? unknown command: {command}") + exit(1) + From dcfa3e7f819001c1d4691d90ac7585999763f04a Mon Sep 17 00:00:00 2001 From: Al Stone Date: Sat, 24 May 2025 18:05:57 -0600 Subject: [PATCH 2/3] Add a workspace creation commmand The command "./make.py gramps60 workplace MyAddon somepath" will create the directory 'somepath', git clone the gramps, addons-source, and addons into 'somepath', create the needed 'MyAddOn' directory in addons-source, and populate it with templates for the required files. This does not handle localization yet. A small config file is created and used to track git user info, as well. Signed-off-by: Al Stone --- make.py | 266 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 265 insertions(+), 1 deletion(-) diff --git a/make.py b/make.py index 88092fb37..5264e11c2 100755 --- a/make.py +++ b/make.py @@ -50,6 +50,7 @@ Extracts strings from the aggregated `po/{lang}.po` files into the `{lang}-local.po` files for each addon. """ +import configparser import shutil import glob import sys @@ -59,6 +60,9 @@ from xml.etree import ElementTree from subprocess import call, Popen, PIPE +CONFIG_FILE = "make.ini" +config = {} # configuration file content + def system(scmd, **kwargs): """ Replace and call system with scmd. @@ -97,6 +101,74 @@ def mkdir(dirname): return os.makedirs(dirname) +def get_config(): + global config + + try: + if not config: + config = configparser.ConfigParser() + config.read(CONFIG_FILE) + except configparser.Error as ce: + print(f"? {ce}") + exit(1) + +def get_user_info(): + global config + + dflt = config["DEFAULT"] + user = os.environ["USER"] + user_name = f"{user}" + user_email = f"{user}@example.com" + if not dflt: + dflt = {} + dflt["user"] = user + dflt["user_name"] = user_name + dflt["user_email"] = user_email + else: + user = dflt["user"] if "user" in dflt else user + user_name = dflt["user_name"] if "user_name" in dflt else user_name + user_email = dflt["user_email"] if "user_email" in dflt else user_email + + config["DEFAULT"]["user"] = user + config["DEFAULT"]["user_name"] = user_name + config["DEFAULT"]["user_email"] = user_email + return user, user_name, user_email + +def get_maintainer_info(): + global config + + dflt = config["DEFAULT"] + maint = os.environ["USER"] + maint_name = f"{maint}" + maint_email = f"{maint}@example.com" + if not dflt: + dflt = {} + dflt["maintainer"] = maint + dflt["maintainer_name"] = maint_name + dflt["maintainer_email"] = maint_email + else: + maint = dflt["maintainer"] if "maintainer" in dflt else maint + maint_name = dflt["maintainer_name"] if "maintainer_name" in dflt else maint_name + maint_email = dflt["maintainer_email"] if "maintainer_email" in dflt else maint_email + + config["DEFAULT"]["maintainer"] = maint + config["DEFAULT"]["maintainer_name"] = maint_name + config["DEFAULT"]["maintainer_email"] = maint_email + return maint, maint_name, maint_email + +def check_installation(): + cmds = {"gh": "See https://githib.com/cli/cli for installation", + "git": "Please install git with your package manager.", + } + missing = False + for ii in cmds.keys(): + if shutil.which(ii): + continue + missing = True + print(f"? Could not find the '{ii}' command") + print(f" {cmds[ii]}") + if missing: + exit(1) def increment_target(filenames): """increment the version number in the gpr file""" @@ -388,6 +460,13 @@ def usage(): print(" update the adddon with the latest translations for that") print(" language (e.g., for 'fr', AddOn/po/fr-local.po)") print(" in camel case); if is given, create an empty") + print() + print(" workspace :") + print(" create a directory tree at with all gramps") + print(" development source trees (gramps, addons-source, addons)") + print(" needed to develop a new addon; additionally, create the") + print(" AddOn source directory called , and add to it the") + print(" basic AddOn.py, AddOn.grp.py, MANIFEST, and localization") print(" files that are required.") @@ -416,13 +495,15 @@ def usage(): gramps_version = sys.argv[1] if gramps_version not in KNOWN_GRAMPS_VERSIONS: - print(f"? {gramps_version} is not a supported version of gramps") + print(f"? '{gramps_version}' is not a supported version of gramps") sys.exit(1) command = sys.argv[2] if len(sys.argv) >= 4: addon = sys.argv[3] +get_config() + if command == "clean": if len(sys.argv) == 3: for addon in [ @@ -1120,6 +1201,189 @@ def register(ptype, **kwargs): print(addon) extract_po(addon) +elif command == "workspace": + import subprocess + + def is_camel_case(s): + return s != s.lower() and s != s.upper() and "_" not in s + + def addon_template(path, addon): + addonpy = os.path.join(path, f"{addon}.py") + if os.path.exists(addonpy): + print(f"? {addon}.py exists, skipping step...") + else: + with open(addonpy, "w+") as fd: + print(f"#/usr/bin/env python3", file=fd) + print(f"#", file=fd) + print(f"# Add your code here", file=fd) + print(f"#", file=fd) + fd.close() + + def gpr_template(path, addon): + global config + + addongpr = os.path.join(path, f"{addon}.gpr.py") + if os.path.exists(addongpr): + print(f"? {addon}.gpr.py exists, skipping step...") + else: + user, user_name, user_email = get_user_info() + maint_user, maint_name, maint_email = get_maintainer_info() + with open(addongpr, "w+") as fd: + print(f"#", file=fd) + print(f"# Registration file for addon {addon}", file=fd) + print(f"#", file=fd) + fd.write(f""" +# +# This file has been generated for you as a starting point. +# +# You MUST edit it for it to be useful. +# + +register(TOOL, # change to the proper type + id = '{addon}', + name = _("{addon}"), + description = _("describe {addon} here."), + version = '1.0.0', + gramps_target_version = '6.0', + status = STABLE, + fname = '{addon}.py', + # your name and email here ... + authors = ["{user_name}"], + authors_email = ["{user_email}"], + # the maintainer's name and email here (can be you, of course) ... + maintainers = ["{maint_name}"], + maintainers_email = ["{maint_email}"], + # change the following as needed + category = TOOL_DBPROC, + toolclass = '{addon}Window', + optionclass = '{addon}Options', + tool_modes = [TOOL_MODE_GUI], + help_url = "Addon:{addon}Tool" +) +""") + fd.close() + + def fork_exists(user, name): + exists = False + res = subprocess.run(["gh", "repo", "list"], + capture_output=True, + text=True) + expected = user + "/" + name + if res.stdout.find(expected): + exists = True + return exists + + if gramps_version < "gramps60": + print(f"? workspace command not supported for '{gramps_version}'") + exit(1) + check_installation() + user, user_name, user_email = get_user_info() + maint, maint_name, maint_email = get_maintainer_info() + + # build the directory structure + if len(sys.argv) < 5: + print("? workspace requires a and an name.") + exit(1) + path = os.path.expandvars(os.path.expanduser(sys.argv[4])) + if os.path.exists(path): + if os.path.isdir(path): + print(f"workspace path '{sys.argv[4]}' already exists, reusing...") + else: + print(f"? workspace path '{sys.argv[4]}' already exists, but not a directory") + exit(1) + else: + os.makedirs(path) + + # get the addon name; we'll need to make a directory for it later + addon = sys.argv[3] + if not is_camel_case(addon): + print(f"? addon name must be camel case, not '{addon}'") + exit(1) + + # collect a copy of the gramps source tree + print("gather up gramps source tree...") + where = os.path.join(path, "gramps") + gitcmd = [] + cwd = path + if os.path.exists(where): + gitcmd = [ + "gh", "repo", "sync", + "--branch", f"maintenance/{gramps_version}", + ] + cwd = os.path.join(cwd, "gramps") + else: + if fork_exists(user, "gramps"): + gitcmd = ["gh", "repo", "clone", "gramps"] + else: + gitcmd = [ + "gh", "repo", "fork", + "https://github.com/gramps-project/gramps.git", + "--", "-b", f"maintenance/{gramps_version}", + ] + res = subprocess.run(gitcmd, cwd=cwd, text=True) + + # collect a copy of the addons-source source tree + print("gather up addons-source tree...") + where = os.path.join(path, "addons-source") + gitcmd = [] + cwd = path + if os.path.exists(where): + gitcmd = [ + "gh", "repo", "sync", + "--branch", f"maintenance/{gramps_version}", + ] + cwd = os.path.join(cwd, "addons-source") + else: + if fork_exists(user, "addons-source"): + gitcmd = ["gh", "repo", "clone", "addons-source"] + else: + gitcmd = [ + "gh", "repo", "fork", + "https://github.com/gramps-project/addons-source.git", + "--", "-b", f"maintenance/{gramps_version}", + ] + res = subprocess.run(gitcmd, cwd=cwd, text=True) + + # collect a copy of the addons source tree + print("gather up addons tree...") + where = os.path.join(path, "addons") + gitcmd = [] + cwd = path + if os.path.exists(where): + gitcmd = [ + "gh", "repo", "sync", + ] + cwd = os.path.join(cwd, "addons") + else: + if fork_exists(user, "addons"): + gitcmd = ["gh", "repo", "clone", "addons"] + else: + gitcmd = [ + "gh", "repo", "fork", + "https://github.com/gramps-project/addons.git", + ] + res = subprocess.run(gitcmd, cwd=cwd, text=True) + + # now we can make the addon directory + addonpath = os.path.join(path, "addons-source", addon) + if os.path.exists(addonpath): + if os.path.isdir(addonpath): + print(f"addon directory '{addon}' already exists, reusing...") + else: + print(f"? addon file '{addon}' exists, but not a directory") + exit(1) + else: + print(f"creating addon directory for '{addon}'...") + os.makedirs(addonpath) + + # add in some templates if needed + addon_template(addonpath, addon) + gpr_template(addonpath, addon) + + # write out the config file + with open(os.path.join(path, "addons-source", CONFIG_FILE), "w+") as cfd: + config.write(cfd) + else: print(f"? unknown command: {command}") exit(1) From 0faf3f0d09de9100d30a041c4d767dd93d132485 Mon Sep 17 00:00:00 2001 From: Al Stone Date: Mon, 26 May 2025 18:06:35 -0600 Subject: [PATCH 3/3] Make the init command a function that can be called Instead of the workspace command duplicating the init command, make it a method that can be called instead. It doesn't really simplify the init function, but it also doesn't have to be written twice and possibly not stay in sync over time. Signed-off-by: Al Stone --- make.py | 127 +++++++++++++++++++++++++++++++------------------------- 1 file changed, 70 insertions(+), 57 deletions(-) diff --git a/make.py b/make.py index 5264e11c2..d4eb75082 100755 --- a/make.py +++ b/make.py @@ -470,58 +470,17 @@ def usage(): print(" files that are required.") -#--- main ------------------------------------------------------------ -if "GRAMPSPATH" in os.environ: - GRAMPSPATH = os.environ["GRAMPSPATH"] -else: - GRAMPSPATH = "../../.." - -#-- should probably convert to parseargs some day .... -if len(sys.argv) < 2: # no parameters provided - usage() - exit(0) -elif len(sys.argv) == 2 and \ - (sys.argv[1] == "help" or sys.argv[1] == "-h" or sys.argv[1] == "--help"): - usage() - exit(0) - -KNOWN_GRAMPS_VERSIONS = [ - "gramps42", - "gramps50", - "gramps51", - "gramps52", - "gramps60", -] - -gramps_version = sys.argv[1] -if gramps_version not in KNOWN_GRAMPS_VERSIONS: - print(f"? '{gramps_version}' is not a supported version of gramps") - sys.exit(1) - -command = sys.argv[2] -if len(sys.argv) >= 4: - addon = sys.argv[3] - -get_config() - -if command == "clean": - if len(sys.argv) == 3: - for addon in [ - name - for name in os.listdir(".") - if os.path.isdir(name) and not name.startswith(".") - ]: - cleanup(addon) - else: - cleanup(addon) - -elif command == "init": - # # Get all of the strings from the addon and create template.po: - if addon == "all": +def init_command(command, path, target_addon): + # Get all of the strings from the addon and create template.po: + pwd = os.environ["PWD"] + os.chdir(path) + if target_addon == "all": dirs = [file for file in glob.glob("*") if os.path.isdir(file)] else: - dirs = [addon] - if len(sys.argv) == 4: + dirs = [os.path.join(path, target_addon)] + if (command == "init" and len(sys.argv) == 4) or \ + (command == "workspace" and len(sys.argv) == 5): + plugins = [] try: sys.path.insert(0, GRAMPSPATH) os.environ["GRAMPS_RESOURCES"] = os.path.abspath(GRAMPSPATH) @@ -532,14 +491,13 @@ def usage(): "'GRAMPSPATH=path python3 make.py %s init'" % (os.path.abspath(GRAMPSPATH), gramps_version) ) - exit() def register(ptype, **kwargs): - global plugins kwargs["ptype"] = ptype plugins.append(kwargs) - for addon in dirs: + for addonpath in dirs: + addon = os.path.basename(addonpath) fnames = glob.glob("%s/*.py" % addon) if not fnames: continue @@ -562,7 +520,8 @@ def register(ptype, **kwargs): if not listed: continue # skip this one if not listed - mkdir(f"{addon}/po") + where = os.path.join(addon, "po") + os.makedirs(where, exist_ok=True) fnames = " ".join(glob.glob(f"{addon}/*.py")) system( f"xgettext --language=Python --keyword=_ --keyword=N_" @@ -607,7 +566,7 @@ def register(ptype, **kwargs): "%s/po/template.pot" % addon, "w", encoding="utf-8", newline="\n" ) as file: file.write(contents) - elif len(sys.argv) > 4: + elif command == "init" and len(sys.argv) > 4: locale = sys.argv[4] # make a copy for locale if os.path.isfile(f"{addon}/po/{locale}-local.po"): @@ -619,7 +578,60 @@ def register(ptype, **kwargs): ) echo(f'You can now edit "{addon}/po/{locale}-local.po"') else: - raise AttributeError("init what?") + print(f"? do not know what to init in {addon}") + exit(1) + + os.chdir(pwd) + return + +#--- main ------------------------------------------------------------ +if "GRAMPSPATH" in os.environ: + GRAMPSPATH = os.environ["GRAMPSPATH"] +else: + GRAMPSPATH = "../../.." + +#-- should probably convert to parseargs some day .... +if len(sys.argv) < 2: # no parameters provided + usage() + exit(0) +elif len(sys.argv) == 2 and \ + (sys.argv[1] == "help" or sys.argv[1] == "-h" or sys.argv[1] == "--help"): + usage() + exit(0) + +KNOWN_GRAMPS_VERSIONS = [ + "gramps42", + "gramps50", + "gramps51", + "gramps52", + "gramps60", +] + +gramps_version = sys.argv[1] +if gramps_version not in KNOWN_GRAMPS_VERSIONS: + print(f"? '{gramps_version}' is not a supported version of gramps") + sys.exit(1) + +command = sys.argv[2] +if len(sys.argv) >= 4: + addon = sys.argv[3] + +get_config() + +if command == "clean": + if len(sys.argv) == 3: + for addon in [ + name + for name in os.listdir(".") + if os.path.isdir(name) and not name.startswith(".") + ]: + cleanup(addon) + else: + cleanup(addon) + +elif command == "init": + # Get all of the strings from the addon and create template.po: + init_command(command, os.environ["PWD"], addon) elif command == "update": locale = sys.argv[4] @@ -1282,7 +1294,7 @@ def fork_exists(user, name): # build the directory structure if len(sys.argv) < 5: - print("? workspace requires a and an name.") + print("? workspace requires an and a name.") exit(1) path = os.path.expandvars(os.path.expanduser(sys.argv[4])) if os.path.exists(path): @@ -1379,6 +1391,7 @@ def fork_exists(user, name): # add in some templates if needed addon_template(addonpath, addon) gpr_template(addonpath, addon) + init_command(command, os.path.join(path, "addons-source"), addon) # write out the config file with open(os.path.join(path, "addons-source", CONFIG_FILE), "w+") as cfd: