]> Nutra Git (v2) - gamesguru/getmyancestors.git/commitdiff
a few more name tweaks and config changes
authorShane Jaroch <chown_tee@proton.me>
Sat, 24 Jan 2026 07:12:21 +0000 (02:12 -0500)
committerShane Jaroch <chown_tee@proton.me>
Sat, 24 Jan 2026 07:12:21 +0000 (02:12 -0500)
.github/workflows/test.yml
Makefile
getmyancestors/classes/gedcom.py
getmyancestors/classes/tree/core.py
getmyancestors/mergemyanc.py
getmyancestors/tests/test_indi_name.py
getmyancestors/utils.py [new file with mode: 0644]
pyproject.toml
res/testdata

index b25f1d43d6ea7b33e0101f40c2e3df6a00a346f0..4a5bffd92b375dd2fc326970bc8632c77e32512c 100644 (file)
@@ -51,6 +51,9 @@ jobs:
         run: make lint
         if: runner.os == 'Linux'
 
+      - name: Test [Install]
+        run: make test/install
+
       - name: Test [Unit]
         run: make test/unit
 
index f9f5e3701ea4a4cc74749a7a9a1b3e327ee20f59..d60754a8d5fa86a8945481aacba10dfdf2d83f2e 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -11,46 +11,6 @@ _help:
 #      @grep -Eh '\s##\s' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
 
 
-# -include .env
-
-
-.PHONY: test/unit
-test/unit:     ##H@@ Run Unit tests only
-       $(PYTHON) -m coverage run -p -m pytest getmyancestors/tests
-
-# Installation
-.PHONY: deps
-deps:  ##H@@ Install dependencies
-       $(PYTHON) -m pip install --no-user ".[dev]"
-
-# Installation tests
-.PHONY: test/install
-test/install:  ##H@@ Run installation tests
-       $(PYTHON) -m coverage run -p -m pytest tests/test_installation.py
-
-.PHONY: test/offline
-test/offline:  ##H@@ Run offline verification (requires fixtures)
-       $(PYTHON) -m pytest tests/offline_test.py
-
-
-# Generate targets for all test files (enables autocomplete)
-TEST_FILES := $(wildcard getmyancestors/tests/test_*.py)
-TEST_TARGETS := $(patsubst getmyancestors/tests/%.py,test/unit/%,$(TEST_FILES))
-
-.PHONY: $(TEST_TARGETS)
-$(TEST_TARGETS): test/unit/%:
-       pytest getmyancestors/tests/$*.py -v
-
-.PHONY: test/
-test/: ##H@@ Run unit & E2E tests
-test/: test/unit test/offline test/cov
-
-.PHONY: test/cov
-test/cov:      ##H@@ Combine all coverage data and show report
-       -$(PYTHON) -m coverage combine
-       $(PYTHON) -m coverage report
-
-
 REMOTE_HEAD ?= origin/master
 PY_CHANGED_FILES ?= $(shell git diff --name-only --diff-filter=MACU $(REMOTE_HEAD) '*.py')
 PY_CHANGED_FILES_FLAG ?= $(if $(PY_CHANGED_FILES),1,)
@@ -122,6 +82,51 @@ mypy:
        fi
 
 
+# -include .env
+
+# Installation
+.PHONY: deps
+deps:  ##H@@ Install dependencies
+       $(PYTHON) -m pip install --no-user ".[dev]"
+
+
+.PHONY: test/unit
+test/unit:     ##H@@ Run Unit tests only
+       $(PYTHON) -m coverage run -p -m pytest getmyancestors/tests
+
+
+# Generate targets for all test files (enables autocomplete)
+TEST_FILES := $(wildcard getmyancestors/tests/test_*.py)
+TEST_TARGETS := $(patsubst getmyancestors/tests/%.py,test/unit/%,$(TEST_FILES))
+
+.PHONY: $(TEST_TARGETS)
+$(TEST_TARGETS): test/unit/%:
+       pytest getmyancestors/tests/$*.py -v
+
+
+.PHONY: test
+test:  ##H@@ Run unit & E2E tests
+test: test/unit test/offline test/install test/cov
+
+.PHONY:test/
+test/:test
+
+.PHONY: test/cov
+test/cov:      ##H@@ Combine all coverage data and show report
+       -$(PYTHON) -m coverage combine
+       $(PYTHON) -m coverage report
+
+
+.PHONY: test/install
+test/install:  ##H@@ Run installation tests
+       $(PYTHON) -m coverage run -p -m pytest tests/test_installation.py
+
+
+.PHONY: test/offline
+test/offline:  ##H@@ Run offline verification (requires fixtures)
+       $(PYTHON) -m pytest tests/offline_test.py
+
+
 .PHONY: clean
 clean: ##H@@ Clean up build files/cache
        rm -rf *.egg-info build dist .coverage .coverage.*
index 7b7b17ee9ae55890b2ab70531e85fd794b8e3588..fd0b8c06fed6ed60831f85a07e3423ad638f3cb4 100644 (file)
@@ -1,5 +1,3 @@
-import os
-import sys
 from typing import Optional
 
 from getmyancestors.classes.constants import FACT_TYPES, ORDINANCES
@@ -13,15 +11,7 @@ from getmyancestors.classes.tree import (
     Ordinance,
     Source,
 )
-
-
-def _warn(msg: str):
-    """Write a warning message to stderr with optional color (if TTY)."""
-    use_color = sys.stderr.isatty() or os.environ.get("FORCE_COLOR", "")
-    if use_color:
-        sys.stderr.write(f"\033[33m{msg}\033[0m\n")
-    else:
-        sys.stderr.write(f"{msg}\n")
+from getmyancestors.utils import _error, _warn
 
 
 class Gedcom:
