@treeviz/gedcom-parser 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/README.md +174 -0
  2. package/bin/gedcom-parser.js +3 -0
  3. package/dist/classes/common.js +7 -7
  4. package/dist/classes/date.js +4 -4
  5. package/dist/classes/fam.js +3 -3
  6. package/dist/classes/fams.js +3 -3
  7. package/dist/classes/gedcom.d.ts +29 -0
  8. package/dist/classes/gedcom.d.ts.map +1 -1
  9. package/dist/classes/gedcom.js +180 -4
  10. package/dist/classes/index.js +18 -18
  11. package/dist/classes/indi.js +13 -13
  12. package/dist/classes/indis.d.ts.map +1 -1
  13. package/dist/classes/indis.js +42 -24
  14. package/dist/classes/list.js +6 -6
  15. package/dist/classes/name.js +2 -2
  16. package/dist/classes/note.js +2 -2
  17. package/dist/classes/obje.js +1 -1
  18. package/dist/classes/objes.js +1 -1
  19. package/dist/classes/repo.js +1 -1
  20. package/dist/classes/repos.js +1 -1
  21. package/dist/classes/sour.js +1 -1
  22. package/dist/classes/sours.js +1 -1
  23. package/dist/classes/subm.js +1 -1
  24. package/dist/classes/subms.js +1 -1
  25. package/dist/cli/commands/convert.d.ts +3 -0
  26. package/dist/cli/commands/convert.d.ts.map +1 -0
  27. package/dist/cli/commands/convert.js +83 -0
  28. package/dist/cli/commands/extract.d.ts +3 -0
  29. package/dist/cli/commands/extract.d.ts.map +1 -0
  30. package/dist/cli/commands/extract.js +85 -0
  31. package/dist/cli/commands/find.d.ts +3 -0
  32. package/dist/cli/commands/find.d.ts.map +1 -0
  33. package/dist/cli/commands/find.js +97 -0
  34. package/dist/cli/commands/info.d.ts +3 -0
  35. package/dist/cli/commands/info.d.ts.map +1 -0
  36. package/dist/cli/commands/info.js +80 -0
  37. package/dist/cli/commands/merge.d.ts +3 -0
  38. package/dist/cli/commands/merge.d.ts.map +1 -0
  39. package/dist/cli/commands/merge.js +93 -0
  40. package/dist/cli/commands/relatives.d.ts +3 -0
  41. package/dist/cli/commands/relatives.d.ts.map +1 -0
  42. package/dist/cli/commands/relatives.js +107 -0
  43. package/dist/cli/commands/show.d.ts +3 -0
  44. package/dist/cli/commands/show.d.ts.map +1 -0
  45. package/dist/cli/commands/show.js +176 -0
  46. package/dist/cli/commands/stats.d.ts +3 -0
  47. package/dist/cli/commands/stats.d.ts.map +1 -0
  48. package/dist/cli/commands/stats.js +59 -0
  49. package/dist/cli/commands/validate.d.ts +3 -0
  50. package/dist/cli/commands/validate.d.ts.map +1 -0
  51. package/dist/cli/commands/validate.js +148 -0
  52. package/dist/cli/index.d.ts +3 -0
  53. package/dist/cli/index.d.ts.map +1 -0
  54. package/dist/cli/index.js +40 -0
  55. package/dist/cli/utils/formatters.d.ts +69 -0
  56. package/dist/cli/utils/formatters.d.ts.map +1 -0
  57. package/dist/cli/utils/formatters.js +125 -0
  58. package/dist/cli/utils/helpers.d.ts +21 -0
  59. package/dist/cli/utils/helpers.d.ts.map +1 -0
  60. package/dist/cli/utils/helpers.js +58 -0
  61. package/dist/constants/filters.js +1 -1
  62. package/dist/constants/index.js +3 -3
  63. package/dist/constants/orders.js +2 -2
  64. package/dist/factories/cache-factory.js +1 -1
  65. package/dist/factories/date-locale-factory.js +1 -1
  66. package/dist/factories/i18n-factory.js +1 -1
  67. package/dist/factories/index.js +4 -4
  68. package/dist/factories/kinship-factory.js +2 -2
  69. package/dist/factories/place-parser-provider.js +1 -1
  70. package/dist/factories/place-translator-provider.js +1 -1
  71. package/dist/index.js +37 -37
  72. package/dist/kinship-translator/index.js +9 -9
  73. package/dist/kinship-translator/kinship-translator.de.js +3 -3
  74. package/dist/kinship-translator/kinship-translator.en.js +4 -4
  75. package/dist/kinship-translator/kinship-translator.es.js +4 -4
  76. package/dist/kinship-translator/kinship-translator.fr.js +4 -4
  77. package/dist/kinship-translator/kinship-translator.hu.js +4 -4
  78. package/dist/kinship-translator/kinship-translator.js +1 -1
  79. package/dist/kinship-translator/translators.js +5 -5
  80. package/dist/types/index.js +3 -3
  81. package/dist/utils/cache.js +2 -2
  82. package/dist/utils/common-creator.js +11 -11
  83. package/dist/utils/date-formatter.js +3 -3
  84. package/dist/utils/get-places.js +1 -1
  85. package/dist/utils/get-product-details.js +1 -1
  86. package/dist/utils/index.js +16 -16
  87. package/dist/utils/name-formatter.js +2 -2
  88. package/dist/utils/nested-group.js +3 -3
  89. package/dist/utils/parser.js +14 -14
  90. package/package.json +13 -4
