@treeviz/gedcom-parser 1.0.1 → 1.0.3
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.
- package/README.md +176 -0
- package/bin/gedcom-parser.js +3 -0
- package/dist/classes/common.js +7 -7
- package/dist/classes/date.js +4 -4
- package/dist/classes/fam.js +3 -3
- package/dist/classes/fams.js +3 -3
- package/dist/classes/gedcom.d.ts +29 -0
- package/dist/classes/gedcom.d.ts.map +1 -1
- package/dist/classes/gedcom.js +180 -4
- package/dist/classes/index.js +18 -18
- package/dist/classes/indi.js +13 -13
- package/dist/classes/indis.d.ts.map +1 -1
- package/dist/classes/indis.js +42 -24
- package/dist/classes/list.js +6 -6
- package/dist/classes/name.js +2 -2
- package/dist/classes/note.js +2 -2
- package/dist/classes/obje.js +1 -1
- package/dist/classes/objes.js +1 -1
- package/dist/classes/repo.js +1 -1
- package/dist/classes/repos.js +1 -1
- package/dist/classes/sour.js +1 -1
- package/dist/classes/sours.js +1 -1
- package/dist/classes/subm.js +1 -1
- package/dist/classes/subms.js +1 -1
- package/dist/cli/commands/convert.d.ts +3 -0
- package/dist/cli/commands/convert.d.ts.map +1 -0
- package/dist/cli/commands/convert.js +83 -0
- package/dist/cli/commands/extract.d.ts +3 -0
- package/dist/cli/commands/extract.d.ts.map +1 -0
- package/dist/cli/commands/extract.js +85 -0
- package/dist/cli/commands/find.d.ts +3 -0
- package/dist/cli/commands/find.d.ts.map +1 -0
- package/dist/cli/commands/find.js +97 -0
- package/dist/cli/commands/info.d.ts +3 -0
- package/dist/cli/commands/info.d.ts.map +1 -0
- package/dist/cli/commands/info.js +80 -0
- package/dist/cli/commands/merge.d.ts +3 -0
- package/dist/cli/commands/merge.d.ts.map +1 -0
- package/dist/cli/commands/merge.js +93 -0
- package/dist/cli/commands/relatives.d.ts +3 -0
- package/dist/cli/commands/relatives.d.ts.map +1 -0
- package/dist/cli/commands/relatives.js +107 -0
- package/dist/cli/commands/show.d.ts +3 -0
- package/dist/cli/commands/show.d.ts.map +1 -0
- package/dist/cli/commands/show.js +176 -0
- package/dist/cli/commands/stats.d.ts +3 -0
- package/dist/cli/commands/stats.d.ts.map +1 -0
- package/dist/cli/commands/stats.js +59 -0
- package/dist/cli/commands/validate.d.ts +3 -0
- package/dist/cli/commands/validate.d.ts.map +1 -0
- package/dist/cli/commands/validate.js +148 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +40 -0
- package/dist/cli/utils/formatters.d.ts +69 -0
- package/dist/cli/utils/formatters.d.ts.map +1 -0
- package/dist/cli/utils/formatters.js +125 -0
- package/dist/cli/utils/helpers.d.ts +21 -0
- package/dist/cli/utils/helpers.d.ts.map +1 -0
- package/dist/cli/utils/helpers.js +58 -0
- package/dist/constants/filters.js +1 -1
- package/dist/constants/index.js +3 -3
- package/dist/constants/orders.js +2 -2
- package/dist/factories/cache-factory.js +1 -1
- package/dist/factories/date-locale-factory.js +1 -1
- package/dist/factories/i18n-factory.js +1 -1
- package/dist/factories/index.js +4 -4
- package/dist/factories/kinship-factory.js +2 -2
- package/dist/factories/place-parser-provider.js +1 -1
- package/dist/factories/place-translator-provider.js +1 -1
- package/dist/index.js +37 -37
- package/dist/kinship-translator/index.js +9 -9
- package/dist/kinship-translator/kinship-translator.de.js +3 -3
- package/dist/kinship-translator/kinship-translator.en.js +4 -4
- package/dist/kinship-translator/kinship-translator.es.js +4 -4
- package/dist/kinship-translator/kinship-translator.fr.js +4 -4
- package/dist/kinship-translator/kinship-translator.hu.js +4 -4
- package/dist/kinship-translator/kinship-translator.js +1 -1
- package/dist/kinship-translator/translators.js +5 -5
- package/dist/types/index.js +3 -3
- package/dist/utils/cache.js +2 -2
- package/dist/utils/common-creator.js +11 -11
- package/dist/utils/date-formatter.js +3 -3
- package/dist/utils/get-places.js +1 -1
- package/dist/utils/get-product-details.js +1 -1
- package/dist/utils/index.js +16 -16
- package/dist/utils/name-formatter.js +2 -2
- package/dist/utils/nested-group.js +3 -3
- package/dist/utils/parser.js +14 -14
- 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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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"}
|