@@ -359,6 +349,11 @@ class Gedcom:
                     f"Warning: Family @F{num}@ ({husb_name} & {wife_name}) missing _FSFTID tag, "
                     f"using GEDCOM pointer as fallback."
                 )
+                if husb_name != "Unknown" and wife_name != "Unknown":
+                    _error(
+                        f"Error: Family @F{num}@ ({husb_name} & {wife_name}) has NO _FSFTID tag! "
+                        "This may imply a problem with the FamilySearch data. You may need to investigate it."
+                    )
                 fam.fid = num  # Use GEDCOM pointer ID as fallback
 
         for _num, fam in self.fam.items():
index 718cc72480a2afd5da631a996c45892baeb6c414..c3aa0d40d10c21a303fd2501e2f2eabd43d598c0 100644 (file)
@@ -375,8 +375,10 @@ class Indi:
     def print(self, file=sys.stdout):
         """print individual in GEDCOM format"""
         file.write("0 @I%s@ INDI\n" % self.id)
+        printed_names = set()
         if self.name:
             self.name.print(file)
+            printed_names.add(self.name)
         for nick in sorted(
             self.nicknames,
             key=lambda x: (
@@ -390,6 +392,7 @@ class Indi:
             ),
         ):
             file.write(cont("2 NICK %s %s" % (nick.given, nick.surname)))
+            printed_names.add(nick)
         for birthname in sorted(
             self.birthnames,
             key=lambda x: (
@@ -402,7 +405,9 @@ class Indi:
                 x.note.text if x.note else "",
             ),
         ):
-            birthname.print(file)
+            if birthname not in printed_names:
+                birthname.print(file)
+                printed_names.add(birthname)
         for aka in sorted(
             self.aka,
             key=lambda x: (
@@ -415,7 +420,9 @@ class Indi:
                 x.note.text if x.note else "",
             ),
         ):
-            aka.print(file, "aka")
+            if aka not in printed_names:
+                aka.print(file, "aka")
+                printed_names.add(aka)
         for married_name in sorted(
             self.married,
             key=lambda x: (
@@ -428,7 +435,9 @@ class Indi:
                 x.note.text if x.note else "",
             ),
         ):
-            married_name.print(file, "married")
+            if married_name not in printed_names:
+                married_name.print(file, "married")
+                printed_names.add(married_name)
         if self.gender:
             file.write("1 SEX %s\n" % self.gender)
         for fact in sorted(
index e3e9e8611859d1b8ad35884f4f1f2c4d38ab7aa6..d0e273f40e09c5faf659f6ffd0866eccbbbd028e 100755 (executable)
@@ -26,15 +26,6 @@ app = typer.Typer(
 )
 
 
-def _warn(msg: str):
-    """Write a warning message to stderr with optional color (if TTY)."""
-    use_color = sys.stderr.isatty() or os.environ.get("FORCE_COLOR", "")
-    if use_color:
-        sys.stderr.write(f"\033[33m{msg}\033[0m\n")
-    else:
-        sys.stderr.write(f"{msg}\n")
-
-
 @app.command()
 def main(
     files: Annotated[
@@ -129,10 +120,20 @@ def main(
                 target_set.clear()
                 seen = set()
                 for n in all_names:
-                    s = str(n)
-                    if s not in seen:
+                    # Use all relevant fields for deduplication key, similar to sort key
+                    # but using the object's hash/eq properties if possible, or a tuple representation
+                    key = (
+                        n.given,
+                        n.surname,
+                        n.prefix,
+                        n.suffix,
+                        n.kind,
+                        n.alternative,
+                        n.note.text if hasattr(n, "note") and n.note else None,
+                    )
+                    if key not in seen:
                         target_set.add(n)
-                        seen.add(s)
+                        seen.add(key)
 
             # Helper for whitespace normalization in quotes
             def norm_space(s):
index ee9924dfc89fe69974770face3e4877e038cec2f..1c6a30c51c6c8c3beb03315f8fac98974ca8adee 100644 (file)
@@ -1,5 +1,3 @@
-
-
 from getmyancestors.classes.tree.core import Indi, Tree
 
 
diff --git a/getmyancestors/utils.py b/getmyancestors/utils.py
new file mode 100644 (file)
index 0000000..25208d6
--- /dev/null
@@ -0,0 +1,20 @@
+import os
+import sys
+
+
+def _warn(msg: str):
+    """Write a yellow warning message to stderr with optional color (if TTY)."""
+    use_color = sys.stderr.isatty() or os.environ.get("FORCE_COLOR", "")
+    if use_color:
+        sys.stderr.write(f"\033[33m{msg}\033[0m\n")
+    else:
+        sys.stderr.write(f"{msg}\n")
+
+
+def _error(msg: str):
+    """Write a red error message to stderr with optional color (if TTY)."""
+    use_color = sys.stderr.isatty() or os.environ.get("FORCE_COLOR", "")
+    if use_color:
+        sys.stderr.write(f"\033[31m{msg}\033[0m\n")
+    else:
+        sys.stderr.write(f"{msg}\n")
index b0f6009c24407eb8f84f41328af81c7534b81106..9aa0fa88d67dff543ac4e5525a042d719172f999 100644 (file)
@@ -148,7 +148,7 @@ source = ["getmyancestors"]
 data_file = ".tmp/.coverage"
 
 [tool.coverage.report]
-fail_under = 67.98
+fail_under = 68.55
 precision = 2
 
 show_missing = true
index 741c02ced0efd708044045e855cb7fafd6eb6aa8..8166780fe62343decac53b265364e2576cdbf195 160000 (submodule)
@@ -1 +1 @@
-Subproject commit 741c02ced0efd708044045e855cb7fafd6eb6aa8
+Subproject commit 8166780fe62343decac53b265364e2576cdbf195