Improve code quality across the codebase
- Add comprehensive type hints to MyTimeline.py methods - Add type hints to __init__, change_db, and tooltip formatting methods - Use Any for Gramps-specific types that aren't easily importable - Refactor generate_demo_family.py to use ElementTree - Replace string concatenation with xml.etree.ElementTree for proper XML generation - Add compatibility handling for Python < 3.9 (ET.indent) - Add EventData, PersonData, and FamilyData dataclasses for better structure - Add comprehensive type hints to all functions - Extract magic numbers to named constants - Add constants for UI dimensions, timeline heights, dialog sizes - Add constants for date calculations and genealogical year ranges - Improve code readability and maintainability - Refactor duplicated code in filter dialog handlers - Extract common checkbox handler logic into reusable methods - Create _make_group_toggle_handler and _make_child_toggle_handler - Eliminate code duplication between event type and family filters - Improve shell scripts with better error handling - Add validation for Gramps installation - Improve error messages with actionable troubleshooting steps - Use set -euo pipefail for better error detection - Add better user guidance in error scenarios
This commit is contained in:
parent
27de315514
commit
5860b3d25c
156
MyTimeline.py
156
MyTimeline.py
@ -32,7 +32,7 @@ import colorsys
|
|||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Optional, List, Tuple, Any, Set, Dict, TYPE_CHECKING
|
from typing import Optional, List, Tuple, Any, Set, Dict, TYPE_CHECKING, Union
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from gramps.gen.lib import Event, Person, Family
|
from gramps.gen.lib import Event, Person, Family
|
||||||
@ -90,6 +90,16 @@ TOOLTIP_DELAY = 500 # milliseconds
|
|||||||
TOOLTIP_MAX_WIDTH = 500
|
TOOLTIP_MAX_WIDTH = 500
|
||||||
LABEL_BACKGROUND_PADDING = 8
|
LABEL_BACKGROUND_PADDING = 8
|
||||||
LABEL_BACKGROUND_RADIUS = 5
|
LABEL_BACKGROUND_RADIUS = 5
|
||||||
|
DEFAULT_TIMELINE_HEIGHT = 1000 # Default height, will be recalculated
|
||||||
|
DEFAULT_TIMELINE_WIDTH = 800
|
||||||
|
DEFAULT_DRAWING_AREA_HEIGHT = 600
|
||||||
|
DEFAULT_DRAWING_AREA_WIDTH = 800
|
||||||
|
EMPTY_TIMELINE_HEIGHT = 600 # Height when no events
|
||||||
|
FILTER_DIALOG_WIDTH = 600
|
||||||
|
FILTER_DIALOG_HEIGHT = 700
|
||||||
|
MIN_GENEALOGICAL_YEAR = 1000 # Minimum year for genealogical data
|
||||||
|
DATE_SORT_YEAR_MULTIPLIER = 10000 # Year component in date sort value
|
||||||
|
DATE_SORT_MONTH_MULTIPLIER = 100 # Month component in date sort value
|
||||||
|
|
||||||
# Font Constants
|
# Font Constants
|
||||||
FONT_FAMILY = "Sans"
|
FONT_FAMILY = "Sans"
|
||||||
@ -360,7 +370,7 @@ class MyTimelineView(NavigationView):
|
|||||||
and filtering of events by type, category, person, and date range.
|
and filtering of events by type, category, person, and date range.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, pdata, dbstate, uistate, nav_group=0):
|
def __init__(self, pdata: Any, dbstate: Any, uistate: Any, nav_group: int = 0) -> None:
|
||||||
"""
|
"""
|
||||||
Initialize the MyTimeline view.
|
Initialize the MyTimeline view.
|
||||||
|
|
||||||
@ -384,7 +394,7 @@ class MyTimelineView(NavigationView):
|
|||||||
# UI components
|
# UI components
|
||||||
self.scrolledwindow = None
|
self.scrolledwindow = None
|
||||||
self.drawing_area = None
|
self.drawing_area = None
|
||||||
self.timeline_height = 1000 # Default height, will be recalculated
|
self.timeline_height = DEFAULT_TIMELINE_HEIGHT
|
||||||
self.zoom_level = 1.0 # Zoom level (1.0 = 100%)
|
self.zoom_level = 1.0 # Zoom level (1.0 = 100%)
|
||||||
self.min_zoom = 0.5
|
self.min_zoom = 0.5
|
||||||
self.max_zoom = 3.0
|
self.max_zoom = 3.0
|
||||||
@ -498,7 +508,7 @@ class MyTimelineView(NavigationView):
|
|||||||
if self.active_event_handle in handle_list:
|
if self.active_event_handle in handle_list:
|
||||||
self._scroll_to_event(self.active_event_handle)
|
self._scroll_to_event(self.active_event_handle)
|
||||||
|
|
||||||
def change_db(self, db) -> None:
|
def change_db(self, db: Any) -> None:
|
||||||
"""
|
"""
|
||||||
Called when the database changes.
|
Called when the database changes.
|
||||||
|
|
||||||
@ -665,6 +675,57 @@ class MyTimelineView(NavigationView):
|
|||||||
else: # 'some'
|
else: # 'some'
|
||||||
group_checkbox.set_inconsistent(True)
|
group_checkbox.set_inconsistent(True)
|
||||||
|
|
||||||
|
def _make_group_toggle_handler(self, child_checkboxes: List[Gtk.CheckButton],
|
||||||
|
updating_flag: List[bool]) -> Any:
|
||||||
|
"""
|
||||||
|
Create a handler for group checkbox toggle that toggles all children.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
child_checkboxes: List of child checkboxes to toggle.
|
||||||
|
updating_flag: List with single boolean to prevent recursion.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Callable handler function.
|
||||||
|
"""
|
||||||
|
def handler(widget: Gtk.Widget) -> None:
|
||||||
|
if updating_flag[0]:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Handle inconsistent state - make it consistent
|
||||||
|
if widget.get_inconsistent():
|
||||||
|
widget.set_inconsistent(False)
|
||||||
|
widget.set_active(True)
|
||||||
|
|
||||||
|
updating_flag[0] = True
|
||||||
|
# Toggle all children
|
||||||
|
is_active = widget.get_active()
|
||||||
|
for child_cb in child_checkboxes:
|
||||||
|
child_cb.set_active(is_active)
|
||||||
|
updating_flag[0] = False
|
||||||
|
|
||||||
|
return handler
|
||||||
|
|
||||||
|
def _make_child_toggle_handler(self, group_checkbox: Gtk.CheckButton,
|
||||||
|
child_checkboxes: List[Gtk.CheckButton],
|
||||||
|
updating_flag: List[bool]) -> Any:
|
||||||
|
"""
|
||||||
|
Create a handler for child checkbox toggle that updates group state.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
group_checkbox: The parent group checkbox to update.
|
||||||
|
child_checkboxes: List of all child checkboxes.
|
||||||
|
updating_flag: List with single boolean to prevent recursion.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Callable handler function.
|
||||||
|
"""
|
||||||
|
def handler(widget: Gtk.Widget) -> None:
|
||||||
|
if updating_flag[0]:
|
||||||
|
return
|
||||||
|
self._update_group_checkbox_state(group_checkbox, child_checkboxes)
|
||||||
|
|
||||||
|
return handler
|
||||||
|
|
||||||
def _build_filter_dialog(self) -> Gtk.Dialog:
|
def _build_filter_dialog(self) -> Gtk.Dialog:
|
||||||
"""
|
"""
|
||||||
Build the filter dialog with all filter controls.
|
Build the filter dialog with all filter controls.
|
||||||
@ -673,7 +734,7 @@ class MyTimelineView(NavigationView):
|
|||||||
Gtk.Dialog: The filter dialog widget.
|
Gtk.Dialog: The filter dialog widget.
|
||||||
"""
|
"""
|
||||||
dialog = Gtk.Dialog(title=_("Filter Events"), parent=self.uistate.window)
|
dialog = Gtk.Dialog(title=_("Filter Events"), parent=self.uistate.window)
|
||||||
dialog.set_default_size(600, 700)
|
dialog.set_default_size(FILTER_DIALOG_WIDTH, FILTER_DIALOG_HEIGHT)
|
||||||
dialog.add_button(_("Clear All"), Gtk.ResponseType.REJECT)
|
dialog.add_button(_("Clear All"), Gtk.ResponseType.REJECT)
|
||||||
dialog.add_button(_("Close"), Gtk.ResponseType.CLOSE)
|
dialog.add_button(_("Close"), Gtk.ResponseType.CLOSE)
|
||||||
dialog.add_button(_("Apply"), Gtk.ResponseType.APPLY)
|
dialog.add_button(_("Apply"), Gtk.ResponseType.APPLY)
|
||||||
@ -784,40 +845,13 @@ class MyTimelineView(NavigationView):
|
|||||||
updating_category = [False]
|
updating_category = [False]
|
||||||
|
|
||||||
# Connect category checkbox to toggle all children
|
# Connect category checkbox to toggle all children
|
||||||
def make_category_toggle_handler(cb_list, updating_flag):
|
category_checkbox.connect("toggled",
|
||||||
def handler(widget):
|
self._make_group_toggle_handler(child_checkboxes, updating_category))
|
||||||
if updating_flag[0]:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Handle inconsistent state - make it consistent
|
|
||||||
if widget.get_inconsistent():
|
|
||||||
widget.set_inconsistent(False)
|
|
||||||
widget.set_active(True)
|
|
||||||
|
|
||||||
updating_flag[0] = True
|
|
||||||
# Toggle all children
|
|
||||||
is_active = widget.get_active()
|
|
||||||
for child_cb in cb_list:
|
|
||||||
child_cb.set_active(is_active)
|
|
||||||
updating_flag[0] = False
|
|
||||||
return handler
|
|
||||||
|
|
||||||
# Connect child checkboxes to update category checkbox state
|
# Connect child checkboxes to update category checkbox state
|
||||||
def make_child_toggle_handler(cat_cb, children, updating_flag):
|
|
||||||
def handler(widget):
|
|
||||||
if updating_flag[0]:
|
|
||||||
return
|
|
||||||
self._update_group_checkbox_state(cat_cb, children)
|
|
||||||
return handler
|
|
||||||
|
|
||||||
# Connect category checkbox
|
|
||||||
category_checkbox.connect("toggled",
|
|
||||||
make_category_toggle_handler(child_checkboxes, updating_category))
|
|
||||||
|
|
||||||
# Connect child checkboxes
|
|
||||||
for child_cb in child_checkboxes:
|
for child_cb in child_checkboxes:
|
||||||
child_cb.connect("toggled",
|
child_cb.connect("toggled",
|
||||||
make_child_toggle_handler(category_checkbox, child_checkboxes, updating_category))
|
self._make_child_toggle_handler(category_checkbox, child_checkboxes, updating_category))
|
||||||
|
|
||||||
self._filter_widgets['event_type_checkboxes'] = event_type_checkboxes
|
self._filter_widgets['event_type_checkboxes'] = event_type_checkboxes
|
||||||
self._filter_widgets['category_checkboxes'] = category_checkboxes
|
self._filter_widgets['category_checkboxes'] = category_checkboxes
|
||||||
@ -906,10 +940,10 @@ class MyTimelineView(NavigationView):
|
|||||||
calendar_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=15)
|
calendar_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=15)
|
||||||
calendar_box.set_homogeneous(True)
|
calendar_box.set_homogeneous(True)
|
||||||
|
|
||||||
# Year range for genealogical data (1000 to current year + 10)
|
# Year range for genealogical data
|
||||||
import datetime
|
import datetime
|
||||||
current_year = datetime.date.today().year
|
current_year = datetime.date.today().year
|
||||||
min_year = 1000
|
min_year = MIN_GENEALOGICAL_YEAR
|
||||||
max_year = current_year + 10
|
max_year = current_year + 10
|
||||||
|
|
||||||
# From date calendar with year selector
|
# From date calendar with year selector
|
||||||
@ -1130,37 +1164,13 @@ class MyTimelineView(NavigationView):
|
|||||||
updating_family = [False]
|
updating_family = [False]
|
||||||
|
|
||||||
# Connect family checkbox to toggle all members
|
# Connect family checkbox to toggle all members
|
||||||
def make_family_toggle_handler(cb_list, updating_flag):
|
family_checkbox.connect("toggled",
|
||||||
def handler(widget):
|
self._make_group_toggle_handler(child_checkboxes, updating_family))
|
||||||
if updating_flag[0]:
|
|
||||||
return
|
|
||||||
# Handle inconsistent state
|
|
||||||
if widget.get_inconsistent():
|
|
||||||
widget.set_inconsistent(False)
|
|
||||||
widget.set_active(True)
|
|
||||||
updating_flag[0] = True
|
|
||||||
is_active = widget.get_active()
|
|
||||||
for child_cb in cb_list:
|
|
||||||
child_cb.set_active(is_active)
|
|
||||||
updating_flag[0] = False
|
|
||||||
return handler
|
|
||||||
|
|
||||||
# Connect child checkboxes to update family checkbox
|
# Connect child checkboxes to update family checkbox
|
||||||
def make_family_member_toggle_handler(fam_cb, children, updating_flag):
|
|
||||||
def handler(widget):
|
|
||||||
if updating_flag[0]:
|
|
||||||
return
|
|
||||||
self._update_group_checkbox_state(fam_cb, children)
|
|
||||||
return handler
|
|
||||||
|
|
||||||
# Connect family checkbox
|
|
||||||
family_checkbox.connect("toggled",
|
|
||||||
make_family_toggle_handler(child_checkboxes, updating_family))
|
|
||||||
|
|
||||||
# Connect child checkboxes
|
|
||||||
for child_cb in child_checkboxes:
|
for child_cb in child_checkboxes:
|
||||||
child_cb.connect("toggled",
|
child_cb.connect("toggled",
|
||||||
make_family_member_toggle_handler(family_checkbox, child_checkboxes, updating_family))
|
self._make_child_toggle_handler(family_checkbox, child_checkboxes, updating_family))
|
||||||
|
|
||||||
# Initialize family checkbox state
|
# Initialize family checkbox state
|
||||||
self._update_group_checkbox_state(family_checkbox, child_checkboxes)
|
self._update_group_checkbox_state(family_checkbox, child_checkboxes)
|
||||||
@ -1182,9 +1192,9 @@ class MyTimelineView(NavigationView):
|
|||||||
min_sort, max_sort = self.date_range_filter
|
min_sort, max_sort = self.date_range_filter
|
||||||
# Convert sort values back to dates for calendar display
|
# Convert sort values back to dates for calendar display
|
||||||
# Approximate conversion: extract year from sort value
|
# Approximate conversion: extract year from sort value
|
||||||
# Sort value is roughly: year * 10000 + month * 100 + day
|
# Sort value is roughly: year * DATE_SORT_YEAR_MULTIPLIER + month * DATE_SORT_MONTH_MULTIPLIER + day
|
||||||
from_year = min_sort // 10000
|
from_year = min_sort // DATE_SORT_YEAR_MULTIPLIER
|
||||||
to_year = max_sort // 10000
|
to_year = max_sort // DATE_SORT_YEAR_MULTIPLIER
|
||||||
|
|
||||||
# Set calendar years (approximate)
|
# Set calendar years (approximate)
|
||||||
current_from_year, current_from_month, current_from_day = from_calendar.get_date()
|
current_from_year, current_from_month, current_from_day = from_calendar.get_date()
|
||||||
@ -1639,10 +1649,10 @@ class MyTimelineView(NavigationView):
|
|||||||
)
|
)
|
||||||
self.timeline_height = int(base_height * self.zoom_level)
|
self.timeline_height = int(base_height * self.zoom_level)
|
||||||
else:
|
else:
|
||||||
self.timeline_height = int(600 * self.zoom_level)
|
self.timeline_height = int(EMPTY_TIMELINE_HEIGHT * self.zoom_level)
|
||||||
|
|
||||||
if self.drawing_area:
|
if self.drawing_area:
|
||||||
self.drawing_area.set_size_request(800, self.timeline_height)
|
self.drawing_area.set_size_request(DEFAULT_DRAWING_AREA_WIDTH, self.timeline_height)
|
||||||
|
|
||||||
def _create_timeline_event(self, event: 'Event', person_obj: Optional['Person'] = None, y_pos: float = 0.0) -> Optional[TimelineEvent]:
|
def _create_timeline_event(self, event: 'Event', person_obj: Optional['Person'] = None, y_pos: float = 0.0) -> Optional[TimelineEvent]:
|
||||||
"""
|
"""
|
||||||
@ -2280,10 +2290,10 @@ class MyTimelineView(NavigationView):
|
|||||||
)
|
)
|
||||||
self.timeline_height = int(base_height * self.zoom_level)
|
self.timeline_height = int(base_height * self.zoom_level)
|
||||||
else:
|
else:
|
||||||
self.timeline_height = int(600 * self.zoom_level)
|
self.timeline_height = int(EMPTY_TIMELINE_HEIGHT * self.zoom_level)
|
||||||
|
|
||||||
if self.drawing_area:
|
if self.drawing_area:
|
||||||
self.drawing_area.set_size_request(800, self.timeline_height)
|
self.drawing_area.set_size_request(DEFAULT_DRAWING_AREA_WIDTH, self.timeline_height)
|
||||||
|
|
||||||
# Invalidate cache when zoom changes
|
# Invalidate cache when zoom changes
|
||||||
self._adjusted_events_cache = None
|
self._adjusted_events_cache = None
|
||||||
@ -2570,7 +2580,7 @@ class MyTimelineView(NavigationView):
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _format_person_tooltip(self, person, person_events: List[TimelineEvent]) -> str:
|
def _format_person_tooltip(self, person: 'Person', person_events: List[TimelineEvent]) -> str:
|
||||||
"""
|
"""
|
||||||
Format tooltip for person with multiple events.
|
Format tooltip for person with multiple events.
|
||||||
|
|
||||||
@ -2607,7 +2617,7 @@ class MyTimelineView(NavigationView):
|
|||||||
|
|
||||||
return tooltip_text
|
return tooltip_text
|
||||||
|
|
||||||
def _format_single_event_tooltip(self, event, event_type: EventType) -> str:
|
def _format_single_event_tooltip(self, event: 'Event', event_type: EventType) -> str:
|
||||||
"""
|
"""
|
||||||
Format tooltip for single event.
|
Format tooltip for single event.
|
||||||
|
|
||||||
|
|||||||
@ -4,14 +4,25 @@ Generate a huge demo family for Gramps testing
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import random
|
import random
|
||||||
from datetime import datetime, timedelta
|
import xml.etree.ElementTree as ET
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Optional, List, Tuple, Dict
|
||||||
|
|
||||||
# Set seed for deterministic event generation
|
# Set seed for deterministic event generation
|
||||||
random.seed(42)
|
random.seed(42)
|
||||||
|
|
||||||
# Generate unique handles
|
# Constants
|
||||||
def gen_handle(prefix, num):
|
EVENT_ID_OFFSET = 10
|
||||||
return f"_{prefix}{num:08d}"
|
FAMILY_ID_OFFSET = 100
|
||||||
|
EVENT_ID_START_OFFSET = 2
|
||||||
|
MIN_MONTH = 1
|
||||||
|
MAX_MONTH = 12
|
||||||
|
MIN_DAY = 1
|
||||||
|
MAX_DAY = 28
|
||||||
|
GRAMPS_XML_VERSION = "5.1.0"
|
||||||
|
GRAMPS_XML_NAMESPACE = "http://gramps-project.org/xml/1.7.1/"
|
||||||
|
GRAMPS_XML_DTD = "http://gramps-project.org/xml/1.7.1/grampsxml.dtd"
|
||||||
|
|
||||||
# Event types to add
|
# Event types to add
|
||||||
EVENT_TYPES = [
|
EVENT_TYPES = [
|
||||||
@ -29,10 +40,115 @@ EVENT_TYPES = [
|
|||||||
("Cremation", 0.2, None, None), # 20% chance if death exists, at death time
|
("Cremation", 0.2, None, None), # 20% chance if death exists, at death time
|
||||||
]
|
]
|
||||||
|
|
||||||
# Generate additional events for a person
|
# Name lists
|
||||||
def gen_additional_events(pid, first_name, surname, birth_year, death_year=None):
|
MALE_NAMES = [
|
||||||
events = []
|
"James", "John", "Robert", "Michael", "William", "David", "Richard", "Joseph",
|
||||||
event_id_offset = pid * 10 + 2 # Start after birth and death events
|
"Thomas", "Charles", "Daniel", "Matthew", "Anthony", "Mark", "Donald", "Steven",
|
||||||
|
"Paul", "Andrew", "Joshua", "Kenneth", "Kevin", "Brian", "George", "Timothy",
|
||||||
|
"Ronald", "Jason", "Edward", "Jeffrey", "Ryan", "Jacob", "Gary", "Nicholas",
|
||||||
|
"Eric", "Jonathan", "Stephen", "Larry", "Justin", "Scott", "Brandon", "Benjamin"
|
||||||
|
]
|
||||||
|
|
||||||
|
FEMALE_NAMES = [
|
||||||
|
"Mary", "Patricia", "Jennifer", "Linda", "Elizabeth", "Barbara", "Susan",
|
||||||
|
"Jessica", "Sarah", "Karen", "Nancy", "Lisa", "Betty", "Margaret", "Sandra",
|
||||||
|
"Ashley", "Kimberly", "Emily", "Donna", "Michelle", "Dorothy", "Carol",
|
||||||
|
"Amanda", "Melissa", "Deborah", "Stephanie", "Rebecca", "Sharon", "Laura",
|
||||||
|
"Cynthia", "Kathleen", "Amy", "Angela", "Shirley", "Anna", "Brenda", "Pamela",
|
||||||
|
"Emma", "Nicole", "Helen", "Samantha", "Katherine", "Christine", "Debra"
|
||||||
|
]
|
||||||
|
|
||||||
|
SURNAMES = [
|
||||||
|
"Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis",
|
||||||
|
"Rodriguez", "Martinez", "Hernandez", "Lopez", "Wilson", "Anderson", "Thomas",
|
||||||
|
"Taylor", "Moore", "Jackson", "Martin", "Lee", "Thompson", "White", "Harris",
|
||||||
|
"Sanchez", "Clark", "Ramirez", "Lewis", "Robinson", "Walker", "Young", "Allen",
|
||||||
|
"King", "Wright", "Scott", "Torres", "Nguyen", "Hill", "Flores", "Green", "Adams"
|
||||||
|
]
|
||||||
|
|
||||||
|
OCCUPATIONS = [
|
||||||
|
"Farmer", "Teacher", "Engineer", "Doctor", "Lawyer", "Merchant",
|
||||||
|
"Carpenter", "Blacksmith", "Sailor", "Soldier", "Clerk", "Nurse"
|
||||||
|
]
|
||||||
|
|
||||||
|
PLACES = [
|
||||||
|
"New York", "London", "Paris", "Berlin", "Rome", "Madrid", "Amsterdam",
|
||||||
|
"Vienna", "Prague", "Warsaw", "Stockholm", "Copenhagen"
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class EventData:
|
||||||
|
"""Data structure for an event."""
|
||||||
|
handle: str
|
||||||
|
event_type: str
|
||||||
|
year: int
|
||||||
|
month: int
|
||||||
|
day: int
|
||||||
|
description: str
|
||||||
|
event_id: int
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PersonData:
|
||||||
|
"""Data structure for person information."""
|
||||||
|
handle: str
|
||||||
|
name: str
|
||||||
|
surname: str
|
||||||
|
birth: int
|
||||||
|
death: Optional[int]
|
||||||
|
gender: str
|
||||||
|
parentin: List[str]
|
||||||
|
childof: List[str]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class FamilyData:
|
||||||
|
"""Data structure for family information."""
|
||||||
|
handle: str
|
||||||
|
father_handle: str
|
||||||
|
mother_handle: str
|
||||||
|
children_handles: List[str]
|
||||||
|
marriage_year: int
|
||||||
|
marriage_handle: str
|
||||||
|
family_id: int
|
||||||
|
|
||||||
|
|
||||||
|
def gen_handle(prefix: str, num: int) -> str:
|
||||||
|
"""Generate unique handle."""
|
||||||
|
return f"_{prefix}{num:08d}"
|
||||||
|
|
||||||
|
|
||||||
|
def create_event_element(event_data: EventData) -> ET.Element:
|
||||||
|
"""Create an XML element for an event."""
|
||||||
|
event_elem = ET.Element("event")
|
||||||
|
event_elem.set("handle", event_data.handle)
|
||||||
|
event_elem.set("change", str(int(datetime.now().timestamp())))
|
||||||
|
event_elem.set("id", f"E{event_data.event_id:04d}")
|
||||||
|
|
||||||
|
type_elem = ET.SubElement(event_elem, "type")
|
||||||
|
type_elem.text = event_data.event_type
|
||||||
|
|
||||||
|
date_elem = ET.SubElement(event_elem, "dateval")
|
||||||
|
date_elem.set("val", f"{event_data.year}-{event_data.month:02d}-{event_data.day:02d}")
|
||||||
|
|
||||||
|
if event_data.description:
|
||||||
|
desc_elem = ET.SubElement(event_elem, "description")
|
||||||
|
desc_elem.text = event_data.description
|
||||||
|
|
||||||
|
return event_elem
|
||||||
|
|
||||||
|
|
||||||
|
def gen_additional_events(
|
||||||
|
pid: int,
|
||||||
|
first_name: str,
|
||||||
|
surname: str,
|
||||||
|
birth_year: int,
|
||||||
|
death_year: Optional[int] = None
|
||||||
|
) -> List[Tuple[str, EventData]]:
|
||||||
|
"""Generate additional events for a person."""
|
||||||
|
events: List[Tuple[str, EventData]] = []
|
||||||
|
event_id_offset = pid * EVENT_ID_OFFSET + EVENT_ID_START_OFFSET
|
||||||
|
|
||||||
for event_type, probability, min_years, max_years in EVENT_TYPES:
|
for event_type, probability, min_years, max_years in EVENT_TYPES:
|
||||||
if random.random() > probability:
|
if random.random() > probability:
|
||||||
@ -43,19 +159,18 @@ def gen_additional_events(pid, first_name, surname, birth_year, death_year=None)
|
|||||||
if not death_year:
|
if not death_year:
|
||||||
continue
|
continue
|
||||||
event_year = death_year
|
event_year = death_year
|
||||||
event_month = random.randint(1, 12)
|
event_month = random.randint(MIN_MONTH, MAX_MONTH)
|
||||||
event_day = random.randint(1, 28)
|
event_day = random.randint(MIN_DAY, MAX_DAY)
|
||||||
else:
|
else:
|
||||||
if max_years is None:
|
if max_years is None:
|
||||||
continue
|
continue
|
||||||
event_year = birth_year + random.randint(min_years, max_years)
|
event_year = birth_year + random.randint(min_years, max_years)
|
||||||
if death_year and event_year > death_year:
|
if death_year and event_year > death_year:
|
||||||
continue
|
continue
|
||||||
event_month = random.randint(1, 12)
|
event_month = random.randint(MIN_MONTH, MAX_MONTH)
|
||||||
event_day = random.randint(1, 28)
|
event_day = random.randint(MIN_DAY, MAX_DAY)
|
||||||
|
|
||||||
event_handle = gen_handle("EVENT", event_id_offset)
|
event_handle = gen_handle("EVENT", event_id_offset)
|
||||||
event_id_offset += 1
|
|
||||||
|
|
||||||
# Generate description based on event type
|
# Generate description based on event type
|
||||||
if event_type == "Education":
|
if event_type == "Education":
|
||||||
@ -63,16 +178,12 @@ def gen_additional_events(pid, first_name, surname, birth_year, death_year=None)
|
|||||||
elif event_type == "Graduation":
|
elif event_type == "Graduation":
|
||||||
description = f"Graduation - {first_name} {surname}"
|
description = f"Graduation - {first_name} {surname}"
|
||||||
elif event_type == "Occupation":
|
elif event_type == "Occupation":
|
||||||
occupations = ["Farmer", "Teacher", "Engineer", "Doctor", "Lawyer", "Merchant",
|
occupation = random.choice(OCCUPATIONS)
|
||||||
"Carpenter", "Blacksmith", "Sailor", "Soldier", "Clerk", "Nurse"]
|
|
||||||
occupation = random.choice(occupations)
|
|
||||||
description = f"{occupation} - {first_name} {surname}"
|
description = f"{occupation} - {first_name} {surname}"
|
||||||
elif event_type == "Military Service":
|
elif event_type == "Military Service":
|
||||||
description = f"Military Service - {first_name} {surname}"
|
description = f"Military Service - {first_name} {surname}"
|
||||||
elif event_type == "Residence":
|
elif event_type == "Residence":
|
||||||
places = ["New York", "London", "Paris", "Berlin", "Rome", "Madrid", "Amsterdam",
|
place = random.choice(PLACES)
|
||||||
"Vienna", "Prague", "Warsaw", "Stockholm", "Copenhagen"]
|
|
||||||
place = random.choice(places)
|
|
||||||
description = f"Residence in {place} - {first_name} {surname}"
|
description = f"Residence in {place} - {first_name} {surname}"
|
||||||
elif event_type == "Emigration":
|
elif event_type == "Emigration":
|
||||||
description = f"Emigration - {first_name} {surname}"
|
description = f"Emigration - {first_name} {surname}"
|
||||||
@ -83,136 +194,218 @@ def gen_additional_events(pid, first_name, surname, birth_year, death_year=None)
|
|||||||
else:
|
else:
|
||||||
description = f"{event_type} of {surname}, {first_name}"
|
description = f"{event_type} of {surname}, {first_name}"
|
||||||
|
|
||||||
event_xml = f""" <event handle="{event_handle}" change="{int(datetime.now().timestamp())}" id="E{event_id_offset-1:04d}">
|
event_data = EventData(
|
||||||
<type>{event_type}</type>
|
handle=event_handle,
|
||||||
<dateval val="{event_year}-{event_month:02d}-{event_day:02d}"/>
|
event_type=event_type,
|
||||||
<description>{description}</description>
|
year=event_year,
|
||||||
</event>
|
month=event_month,
|
||||||
"""
|
day=event_day,
|
||||||
events.append((event_handle, event_xml))
|
description=description,
|
||||||
|
event_id=event_id_offset
|
||||||
|
)
|
||||||
|
|
||||||
|
events.append((event_handle, event_data))
|
||||||
|
event_id_offset += 1
|
||||||
|
|
||||||
return events
|
return events
|
||||||
|
|
||||||
# Generate a person
|
|
||||||
def gen_person(pid, first_name, surname, birth_year, death_year=None, gender="M",
|
def gen_person(
|
||||||
parentin_families=None, childof_families=None, reuse_additional_events=None):
|
pid: int,
|
||||||
|
first_name: str,
|
||||||
|
surname: str,
|
||||||
|
birth_year: int,
|
||||||
|
death_year: Optional[int] = None,
|
||||||
|
gender: str = "M",
|
||||||
|
parentin_families: Optional[List[str]] = None,
|
||||||
|
childof_families: Optional[List[str]] = None,
|
||||||
|
reuse_additional_events: Optional[List[Tuple[str, EventData]]] = None
|
||||||
|
) -> Tuple[ET.Element, ET.Element, Optional[ET.Element], List[ET.Element], List[Tuple[str, EventData]]]:
|
||||||
|
"""Generate a person with all associated events."""
|
||||||
handle = gen_handle("PERSON", pid)
|
handle = gen_handle("PERSON", pid)
|
||||||
birth_handle = gen_handle("EVENT", pid * 10)
|
birth_handle = gen_handle("EVENT", pid * EVENT_ID_OFFSET)
|
||||||
death_handle = gen_handle("EVENT", pid * 10 + 1) if death_year else None
|
death_handle = gen_handle("EVENT", pid * EVENT_ID_OFFSET + 1) if death_year else None
|
||||||
|
|
||||||
person_xml = f""" <person handle="{handle}" change="{int(datetime.now().timestamp())}" id="I{pid:04d}">
|
# Create person element
|
||||||
<gender>{gender}</gender>
|
person_elem = ET.Element("person")
|
||||||
<name type="Birth Name">
|
person_elem.set("handle", handle)
|
||||||
<first>{first_name}</first>
|
person_elem.set("change", str(int(datetime.now().timestamp())))
|
||||||
<surname>{surname}</surname>
|
person_elem.set("id", f"I{pid:04d}")
|
||||||
</name>
|
|
||||||
<eventref hlink="{birth_handle}" role="Primary"/>
|
gender_elem = ET.SubElement(person_elem, "gender")
|
||||||
"""
|
gender_elem.text = gender
|
||||||
|
|
||||||
|
name_elem = ET.SubElement(person_elem, "name")
|
||||||
|
name_elem.set("type", "Birth Name")
|
||||||
|
first_elem = ET.SubElement(name_elem, "first")
|
||||||
|
first_elem.text = first_name
|
||||||
|
surname_elem = ET.SubElement(name_elem, "surname")
|
||||||
|
surname_elem.text = surname
|
||||||
|
|
||||||
|
# Birth event reference
|
||||||
|
birth_ref = ET.SubElement(person_elem, "eventref")
|
||||||
|
birth_ref.set("hlink", birth_handle)
|
||||||
|
birth_ref.set("role", "Primary")
|
||||||
|
|
||||||
|
# Death event reference
|
||||||
if death_handle:
|
if death_handle:
|
||||||
person_xml += f""" <eventref hlink="{death_handle}" role="Primary"/>
|
death_ref = ET.SubElement(person_elem, "eventref")
|
||||||
"""
|
death_ref.set("hlink", death_handle)
|
||||||
|
death_ref.set("role", "Primary")
|
||||||
|
|
||||||
# Add additional events - reuse if provided, otherwise generate new
|
# Add additional events - reuse if provided, otherwise generate new
|
||||||
if reuse_additional_events is not None:
|
if reuse_additional_events is not None:
|
||||||
# reuse_additional_events is a list of (handle, xml) tuples
|
|
||||||
additional_events = reuse_additional_events
|
additional_events = reuse_additional_events
|
||||||
else:
|
else:
|
||||||
additional_events = gen_additional_events(pid, first_name, surname, birth_year, death_year)
|
additional_events = gen_additional_events(pid, first_name, surname, birth_year, death_year)
|
||||||
|
|
||||||
for event_handle, _ in additional_events:
|
for event_handle, _ in additional_events:
|
||||||
person_xml += f""" <eventref hlink="{event_handle}" role="Primary"/>
|
event_ref = ET.SubElement(person_elem, "eventref")
|
||||||
"""
|
event_ref.set("hlink", event_handle)
|
||||||
|
event_ref.set("role", "Primary")
|
||||||
|
|
||||||
# Add parentin references (for fathers and mothers)
|
# Add parentin references
|
||||||
if parentin_families:
|
if parentin_families:
|
||||||
for family_handle in parentin_families:
|
for family_handle in parentin_families:
|
||||||
person_xml += f""" <parentin hlink="{family_handle}"/>
|
parentin_elem = ET.SubElement(person_elem, "parentin")
|
||||||
"""
|
parentin_elem.set("hlink", family_handle)
|
||||||
# Add childof references (for children)
|
|
||||||
|
# Add childof references
|
||||||
if childof_families:
|
if childof_families:
|
||||||
for family_handle in childof_families:
|
for family_handle in childof_families:
|
||||||
person_xml += f""" <childof hlink="{family_handle}"/>
|
childof_elem = ET.SubElement(person_elem, "childof")
|
||||||
"""
|
childof_elem.set("hlink", family_handle)
|
||||||
person_xml += """ </person>
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Birth event
|
# Birth event
|
||||||
birth_month = random.randint(1, 12)
|
birth_month = random.randint(MIN_MONTH, MAX_MONTH)
|
||||||
birth_day = random.randint(1, 28)
|
birth_day = random.randint(MIN_DAY, MAX_DAY)
|
||||||
birth_event = f""" <event handle="{birth_handle}" change="{int(datetime.now().timestamp())}" id="E{pid*10:04d}">
|
birth_event_data = EventData(
|
||||||
<type>Birth</type>
|
handle=birth_handle,
|
||||||
<dateval val="{birth_year}-{birth_month:02d}-{birth_day:02d}"/>
|
event_type="Birth",
|
||||||
<description>Birth of {surname}, {first_name}</description>
|
year=birth_year,
|
||||||
</event>
|
month=birth_month,
|
||||||
"""
|
day=birth_day,
|
||||||
|
description=f"Birth of {surname}, {first_name}",
|
||||||
|
event_id=pid * EVENT_ID_OFFSET
|
||||||
|
)
|
||||||
|
birth_event = create_event_element(birth_event_data)
|
||||||
|
|
||||||
# Death event
|
# Death event
|
||||||
death_event = ""
|
death_event: Optional[ET.Element] = None
|
||||||
if death_handle and death_year:
|
if death_handle and death_year:
|
||||||
death_month = random.randint(1, 12)
|
death_month = random.randint(MIN_MONTH, MAX_MONTH)
|
||||||
death_day = random.randint(1, 28)
|
death_day = random.randint(MIN_DAY, MAX_DAY)
|
||||||
death_event = f""" <event handle="{death_handle}" change="{int(datetime.now().timestamp())}" id="E{pid*10+1:04d}">
|
death_event_data = EventData(
|
||||||
<type>Death</type>
|
handle=death_handle,
|
||||||
<dateval val="{death_year}-{death_month:02d}-{death_day:02d}"/>
|
event_type="Death",
|
||||||
<description>Death of {surname}, {first_name}</description>
|
year=death_year,
|
||||||
</event>
|
month=death_month,
|
||||||
"""
|
day=death_day,
|
||||||
|
description=f"Death of {surname}, {first_name}",
|
||||||
|
event_id=pid * EVENT_ID_OFFSET + 1
|
||||||
|
)
|
||||||
|
death_event = create_event_element(death_event_data)
|
||||||
|
|
||||||
# Collect all additional events (return tuples for reuse, XML strings for output)
|
# Convert additional events to XML elements
|
||||||
all_additional_events_xml = [event_xml for _, event_xml in additional_events]
|
all_additional_events_xml = [create_event_element(event_data) for _, event_data in additional_events]
|
||||||
|
|
||||||
return person_xml, birth_event, death_event, all_additional_events_xml, additional_events
|
return person_elem, birth_event, death_event, all_additional_events_xml, additional_events
|
||||||
|
|
||||||
# Generate a family
|
|
||||||
def gen_family(fid, father_handle, mother_handle, marriage_year, children_handles):
|
def gen_family(
|
||||||
|
fid: int,
|
||||||
|
father_handle: str,
|
||||||
|
mother_handle: str,
|
||||||
|
marriage_year: int,
|
||||||
|
children_handles: List[str]
|
||||||
|
) -> Tuple[ET.Element, ET.Element]:
|
||||||
|
"""Generate a family with marriage event."""
|
||||||
handle = gen_handle("FAMILY", fid)
|
handle = gen_handle("FAMILY", fid)
|
||||||
marriage_handle = gen_handle("EVENT", fid * 100)
|
marriage_handle = gen_handle("EVENT", fid * FAMILY_ID_OFFSET)
|
||||||
|
|
||||||
|
# Create family element
|
||||||
|
family_elem = ET.Element("family")
|
||||||
|
family_elem.set("handle", handle)
|
||||||
|
family_elem.set("change", str(int(datetime.now().timestamp())))
|
||||||
|
family_elem.set("id", f"F{fid:04d}")
|
||||||
|
|
||||||
|
rel_elem = ET.SubElement(family_elem, "rel")
|
||||||
|
rel_elem.set("type", "Married")
|
||||||
|
|
||||||
|
father_elem = ET.SubElement(family_elem, "father")
|
||||||
|
father_elem.set("hlink", father_handle)
|
||||||
|
|
||||||
|
mother_elem = ET.SubElement(family_elem, "mother")
|
||||||
|
mother_elem.set("hlink", mother_handle)
|
||||||
|
|
||||||
family_xml = f""" <family handle="{handle}" change="{int(datetime.now().timestamp())}" id="F{fid:04d}">
|
|
||||||
<rel type="Married"/>
|
|
||||||
<father hlink="{father_handle}"/>
|
|
||||||
<mother hlink="{mother_handle}"/>
|
|
||||||
"""
|
|
||||||
for child_handle in children_handles:
|
for child_handle in children_handles:
|
||||||
family_xml += f""" <childref hlink="{child_handle}"/>
|
child_elem = ET.SubElement(family_elem, "childref")
|
||||||
"""
|
child_elem.set("hlink", child_handle)
|
||||||
family_xml += f""" <eventref hlink="{marriage_handle}" role="Family"/>
|
|
||||||
</family>
|
marriage_ref = ET.SubElement(family_elem, "eventref")
|
||||||
"""
|
marriage_ref.set("hlink", marriage_handle)
|
||||||
|
marriage_ref.set("role", "Family")
|
||||||
|
|
||||||
# Marriage event
|
# Marriage event
|
||||||
marriage_month = random.randint(1, 12)
|
marriage_month = random.randint(MIN_MONTH, MAX_MONTH)
|
||||||
marriage_day = random.randint(1, 28)
|
marriage_day = random.randint(MIN_DAY, MAX_DAY)
|
||||||
marriage_event = f""" <event handle="{marriage_handle}" change="{int(datetime.now().timestamp())}" id="E{fid*100:04d}">
|
marriage_event_data = EventData(
|
||||||
<type>Marriage</type>
|
handle=marriage_handle,
|
||||||
<dateval val="{marriage_year}-{marriage_month:02d}-{marriage_day:02d}"/>
|
event_type="Marriage",
|
||||||
<description>Marriage</description>
|
year=marriage_year,
|
||||||
</event>
|
month=marriage_month,
|
||||||
"""
|
day=marriage_day,
|
||||||
|
description="Marriage",
|
||||||
|
event_id=fid * FAMILY_ID_OFFSET
|
||||||
|
)
|
||||||
|
marriage_event = create_event_element(marriage_event_data)
|
||||||
|
|
||||||
return family_xml, marriage_event
|
return family_elem, marriage_event
|
||||||
|
|
||||||
# First names
|
|
||||||
male_names = ["James", "John", "Robert", "Michael", "William", "David", "Richard", "Joseph",
|
|
||||||
"Thomas", "Charles", "Daniel", "Matthew", "Anthony", "Mark", "Donald", "Steven",
|
|
||||||
"Paul", "Andrew", "Joshua", "Kenneth", "Kevin", "Brian", "George", "Timothy",
|
|
||||||
"Ronald", "Jason", "Edward", "Jeffrey", "Ryan", "Jacob", "Gary", "Nicholas",
|
|
||||||
"Eric", "Jonathan", "Stephen", "Larry", "Justin", "Scott", "Brandon", "Benjamin"]
|
|
||||||
|
|
||||||
female_names = ["Mary", "Patricia", "Jennifer", "Linda", "Elizabeth", "Barbara", "Susan",
|
def create_gramps_xml_document(
|
||||||
"Jessica", "Sarah", "Karen", "Nancy", "Lisa", "Betty", "Margaret", "Sandra",
|
events: List[ET.Element],
|
||||||
"Ashley", "Kimberly", "Emily", "Donna", "Michelle", "Dorothy", "Carol",
|
people: List[ET.Element],
|
||||||
"Amanda", "Melissa", "Deborah", "Stephanie", "Rebecca", "Sharon", "Laura",
|
families: List[ET.Element]
|
||||||
"Cynthia", "Kathleen", "Amy", "Angela", "Shirley", "Anna", "Brenda", "Pamela",
|
) -> ET.ElementTree:
|
||||||
"Emma", "Nicole", "Helen", "Samantha", "Katherine", "Christine", "Debra"]
|
"""Create the complete Gramps XML document."""
|
||||||
|
# Create root element
|
||||||
|
database = ET.Element("database")
|
||||||
|
database.set("xmlns", GRAMPS_XML_NAMESPACE)
|
||||||
|
|
||||||
|
# Header
|
||||||
|
header = ET.SubElement(database, "header")
|
||||||
|
created = ET.SubElement(header, "created")
|
||||||
|
created.set("date", datetime.now().strftime('%Y-%m-%d'))
|
||||||
|
created.set("version", GRAMPS_XML_VERSION)
|
||||||
|
|
||||||
|
researcher = ET.SubElement(header, "researcher")
|
||||||
|
resname = ET.SubElement(researcher, "resname")
|
||||||
|
resname.text = "Demo Family Generator"
|
||||||
|
|
||||||
|
# Tags (empty)
|
||||||
|
ET.SubElement(database, "tags")
|
||||||
|
|
||||||
|
# Events
|
||||||
|
events_elem = ET.SubElement(database, "events")
|
||||||
|
for event in events:
|
||||||
|
events_elem.append(event)
|
||||||
|
|
||||||
|
# People
|
||||||
|
people_elem = ET.SubElement(database, "people")
|
||||||
|
for person in people:
|
||||||
|
people_elem.append(person)
|
||||||
|
|
||||||
|
# Families
|
||||||
|
families_elem = ET.SubElement(database, "families")
|
||||||
|
for family in families:
|
||||||
|
families_elem.append(family)
|
||||||
|
|
||||||
|
return ET.ElementTree(database)
|
||||||
|
|
||||||
surnames = ["Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis",
|
|
||||||
"Rodriguez", "Martinez", "Hernandez", "Lopez", "Wilson", "Anderson", "Thomas",
|
|
||||||
"Taylor", "Moore", "Jackson", "Martin", "Lee", "Thompson", "White", "Harris",
|
|
||||||
"Sanchez", "Clark", "Ramirez", "Lewis", "Robinson", "Walker", "Young", "Allen",
|
|
||||||
"King", "Wright", "Scott", "Torres", "Nguyen", "Hill", "Flores", "Green", "Adams"]
|
|
||||||
|
|
||||||
def main():
|
def main() -> None:
|
||||||
|
"""Main function to generate the demo family."""
|
||||||
print("Generating huge demo family...")
|
print("Generating huge demo family...")
|
||||||
|
|
||||||
# Generate main family
|
# Generate main family
|
||||||
@ -221,7 +414,7 @@ def main():
|
|||||||
father_handle = gen_handle("PERSON", father_id)
|
father_handle = gen_handle("PERSON", father_id)
|
||||||
main_family_handle = gen_handle("FAMILY", 1)
|
main_family_handle = gen_handle("FAMILY", 1)
|
||||||
father_person, father_birth, father_death, father_additional_xml, _ = gen_person(
|
father_person, father_birth, father_death, father_additional_xml, _ = gen_person(
|
||||||
father_id, "John", "Smith", 1950, 2010, "M",
|
father_id, "John", "Smith", 1950, 2010, "M",
|
||||||
parentin_families=[main_family_handle]
|
parentin_families=[main_family_handle]
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -234,17 +427,21 @@ def main():
|
|||||||
)
|
)
|
||||||
|
|
||||||
all_additional_events = father_additional_xml + mother_additional_xml
|
all_additional_events = father_additional_xml + mother_additional_xml
|
||||||
|
all_events = [father_birth, mother_birth]
|
||||||
|
if father_death:
|
||||||
|
all_events.append(father_death)
|
||||||
|
if mother_death:
|
||||||
|
all_events.append(mother_death)
|
||||||
|
|
||||||
# Generate 15 children
|
# Generate 15 children
|
||||||
children = []
|
children: List[ET.Element] = []
|
||||||
child_handles = []
|
child_handles: List[str] = []
|
||||||
child_events = []
|
child_additional_events_map: Dict[int, List[Tuple[str, EventData]]] = {}
|
||||||
child_additional_events_map = {} # Store additional events by child_id
|
|
||||||
child_id = 3
|
child_id = 3
|
||||||
|
|
||||||
for i in range(15):
|
for i in range(15):
|
||||||
gender = "M" if i % 2 == 0 else "F"
|
gender = "M" if i % 2 == 0 else "F"
|
||||||
first_name = random.choice(male_names if gender == "M" else female_names)
|
first_name = random.choice(MALE_NAMES if gender == "M" else FEMALE_NAMES)
|
||||||
birth_year = 1970 + (i * 2) # Spread births from 1970 to 1998
|
birth_year = 1970 + (i * 2) # Spread births from 1970 to 1998
|
||||||
death_year = birth_year + random.randint(60, 90) if random.random() < 0.3 else None # 30% chance of death
|
death_year = birth_year + random.randint(60, 90) if random.random() < 0.3 else None # 30% chance of death
|
||||||
|
|
||||||
@ -256,9 +453,9 @@ def main():
|
|||||||
|
|
||||||
children.append(child_person)
|
children.append(child_person)
|
||||||
child_handles.append(child_handle)
|
child_handles.append(child_handle)
|
||||||
child_events.append(child_birth)
|
all_events.append(child_birth)
|
||||||
if child_death:
|
if child_death:
|
||||||
child_events.append(child_death)
|
all_events.append(child_death)
|
||||||
# Store tuples for reuse when regenerating
|
# Store tuples for reuse when regenerating
|
||||||
child_additional_events_map[child_id] = child_additional_tuples
|
child_additional_events_map[child_id] = child_additional_tuples
|
||||||
all_additional_events.extend(child_additional_xml)
|
all_additional_events.extend(child_additional_xml)
|
||||||
@ -266,40 +463,66 @@ def main():
|
|||||||
|
|
||||||
# Generate family
|
# Generate family
|
||||||
family_id = 1
|
family_id = 1
|
||||||
family_xml, marriage_event = gen_family(family_id, father_handle, mother_handle, 1969, child_handles)
|
family_elem, marriage_event = gen_family(family_id, father_handle, mother_handle, 1969, child_handles)
|
||||||
|
all_events.append(marriage_event)
|
||||||
|
families: List[ET.Element] = [family_elem]
|
||||||
|
|
||||||
# Track person data for regeneration (needed for children who become parents)
|
# Track person data for regeneration (needed for children who become parents)
|
||||||
import re
|
person_data: Dict[int, PersonData] = {}
|
||||||
person_data = {}
|
|
||||||
# Store initial person data
|
# Store initial person data
|
||||||
person_data[father_id] = {"handle": father_handle, "name": "John", "surname": "Smith",
|
person_data[father_id] = PersonData(
|
||||||
"birth": 1950, "death": 2010, "gender": "M",
|
handle=father_handle,
|
||||||
"parentin": [main_family_handle], "childof": []}
|
name="John",
|
||||||
person_data[mother_id] = {"handle": mother_handle, "name": "Mary", "surname": "Smith",
|
surname="Smith",
|
||||||
"birth": 1952, "death": 2015, "gender": "F",
|
birth=1950,
|
||||||
"parentin": [main_family_handle], "childof": []}
|
death=2010,
|
||||||
|
gender="M",
|
||||||
|
parentin=[main_family_handle],
|
||||||
|
childof=[]
|
||||||
|
)
|
||||||
|
person_data[mother_id] = PersonData(
|
||||||
|
handle=mother_handle,
|
||||||
|
name="Mary",
|
||||||
|
surname="Smith",
|
||||||
|
birth=1952,
|
||||||
|
death=2015,
|
||||||
|
gender="F",
|
||||||
|
parentin=[main_family_handle],
|
||||||
|
childof=[]
|
||||||
|
)
|
||||||
|
|
||||||
for i, child_handle in enumerate(child_handles):
|
for i, child_handle in enumerate(child_handles):
|
||||||
child_pid = 3 + i
|
child_pid = 3 + i
|
||||||
gender = "M" if i % 2 == 0 else "F"
|
gender = "M" if i % 2 == 0 else "F"
|
||||||
# Extract name from generated child XML
|
# Extract name from generated child XML
|
||||||
child_xml = children[i]
|
name_elem = children[i].find(".//first")
|
||||||
name_match = re.search(r'<first>([^<]+)</first>', child_xml)
|
first_name = name_elem.text if name_elem is not None and name_elem.text else random.choice(MALE_NAMES if gender == "M" else FEMALE_NAMES)
|
||||||
first_name = name_match.group(1) if name_match else random.choice(male_names if gender == "M" else female_names)
|
|
||||||
birth_year = 1970 + (i * 2)
|
birth_year = 1970 + (i * 2)
|
||||||
# Extract death year from child_events if it exists
|
# Extract death year from events if it exists
|
||||||
death_year = None
|
death_year = None
|
||||||
for event in child_events:
|
for event in all_events:
|
||||||
if f"id=\"E{child_pid*10+1:04d}\"" in event:
|
if event.get("id") == f"E{child_pid * EVENT_ID_OFFSET + 1:04d}":
|
||||||
match = re.search(r'val="(\d{4})', event)
|
date_elem = event.find(".//dateval")
|
||||||
if match:
|
if date_elem is not None:
|
||||||
death_year = int(match.group(1))
|
date_val = date_elem.get("val", "")
|
||||||
person_data[child_pid] = {"handle": child_handle, "name": first_name, "surname": "Smith",
|
if date_val:
|
||||||
"birth": birth_year, "death": death_year, "gender": gender,
|
try:
|
||||||
"parentin": [], "childof": [main_family_handle]}
|
death_year = int(date_val.split("-")[0])
|
||||||
|
except (ValueError, IndexError):
|
||||||
|
pass
|
||||||
|
person_data[child_pid] = PersonData(
|
||||||
|
handle=child_handle,
|
||||||
|
name=first_name,
|
||||||
|
surname="Smith",
|
||||||
|
birth=birth_year,
|
||||||
|
death=death_year,
|
||||||
|
gender=gender,
|
||||||
|
parentin=[],
|
||||||
|
childof=[main_family_handle]
|
||||||
|
)
|
||||||
|
|
||||||
# Generate grandchildren (children of first 5 children)
|
# Generate grandchildren (children of first 5 children)
|
||||||
grandchildren = []
|
grandchildren: List[ET.Element] = []
|
||||||
grandchild_events = []
|
|
||||||
grandchild_id = child_id
|
grandchild_id = child_id
|
||||||
|
|
||||||
for i in range(5): # First 5 children have children
|
for i in range(5): # First 5 children have children
|
||||||
@ -309,41 +532,55 @@ def main():
|
|||||||
spouse_gender = "F" if parent_gender == "M" else "M"
|
spouse_gender = "F" if parent_gender == "M" else "M"
|
||||||
|
|
||||||
# Create spouse
|
# Create spouse
|
||||||
spouse_name = random.choice(female_names if spouse_gender == "F" else male_names)
|
spouse_name = random.choice(FEMALE_NAMES if spouse_gender == "F" else MALE_NAMES)
|
||||||
spouse_birth = 1970 + (i * 2) + random.randint(-2, 2)
|
spouse_birth = 1970 + (i * 2) + random.randint(-2, 2)
|
||||||
spouse_handle = gen_handle("PERSON", grandchild_id)
|
spouse_handle = gen_handle("PERSON", grandchild_id)
|
||||||
child_family_handle = gen_handle("FAMILY", family_id + 1)
|
child_family_handle = gen_handle("FAMILY", family_id + 1)
|
||||||
|
|
||||||
person_data[grandchild_id] = {"handle": spouse_handle, "name": spouse_name, "surname": "Smith",
|
person_data[grandchild_id] = PersonData(
|
||||||
"birth": spouse_birth, "death": None, "gender": spouse_gender,
|
handle=spouse_handle,
|
||||||
"parentin": [child_family_handle], "childof": []}
|
name=spouse_name,
|
||||||
|
surname="Smith",
|
||||||
|
birth=spouse_birth,
|
||||||
|
death=None,
|
||||||
|
gender=spouse_gender,
|
||||||
|
parentin=[child_family_handle],
|
||||||
|
childof=[]
|
||||||
|
)
|
||||||
|
|
||||||
spouse_person, spouse_birth_event, spouse_death_event, spouse_additional_xml, _ = gen_person(
|
spouse_person, spouse_birth_event, spouse_death_event, spouse_additional_xml, _ = gen_person(
|
||||||
grandchild_id, spouse_name, "Smith", spouse_birth, None, spouse_gender,
|
grandchild_id, spouse_name, "Smith", spouse_birth, None, spouse_gender,
|
||||||
parentin_families=[child_family_handle]
|
parentin_families=[child_family_handle]
|
||||||
)
|
)
|
||||||
grandchildren.append(spouse_person)
|
grandchildren.append(spouse_person)
|
||||||
grandchild_events.append(spouse_birth_event)
|
all_events.append(spouse_birth_event)
|
||||||
if spouse_death_event:
|
if spouse_death_event:
|
||||||
grandchild_events.append(spouse_death_event)
|
all_events.append(spouse_death_event)
|
||||||
all_additional_events.extend(spouse_additional_xml)
|
all_additional_events.extend(spouse_additional_xml)
|
||||||
grandchild_id += 1
|
grandchild_id += 1
|
||||||
|
|
||||||
# Update parent to include parentin reference
|
# Update parent to include parentin reference
|
||||||
person_data[parent_pid]["parentin"].append(child_family_handle)
|
person_data[parent_pid].parentin.append(child_family_handle)
|
||||||
|
|
||||||
# Create 3-5 children per couple
|
# Create 3-5 children per couple
|
||||||
num_grandchildren = random.randint(3, 5)
|
num_grandchildren = random.randint(3, 5)
|
||||||
grandchild_handles = []
|
grandchild_handles: List[str] = []
|
||||||
for j in range(num_grandchildren):
|
for j in range(num_grandchildren):
|
||||||
gchild_gender = "M" if j % 2 == 0 else "F"
|
gchild_gender = "M" if j % 2 == 0 else "F"
|
||||||
gchild_name = random.choice(male_names if gchild_gender == "M" else female_names)
|
gchild_name = random.choice(MALE_NAMES if gchild_gender == "M" else FEMALE_NAMES)
|
||||||
gchild_birth = 1995 + (i * 3) + j
|
gchild_birth = 1995 + (i * 3) + j
|
||||||
gchild_handle = gen_handle("PERSON", grandchild_id)
|
gchild_handle = gen_handle("PERSON", grandchild_id)
|
||||||
|
|
||||||
person_data[grandchild_id] = {"handle": gchild_handle, "name": gchild_name, "surname": "Smith",
|
person_data[grandchild_id] = PersonData(
|
||||||
"birth": gchild_birth, "death": None, "gender": gchild_gender,
|
handle=gchild_handle,
|
||||||
"parentin": [], "childof": [child_family_handle]}
|
name=gchild_name,
|
||||||
|
surname="Smith",
|
||||||
|
birth=gchild_birth,
|
||||||
|
death=None,
|
||||||
|
gender=gchild_gender,
|
||||||
|
parentin=[],
|
||||||
|
childof=[child_family_handle]
|
||||||
|
)
|
||||||
|
|
||||||
gchild_person, gchild_birth_event, gchild_death_event, gchild_additional_xml, _ = gen_person(
|
gchild_person, gchild_birth_event, gchild_death_event, gchild_additional_xml, _ = gen_person(
|
||||||
grandchild_id, gchild_name, "Smith", gchild_birth, None, gchild_gender,
|
grandchild_id, gchild_name, "Smith", gchild_birth, None, gchild_gender,
|
||||||
@ -351,17 +588,17 @@ def main():
|
|||||||
)
|
)
|
||||||
grandchildren.append(gchild_person)
|
grandchildren.append(gchild_person)
|
||||||
grandchild_handles.append(gchild_handle)
|
grandchild_handles.append(gchild_handle)
|
||||||
grandchild_events.append(gchild_birth_event)
|
all_events.append(gchild_birth_event)
|
||||||
if gchild_death_event:
|
if gchild_death_event:
|
||||||
grandchild_events.append(gchild_death_event)
|
all_events.append(gchild_death_event)
|
||||||
all_additional_events.extend(gchild_additional_xml)
|
all_additional_events.extend(gchild_additional_xml)
|
||||||
grandchild_id += 1
|
grandchild_id += 1
|
||||||
|
|
||||||
# Create family for this couple
|
# Create family for this couple
|
||||||
family_id += 1
|
family_id += 1
|
||||||
fam_xml, fam_marriage = gen_family(family_id, parent_handle, spouse_handle, 1990 + i, grandchild_handles)
|
fam_elem, fam_marriage = gen_family(family_id, parent_handle, spouse_handle, 1990 + i, grandchild_handles)
|
||||||
family_xml += fam_xml
|
families.append(fam_elem)
|
||||||
child_events.append(fam_marriage)
|
all_events.append(fam_marriage)
|
||||||
|
|
||||||
# Regenerate children XMLs with updated family references
|
# Regenerate children XMLs with updated family references
|
||||||
# We need to regenerate to update family references, but reuse the same events
|
# We need to regenerate to update family references, but reuse the same events
|
||||||
@ -372,63 +609,41 @@ def main():
|
|||||||
# Reuse the original additional events to ensure consistency
|
# Reuse the original additional events to ensure consistency
|
||||||
original_additional_events = child_additional_events_map.get(child_pid, [])
|
original_additional_events = child_additional_events_map.get(child_pid, [])
|
||||||
child_person, _, _, _, _ = gen_person(
|
child_person, _, _, _, _ = gen_person(
|
||||||
child_pid, data["name"], data["surname"], data["birth"], data["death"], data["gender"],
|
child_pid, data.name, data.surname, data.birth, data.death, data.gender,
|
||||||
parentin_families=data["parentin"], childof_families=data["childof"],
|
parentin_families=data.parentin, childof_families=data.childof,
|
||||||
reuse_additional_events=original_additional_events
|
reuse_additional_events=original_additional_events
|
||||||
)
|
)
|
||||||
children.append(child_person)
|
children.append(child_person)
|
||||||
|
|
||||||
# Write XML file
|
# Add all additional events to events list
|
||||||
xml_content = f"""<?xml version="1.0" encoding="UTF-8"?>
|
all_events.extend(all_additional_events)
|
||||||
<!DOCTYPE database PUBLIC "-//Gramps//DTD Gramps XML 1.7.1//EN"
|
|
||||||
"http://gramps-project.org/xml/1.7.1/grampsxml.dtd">
|
|
||||||
<database xmlns="http://gramps-project.org/xml/1.7.1/">
|
|
||||||
<header>
|
|
||||||
<created date="{datetime.now().strftime('%Y-%m-%d')}" version="5.1.0"/>
|
|
||||||
<researcher>
|
|
||||||
<resname>Demo Family Generator</resname>
|
|
||||||
</researcher>
|
|
||||||
</header>
|
|
||||||
<tags>
|
|
||||||
</tags>
|
|
||||||
<events>
|
|
||||||
{father_birth}
|
|
||||||
{father_death}
|
|
||||||
{mother_birth}
|
|
||||||
{mother_death}
|
|
||||||
{marriage_event}
|
|
||||||
"""
|
|
||||||
|
|
||||||
for event in child_events:
|
# Create complete XML document
|
||||||
xml_content += event
|
people = [father_person, mother_person] + children + grandchildren
|
||||||
for event in grandchild_events:
|
tree = create_gramps_xml_document(all_events, people, families)
|
||||||
xml_content += event
|
|
||||||
for event in all_additional_events:
|
|
||||||
xml_content += event
|
|
||||||
|
|
||||||
xml_content += """ </events>
|
# Write XML file with proper formatting
|
||||||
<people>
|
# ET.indent is only available in Python 3.9+, so we'll format manually if needed
|
||||||
"""
|
try:
|
||||||
xml_content += father_person
|
ET.indent(tree, space=" ")
|
||||||
xml_content += mother_person
|
except AttributeError:
|
||||||
for child in children:
|
# Python < 3.9 doesn't have indent, will write without indentation
|
||||||
xml_content += child
|
pass
|
||||||
for grandchild in grandchildren:
|
tree.write("demo_family.gramps", encoding="utf-8", xml_declaration=True)
|
||||||
xml_content += grandchild
|
|
||||||
|
|
||||||
xml_content += """ </people>
|
# Add DOCTYPE declaration (ElementTree doesn't support this directly)
|
||||||
<families>
|
with open("demo_family.gramps", "r", encoding="utf-8") as f:
|
||||||
"""
|
content = f.read()
|
||||||
xml_content += family_xml
|
|
||||||
|
|
||||||
xml_content += """ </families>
|
# Insert DOCTYPE after XML declaration
|
||||||
</database>
|
doctype = f'<!DOCTYPE database PUBLIC "-//Gramps//DTD Gramps XML 1.7.1//EN"\n"{GRAMPS_XML_DTD}">\n'
|
||||||
"""
|
content = content.replace('<?xml version="1.0" encoding="utf-8"?>',
|
||||||
|
f'<?xml version="1.0" encoding="UTF-8"?>\n{doctype}', 1)
|
||||||
|
|
||||||
with open("demo_family.gramps", "w", encoding="utf-8") as f:
|
with open("demo_family.gramps", "w", encoding="utf-8") as f:
|
||||||
f.write(xml_content)
|
f.write(content)
|
||||||
|
|
||||||
total_events = len(child_events) + len(grandchild_events) + len(all_additional_events)
|
total_events = len(all_events)
|
||||||
print(f"Generated demo_family.gramps with:")
|
print(f"Generated demo_family.gramps with:")
|
||||||
print(f" - 2 parents (John and Mary Smith)")
|
print(f" - 2 parents (John and Mary Smith)")
|
||||||
print(f" - 15 children")
|
print(f" - 15 children")
|
||||||
@ -439,6 +654,6 @@ def main():
|
|||||||
print(f" - {len(all_additional_events)} additional events (Baptism, Education, Occupation, etc.)")
|
print(f" - {len(all_additional_events)} additional events (Baptism, Education, Occupation, etc.)")
|
||||||
print(f" - Total events: {total_events}")
|
print(f" - Total events: {total_events}")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
|||||||
@ -6,12 +6,13 @@
|
|||||||
# so it can be used in the snap-installed version of Gramps 6.0
|
# so it can be used in the snap-installed version of Gramps 6.0
|
||||||
#
|
#
|
||||||
|
|
||||||
set -e # Exit on error
|
set -euo pipefail # Exit on error, undefined vars, pipe failures
|
||||||
|
|
||||||
# Colors for output
|
# Colors for output
|
||||||
RED='\033[0;31m'
|
RED='\033[0;31m'
|
||||||
GREEN='\033[0;32m'
|
GREEN='\033[0;32m'
|
||||||
YELLOW='\033[1;33m'
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
NC='\033[0m' # No Color
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
# Get the directory where this script is located
|
# Get the directory where this script is located
|
||||||
@ -40,6 +41,22 @@ echo "MyTimeline Plugin Installer for Snap Gramps"
|
|||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
# Function to check if Gramps is installed via snap
|
||||||
|
check_gramps_installed() {
|
||||||
|
if ! command -v gramps &> /dev/null && ! snap list gramps &> /dev/null; then
|
||||||
|
echo -e "${YELLOW}Warning: Gramps may not be installed via snap.${NC}"
|
||||||
|
echo "This script is designed for snap-installed Gramps."
|
||||||
|
echo "If Gramps is installed differently, you may need to manually install the plugin."
|
||||||
|
echo ""
|
||||||
|
read -p "Continue anyway? (y/N): " -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
echo "Installation cancelled."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# Function to find the correct plugin directory
|
# Function to find the correct plugin directory
|
||||||
find_plugin_dir() {
|
find_plugin_dir() {
|
||||||
# Try the primary location first
|
# Try the primary location first
|
||||||
@ -65,6 +82,9 @@ find_plugin_dir() {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Check if Gramps is installed
|
||||||
|
check_gramps_installed
|
||||||
|
|
||||||
# Verify source files exist
|
# Verify source files exist
|
||||||
echo "Checking source files..."
|
echo "Checking source files..."
|
||||||
MISSING_FILES=()
|
MISSING_FILES=()
|
||||||
@ -82,6 +102,11 @@ if [ ${#MISSING_FILES[@]} -gt 0 ]; then
|
|||||||
for file in "${MISSING_FILES[@]}"; do
|
for file in "${MISSING_FILES[@]}"; do
|
||||||
echo " - $file"
|
echo " - $file"
|
||||||
done
|
done
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}Troubleshooting:${NC}"
|
||||||
|
echo " 1. Make sure you're running this script from the directory containing the plugin files"
|
||||||
|
echo " 2. Verify that MyTimeline.gpr.py and MyTimeline.py exist in: $PLUGIN_SRC_DIR"
|
||||||
|
echo " 3. Check file permissions: ls -la $PLUGIN_SRC_DIR"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -92,7 +117,14 @@ TARGET_DIR=$(find_plugin_dir)
|
|||||||
|
|
||||||
if [ -z "$TARGET_DIR" ]; then
|
if [ -z "$TARGET_DIR" ]; then
|
||||||
echo -e "${RED}Error: Could not determine plugin directory location.${NC}"
|
echo -e "${RED}Error: Could not determine plugin directory location.${NC}"
|
||||||
echo "Please check if Gramps is installed via snap."
|
echo ""
|
||||||
|
echo -e "${YELLOW}Troubleshooting:${NC}"
|
||||||
|
echo " 1. Verify Gramps is installed: snap list gramps"
|
||||||
|
echo " 2. Check if snap directory exists: ls -la $HOME/snap/gramps/"
|
||||||
|
echo " 3. Try running Gramps at least once to create the plugin directory"
|
||||||
|
echo " 4. Manually create the directory: mkdir -p $PLUGIN_DEST_DIR"
|
||||||
|
echo ""
|
||||||
|
echo "If the issue persists, you may need to install the plugin manually."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -126,7 +158,11 @@ fi
|
|||||||
# Copy plugin files
|
# Copy plugin files
|
||||||
echo "Installing plugin files..."
|
echo "Installing plugin files..."
|
||||||
for file in "${PLUGIN_FILES[@]}"; do
|
for file in "${PLUGIN_FILES[@]}"; do
|
||||||
cp "$PLUGIN_SRC_DIR/$file" "$TARGET_DIR/"
|
if ! cp "$PLUGIN_SRC_DIR/$file" "$TARGET_DIR/"; then
|
||||||
|
echo -e "${RED}Error: Failed to copy $file${NC}"
|
||||||
|
echo "Check file permissions and disk space."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
echo -e "${GREEN}✓${NC} Installed: $file"
|
echo -e "${GREEN}✓${NC} Installed: $file"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|||||||
@ -5,12 +5,13 @@
|
|||||||
# This script removes the MyTimeline plugin files from the Gramps snap plugin directory
|
# This script removes the MyTimeline plugin files from the Gramps snap plugin directory
|
||||||
#
|
#
|
||||||
|
|
||||||
set -e # Exit on error
|
set -euo pipefail # Exit on error, undefined vars, pipe failures
|
||||||
|
|
||||||
# Colors for output
|
# Colors for output
|
||||||
RED='\033[0;31m'
|
RED='\033[0;31m'
|
||||||
GREEN='\033[0;32m'
|
GREEN='\033[0;32m'
|
||||||
YELLOW='\033[1;33m'
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
NC='\033[0m' # No Color
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
# Plugin files to remove
|
# Plugin files to remove
|
||||||
@ -60,6 +61,12 @@ TARGET_DIR=$(find_plugin_dir)
|
|||||||
if [ -z "$TARGET_DIR" ]; then
|
if [ -z "$TARGET_DIR" ]; then
|
||||||
echo -e "${YELLOW}Warning: Plugin directory not found.${NC}"
|
echo -e "${YELLOW}Warning: Plugin directory not found.${NC}"
|
||||||
echo "The plugin may already be uninstalled or Gramps may not be installed via snap."
|
echo "The plugin may already be uninstalled or Gramps may not be installed via snap."
|
||||||
|
echo ""
|
||||||
|
echo -e "${BLUE}Note:${NC} If you installed the plugin manually, you may need to remove it manually."
|
||||||
|
echo "Common locations to check:"
|
||||||
|
for dir in "${ALTERNATIVE_DIRS[@]}"; do
|
||||||
|
echo " - $dir"
|
||||||
|
done
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -88,7 +95,11 @@ echo ""
|
|||||||
echo "Removing plugin files..."
|
echo "Removing plugin files..."
|
||||||
for file in "${PLUGIN_FILES[@]}"; do
|
for file in "${PLUGIN_FILES[@]}"; do
|
||||||
if [ -f "$TARGET_DIR/$file" ]; then
|
if [ -f "$TARGET_DIR/$file" ]; then
|
||||||
rm "$TARGET_DIR/$file"
|
if ! rm "$TARGET_DIR/$file"; then
|
||||||
|
echo -e "${RED}Error: Failed to remove $file${NC}"
|
||||||
|
echo "Check file permissions."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
echo -e "${GREEN}✓${NC} Removed: $file"
|
echo -e "${GREEN}✓${NC} Removed: $file"
|
||||||
FILES_REMOVED=$((FILES_REMOVED + 1))
|
FILES_REMOVED=$((FILES_REMOVED + 1))
|
||||||
fi
|
fi
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user