@@ -0,0 +1,107 @@
1
+ import { writeFileSync } from 'fs';
2
+ import GedcomTree from '../../utils/parser.js';
3
+ import { formatHeader, formatSuccess, formatListItem, formatJson, formatId, formatName, formatError, } from '../utils/formatters.js';
4
+ import { readGedcomFile, handleError, cleanGedcomName, formatLifespan } from '../utils/helpers.js';
5
+ export function registerRelativesCommand(program) {
6
+ program
7
+ .command('relatives <file> <id>')
8
+ .description('Get ancestors and/or descendants of an individual')
9
+ .option('-a, --ancestors', 'Include ancestors')
10
+ .option('-d, --descendants', 'Include descendants')
11
+ .option('-t, --tree', 'Include both ancestors and descendants')
12
+ .option('--depth <n>', 'Limit depth (generations)', '999')
13
+ .option('-o, --output <file>', 'Save to new GEDCOM file')
14
+ .option('-j, --json', 'Output in JSON format')
15
+ .action((file, id, options) => {
16
+ try {
17
+ const content = readGedcomFile(file);
18
+ const { gedcom: tree } = GedcomTree.parse(content);
19
+ const individual = tree.indi(id);
20
+ if (!individual) {
21
+ console.error(formatError(`Individual ${id} not found`));
22
+ process.exit(1);
23
+ }
24
+ const maxDepth = parseInt(options.depth || '999', 10);
25
+ const relatives = new Set();
26
+ relatives.add(id);
27
+ // Include both if --tree is specified
28
+ const includeAncestors = options.ancestors || options.tree;
29
+ const includeDescendants = options.descendants || options.tree;
30
+ // Get ancestors
31
+ if (includeAncestors) {
32
+ const getAncestors = (indi, depth) => {
33
+ if (depth > maxDepth)
34
+ return;
35
+ const parents = indi.getParents();
36
+ parents?.forEach(parent => {
37
+ relatives.add(parent.id);
38
+ getAncestors(parent, depth + 1);
39
+ });
40
+ };
41
+ getAncestors(individual, 1);
42
+ }
43
+ // Get descendants
44
+ if (includeDescendants) {
45
+ const getDescendants = (indi, depth) => {
46
+ if (depth > maxDepth)
47
+ return;
48
+ const children = indi.getChildren();
49
+ children?.forEach(child => {
50
+ relatives.add(child.id);
51
+ getDescendants(child, depth + 1);
52
+ });
53
+ };
54
+ getDescendants(individual, 1);
55
+ }
56
+ // Get all individuals
57
+ const allRelatives = Array.from(relatives)
58
+ .map(relId => tree.indi(relId))
59
+ .filter(indi => indi !== null);
60
+ if (options.output) {
61
+ // Create a new GEDCOM with only these individuals
62
+ const newContent = createSubsetGedcom(tree, allRelatives);
63
+ writeFileSync(options.output, newContent, 'utf-8');
64
+ console.log(formatSuccess(`Saved ${allRelatives.length} individuals to ${options.output}`));
65
+ }
66
+ else if (options.json) {
67
+ const jsonData = allRelatives.map(indi => ({
68
+ id: indi.id,
69
+ name: cleanGedcomName(indi.NAME?.toValue()),
70
+ birthDate: indi.BIRT?.DATE?.toValue() || null,
71
+ deathDate: indi.DEAT?.DATE?.toValue() || null,
72
+ }));
73
+ console.log(formatJson({ count: jsonData.length, individuals: jsonData }));
74
+ }
75
+ else {
76
+ console.log(formatHeader(`Found ${allRelatives.length} relative(s)\n`));
77
+ allRelatives.forEach(indi => {
78
+ const name = cleanGedcomName(indi.NAME?.toValue());
79
+ const lifespan = formatLifespan(indi.BIRT?.DATE?.toValue(), indi.DEAT?.DATE?.toValue());
80
+ console.log(formatListItem(`${formatId(indi.id)} ${formatName(name)} ${lifespan}`));
81
+ });
82
+ }
83
+ }
84
+ catch (error) {
85
+ handleError(error, 'Failed to extract relatives');
86
+ }
87
+ });
88
+ }
89
+ function createSubsetGedcom(tree, individuals) {
90
+ const lines = [];
91
+ // Add header
92
+ lines.push('0 HEAD');
93
+ lines.push('1 SOUR gedcom-parser CLI');
94
+ lines.push('1 GEDC');
95
+ lines.push('2 VERS 5.5.1');
96
+ lines.push('1 CHAR UTF-8');
97
+ // Add individuals
98
+ individuals.forEach(indi => {
99
+ const raw = indi.raw();
100
+ if (raw) {
101
+ lines.push(...raw.split('\n').filter(line => line.trim()));
102
+ }
103
+ });
104
+ // Add trailer
105
+ lines.push('0 TRLR');
106
+ return lines.join('\n');
107
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerShowCommand(program: Command): void;
3
+ //# sourceMappingURL=show.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"show.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/show.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAuBpC,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAiN1D"}
@@ -0,0 +1,176 @@
1
+ import GedcomTree from '../../utils/parser.js';
2
+ import { formatHeader, formatLabel, formatValue, formatListItem, formatJson, formatId, formatName, formatDate, formatPlace, formatError, } from '../utils/formatters.js';
3
+ import { readGedcomFile, handleError, cleanGedcomName, formatLifespan } from '../utils/helpers.js';
4
+ export function registerShowCommand(program) {
5
+ program
6
+ .command('show <file> <id>')
7
+ .description('Display detailed information about an individual')
8
+ .option('-j, --json', 'Output in JSON format')
9
+ .option('-f, --format <format>', 'Output format: text, json, markdown', 'text')
10
+ .option('--include-events', 'Include all life events')
11
+ .option('--include-sources', 'Include sources')
12
+ .action((file, id, options) => {
13
+ try {
14
+ const content = readGedcomFile(file);
15
+ const { gedcom: tree } = GedcomTree.parse(content);
16
+ const individual = tree.indi(id);
17
+ if (!individual) {
18
+ console.error(formatError(`Individual ${id} not found`));
19
+ process.exit(1);
20
+ }
21
+ const name = cleanGedcomName(individual.NAME?.toValue());
22
+ const birthDate = individual.BIRT?.DATE?.toValue();
23
+ const birthPlace = individual.BIRT?.PLAC?.value;
24
+ const deathDate = individual.DEAT?.DATE?.toValue();
25
+ const deathPlace = individual.DEAT?.PLAC?.value;
26
+ const sex = individual.SEX?.value;
27
+ // Get parents
28
+ const parents = individual.getParents();
29
+ const father = parents?.find(p => p.SEX?.value === 'M');
30
+ const mother = parents?.find(p => p.SEX?.value === 'F');
31
+ // Get spouses
32
+ const spouses = individual.getSpouses();
33
+ // Get children
34
+ const children = individual.getChildren();
35
+ if (options.json || options.format === 'json') {
36
+ const jsonData = {
37
+ id: individual.id,
38
+ name,
39
+ sex,
40
+ birth: {
41
+ date: birthDate || null,
42
+ place: birthPlace || null,
43
+ },
44
+ death: {
45
+ date: deathDate || null,
46
+ place: deathPlace || null,
47
+ },
48
+ parents: {
49
+ father: father ? {
50
+ id: father.id,
51
+ name: cleanGedcomName(father.NAME?.toValue()),
52
+ } : null,
53
+ mother: mother ? {
54
+ id: mother.id,
55
+ name: cleanGedcomName(mother.NAME?.toValue()),
56
+ } : null,
57
+ },
58
+ spouses: spouses.map(spouse => ({
59
+ id: spouse.id,
60
+ name: cleanGedcomName(spouse.NAME?.toValue()),
61
+ birthDate: spouse.BIRT?.DATE?.toValue() || null,
62
+ deathDate: spouse.DEAT?.DATE?.toValue() || null,
63
+ })),
64
+ children: children.map(child => ({
65
+ id: child.id,
66
+ name: cleanGedcomName(child.NAME?.toValue()),
67
+ birthDate: child.BIRT?.DATE?.toValue() || null,
68
+ deathDate: child.DEAT?.DATE?.toValue() || null,
69
+ })),
70
+ };
71
+ console.log(formatJson(jsonData));
72
+ }
73
+ else if (options.format === 'markdown') {
74
+ console.log(`# ${formatId(individual.id)} ${name}\n`);
75
+ if (birthDate || birthPlace) {
76
+ console.log(`**Born:** ${birthDate || '?'}`);
77
+ if (birthPlace)
78
+ console.log(` in ${birthPlace}`);
79
+ console.log();
80
+ }
81
+ if (deathDate || deathPlace) {
82
+ console.log(`**Died:** ${deathDate || '?'}`);
83
+ if (deathPlace)
84
+ console.log(` in ${deathPlace}`);
85
+ console.log();
86
+ }
87
+ if (father || mother) {
88
+ console.log('## Parents\n');
89
+ if (father) {
90
+ const fatherName = cleanGedcomName(father.NAME?.toValue());
91
+ const fatherLifespan = formatLifespan(father.BIRT?.DATE?.toValue(), father.DEAT?.DATE?.toValue());
92
+ console.log(`- **Father:** ${father.id} ${fatherName} ${fatherLifespan}`);
93
+ }
94
+ if (mother) {
95
+ const motherName = cleanGedcomName(mother.NAME?.toValue());
96
+ const motherLifespan = formatLifespan(mother.BIRT?.DATE?.toValue(), mother.DEAT?.DATE?.toValue());
97
+ console.log(`- **Mother:** ${mother.id} ${motherName} ${motherLifespan}`);
98
+ }
99
+ console.log();
100
+ }
101
+ if (spouses.length > 0) {
102
+ console.log('## Spouses\n');
103
+ spouses.forEach(spouse => {
104
+ const spouseName = cleanGedcomName(spouse.NAME?.toValue());
105
+ const spouseLifespan = formatLifespan(spouse.BIRT?.DATE?.toValue(), spouse.DEAT?.DATE?.toValue());
106
+ console.log(`- ${spouse.id} ${spouseName} ${spouseLifespan}`);
107
+ });
108
+ console.log();
109
+ }
110
+ if (children.length > 0) {
111
+ console.log('## Children\n');
112
+ children.forEach(child => {
113
+ const childName = cleanGedcomName(child.NAME?.toValue());
114
+ const childLifespan = formatLifespan(child.BIRT?.DATE?.toValue(), child.DEAT?.DATE?.toValue());
115
+ console.log(`- ${child.id} ${childName} ${childLifespan}`);
116
+ });
117
+ console.log();
118
+ }
119
+ }
120
+ else {
121
+ // Text format (default)
122
+ console.log(formatHeader(`${formatId(individual.id)} ${formatName(name)}\n`));
123
+ if (sex) {
124
+ console.log(`${formatLabel('Sex')} ${formatValue(sex === 'M' ? 'Male' : sex === 'F' ? 'Female' : sex)}`);
125
+ }
126
+ if (birthDate || birthPlace) {
127
+ console.log(`${formatLabel('Born')} ${formatDate(birthDate)}`);
128
+ if (birthPlace) {
129
+ console.log(`${formatLabel('Birth Place')} ${formatPlace(birthPlace)}`);
130
+ }
131
+ }
132
+ if (deathDate || deathPlace) {
133
+ console.log(`${formatLabel('Died')} ${formatDate(deathDate)}`);
134
+ if (deathPlace) {
135
+ console.log(`${formatLabel('Death Place')} ${formatPlace(deathPlace)}`);
136
+ }
137
+ }
138
+ if (father || mother) {
139
+ console.log();
140
+ console.log(formatHeader('Parents'));
141
+ if (father) {
142
+ const fatherName = cleanGedcomName(father.NAME?.toValue());
143
+ const fatherLifespan = formatLifespan(father.BIRT?.DATE?.toValue(), father.DEAT?.DATE?.toValue());
144
+ console.log(formatListItem(`${formatLabel('Father')} ${formatId(father.id)} ${formatName(fatherName)} ${fatherLifespan}`));
145
+ }
146
+ if (mother) {
147
+ const motherName = cleanGedcomName(mother.NAME?.toValue());
148
+ const motherLifespan = formatLifespan(mother.BIRT?.DATE?.toValue(), mother.DEAT?.DATE?.toValue());
149
+ console.log(formatListItem(`${formatLabel('Mother')} ${formatId(mother.id)} ${formatName(motherName)} ${motherLifespan}`));
150
+ }
151
+ }
152
+ if (spouses.length > 0) {
153
+ console.log();
154
+ console.log(formatHeader('Spouses'));
155
+ spouses.forEach(spouse => {
156
+ const spouseName = cleanGedcomName(spouse.NAME?.toValue());
157
+ const spouseLifespan = formatLifespan(spouse.BIRT?.DATE?.toValue(), spouse.DEAT?.DATE?.toValue());
158
+ console.log(formatListItem(`${formatId(spouse.id)} ${formatName(spouseName)} ${spouseLifespan}`));
159
+ });
160
+ }
161
+ if (children.length > 0) {
162
+ console.log();
163
+ console.log(formatHeader('Children'));
164
+ children.forEach(child => {
165
+ const childName = cleanGedcomName(child.NAME?.toValue());
166
+ const childLifespan = formatLifespan(child.BIRT?.DATE?.toValue(), child.DEAT?.DATE?.toValue());
167
+ console.log(formatListItem(`${formatId(child.id)} ${formatName(childName)} ${childLifespan}`));
168
+ });
169
+ }
170
+ }
171
+ }
172
+ catch (error) {
173
+ handleError(error, 'Failed to show individual details');
174
+ }
175
+ });
176
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerStatsCommand(program: Command): void;
3
+ //# sourceMappingURL=stats.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stats.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/stats.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAcpC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA6D3D"}
@@ -0,0 +1,59 @@
1
+ import { formatHeader, formatLabel, formatCount, formatJson, } from '../utils/formatters.js';
2
+ import { readGedcomFile, handleError } from '../utils/helpers.js';
3
+ import GedcomTree from '../../utils/parser.js';
4
+ export function registerStatsCommand(program) {
5
+ program
6
+ .command('stats <file>')
7
+ .description('Generate statistics about a GEDCOM file')
8
+ .option('-j, --json', 'Output in JSON format')
9
+ .action((file, options) => {
10
+ try {
11
+ const content = readGedcomFile(file);
12
+ const { gedcom: tree } = GedcomTree.parse(content);
13
+ // Use the stats() method from the GedCom class
14
+ const stats = tree.stats();
15
+ if (options.json) {
16
+ console.log(formatJson(stats));
17
+ }
18
+ else {
19
+ console.log(formatHeader('GEDCOM File Statistics\n'));
20
+ console.log(formatLabel('Total Individuals'));
21
+ console.log(` ${formatCount(stats.totalIndividuals)}`);
22
+ console.log(formatLabel('Total Families'));
23
+ console.log(` ${formatCount(stats.totalFamilies)}`);
24
+ console.log();
25
+ console.log(formatLabel('By Gender'));
26
+ console.log(` Males: ${formatCount(stats.byGender.males)}`);
27
+ console.log(` Females: ${formatCount(stats.byGender.females)}`);
28
+ console.log(` Unknown: ${formatCount(stats.byGender.unknown)}`);
29
+ console.log();
30
+ if (stats.dateRange.earliest && stats.dateRange.latest) {
31
+ console.log(formatLabel('Date Range'));
32
+ console.log(` ${stats.dateRange.earliest} - ${stats.dateRange.latest}`);
33
+ console.log();
34
+ }
35
+ if (stats.averageLifespan) {
36
+ console.log(formatLabel('Average Lifespan'));
37
+ console.log(` ${stats.averageLifespan.toFixed(1)} years`);
38
+ console.log();
39
+ }
40
+ if (stats.topSurnames.length > 0) {
41
+ console.log(formatLabel('Most Common Surnames'));
42
+ stats.topSurnames.forEach(({ surname, count }) => {
43
+ console.log(` ${surname}: ${formatCount(count)}`);
44
+ });
45
+ console.log();
46
+ }
47
+ if (stats.topBirthPlaces.length > 0) {
48
+ console.log(formatLabel('Most Common Birth Places'));
49
+ stats.topBirthPlaces.slice(0, 5).forEach(({ place, count }) => {
50
+ console.log(` ${place}: ${formatCount(count)}`);
51
+ });
52
+ }
53
+ }
54
+ }
55
+ catch (error) {
56
+ handleError(error, 'Failed to generate statistics');
57
+ }
58
+ });
59
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerValidateCommand(program: Command): void;
3
+ //# sourceMappingURL=validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA0BpC,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAqK9D"}
@@ -0,0 +1,148 @@
1
+ import GedcomTree from '../../utils/parser.js';
2
+ import { formatHeader, formatSuccess, formatWarning, formatError, formatListItem, formatJson, formatCount, } from '../utils/formatters.js';
3
+ import { readGedcomFile, handleError } from '../utils/helpers.js';
4
+ export function registerValidateCommand(program) {
5
+ program
6
+ .command('validate <file>')
7
+ .description('Validate a GEDCOM file')
8
+ .option('-j, --json', 'Output in JSON format')
9
+ .option('-s, --strict', 'Enable strict validation')
10
+ .option('--fix', 'Attempt to fix common issues (not implemented)')
11
+ .action((file, options) => {
12
+ try {
13
+ const content = readGedcomFile(file);
14
+ const { gedcom: tree } = GedcomTree.parse(content);
15
+ const errors = [];
16
+ const warnings = [];
17
+ // Get GEDCOM version
18
+ const header = tree.HEAD;
19
+ const version = header?.GEDC?.VERS?.value || 'Unknown';
20
+ // Basic validation checks
21
+ const individuals = tree.indis();
22
+ const families = tree.fams();
23
+ // Check for individuals without names
24
+ let missingNames = 0;
25
+ individuals.forEach(indi => {
26
+ if (!indi.NAME?.toValue()) {
27
+ missingNames++;
28
+ warnings.push(`Individual ${indi.id} is missing a name`);
29
+ }
30
+ });
31
+ // Check for individuals without birth dates
32
+ let missingBirthDates = 0;
33
+ individuals.forEach(indi => {
34
+ if (!indi.BIRT?.DATE?.toValue()) {
35
+ missingBirthDates++;
36
+ }
37
+ });
38
+ // Check for individuals without death dates (but marked as deceased)
39
+ let missingDeathDates = 0;
40
+ individuals.forEach(indi => {
41
+ if (indi.DEAT && !indi.DEAT.DATE?.toValue()) {
42
+ missingDeathDates++;
43
+ }
44
+ });
45
+ // Check for duplicate IDs
46
+ const seenIds = new Set();
47
+ const duplicateIds = [];
48
+ const checkDuplicates = (item) => {
49
+ if (seenIds.has(item.id)) {
50
+ duplicateIds.push(item.id);
51
+ errors.push(`Duplicate ID found: ${item.id}`);
52
+ }
53
+ seenIds.add(item.id);
54
+ };
55
+ individuals.forEach(checkDuplicates);
56
+ families.forEach(checkDuplicates);
57
+ // Check for missing family members
58
+ families.forEach(fam => {
59
+ const husb = fam.HUSB?.value;
60
+ const wife = fam.WIFE?.value;
61
+ if (!husb && !wife) {
62
+ warnings.push(`Family ${fam.id} has no husband or wife`);
63
+ }
64
+ if (husb && !tree.indi(husb)) {
65
+ errors.push(`Family ${fam.id} references non-existent husband ${husb}`);
66
+ }
67
+ if (wife && !tree.indi(wife)) {
68
+ errors.push(`Family ${fam.id} references non-existent wife ${wife}`);
69
+ }
70
+ });
71
+ // Check for invalid date formats (basic check)
72
+ individuals.forEach(indi => {
73
+ const birthDate = indi.BIRT?.DATE?.toValue();
74
+ const deathDate = indi.DEAT?.DATE?.toValue();
75
+ if (birthDate && birthDate.includes('INVALID')) {
76
+ errors.push(`Invalid birth date format for ${indi.id}`);
77
+ }
78
+ if (deathDate && deathDate.includes('INVALID')) {
79
+ errors.push(`Invalid death date format for ${indi.id}`);
80
+ }
81
+ });
82
+ const result = {
83
+ valid: errors.length === 0,
84
+ version,
85
+ errors,
86
+ warnings,
87
+ };
88
+ if (options.json) {
89
+ console.log(formatJson(result));
90
+ }
91
+ else {
92
+ if (result.valid) {
93
+ console.log(formatSuccess(`Valid GEDCOM ${version} file`));
94
+ }
95
+ else {
96
+ console.log(formatError(`Invalid GEDCOM file - ${errors.length} error(s) found`));
97
+ }
98
+ console.log();
99
+ console.log(formatHeader('Validation Summary'));
100
+ console.log(`${formatError('Errors:')} ${formatCount(errors.length)}`);
101
+ console.log(`${formatWarning('Warnings:')} ${formatCount(warnings.length)}`);
102
+ if (errors.length > 0) {
103
+ console.log();
104
+ console.log(formatHeader('Errors'));
105
+ errors.slice(0, 10).forEach(error => {
106
+ console.log(formatListItem(formatError(error)));
107
+ });
108
+ if (errors.length > 10) {
109
+ console.log(formatListItem(`... and ${errors.length - 10} more errors`));
110
+ }
111
+ }
112
+ if (warnings.length > 0) {
113
+ console.log();
114
+ console.log(formatHeader('Warnings'));
115
+ // Summarize warnings
116
+ if (missingBirthDates > 0) {
117
+ console.log(formatListItem(formatWarning(`Missing birth dates: ${missingBirthDates} individuals`)));
118
+ }
119
+ if (missingDeathDates > 0) {
120
+ console.log(formatListItem(formatWarning(`Missing death dates: ${missingDeathDates} individuals`)));
121
+ }
122
+ if (duplicateIds.length > 0) {
123
+ console.log(formatListItem(formatWarning(`Duplicate IDs: ${duplicateIds.length}`)));
124
+ }
125
+ // Show first few specific warnings
126
+ const otherWarnings = warnings.filter(w => !w.includes('birth') && !w.includes('death'));
127
+ otherWarnings.slice(0, 5).forEach(warning => {
128
+ console.log(formatListItem(formatWarning(warning)));
129
+ });
130
+ if (otherWarnings.length > 5) {
131
+ console.log(formatListItem(`... and ${otherWarnings.length - 5} more warnings`));
132
+ }
133
+ }
134
+ if (options.fix) {
135
+ console.log();
136
+ console.log(formatWarning('Fix option is not yet implemented'));
137
+ }
138
+ }
139
+ // Exit with error code if validation failed
140
+ if (!result.valid) {
141
+ process.exit(1);
142
+ }
143
+ }
144
+ catch (error) {
145
+ handleError(error, 'Failed to validate GEDCOM file');
146
+ }
147
+ });
148
+ }
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":""}
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { readFileSync } from 'fs';
4
+ import { dirname, join } from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ import { registerInfoCommand } from './commands/info.js';
7
+ import { registerFindCommand } from './commands/find.js';
8
+ import { registerShowCommand } from './commands/show.js';
9
+ import { registerValidateCommand } from './commands/validate.js';
10
+ import { registerRelativesCommand } from './commands/relatives.js';
11
+ import { registerExtractCommand } from './commands/extract.js';
12
+ import { registerStatsCommand } from './commands/stats.js';
13
+ import { registerMergeCommand } from './commands/merge.js';
14
+ import { registerConvertCommand } from './commands/convert.js';
15
+ // Get package version
16
+ const __filename = fileURLToPath(import.meta.url);
17
+ const __dirname = dirname(__filename);
18
+ const packageJsonPath = join(__dirname, '../../package.json');
19
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
20
+ const program = new Command();
21
+ program
22
+ .name('gedcom-parser')
23
+ .description('CLI tool for parsing and manipulating GEDCOM files')
24
+ .version(packageJson.version);
25
+ // Register all commands
26
+ registerInfoCommand(program);
27
+ registerFindCommand(program);
28
+ registerShowCommand(program);
29
+ registerValidateCommand(program);
30
+ registerRelativesCommand(program);
31
+ registerExtractCommand(program);
32
+ registerStatsCommand(program);
33
+ registerMergeCommand(program);
34
+ registerConvertCommand(program);
35
+ // Parse arguments
36
+ program.parse(process.argv);
37
+ // Show help if no command provided
38
+ if (!process.argv.slice(2).length) {
39
+ program.outputHelp();
40
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Format a success message
3
+ */
4
+ export declare function formatSuccess(message: string): string;
5
+ /**
6
+ * Format a warning message
7
+ */
8
+ export declare function formatWarning(message: string): string;
9
+ /**
10
+ * Format an error message
11
+ */
12
+ export declare function formatError(message: string): string;
13
+ /**
14
+ * Format an info message
15
+ */
16
+ export declare function formatInfo(message: string): string;
17
+ /**
18
+ * Format a header
19
+ */
20
+ export declare function formatHeader(text: string): string;
21
+ /**
22
+ * Format a field label
23
+ */
24
+ export declare function formatLabel(label: string): string;
25
+ /**
26
+ * Format a value
27
+ */
28
+ export declare function formatValue(value: string | number | null | undefined): string;
29
+ /**
30
+ * Format an ID
31
+ */
32
+ export declare function formatId(id: string): string;
33
+ /**
34
+ * Format a count
35
+ */
36
+ export declare function formatCount(count: number): string;
37
+ /**
38
+ * Format a date
39
+ */
40
+ export declare function formatDate(date: string | null | undefined): string;
41
+ /**
42
+ * Format a place
43
+ */
44
+ export declare function formatPlace(place: string | null | undefined): string;
45
+ /**
46
+ * Format a name
47
+ */
48
+ export declare function formatName(name: string | null | undefined): string;
49
+ /**
50
+ * Format a list item
51
+ */
52
+ export declare function formatListItem(text: string, indent?: number): string;
53
+ /**
54
+ * Format a table row
55
+ */
56
+ export declare function formatTableRow(columns: string[], widths: number[]): string;
57
+ /**
58
+ * Format a table separator
59
+ */
60
+ export declare function formatTableSeparator(widths: number[]): string;
61
+ /**
62
+ * Pretty print JSON
63
+ */
64
+ export declare function formatJson(data: unknown): string;
65
+ /**
66
+ * Format a progress indicator
67
+ */
68
+ export declare function formatProgress(current: number, total: number): string;
69
+ //# sourceMappingURL=formatters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatters.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/formatters.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAErD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAErD;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAElD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAK7E;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAE3C;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAKlE;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAKpE;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAOlE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,MAAU,GAAG,MAAM,CAGvE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAI1E;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAI7D;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAEhD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAKrE"}