@treeviz/gedcom-parser 1.0.22 → 2.0.0
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 +45 -0
- package/dist/classes/index.d.ts +1 -1
- package/dist/classes/index.js +427 -58
- package/dist/cli/index.js +1615 -384
- package/dist/constants/index.d.ts +1 -1
- package/dist/constants/index.js +426 -57
- package/dist/factories/index.d.ts +2 -2
- package/dist/factories/index.js +426 -57
- package/dist/{index-BPEVN_DY.d.ts → index-CzYZg44D.d.ts} +32 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +464 -62
- package/dist/interfaces/index.d.ts +1 -1
- package/dist/kinship-translator/index.d.ts +2 -2
- package/dist/kinship-translator/index.js +426 -57
- package/dist/place-parser-CJ3EbFmb.d.ts +40 -0
- package/dist/{place-translator-CRiaOO9v.d.ts → place-translator-Ci5rEY6p.d.ts} +4 -2
- package/dist/structures/index.d.ts +2 -2
- package/dist/types/index.d.ts +1 -1
- package/dist/utils/index.d.ts +3 -3
- package/dist/utils/index.js +462 -60
- package/package.json +1 -1
- package/dist/place-parser-CM0TJFj8.d.ts +0 -37
package/dist/cli/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { Command } from 'commander';
|
|
|
6
6
|
import { debounce, set, get, uniqBy, unset, difference, intersectionBy, differenceBy } from 'lodash-es';
|
|
7
7
|
import { format, isValid, parse } from 'date-fns';
|
|
8
8
|
import chalk from 'chalk';
|
|
9
|
+
import * as readline from 'readline';
|
|
9
10
|
|
|
10
11
|
var __defProp = Object.defineProperty;
|
|
11
12
|
var __export = (target, all) => {
|
|
@@ -786,11 +787,30 @@ var Common = class _Common {
|
|
|
786
787
|
const sour = get(head, "SOUR.value");
|
|
787
788
|
return !!sour?.toLowerCase()?.startsWith("myheritage");
|
|
788
789
|
}
|
|
790
|
+
/**
|
|
791
|
+
* Get the source type as a string (for prefixing tree IDs and names)
|
|
792
|
+
* Returns the detected source type or undefined if unknown
|
|
793
|
+
*/
|
|
794
|
+
getSourceType() {
|
|
795
|
+
if (this.isAncestry()) return "Ancestry";
|
|
796
|
+
if (this.isMyHeritage()) return "MyHeritage";
|
|
797
|
+
if (this.isFamilySearch()) return "FamilySearch";
|
|
798
|
+
if (this.isGNO2GED()) return "GNO2GED";
|
|
799
|
+
if (this.isGenoPro()) return "GenoPro";
|
|
800
|
+
if (this.isAhnenblatt()) return "Ahnenblatt";
|
|
801
|
+
if (this.isGeni()) return "Geni";
|
|
802
|
+
return void 0;
|
|
803
|
+
}
|
|
789
804
|
isFamilySearch() {
|
|
790
805
|
const head = get(this, "HEAD") || get(this.getGedcom(), "HEAD");
|
|
791
806
|
const sourName = get(head, "SOUR.NAME.value");
|
|
792
807
|
return sourName === "FamilySearch API";
|
|
793
808
|
}
|
|
809
|
+
isGNO2GED() {
|
|
810
|
+
const head = get(this, "HEAD") || get(this.getGedcom(), "HEAD");
|
|
811
|
+
const sour = get(head, "SOUR.value");
|
|
812
|
+
return sour === "GNO2GED";
|
|
813
|
+
}
|
|
794
814
|
getAncestryTreeId() {
|
|
795
815
|
const path = "HEAD.SOUR._TREE.RIN.value";
|
|
796
816
|
return get(this, path) || get(this.getGedcom(), path);
|
|
@@ -801,11 +821,34 @@ var Common = class _Common {
|
|
|
801
821
|
}
|
|
802
822
|
getTreeId() {
|
|
803
823
|
if (this?.isAncestry()) {
|
|
804
|
-
|
|
824
|
+
const id = this.getAncestryTreeId();
|
|
825
|
+
if (id !== void 0) return id;
|
|
805
826
|
}
|
|
806
827
|
if (this?.isMyHeritage()) {
|
|
807
|
-
|
|
828
|
+
const id = this.getMyHeritageTreeId();
|
|
829
|
+
if (id !== void 0) return id;
|
|
830
|
+
}
|
|
831
|
+
if (this?.isFamilySearch()) {
|
|
832
|
+
const id = this.getFamilySearchTreeId();
|
|
833
|
+
if (id !== void 0) return id;
|
|
834
|
+
}
|
|
835
|
+
if (this?.isGNO2GED()) {
|
|
836
|
+
const id = this.getGNO2GEDTreeId();
|
|
837
|
+
if (id !== void 0) return id;
|
|
838
|
+
}
|
|
839
|
+
if (this?.isAhnenblatt()) {
|
|
840
|
+
const id = this.getAhnenblattTreeId();
|
|
841
|
+
if (id !== void 0) return id;
|
|
842
|
+
}
|
|
843
|
+
if (this?.isGeni()) {
|
|
844
|
+
const id = this.getGeniTreeId();
|
|
845
|
+
if (id !== void 0) return id;
|
|
808
846
|
}
|
|
847
|
+
if (this?.isGenoPro()) {
|
|
848
|
+
const id = this.getGenoProTreeId();
|
|
849
|
+
if (id !== void 0) return id;
|
|
850
|
+
}
|
|
851
|
+
return this.getUniversalTreeId();
|
|
809
852
|
}
|
|
810
853
|
getAncestryTreeName() {
|
|
811
854
|
const path = "HEAD.SOUR._TREE.value";
|
|
@@ -818,13 +861,146 @@ var Common = class _Common {
|
|
|
818
861
|
/Exported by MyHeritage.com from (?<tree>.+) in.+$/
|
|
819
862
|
)?.groups?.tree;
|
|
820
863
|
}
|
|
864
|
+
getFamilySearchTreeId() {
|
|
865
|
+
const rin = get(this, "HEAD.SOUR._TREE.RIN.value") || get(this.getGedcom(), "HEAD.SOUR._TREE.RIN.value");
|
|
866
|
+
if (rin) {
|
|
867
|
+
return rin;
|
|
868
|
+
}
|
|
869
|
+
return "familysearch";
|
|
870
|
+
}
|
|
871
|
+
getFamilySearchTreeName() {
|
|
872
|
+
const treeName = get(this, "HEAD.SOUR._TREE.value") || get(this.getGedcom(), "HEAD.SOUR._TREE.value");
|
|
873
|
+
if (treeName) {
|
|
874
|
+
return treeName;
|
|
875
|
+
}
|
|
876
|
+
const fileName = get(this, "HEAD.FILE.value") || get(this.getGedcom(), "HEAD.FILE.value");
|
|
877
|
+
return fileName || "FamilySearch Import";
|
|
878
|
+
}
|
|
879
|
+
getAhnenblattTreeId() {
|
|
880
|
+
const fileName = get(this, "HEAD.FILE.value") || get(this.getGedcom(), "HEAD.FILE.value");
|
|
881
|
+
if (fileName) {
|
|
882
|
+
const idMatch = fileName.match(/_(\d+)/);
|
|
883
|
+
if (idMatch) {
|
|
884
|
+
return idMatch[1];
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
return void 0;
|
|
888
|
+
}
|
|
889
|
+
getAhnenblattTreeName() {
|
|
890
|
+
const fileName = get(this, "HEAD.FILE.value") || get(this.getGedcom(), "HEAD.FILE.value");
|
|
891
|
+
return fileName?.replace(/\.ged$/i, "").replace(/_/g, " ");
|
|
892
|
+
}
|
|
893
|
+
getGeniTreeId() {
|
|
894
|
+
const fileName = get(this, "HEAD.FILE.value") || get(this.getGedcom(), "HEAD.FILE.value");
|
|
895
|
+
if (fileName) {
|
|
896
|
+
const idMatch = fileName.match(/_(\d+)/);
|
|
897
|
+
if (idMatch) {
|
|
898
|
+
return idMatch[1];
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
return void 0;
|
|
902
|
+
}
|
|
903
|
+
getGeniTreeName() {
|
|
904
|
+
const fileName = get(this, "HEAD.FILE.value") || get(this.getGedcom(), "HEAD.FILE.value");
|
|
905
|
+
return fileName?.replace(/\.ged$/i, "").replace(/_/g, " ");
|
|
906
|
+
}
|
|
907
|
+
getGenoProTreeId() {
|
|
908
|
+
const fileName = get(this, "HEAD.FILE.value") || get(this.getGedcom(), "HEAD.FILE.value");
|
|
909
|
+
if (fileName) {
|
|
910
|
+
const idMatch = fileName.match(/_(\d+)/);
|
|
911
|
+
if (idMatch) {
|
|
912
|
+
return idMatch[1];
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
return void 0;
|
|
916
|
+
}
|
|
917
|
+
getGenoProTreeName() {
|
|
918
|
+
const fileName = get(this, "HEAD.FILE.value") || get(this.getGedcom(), "HEAD.FILE.value");
|
|
919
|
+
return fileName?.replace(/\.ged$/i, "").replace(/_/g, " ");
|
|
920
|
+
}
|
|
921
|
+
getGNO2GEDTreeId() {
|
|
922
|
+
const rin = get(this, "HEAD._TREE.RIN.value") || get(this.getGedcom(), "HEAD._TREE.RIN.value");
|
|
923
|
+
if (rin) {
|
|
924
|
+
return rin;
|
|
925
|
+
}
|
|
926
|
+
return `gno_${this._gedcom?.refcount || "unknown"}`;
|
|
927
|
+
}
|
|
928
|
+
getGNO2GEDTreeName() {
|
|
929
|
+
const treeName = get(this, "HEAD._TREE.value") || get(this.getGedcom(), "HEAD._TREE.value");
|
|
930
|
+
if (treeName) {
|
|
931
|
+
return treeName;
|
|
932
|
+
}
|
|
933
|
+
const fileName = get(this, "HEAD.FILE.value") || get(this.getGedcom(), "HEAD.FILE.value");
|
|
934
|
+
return fileName || "GNO2GED Export";
|
|
935
|
+
}
|
|
936
|
+
/**
|
|
937
|
+
* Universal tree ID getter for unknown/unrecognized GEDCOM sources
|
|
938
|
+
* Tries to extract an ID from various common locations
|
|
939
|
+
*/
|
|
940
|
+
getUniversalTreeId() {
|
|
941
|
+
const sourceType = this.getSourceType();
|
|
942
|
+
const prefix = sourceType ? sourceType.toLowerCase() : "tree";
|
|
943
|
+
const fileName = get(this, "HEAD.FILE.value") || get(this.getGedcom(), "HEAD.FILE.value");
|
|
944
|
+
if (fileName) {
|
|
945
|
+
const idMatch = fileName.match(/_(\d+)/);
|
|
946
|
+
if (idMatch) {
|
|
947
|
+
return `${prefix}_${idMatch[1]}`;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
return `${prefix}_${this._gedcom?.refcount || "unknown"}`;
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* Universal tree name getter for unknown/unrecognized GEDCOM sources
|
|
954
|
+
* Tries to extract a name from various common locations
|
|
955
|
+
*/
|
|
956
|
+
getUniversalTreeName() {
|
|
957
|
+
const sourceType = this.getSourceType();
|
|
958
|
+
const prefix = sourceType ? `${sourceType}-` : "";
|
|
959
|
+
const fileName = get(this, "HEAD.FILE.value") || get(this.getGedcom(), "HEAD.FILE.value");
|
|
960
|
+
if (fileName) {
|
|
961
|
+
const cleanName = fileName.replace(/\.ged$/i, "").replace(/_/g, " ");
|
|
962
|
+
return `${prefix}${cleanName}`;
|
|
963
|
+
}
|
|
964
|
+
const sourName = get(this, "HEAD.SOUR.NAME.value") || get(this.getGedcom(), "HEAD.SOUR.NAME.value");
|
|
965
|
+
if (sourName) {
|
|
966
|
+
return `${prefix}${sourName}`;
|
|
967
|
+
}
|
|
968
|
+
const sourValue = get(this, "HEAD.SOUR.value") || get(this.getGedcom(), "HEAD.SOUR.value");
|
|
969
|
+
if (sourValue) {
|
|
970
|
+
return `${prefix}${sourValue}`;
|
|
971
|
+
}
|
|
972
|
+
return `${prefix}Unknown Tree`;
|
|
973
|
+
}
|
|
821
974
|
getTreeName() {
|
|
822
975
|
if (this?.isAncestry()) {
|
|
823
|
-
|
|
976
|
+
const name = this.getAncestryTreeName();
|
|
977
|
+
if (name) return name;
|
|
824
978
|
}
|
|
825
979
|
if (this?.isMyHeritage()) {
|
|
826
|
-
|
|
980
|
+
const name = this.getMyHeritageTreeName();
|
|
981
|
+
if (name) return name;
|
|
982
|
+
}
|
|
983
|
+
if (this?.isFamilySearch()) {
|
|
984
|
+
const name = this.getFamilySearchTreeName();
|
|
985
|
+
if (name) return name;
|
|
986
|
+
}
|
|
987
|
+
if (this?.isGNO2GED()) {
|
|
988
|
+
const name = this.getGNO2GEDTreeName();
|
|
989
|
+
if (name) return name;
|
|
990
|
+
}
|
|
991
|
+
if (this?.isAhnenblatt()) {
|
|
992
|
+
const name = this.getAhnenblattTreeName();
|
|
993
|
+
if (name) return name;
|
|
994
|
+
}
|
|
995
|
+
if (this?.isGeni()) {
|
|
996
|
+
const name = this.getGeniTreeName();
|
|
997
|
+
if (name) return name;
|
|
827
998
|
}
|
|
999
|
+
if (this?.isGenoPro()) {
|
|
1000
|
+
const name = this.getGenoProTreeName();
|
|
1001
|
+
if (name) return name;
|
|
1002
|
+
}
|
|
1003
|
+
return this.getUniversalTreeName();
|
|
828
1004
|
}
|
|
829
1005
|
};
|
|
830
1006
|
var createProxy = (target) => {
|
|
@@ -962,66 +1138,125 @@ var PARTNER = {
|
|
|
962
1138
|
var SPOUSE = {
|
|
963
1139
|
PART: "spouse" /* SPOUSE */
|
|
964
1140
|
};
|
|
1141
|
+
var getGedcomId = (gedcom) => {
|
|
1142
|
+
if (!gedcom) {
|
|
1143
|
+
return "unknown";
|
|
1144
|
+
}
|
|
1145
|
+
const treeId = gedcom.getTreeId?.() || "";
|
|
1146
|
+
const treeName = gedcom.getTreeName?.() || "";
|
|
1147
|
+
const sanitizedName = treeName.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
1148
|
+
if (treeId && sanitizedName) {
|
|
1149
|
+
return `${treeId}_${sanitizedName}`;
|
|
1150
|
+
} else if (treeId) {
|
|
1151
|
+
return treeId;
|
|
1152
|
+
} else if (sanitizedName) {
|
|
1153
|
+
return sanitizedName;
|
|
1154
|
+
}
|
|
1155
|
+
return `gedcom_${gedcom.refcount}`;
|
|
1156
|
+
};
|
|
965
1157
|
var caches = {
|
|
966
1158
|
pathCache: {},
|
|
967
1159
|
relativesOnDegreeCache: {},
|
|
968
|
-
relativesOnLevelCache: {}
|
|
1160
|
+
relativesOnLevelCache: {},
|
|
1161
|
+
profilePictureCache: {}
|
|
969
1162
|
};
|
|
970
|
-
var
|
|
971
|
-
var
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
1163
|
+
var cacheDbs;
|
|
1164
|
+
var getCacheDbs = () => {
|
|
1165
|
+
if (!cacheDbs) {
|
|
1166
|
+
const getInstance = getCacheManagerFactory();
|
|
1167
|
+
cacheDbs = {
|
|
1168
|
+
pathCache: getInstance(
|
|
1169
|
+
"ftv",
|
|
1170
|
+
"Main",
|
|
1171
|
+
"path",
|
|
1172
|
+
true
|
|
1173
|
+
),
|
|
1174
|
+
relativesOnDegreeCache: getInstance("ftv", "Main", "path", true),
|
|
1175
|
+
relativesOnLevelCache: getInstance(
|
|
1176
|
+
"ftv",
|
|
1177
|
+
"Main",
|
|
1178
|
+
"path",
|
|
1179
|
+
true
|
|
1180
|
+
),
|
|
1181
|
+
profilePictureCache: getInstance(
|
|
1182
|
+
"ftv",
|
|
1183
|
+
"Main",
|
|
1184
|
+
"images",
|
|
1185
|
+
false
|
|
1186
|
+
)
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
return cacheDbs;
|
|
985
1190
|
};
|
|
986
|
-
|
|
1191
|
+
var storeCache = {
|
|
1192
|
+
// NOTE: pathCache, relativesOnLevelCache, and relativesOnDegreeCache are intentionally
|
|
1193
|
+
// kept in memory only. These debounced functions exist to satisfy the type system
|
|
1194
|
+
// but are never called.
|
|
987
1195
|
pathCache: debounce((value) => {
|
|
988
1196
|
if (value) {
|
|
989
|
-
|
|
1197
|
+
getCacheDbs().pathCache.setItem(value);
|
|
990
1198
|
}
|
|
991
1199
|
}, 50),
|
|
992
1200
|
relativesOnLevelCache: debounce((value) => {
|
|
993
1201
|
if (value) {
|
|
994
|
-
|
|
1202
|
+
getCacheDbs().relativesOnLevelCache.setItem(value);
|
|
995
1203
|
}
|
|
996
1204
|
}, 50),
|
|
997
1205
|
relativesOnDegreeCache: debounce((value) => {
|
|
998
1206
|
if (value) {
|
|
999
|
-
|
|
1207
|
+
getCacheDbs().relativesOnDegreeCache.setItem(value);
|
|
1000
1208
|
}
|
|
1001
|
-
}, 50)
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1209
|
+
}, 50),
|
|
1210
|
+
// profilePictureCache IS persisted to IndexedDB
|
|
1211
|
+
profilePictureCache: debounce((value) => {
|
|
1212
|
+
if (value) {
|
|
1213
|
+
getCacheDbs().profilePictureCache.setItem(value);
|
|
1214
|
+
}
|
|
1215
|
+
}, 100)
|
|
1216
|
+
};
|
|
1217
|
+
var relativesCache = (cacheKey) => (gedcom, key, subKey, value) => {
|
|
1218
|
+
const gedcomId = getGedcomId(gedcom);
|
|
1219
|
+
const fullKey = `${gedcomId}:${key}`;
|
|
1220
|
+
const cache = caches[cacheKey];
|
|
1221
|
+
if (!cache) {
|
|
1005
1222
|
caches[cacheKey] = {};
|
|
1006
1223
|
}
|
|
1007
|
-
if (value
|
|
1008
|
-
|
|
1009
|
-
|
|
1224
|
+
if (value) {
|
|
1225
|
+
const typedCache2 = caches[cacheKey];
|
|
1226
|
+
if (!typedCache2[fullKey]) {
|
|
1227
|
+
typedCache2[fullKey] = {};
|
|
1010
1228
|
}
|
|
1011
|
-
|
|
1012
|
-
return
|
|
1229
|
+
typedCache2[fullKey][subKey] = value;
|
|
1230
|
+
return typedCache2[fullKey][subKey];
|
|
1013
1231
|
}
|
|
1014
|
-
|
|
1232
|
+
const typedCache = caches[cacheKey];
|
|
1233
|
+
return typedCache?.[fullKey]?.[subKey];
|
|
1015
1234
|
};
|
|
1016
|
-
var pathCache = (key, value) => {
|
|
1235
|
+
var pathCache = (gedcom, key, value) => {
|
|
1236
|
+
const gedcomId = getGedcomId(gedcom);
|
|
1237
|
+
const fullKey = `${gedcomId}:${key}`;
|
|
1017
1238
|
if (!caches.pathCache) {
|
|
1018
1239
|
caches.pathCache = {};
|
|
1019
1240
|
}
|
|
1020
1241
|
if (value && caches.pathCache) {
|
|
1021
|
-
caches.pathCache[
|
|
1022
|
-
return caches.pathCache[
|
|
1242
|
+
caches.pathCache[fullKey] = value;
|
|
1243
|
+
return caches.pathCache[fullKey];
|
|
1023
1244
|
}
|
|
1024
|
-
return caches.pathCache?.[
|
|
1245
|
+
return caches.pathCache?.[fullKey];
|
|
1246
|
+
};
|
|
1247
|
+
var profilePictureCache = (gedcom, key, value) => {
|
|
1248
|
+
const gedcomId = getGedcomId(gedcom);
|
|
1249
|
+
const fullKey = `${gedcomId}:${key}`;
|
|
1250
|
+
if (!caches.profilePictureCache) {
|
|
1251
|
+
caches.profilePictureCache = {};
|
|
1252
|
+
}
|
|
1253
|
+
if (value && caches.profilePictureCache) {
|
|
1254
|
+
caches.profilePictureCache[fullKey] = value;
|
|
1255
|
+
storeCache.profilePictureCache(caches.profilePictureCache);
|
|
1256
|
+
return caches.profilePictureCache[fullKey];
|
|
1257
|
+
}
|
|
1258
|
+
const cached = caches.profilePictureCache?.[fullKey];
|
|
1259
|
+
return cached;
|
|
1025
1260
|
};
|
|
1026
1261
|
|
|
1027
1262
|
// src/utils/get-all-prop.ts
|
|
@@ -1927,7 +2162,7 @@ var Indi = class extends Common {
|
|
|
1927
2162
|
}
|
|
1928
2163
|
async ancestryMedia(namespace) {
|
|
1929
2164
|
const list = {};
|
|
1930
|
-
const objeList = this.get("OBJE")?.toList();
|
|
2165
|
+
const objeList = this.get("OBJE")?.toList().copy();
|
|
1931
2166
|
const www = this._gedcom?.HEAD?.SOUR?.CORP?.WWW?.value;
|
|
1932
2167
|
const tree = this.getAncestryTreeId();
|
|
1933
2168
|
if (objeList) {
|
|
@@ -2001,11 +2236,12 @@ var Indi = class extends Common {
|
|
|
2001
2236
|
if (!tree) {
|
|
2002
2237
|
return;
|
|
2003
2238
|
}
|
|
2004
|
-
const objeList = this.get("OBJE")?.toList();
|
|
2005
|
-
const birthObj = this.get("BIRT.OBJE")?.toList();
|
|
2006
|
-
const deathObj = this.get("DEAT.OBJE")?.toList();
|
|
2239
|
+
const objeList = this.get("OBJE")?.toList().copy();
|
|
2240
|
+
const birthObj = this.get("BIRT.OBJE")?.toList().copy();
|
|
2241
|
+
const deathObj = this.get("DEAT.OBJE")?.toList().copy();
|
|
2242
|
+
objeList?.merge(birthObj).merge(deathObj);
|
|
2007
2243
|
(this.get("FAMS")?.toValueList().values() ?? []).concat(this.get("FAMC")?.toValueList().values() ?? []).forEach((fam) => {
|
|
2008
|
-
objeList
|
|
2244
|
+
objeList.merge(fam?.get("MARR.OBJE"));
|
|
2009
2245
|
});
|
|
2010
2246
|
objeList?.forEach((o, index) => {
|
|
2011
2247
|
if (!o) {
|
|
@@ -2123,6 +2359,88 @@ var Indi = class extends Common {
|
|
|
2123
2359
|
};
|
|
2124
2360
|
});
|
|
2125
2361
|
}
|
|
2362
|
+
geniMedia() {
|
|
2363
|
+
const list = {};
|
|
2364
|
+
const objeList = this.get("OBJE")?.toList().copy();
|
|
2365
|
+
const sourList = this.get("SOUR")?.toList().copy();
|
|
2366
|
+
sourList?.forEach((sour) => {
|
|
2367
|
+
const sourObje = sour?.get("OBJE")?.toList();
|
|
2368
|
+
objeList.merge(sourObje);
|
|
2369
|
+
});
|
|
2370
|
+
if (!objeList || objeList.length === 0) {
|
|
2371
|
+
return void 0;
|
|
2372
|
+
}
|
|
2373
|
+
const rfn = this.get("RFN")?.toValue();
|
|
2374
|
+
const geniId = rfn?.replace(/^geni:/, "") || "unknown";
|
|
2375
|
+
objeList.forEach((obje, index) => {
|
|
2376
|
+
if (!obje) {
|
|
2377
|
+
return;
|
|
2378
|
+
}
|
|
2379
|
+
const key = `@O${index}@`;
|
|
2380
|
+
const isPrimary = obje?.get("_PRIM")?.toValue() === "Y";
|
|
2381
|
+
const url = obje?.get("FILE")?.toValue();
|
|
2382
|
+
const title = obje?.get("TITL")?.toValue() ?? "";
|
|
2383
|
+
const type = obje?.get("FORM")?.toValue() ?? "raw";
|
|
2384
|
+
if (url) {
|
|
2385
|
+
const urlMatch = url.match(/\/([^/]+)\?hash=/);
|
|
2386
|
+
const imgId = urlMatch?.[1] || `img-${index}-${Date.now().toString(36)}`;
|
|
2387
|
+
const id = `geni-${geniId}-${imgId}`;
|
|
2388
|
+
list[id] = {
|
|
2389
|
+
isPrimary,
|
|
2390
|
+
key,
|
|
2391
|
+
id,
|
|
2392
|
+
tree: geniId,
|
|
2393
|
+
imgId,
|
|
2394
|
+
person: this.id,
|
|
2395
|
+
title,
|
|
2396
|
+
url,
|
|
2397
|
+
contentType: type,
|
|
2398
|
+
downloadName: `${this.id.replaceAll("@", "")}_${this.toNaturalName()?.replaceAll(" ", "-") || ""}_${(title || key.replaceAll("@", "").toString()).replaceAll(" ", "-")}`
|
|
2399
|
+
};
|
|
2400
|
+
}
|
|
2401
|
+
});
|
|
2402
|
+
return list;
|
|
2403
|
+
}
|
|
2404
|
+
universalMedia() {
|
|
2405
|
+
const list = {};
|
|
2406
|
+
if (!this.id) {
|
|
2407
|
+
return list;
|
|
2408
|
+
}
|
|
2409
|
+
const objeList = this.get("OBJE")?.toList().copy();
|
|
2410
|
+
if (!objeList || objeList.length === 0) {
|
|
2411
|
+
return list;
|
|
2412
|
+
}
|
|
2413
|
+
const rfn = this.get("RFN")?.toValue();
|
|
2414
|
+
const treeId = rfn || "universal";
|
|
2415
|
+
objeList.forEach((obje, index) => {
|
|
2416
|
+
if (!obje) {
|
|
2417
|
+
return;
|
|
2418
|
+
}
|
|
2419
|
+
const key = `@O${index}@`;
|
|
2420
|
+
obje.standardizeMedia();
|
|
2421
|
+
const isPrimary = obje?.get("_PRIM")?.toValue() === "Y";
|
|
2422
|
+
const url = obje?.get("FILE")?.toValue();
|
|
2423
|
+
const title = obje?.get("TITL")?.toValue() ?? "";
|
|
2424
|
+
const type = obje?.get("FORM")?.toValue() ?? "raw";
|
|
2425
|
+
if (url) {
|
|
2426
|
+
const imgId = `media-${index}-${url.split("/").pop()?.split("?")[0]?.substring(0, 20) || Date.now().toString(36)}`;
|
|
2427
|
+
const id = `${treeId}-${this.id}-${imgId}`;
|
|
2428
|
+
list[id] = {
|
|
2429
|
+
isPrimary,
|
|
2430
|
+
key,
|
|
2431
|
+
id,
|
|
2432
|
+
tree: treeId,
|
|
2433
|
+
imgId,
|
|
2434
|
+
person: this.id,
|
|
2435
|
+
title,
|
|
2436
|
+
url,
|
|
2437
|
+
contentType: type,
|
|
2438
|
+
downloadName: `${this.id.replaceAll("@", "")}_${this.toNaturalName()?.replaceAll(" ", "-") || ""}_${(title || key.replaceAll("@", "").toString()).replaceAll(" ", "-")}`
|
|
2439
|
+
};
|
|
2440
|
+
}
|
|
2441
|
+
});
|
|
2442
|
+
return list;
|
|
2443
|
+
}
|
|
2126
2444
|
async multimedia(namespace) {
|
|
2127
2445
|
if (this?.isAncestry()) {
|
|
2128
2446
|
return await this.ancestryMedia(namespace);
|
|
@@ -2130,11 +2448,30 @@ var Indi = class extends Common {
|
|
|
2130
2448
|
if (this?.isMyHeritage()) {
|
|
2131
2449
|
return this.myheritageMedia();
|
|
2132
2450
|
}
|
|
2133
|
-
|
|
2451
|
+
if (this?.isGeni()) {
|
|
2452
|
+
return this.geniMedia();
|
|
2453
|
+
}
|
|
2454
|
+
return this.universalMedia();
|
|
2134
2455
|
}
|
|
2135
|
-
async getProfilePicture(namespace) {
|
|
2456
|
+
async getProfilePicture(namespace, onlyPrimary = true) {
|
|
2457
|
+
if (!this.id) {
|
|
2458
|
+
return void 0;
|
|
2459
|
+
}
|
|
2460
|
+
const cacheKey = this.id;
|
|
2461
|
+
const cached = profilePictureCache(
|
|
2462
|
+
this._gedcom,
|
|
2463
|
+
cacheKey
|
|
2464
|
+
);
|
|
2465
|
+
if (cached !== void 0) {
|
|
2466
|
+
return cached;
|
|
2467
|
+
}
|
|
2136
2468
|
const mediaList = await this.multimedia(namespace);
|
|
2137
2469
|
if (!mediaList) {
|
|
2470
|
+
profilePictureCache(
|
|
2471
|
+
this._gedcom,
|
|
2472
|
+
cacheKey,
|
|
2473
|
+
void 0
|
|
2474
|
+
);
|
|
2138
2475
|
return void 0;
|
|
2139
2476
|
}
|
|
2140
2477
|
const mediaArray = Object.values(mediaList);
|
|
@@ -2142,24 +2479,41 @@ var Indi = class extends Common {
|
|
|
2142
2479
|
(media) => media.isPrimary && isImageFormat(media.contentType || getFileExtension(media.url))
|
|
2143
2480
|
);
|
|
2144
2481
|
if (primaryMedia) {
|
|
2145
|
-
|
|
2482
|
+
const result = {
|
|
2146
2483
|
file: primaryMedia.url,
|
|
2147
2484
|
form: primaryMedia.contentType,
|
|
2148
2485
|
title: primaryMedia.title,
|
|
2149
2486
|
isPrimary: true
|
|
2150
2487
|
};
|
|
2488
|
+
profilePictureCache(this._gedcom, cacheKey, result);
|
|
2489
|
+
return result;
|
|
2490
|
+
}
|
|
2491
|
+
if (onlyPrimary) {
|
|
2492
|
+
profilePictureCache(
|
|
2493
|
+
this._gedcom,
|
|
2494
|
+
cacheKey,
|
|
2495
|
+
void 0
|
|
2496
|
+
);
|
|
2497
|
+
return void 0;
|
|
2151
2498
|
}
|
|
2152
2499
|
const secondaryMedia = mediaArray.find(
|
|
2153
2500
|
(media) => isImageFormat(media.contentType || getFileExtension(media.url))
|
|
2154
2501
|
);
|
|
2155
2502
|
if (secondaryMedia) {
|
|
2156
|
-
|
|
2503
|
+
const result = {
|
|
2157
2504
|
file: secondaryMedia.url,
|
|
2158
2505
|
form: secondaryMedia.contentType,
|
|
2159
2506
|
title: secondaryMedia.title,
|
|
2160
2507
|
isPrimary: false
|
|
2161
2508
|
};
|
|
2509
|
+
profilePictureCache(this._gedcom, cacheKey, result);
|
|
2510
|
+
return result;
|
|
2162
2511
|
}
|
|
2512
|
+
profilePictureCache(
|
|
2513
|
+
this._gedcom,
|
|
2514
|
+
cacheKey,
|
|
2515
|
+
void 0
|
|
2516
|
+
);
|
|
2163
2517
|
return void 0;
|
|
2164
2518
|
}
|
|
2165
2519
|
link(poolId) {
|
|
@@ -2527,7 +2881,7 @@ var Indi = class extends Common {
|
|
|
2527
2881
|
return;
|
|
2528
2882
|
}
|
|
2529
2883
|
const cacheKey = `${this.id}|${usedIndi.id}`;
|
|
2530
|
-
const cache = pathCache(cacheKey);
|
|
2884
|
+
const cache = pathCache(this._gedcom, cacheKey);
|
|
2531
2885
|
if (cache) {
|
|
2532
2886
|
return cache;
|
|
2533
2887
|
}
|
|
@@ -2572,7 +2926,7 @@ var Indi = class extends Common {
|
|
|
2572
2926
|
if (breakOnNext) {
|
|
2573
2927
|
return void 0;
|
|
2574
2928
|
}
|
|
2575
|
-
pathCache(cacheKey, path2);
|
|
2929
|
+
pathCache(this._gedcom, cacheKey, path2);
|
|
2576
2930
|
return path2;
|
|
2577
2931
|
}
|
|
2578
2932
|
visited.append(indi);
|
|
@@ -2742,7 +3096,7 @@ var Indi = class extends Common {
|
|
|
2742
3096
|
}
|
|
2743
3097
|
getRelativesOnDegree(degree = 0) {
|
|
2744
3098
|
this.id = this.id || `@I${Math.random()}@`;
|
|
2745
|
-
const cache = relativesOnDegreeCache(this.id, degree);
|
|
3099
|
+
const cache = relativesOnDegreeCache(this._gedcom, this.id, degree);
|
|
2746
3100
|
if (cache) {
|
|
2747
3101
|
return cache;
|
|
2748
3102
|
}
|
|
@@ -2750,6 +3104,7 @@ var Indi = class extends Common {
|
|
|
2750
3104
|
const excludes = persons;
|
|
2751
3105
|
if (!Math.abs(degree)) {
|
|
2752
3106
|
return relativesOnDegreeCache(
|
|
3107
|
+
this._gedcom,
|
|
2753
3108
|
this.id,
|
|
2754
3109
|
degree,
|
|
2755
3110
|
persons.except(this)
|
|
@@ -2760,11 +3115,11 @@ var Indi = class extends Common {
|
|
|
2760
3115
|
excludes.merge(persons);
|
|
2761
3116
|
persons = this.getRelativesOnLevel(validDegree).getRelativesOnDegree(-validDegree).copy().exclude(excludes);
|
|
2762
3117
|
}
|
|
2763
|
-
return relativesOnDegreeCache(this.id, degree, persons);
|
|
3118
|
+
return relativesOnDegreeCache(this._gedcom, this.id, degree, persons);
|
|
2764
3119
|
}
|
|
2765
3120
|
getRelativesOnLevel(level = 0, filter) {
|
|
2766
3121
|
this.id = this.id || `@I${Math.random()}@`;
|
|
2767
|
-
const cache = relativesOnLevelCache(this.id, level);
|
|
3122
|
+
const cache = relativesOnLevelCache(this._gedcom, this.id, level);
|
|
2768
3123
|
if (cache) {
|
|
2769
3124
|
return cache;
|
|
2770
3125
|
}
|
|
@@ -2775,7 +3130,7 @@ var Indi = class extends Common {
|
|
|
2775
3130
|
};
|
|
2776
3131
|
let families = this.get(config.key)?.toValueList();
|
|
2777
3132
|
if (!families) {
|
|
2778
|
-
return relativesOnLevelCache(this.id, level, persons);
|
|
3133
|
+
return relativesOnLevelCache(this._gedcom, this.id, level, persons);
|
|
2779
3134
|
}
|
|
2780
3135
|
if (filter) {
|
|
2781
3136
|
families = families.filter(filter);
|
|
@@ -2786,7 +3141,12 @@ var Indi = class extends Common {
|
|
|
2786
3141
|
persons = this.toFamilies(families).getParents();
|
|
2787
3142
|
}
|
|
2788
3143
|
if (level >= -1 && level <= 1) {
|
|
2789
|
-
return relativesOnLevelCache(
|
|
3144
|
+
return relativesOnLevelCache(
|
|
3145
|
+
this._gedcom,
|
|
3146
|
+
this.id,
|
|
3147
|
+
level,
|
|
3148
|
+
persons.except(this)
|
|
3149
|
+
);
|
|
2790
3150
|
}
|
|
2791
3151
|
for (let i = 1; i < Math.abs(level); i++) {
|
|
2792
3152
|
if (config.isAscendant) {
|
|
@@ -2795,7 +3155,12 @@ var Indi = class extends Common {
|
|
|
2795
3155
|
persons = persons.getParents();
|
|
2796
3156
|
}
|
|
2797
3157
|
}
|
|
2798
|
-
return relativesOnLevelCache(
|
|
3158
|
+
return relativesOnLevelCache(
|
|
3159
|
+
this._gedcom,
|
|
3160
|
+
this.id,
|
|
3161
|
+
level,
|
|
3162
|
+
persons.except(this)
|
|
3163
|
+
);
|
|
2799
3164
|
}
|
|
2800
3165
|
getAscendants(level = 0, filter) {
|
|
2801
3166
|
if (!level) {
|
|
@@ -2834,7 +3199,12 @@ var Indi = class extends Common {
|
|
|
2834
3199
|
}
|
|
2835
3200
|
currentGen++;
|
|
2836
3201
|
generations[currentGen] = descentants;
|
|
2837
|
-
relativesOnLevelCache(
|
|
3202
|
+
relativesOnLevelCache(
|
|
3203
|
+
this._gedcom,
|
|
3204
|
+
this.id,
|
|
3205
|
+
-currentGen,
|
|
3206
|
+
descentants
|
|
3207
|
+
);
|
|
2838
3208
|
descentants && relatives.merge(descentants);
|
|
2839
3209
|
}
|
|
2840
3210
|
return { relatives, generations };
|
|
@@ -2867,7 +3237,7 @@ var Indi = class extends Common {
|
|
|
2867
3237
|
}
|
|
2868
3238
|
currentGen++;
|
|
2869
3239
|
generations[currentGen] = parents;
|
|
2870
|
-
relativesOnLevelCache(this.id, currentGen, parents);
|
|
3240
|
+
relativesOnLevelCache(this._gedcom, this.id, currentGen, parents);
|
|
2871
3241
|
parents && relatives.merge(parents);
|
|
2872
3242
|
}
|
|
2873
3243
|
return { relatives, generations };
|
|
@@ -5878,7 +6248,7 @@ var Families = class _Families extends List {
|
|
|
5878
6248
|
|
|
5879
6249
|
// package.json
|
|
5880
6250
|
var package_default = {
|
|
5881
|
-
version: "
|
|
6251
|
+
version: "2.0.0"};
|
|
5882
6252
|
|
|
5883
6253
|
// src/utils/get-product-details.ts
|
|
5884
6254
|
var isDevelopment = () => {
|
|
@@ -6869,7 +7239,7 @@ var GedcomTree = {
|
|
|
6869
7239
|
return this.parseHierarchy(content, options);
|
|
6870
7240
|
},
|
|
6871
7241
|
parseHierarchy: function(content, options) {
|
|
6872
|
-
const { settings } = options ?? {};
|
|
7242
|
+
const { settings, filename = "" } = options ?? {};
|
|
6873
7243
|
const { linkedPersons = "skip", linkingKey } = settings ?? {};
|
|
6874
7244
|
const gedcom = createGedCom();
|
|
6875
7245
|
gedcom.removeValue();
|
|
@@ -6904,6 +7274,25 @@ var GedcomTree = {
|
|
|
6904
7274
|
if (lineMatch) {
|
|
6905
7275
|
const lineIndent = Number(lineMatch?.groups?.indent ?? 0);
|
|
6906
7276
|
const lineValue = lineMatch?.groups?.value ?? "";
|
|
7277
|
+
const lineType = lineMatch?.groups?.type ?? "";
|
|
7278
|
+
const linesAcc = acc;
|
|
7279
|
+
if (lineIndent === 0 && lineType === "HEAD") {
|
|
7280
|
+
acc.push(line);
|
|
7281
|
+
linesAcc._inHead = true;
|
|
7282
|
+
linesAcc._hasFile = false;
|
|
7283
|
+
return acc;
|
|
7284
|
+
}
|
|
7285
|
+
if (lineIndent === 0 && linesAcc._inHead) {
|
|
7286
|
+
if (filename && !linesAcc._hasFile) {
|
|
7287
|
+
acc.push(`1 FILE ${filename}`);
|
|
7288
|
+
}
|
|
7289
|
+
linesAcc._inHead = false;
|
|
7290
|
+
acc.push(line);
|
|
7291
|
+
return acc;
|
|
7292
|
+
}
|
|
7293
|
+
if (linesAcc._inHead && lineIndent === 1 && lineType === "FILE") {
|
|
7294
|
+
linesAcc._hasFile = true;
|
|
7295
|
+
}
|
|
6907
7296
|
if (lineIndent > 0 && lineIndent > prevLineIndent && lineValue && isId(lineValue)) {
|
|
6908
7297
|
const refLines = lineValue.split(/,\s*/).map((id) => line.replace(lineValue, id));
|
|
6909
7298
|
if (refLines.length > 1) {
|
|
@@ -7118,12 +7507,6 @@ function formatDate(date) {
|
|
|
7118
7507
|
}
|
|
7119
7508
|
return chalk.white(date);
|
|
7120
7509
|
}
|
|
7121
|
-
function formatPlace(place) {
|
|
7122
|
-
if (!place) {
|
|
7123
|
-
return chalk.gray("(unknown)");
|
|
7124
|
-
}
|
|
7125
|
-
return chalk.white(place);
|
|
7126
|
-
}
|
|
7127
7510
|
function formatName(name) {
|
|
7128
7511
|
if (!name) {
|
|
7129
7512
|
return chalk.gray("(unnamed)");
|
|
@@ -7306,102 +7689,327 @@ function registerExtractCommand(program2) {
|
|
|
7306
7689
|
}
|
|
7307
7690
|
|
|
7308
7691
|
// src/cli/commands/find.ts
|
|
7309
|
-
function
|
|
7310
|
-
|
|
7311
|
-
|
|
7312
|
-
|
|
7313
|
-
|
|
7314
|
-
|
|
7315
|
-
|
|
7316
|
-
|
|
7317
|
-
|
|
7318
|
-
|
|
7319
|
-
|
|
7320
|
-
|
|
7321
|
-
|
|
7322
|
-
|
|
7323
|
-
|
|
7324
|
-
|
|
7325
|
-
|
|
7326
|
-
|
|
7692
|
+
function findIndividuals(tree, options) {
|
|
7693
|
+
const individuals = tree.indis();
|
|
7694
|
+
const results = [];
|
|
7695
|
+
individuals.forEach((indi) => {
|
|
7696
|
+
let matches = true;
|
|
7697
|
+
if (options.id && indi.id !== options.id) {
|
|
7698
|
+
matches = false;
|
|
7699
|
+
}
|
|
7700
|
+
if ((options.name || options.query) && matches) {
|
|
7701
|
+
const searchName = (options.name || options.query || "").toLowerCase();
|
|
7702
|
+
const name = cleanGedcomName(indi?.toNaturalName()).toLowerCase();
|
|
7703
|
+
if (!name.includes(searchName)) {
|
|
7704
|
+
matches = false;
|
|
7705
|
+
}
|
|
7706
|
+
}
|
|
7707
|
+
if (options.birthYear && matches) {
|
|
7708
|
+
const year = options.birthYear;
|
|
7709
|
+
const birthDate = indi.BIRT?.DATE?.toValue();
|
|
7710
|
+
if (!birthDate?.includes(String(year))) {
|
|
7711
|
+
matches = false;
|
|
7712
|
+
}
|
|
7713
|
+
}
|
|
7714
|
+
if (options.deathYear && matches) {
|
|
7715
|
+
const year = options.deathYear;
|
|
7716
|
+
const deathDate = indi.DEAT?.DATE?.toValue();
|
|
7717
|
+
if (!deathDate?.includes(String(year))) {
|
|
7718
|
+
matches = false;
|
|
7719
|
+
}
|
|
7720
|
+
}
|
|
7721
|
+
if (matches) {
|
|
7722
|
+
results.push(indi);
|
|
7723
|
+
}
|
|
7724
|
+
});
|
|
7725
|
+
return results;
|
|
7726
|
+
}
|
|
7727
|
+
function formatFindResults(results, json = false) {
|
|
7728
|
+
if (json) {
|
|
7729
|
+
const jsonResults = results.map((indi) => ({
|
|
7730
|
+
id: indi.id,
|
|
7731
|
+
name: cleanGedcomName(indi.NAME?.toValue()),
|
|
7732
|
+
birthDate: indi.BIRT?.DATE?.toValue() || null,
|
|
7733
|
+
birthPlace: indi.BIRT?.PLAC?.value || null,
|
|
7734
|
+
deathDate: indi.DEAT?.DATE?.toValue() || null,
|
|
7735
|
+
deathPlace: indi.DEAT?.PLAC?.value || null,
|
|
7736
|
+
sex: indi.SEX?.value || null
|
|
7737
|
+
}));
|
|
7738
|
+
console.log(
|
|
7739
|
+
formatJson({ count: jsonResults.length, individuals: jsonResults })
|
|
7740
|
+
);
|
|
7741
|
+
} else {
|
|
7742
|
+
if (results.length === 0) {
|
|
7743
|
+
console.log(
|
|
7744
|
+
formatWarning("No individuals found matching the criteria")
|
|
7745
|
+
);
|
|
7746
|
+
} else {
|
|
7747
|
+
console.log(
|
|
7748
|
+
formatHeader(`Found ${results.length} individual(s)
|
|
7749
|
+
`)
|
|
7750
|
+
);
|
|
7751
|
+
results.forEach((indi) => {
|
|
7752
|
+
const name = cleanGedcomName(indi.NAME?.toValue());
|
|
7753
|
+
const birthDate = indi.BIRT?.DATE?.toValue();
|
|
7754
|
+
const deathDate = indi.DEAT?.DATE?.toValue();
|
|
7755
|
+
const lifespan = formatLifespan(birthDate, deathDate);
|
|
7756
|
+
console.log(
|
|
7757
|
+
formatListItem(
|
|
7758
|
+
`${formatId(indi.id)} ${formatName(name)} ${lifespan}`
|
|
7759
|
+
)
|
|
7760
|
+
);
|
|
7761
|
+
const birthPlace = indi.BIRT?.PLAC?.value;
|
|
7762
|
+
if (birthPlace) {
|
|
7763
|
+
console.log(
|
|
7764
|
+
formatListItem(
|
|
7765
|
+
`Birth: ${formatDate(birthDate)} in ${birthPlace}`,
|
|
7766
|
+
1
|
|
7767
|
+
)
|
|
7768
|
+
);
|
|
7327
7769
|
}
|
|
7328
|
-
|
|
7329
|
-
|
|
7330
|
-
|
|
7331
|
-
|
|
7332
|
-
|
|
7333
|
-
|
|
7770
|
+
const deathPlace = indi.DEAT?.PLAC?.value;
|
|
7771
|
+
if (deathPlace) {
|
|
7772
|
+
console.log(
|
|
7773
|
+
formatListItem(
|
|
7774
|
+
`Death: ${formatDate(deathDate)} in ${deathPlace}`,
|
|
7775
|
+
1
|
|
7776
|
+
)
|
|
7777
|
+
);
|
|
7334
7778
|
}
|
|
7335
|
-
|
|
7336
|
-
|
|
7337
|
-
|
|
7338
|
-
|
|
7339
|
-
|
|
7779
|
+
console.log();
|
|
7780
|
+
});
|
|
7781
|
+
}
|
|
7782
|
+
}
|
|
7783
|
+
}
|
|
7784
|
+
function registerFindCommand(program2) {
|
|
7785
|
+
program2.command("find <file> [query]").description("Find individuals in a GEDCOM file").option("--id <id>", "Find by GEDCOM ID").option("--name <name>", "Find by name (substring search)").option("--birth-year <year>", "Filter by birth year").option("--death-year <year>", "Filter by death year").option("-j, --json", "Output in JSON format").action(
|
|
7786
|
+
(file, query, options) => {
|
|
7787
|
+
try {
|
|
7788
|
+
const content = readGedcomFile(file);
|
|
7789
|
+
const { gedcom: tree } = parser_default.parse(content);
|
|
7790
|
+
const results = findIndividuals(tree, {
|
|
7791
|
+
...options,
|
|
7792
|
+
query
|
|
7793
|
+
});
|
|
7794
|
+
formatFindResults(results, options.json);
|
|
7795
|
+
} catch (error) {
|
|
7796
|
+
handleError(error, "Failed to search GEDCOM file");
|
|
7797
|
+
}
|
|
7798
|
+
}
|
|
7799
|
+
);
|
|
7800
|
+
}
|
|
7801
|
+
|
|
7802
|
+
// src/cli/commands/get.ts
|
|
7803
|
+
function getValueByPath(record, path) {
|
|
7804
|
+
return record.get(path);
|
|
7805
|
+
}
|
|
7806
|
+
function formatOutput(value, options) {
|
|
7807
|
+
if (value === void 0 || value === null) {
|
|
7808
|
+
return "(not found)";
|
|
7809
|
+
}
|
|
7810
|
+
if (value instanceof List) {
|
|
7811
|
+
if (options.json) {
|
|
7812
|
+
const items2 = [];
|
|
7813
|
+
value.forEach((item) => {
|
|
7814
|
+
if (item) {
|
|
7815
|
+
try {
|
|
7816
|
+
items2.push(JSON.parse(item.toJson()));
|
|
7817
|
+
} catch {
|
|
7818
|
+
items2.push(item.toValue());
|
|
7340
7819
|
}
|
|
7341
7820
|
}
|
|
7342
|
-
|
|
7343
|
-
|
|
7821
|
+
});
|
|
7822
|
+
return JSON.stringify(items2, null, 2);
|
|
7823
|
+
}
|
|
7824
|
+
if (options.raw) {
|
|
7825
|
+
const items2 = [];
|
|
7826
|
+
value.forEach((item) => {
|
|
7827
|
+
if (item) {
|
|
7828
|
+
items2.push(item.toGedcom());
|
|
7344
7829
|
}
|
|
7345
7830
|
});
|
|
7346
|
-
|
|
7347
|
-
|
|
7348
|
-
|
|
7349
|
-
|
|
7350
|
-
|
|
7351
|
-
|
|
7352
|
-
|
|
7353
|
-
|
|
7354
|
-
|
|
7355
|
-
|
|
7356
|
-
|
|
7357
|
-
|
|
7358
|
-
|
|
7359
|
-
|
|
7360
|
-
|
|
7361
|
-
|
|
7362
|
-
|
|
7363
|
-
|
|
7364
|
-
|
|
7365
|
-
|
|
7366
|
-
|
|
7367
|
-
|
|
7368
|
-
|
|
7369
|
-
|
|
7370
|
-
|
|
7371
|
-
|
|
7372
|
-
|
|
7373
|
-
|
|
7374
|
-
|
|
7375
|
-
|
|
7376
|
-
|
|
7377
|
-
|
|
7378
|
-
|
|
7379
|
-
|
|
7380
|
-
|
|
7381
|
-
|
|
7382
|
-
|
|
7831
|
+
return items2.join("\n");
|
|
7832
|
+
}
|
|
7833
|
+
const items = [];
|
|
7834
|
+
let index = 0;
|
|
7835
|
+
value.forEach((item) => {
|
|
7836
|
+
if (item) {
|
|
7837
|
+
const exportedValue = item.exportValue();
|
|
7838
|
+
items.push(
|
|
7839
|
+
`[${index}] ${exportedValue || item.toValue() || "(empty)"}`
|
|
7840
|
+
);
|
|
7841
|
+
}
|
|
7842
|
+
index++;
|
|
7843
|
+
});
|
|
7844
|
+
return items.join("\n");
|
|
7845
|
+
}
|
|
7846
|
+
if (value instanceof Common) {
|
|
7847
|
+
if (options.json) {
|
|
7848
|
+
return value.toJson();
|
|
7849
|
+
}
|
|
7850
|
+
if (options.raw) {
|
|
7851
|
+
return value.toValue() || "(empty)";
|
|
7852
|
+
}
|
|
7853
|
+
return value.exportValue() || value.toValue() || "(empty)";
|
|
7854
|
+
}
|
|
7855
|
+
return String(value);
|
|
7856
|
+
}
|
|
7857
|
+
function getValue(tree, id, path, json = false, raw = false) {
|
|
7858
|
+
let record = null;
|
|
7859
|
+
let recordType = "";
|
|
7860
|
+
if (id.startsWith("@I")) {
|
|
7861
|
+
record = tree.indi(id);
|
|
7862
|
+
recordType = "Individual";
|
|
7863
|
+
} else if (id.startsWith("@F")) {
|
|
7864
|
+
record = tree.fam(id);
|
|
7865
|
+
recordType = "Family";
|
|
7866
|
+
} else if (id.startsWith("@S")) {
|
|
7867
|
+
record = tree.sour(id);
|
|
7868
|
+
recordType = "Source";
|
|
7869
|
+
} else {
|
|
7870
|
+
record = tree.indi(id);
|
|
7871
|
+
recordType = "Individual";
|
|
7872
|
+
}
|
|
7873
|
+
if (!record) {
|
|
7874
|
+
throw new Error(`Record ${id} not found`);
|
|
7875
|
+
}
|
|
7876
|
+
if (!path) {
|
|
7877
|
+
if (json) {
|
|
7878
|
+
const simplified = {
|
|
7879
|
+
id: record.id || id,
|
|
7880
|
+
type: recordType
|
|
7881
|
+
};
|
|
7882
|
+
if (recordType === "Individual") {
|
|
7883
|
+
if (record.NAME) {
|
|
7884
|
+
simplified.name = record.toNaturalName();
|
|
7885
|
+
}
|
|
7886
|
+
if (record.BIRT) {
|
|
7887
|
+
simplified.birth = {
|
|
7888
|
+
date: record.BIRT.DATE?.toValue(),
|
|
7889
|
+
place: record.BIRT.PLAC?.value
|
|
7890
|
+
};
|
|
7891
|
+
}
|
|
7892
|
+
if (record.DEAT) {
|
|
7893
|
+
simplified.death = {
|
|
7894
|
+
date: record.DEAT.DATE?.toValue(),
|
|
7895
|
+
place: record.DEAT.PLAC?.value
|
|
7896
|
+
};
|
|
7897
|
+
}
|
|
7898
|
+
if (record.SEX) {
|
|
7899
|
+
simplified.sex = record.SEX.value;
|
|
7383
7900
|
}
|
|
7384
7901
|
}
|
|
7385
|
-
|
|
7386
|
-
|
|
7902
|
+
return formatJson(simplified);
|
|
7903
|
+
} else {
|
|
7904
|
+
let result = `${recordType}: ${id}`;
|
|
7905
|
+
if (recordType === "Individual" && record.NAME) {
|
|
7906
|
+
result += `
|
|
7907
|
+
Name: ${record.NAME.toValue()}`;
|
|
7908
|
+
}
|
|
7909
|
+
return result;
|
|
7387
7910
|
}
|
|
7388
|
-
}
|
|
7911
|
+
}
|
|
7912
|
+
const value = getValueByPath(record, path);
|
|
7913
|
+
if (value === void 0) {
|
|
7914
|
+
throw new Error(`Path "${path}" not found in ${recordType} ${id}`);
|
|
7915
|
+
}
|
|
7916
|
+
return formatOutput(value, { json, raw });
|
|
7389
7917
|
}
|
|
7390
|
-
|
|
7391
|
-
|
|
7392
|
-
|
|
7393
|
-
|
|
7918
|
+
function registerGetCommand(program2) {
|
|
7919
|
+
program2.command("get <file> <id>").description("Get a value from a GEDCOM record").option(
|
|
7920
|
+
"-p, --path <path>",
|
|
7921
|
+
'Dot-separated path to the value (e.g., "BIRT.PLAC", "NAME")'
|
|
7922
|
+
).option("-j, --json", "Output in JSON format").option("-r, --raw", "Output raw value only (no formatting)").action((file, id, options) => {
|
|
7394
7923
|
try {
|
|
7395
7924
|
const content = readGedcomFile(file);
|
|
7396
7925
|
const { gedcom: tree } = parser_default.parse(content);
|
|
7397
|
-
|
|
7398
|
-
|
|
7399
|
-
|
|
7400
|
-
|
|
7401
|
-
|
|
7402
|
-
|
|
7403
|
-
|
|
7404
|
-
|
|
7926
|
+
let record = null;
|
|
7927
|
+
let recordType = "";
|
|
7928
|
+
if (id.startsWith("@I")) {
|
|
7929
|
+
record = tree.indi(id);
|
|
7930
|
+
recordType = "Individual";
|
|
7931
|
+
} else if (id.startsWith("@F")) {
|
|
7932
|
+
record = tree.fam(id);
|
|
7933
|
+
recordType = "Family";
|
|
7934
|
+
} else if (id.startsWith("@S")) {
|
|
7935
|
+
record = tree.sour(id);
|
|
7936
|
+
recordType = "Source";
|
|
7937
|
+
} else if (id.startsWith("@N")) {
|
|
7938
|
+
record = tree.note(id);
|
|
7939
|
+
recordType = "Note";
|
|
7940
|
+
} else {
|
|
7941
|
+
record = tree.indi(id);
|
|
7942
|
+
recordType = "Individual";
|
|
7943
|
+
}
|
|
7944
|
+
if (!record) {
|
|
7945
|
+
console.error(formatError(`Record ${id} not found`));
|
|
7946
|
+
process.exit(1);
|
|
7947
|
+
}
|
|
7948
|
+
if (!options.path) {
|
|
7949
|
+
if (options.json) {
|
|
7950
|
+
const simplified = {
|
|
7951
|
+
id: record.id || id,
|
|
7952
|
+
type: recordType
|
|
7953
|
+
};
|
|
7954
|
+
if (recordType === "Individual") {
|
|
7955
|
+
if (record.NAME) {
|
|
7956
|
+
simplified.name = record.NAME.toValue();
|
|
7957
|
+
}
|
|
7958
|
+
if (record.BIRT) {
|
|
7959
|
+
simplified.birth = {
|
|
7960
|
+
date: record.BIRT.DATE?.toValue(),
|
|
7961
|
+
place: record.BIRT.PLAC?.value
|
|
7962
|
+
};
|
|
7963
|
+
}
|
|
7964
|
+
if (record.DEAT) {
|
|
7965
|
+
simplified.death = {
|
|
7966
|
+
date: record.DEAT.DATE?.toValue(),
|
|
7967
|
+
place: record.DEAT.PLAC?.value
|
|
7968
|
+
};
|
|
7969
|
+
}
|
|
7970
|
+
if (record.SEX) {
|
|
7971
|
+
simplified.sex = record.SEX.value;
|
|
7972
|
+
}
|
|
7973
|
+
}
|
|
7974
|
+
console.log(formatJson(simplified));
|
|
7975
|
+
} else {
|
|
7976
|
+
console.log(`${recordType}: ${id}`);
|
|
7977
|
+
if (recordType === "Individual" && record.NAME) {
|
|
7978
|
+
console.log(`Name: ${record.NAME.toValue()}`);
|
|
7979
|
+
}
|
|
7980
|
+
}
|
|
7981
|
+
return;
|
|
7982
|
+
}
|
|
7983
|
+
const value = getValueByPath(record, options.path);
|
|
7984
|
+
if (value === void 0) {
|
|
7985
|
+
console.error(
|
|
7986
|
+
formatError(
|
|
7987
|
+
`Path "${options.path}" not found in ${recordType} ${id}`
|
|
7988
|
+
)
|
|
7989
|
+
);
|
|
7990
|
+
process.exit(1);
|
|
7991
|
+
}
|
|
7992
|
+
console.log(formatOutput(value, options));
|
|
7993
|
+
} catch (error) {
|
|
7994
|
+
handleError(error);
|
|
7995
|
+
}
|
|
7996
|
+
});
|
|
7997
|
+
}
|
|
7998
|
+
|
|
7999
|
+
// src/cli/commands/info.ts
|
|
8000
|
+
function registerInfoCommand(program2) {
|
|
8001
|
+
program2.command("info <file>").description("Display basic information about a GEDCOM file").option("-j, --json", "Output in JSON format").option("-v, --verbose", "Show detailed information").action((file, options) => {
|
|
8002
|
+
try {
|
|
8003
|
+
const content = readGedcomFile(file);
|
|
8004
|
+
const { gedcom: tree } = parser_default.parse(content);
|
|
8005
|
+
const individuals = tree.indis();
|
|
8006
|
+
const families = tree.fams();
|
|
8007
|
+
const sources = tree.sours();
|
|
8008
|
+
const repos = tree.repos();
|
|
8009
|
+
const objes = tree.objes();
|
|
8010
|
+
const submitters = tree.subms();
|
|
8011
|
+
const header = tree.HEAD;
|
|
8012
|
+
const version = header?.GEDC?.VERS?.value || "Unknown";
|
|
7405
8013
|
const info = {
|
|
7406
8014
|
file,
|
|
7407
8015
|
version,
|
|
@@ -7511,6 +8119,862 @@ function registerMergeCommand(program2) {
|
|
|
7511
8119
|
}
|
|
7512
8120
|
});
|
|
7513
8121
|
}
|
|
8122
|
+
|
|
8123
|
+
// src/cli/commands/select.ts
|
|
8124
|
+
function selectIndividual(tree, input, searchResults) {
|
|
8125
|
+
if (/^\d+$/.test(input) && searchResults && searchResults.length > 0) {
|
|
8126
|
+
const index = parseInt(input) - 1;
|
|
8127
|
+
if (index >= 0 && index < searchResults.length) {
|
|
8128
|
+
return searchResults[index];
|
|
8129
|
+
}
|
|
8130
|
+
return void 0;
|
|
8131
|
+
}
|
|
8132
|
+
return tree.indi(input);
|
|
8133
|
+
}
|
|
8134
|
+
function formatSelectResult(individual, input) {
|
|
8135
|
+
if (individual) {
|
|
8136
|
+
const name = cleanGedcomName(individual.NAME?.toValue());
|
|
8137
|
+
console.log(
|
|
8138
|
+
formatSuccess(
|
|
8139
|
+
`Selected: ${formatId(individual.id)} ${formatName(name)}`
|
|
8140
|
+
)
|
|
8141
|
+
);
|
|
8142
|
+
} else {
|
|
8143
|
+
console.log(formatError(`Individual ${input} not found`));
|
|
8144
|
+
}
|
|
8145
|
+
}
|
|
8146
|
+
function registerSelectCommand(program2) {
|
|
8147
|
+
program2.command("select <file> <id>").description("Select an individual by ID").action((file, id) => {
|
|
8148
|
+
try {
|
|
8149
|
+
const content = readGedcomFile(file);
|
|
8150
|
+
const { gedcom: tree } = parser_default.parse(content);
|
|
8151
|
+
const individual = selectIndividual(tree, id);
|
|
8152
|
+
formatSelectResult(individual, id);
|
|
8153
|
+
if (!individual) {
|
|
8154
|
+
process.exit(1);
|
|
8155
|
+
}
|
|
8156
|
+
} catch (error) {
|
|
8157
|
+
handleError(error, "Failed to select individual");
|
|
8158
|
+
}
|
|
8159
|
+
});
|
|
8160
|
+
}
|
|
8161
|
+
|
|
8162
|
+
// src/cli/commands/show.ts
|
|
8163
|
+
function showIndividual(tree, individual) {
|
|
8164
|
+
const name = cleanGedcomName(individual.NAME?.toValue());
|
|
8165
|
+
console.log(
|
|
8166
|
+
formatHeader(`
|
|
8167
|
+
${formatId(individual.id)} ${formatName(name)}`)
|
|
8168
|
+
);
|
|
8169
|
+
console.log("");
|
|
8170
|
+
if (individual.SEX?.value) {
|
|
8171
|
+
console.log(formatListItem(`Sex: ${individual.SEX.value}`));
|
|
8172
|
+
}
|
|
8173
|
+
if (individual.BIRT) {
|
|
8174
|
+
const date = individual.BIRT.DATE?.toValue();
|
|
8175
|
+
const place = individual.BIRT.PLAC?.value;
|
|
8176
|
+
console.log(
|
|
8177
|
+
formatListItem(
|
|
8178
|
+
`Birth: ${date || "?"}${place ? ` at ${place}` : ""}`
|
|
8179
|
+
)
|
|
8180
|
+
);
|
|
8181
|
+
}
|
|
8182
|
+
if (individual.DEAT) {
|
|
8183
|
+
const date = individual.DEAT.DATE?.toValue();
|
|
8184
|
+
const place = individual.DEAT.PLAC?.value;
|
|
8185
|
+
console.log(
|
|
8186
|
+
formatListItem(
|
|
8187
|
+
`Death: ${date || "?"}${place ? ` at ${place}` : ""}`
|
|
8188
|
+
)
|
|
8189
|
+
);
|
|
8190
|
+
}
|
|
8191
|
+
const parentFams = individual.FAMC;
|
|
8192
|
+
if (parentFams) {
|
|
8193
|
+
const parentList = parentFams instanceof List ? parentFams.values() : [parentFams];
|
|
8194
|
+
console.log(formatListItem("\nParents:"));
|
|
8195
|
+
parentList.forEach((famRef) => {
|
|
8196
|
+
if (famRef) {
|
|
8197
|
+
const fam = tree.fam(famRef.value);
|
|
8198
|
+
if (fam) {
|
|
8199
|
+
const father = fam.HUSB ? tree.indi(fam.HUSB.value) : null;
|
|
8200
|
+
const mother = fam.WIFE ? tree.indi(fam.WIFE.value) : null;
|
|
8201
|
+
if (father) {
|
|
8202
|
+
const fatherName = cleanGedcomName(
|
|
8203
|
+
father.NAME?.toValue()
|
|
8204
|
+
);
|
|
8205
|
+
console.log(
|
|
8206
|
+
formatListItem(
|
|
8207
|
+
` ${formatId(father.id)} ${formatName(fatherName)}`
|
|
8208
|
+
)
|
|
8209
|
+
);
|
|
8210
|
+
}
|
|
8211
|
+
if (mother) {
|
|
8212
|
+
const motherName = cleanGedcomName(
|
|
8213
|
+
mother.NAME?.toValue()
|
|
8214
|
+
);
|
|
8215
|
+
console.log(
|
|
8216
|
+
formatListItem(
|
|
8217
|
+
` ${formatId(mother.id)} ${formatName(motherName)}`
|
|
8218
|
+
)
|
|
8219
|
+
);
|
|
8220
|
+
}
|
|
8221
|
+
}
|
|
8222
|
+
}
|
|
8223
|
+
});
|
|
8224
|
+
}
|
|
8225
|
+
const spouseFams = individual.FAMS;
|
|
8226
|
+
if (spouseFams) {
|
|
8227
|
+
const famList = spouseFams instanceof List ? spouseFams.values() : [spouseFams];
|
|
8228
|
+
console.log(formatListItem("\nSpouses:"));
|
|
8229
|
+
famList.forEach((famRef) => {
|
|
8230
|
+
if (famRef) {
|
|
8231
|
+
const fam = tree.fam(famRef.value);
|
|
8232
|
+
if (fam) {
|
|
8233
|
+
const spouseRef = fam.HUSB?.value === individual.id ? fam.WIFE : fam.HUSB;
|
|
8234
|
+
if (spouseRef) {
|
|
8235
|
+
const spouse = tree.indi(spouseRef.value);
|
|
8236
|
+
if (spouse) {
|
|
8237
|
+
const spouseName = cleanGedcomName(
|
|
8238
|
+
spouse.NAME?.toValue()
|
|
8239
|
+
);
|
|
8240
|
+
console.log(
|
|
8241
|
+
formatListItem(
|
|
8242
|
+
` ${formatId(spouse.id)} ${formatName(spouseName)}`
|
|
8243
|
+
)
|
|
8244
|
+
);
|
|
8245
|
+
}
|
|
8246
|
+
}
|
|
8247
|
+
}
|
|
8248
|
+
}
|
|
8249
|
+
});
|
|
8250
|
+
}
|
|
8251
|
+
if (spouseFams) {
|
|
8252
|
+
const famList = spouseFams instanceof List ? spouseFams.values() : [spouseFams];
|
|
8253
|
+
let hasChildren = false;
|
|
8254
|
+
famList.forEach((famRef) => {
|
|
8255
|
+
if (famRef) {
|
|
8256
|
+
const fam = tree.fam(famRef.value);
|
|
8257
|
+
if (fam && fam.CHIL) {
|
|
8258
|
+
if (!hasChildren) {
|
|
8259
|
+
console.log(formatListItem("\nChildren:"));
|
|
8260
|
+
hasChildren = true;
|
|
8261
|
+
}
|
|
8262
|
+
const children = fam.CHIL instanceof List ? fam.CHIL.values() : [fam.CHIL];
|
|
8263
|
+
children.forEach((childRef) => {
|
|
8264
|
+
if (childRef) {
|
|
8265
|
+
const child = tree.indi(childRef.value);
|
|
8266
|
+
if (child) {
|
|
8267
|
+
const childName = cleanGedcomName(
|
|
8268
|
+
child.NAME?.toValue()
|
|
8269
|
+
);
|
|
8270
|
+
console.log(
|
|
8271
|
+
formatListItem(
|
|
8272
|
+
` ${formatId(child.id)} ${formatName(childName)}`
|
|
8273
|
+
)
|
|
8274
|
+
);
|
|
8275
|
+
}
|
|
8276
|
+
}
|
|
8277
|
+
});
|
|
8278
|
+
}
|
|
8279
|
+
}
|
|
8280
|
+
});
|
|
8281
|
+
}
|
|
8282
|
+
console.log("");
|
|
8283
|
+
}
|
|
8284
|
+
function registerShowCommand(program2) {
|
|
8285
|
+
program2.command("show <file> <id>").description("Display detailed information about an individual").action((file, id) => {
|
|
8286
|
+
try {
|
|
8287
|
+
const content = readGedcomFile(file);
|
|
8288
|
+
const { gedcom: tree } = parser_default.parse(content);
|
|
8289
|
+
const individual = tree.indi(id);
|
|
8290
|
+
if (!individual) {
|
|
8291
|
+
console.error(formatError(`Individual ${id} not found`));
|
|
8292
|
+
process.exit(1);
|
|
8293
|
+
}
|
|
8294
|
+
showIndividual(tree, individual);
|
|
8295
|
+
} catch (error) {
|
|
8296
|
+
handleError(error, "Failed to show individual");
|
|
8297
|
+
}
|
|
8298
|
+
});
|
|
8299
|
+
}
|
|
8300
|
+
|
|
8301
|
+
// src/cli/commands/stats.ts
|
|
8302
|
+
function displayStats(tree, json = false) {
|
|
8303
|
+
const stats = tree.stats();
|
|
8304
|
+
if (json) {
|
|
8305
|
+
console.log(formatJson(stats));
|
|
8306
|
+
} else {
|
|
8307
|
+
console.log(formatHeader("GEDCOM File Statistics\n"));
|
|
8308
|
+
console.log(formatLabel("Total Individuals"));
|
|
8309
|
+
console.log(` ${formatCount(stats.totalIndividuals)}`);
|
|
8310
|
+
console.log(formatLabel("Total Families"));
|
|
8311
|
+
console.log(` ${formatCount(stats.totalFamilies)}`);
|
|
8312
|
+
console.log();
|
|
8313
|
+
console.log(formatLabel("By Gender"));
|
|
8314
|
+
console.log(` Males: ${formatCount(stats.byGender.males)}`);
|
|
8315
|
+
console.log(` Females: ${formatCount(stats.byGender.females)}`);
|
|
8316
|
+
console.log(` Unknown: ${formatCount(stats.byGender.unknown)}`);
|
|
8317
|
+
console.log();
|
|
8318
|
+
if (stats.dateRange.earliest && stats.dateRange.latest) {
|
|
8319
|
+
console.log(formatLabel("Date Range"));
|
|
8320
|
+
console.log(
|
|
8321
|
+
` ${stats.dateRange.earliest} - ${stats.dateRange.latest}`
|
|
8322
|
+
);
|
|
8323
|
+
console.log();
|
|
8324
|
+
}
|
|
8325
|
+
if (stats.averageLifespan) {
|
|
8326
|
+
console.log(formatLabel("Average Lifespan"));
|
|
8327
|
+
console.log(` ${stats.averageLifespan.toFixed(1)} years`);
|
|
8328
|
+
console.log();
|
|
8329
|
+
}
|
|
8330
|
+
if (stats.topSurnames.length > 0) {
|
|
8331
|
+
console.log(formatLabel("Most Common Surnames"));
|
|
8332
|
+
stats.topSurnames.forEach(({ surname, count }) => {
|
|
8333
|
+
console.log(` ${surname}: ${formatCount(count)}`);
|
|
8334
|
+
});
|
|
8335
|
+
console.log();
|
|
8336
|
+
}
|
|
8337
|
+
if (stats.topBirthPlaces.length > 0) {
|
|
8338
|
+
console.log(formatLabel("Most Common Birth Places"));
|
|
8339
|
+
stats.topBirthPlaces.slice(0, 5).forEach(({ place, count }) => {
|
|
8340
|
+
console.log(` ${place}: ${formatCount(count)}`);
|
|
8341
|
+
});
|
|
8342
|
+
}
|
|
8343
|
+
}
|
|
8344
|
+
}
|
|
8345
|
+
function registerStatsCommand(program2) {
|
|
8346
|
+
program2.command("stats <file>").description("Generate statistics about a GEDCOM file").option("-j, --json", "Output in JSON format").action((file, options) => {
|
|
8347
|
+
try {
|
|
8348
|
+
const content = readGedcomFile(file);
|
|
8349
|
+
const { gedcom: tree } = parser_default.parse(content);
|
|
8350
|
+
displayStats(tree, options.json);
|
|
8351
|
+
} catch (error) {
|
|
8352
|
+
handleError(error, "Failed to generate statistics");
|
|
8353
|
+
}
|
|
8354
|
+
});
|
|
8355
|
+
}
|
|
8356
|
+
|
|
8357
|
+
// src/cli/repl.ts
|
|
8358
|
+
function parseArgs(line) {
|
|
8359
|
+
const args = [];
|
|
8360
|
+
let current = "";
|
|
8361
|
+
let inQuotes = false;
|
|
8362
|
+
let quoteChar = "";
|
|
8363
|
+
for (let i = 0; i < line.length; i++) {
|
|
8364
|
+
const char = line[i];
|
|
8365
|
+
if ((char === '"' || char === "'") && !inQuotes) {
|
|
8366
|
+
inQuotes = true;
|
|
8367
|
+
quoteChar = char;
|
|
8368
|
+
} else if (char === quoteChar && inQuotes) {
|
|
8369
|
+
inQuotes = false;
|
|
8370
|
+
quoteChar = "";
|
|
8371
|
+
} else if (char === " " && !inQuotes) {
|
|
8372
|
+
if (current) {
|
|
8373
|
+
args.push(current);
|
|
8374
|
+
current = "";
|
|
8375
|
+
}
|
|
8376
|
+
} else {
|
|
8377
|
+
current += char;
|
|
8378
|
+
}
|
|
8379
|
+
}
|
|
8380
|
+
if (current) {
|
|
8381
|
+
args.push(current);
|
|
8382
|
+
}
|
|
8383
|
+
return args;
|
|
8384
|
+
}
|
|
8385
|
+
var GedcomRepl = class {
|
|
8386
|
+
constructor(tree) {
|
|
8387
|
+
this.context = { tree };
|
|
8388
|
+
this.rl = readline.createInterface({
|
|
8389
|
+
input: process.stdin,
|
|
8390
|
+
output: process.stdout,
|
|
8391
|
+
prompt: chalk.blue("gedcom> ")
|
|
8392
|
+
});
|
|
8393
|
+
this.setupHandlers();
|
|
8394
|
+
}
|
|
8395
|
+
setupHandlers() {
|
|
8396
|
+
this.rl.on("line", (line) => {
|
|
8397
|
+
this.handleCommand(line.trim());
|
|
8398
|
+
});
|
|
8399
|
+
this.rl.on("close", () => {
|
|
8400
|
+
console.log("\nGoodbye!");
|
|
8401
|
+
process.exit(0);
|
|
8402
|
+
});
|
|
8403
|
+
}
|
|
8404
|
+
handleCommand(line) {
|
|
8405
|
+
if (!line) {
|
|
8406
|
+
this.rl.prompt();
|
|
8407
|
+
return;
|
|
8408
|
+
}
|
|
8409
|
+
const args = parseArgs(line);
|
|
8410
|
+
const command = args[0];
|
|
8411
|
+
const commandArgs = args.slice(1);
|
|
8412
|
+
switch (command.toLowerCase()) {
|
|
8413
|
+
case "help":
|
|
8414
|
+
this.showHelp(commandArgs[0]);
|
|
8415
|
+
break;
|
|
8416
|
+
case "stats":
|
|
8417
|
+
displayStats(this.context.tree, commandArgs.includes("--json"));
|
|
8418
|
+
break;
|
|
8419
|
+
case "find":
|
|
8420
|
+
this.handleFind(commandArgs);
|
|
8421
|
+
break;
|
|
8422
|
+
case "select":
|
|
8423
|
+
this.handleSelect(commandArgs);
|
|
8424
|
+
break;
|
|
8425
|
+
case "show":
|
|
8426
|
+
this.handleShow(commandArgs);
|
|
8427
|
+
break;
|
|
8428
|
+
case "get":
|
|
8429
|
+
this.handleGet(commandArgs);
|
|
8430
|
+
break;
|
|
8431
|
+
case "relatives":
|
|
8432
|
+
this.handleRelatives(commandArgs);
|
|
8433
|
+
break;
|
|
8434
|
+
case "validate":
|
|
8435
|
+
this.handleValidate(commandArgs);
|
|
8436
|
+
break;
|
|
8437
|
+
case "clear":
|
|
8438
|
+
console.clear();
|
|
8439
|
+
break;
|
|
8440
|
+
case "exit":
|
|
8441
|
+
case "quit":
|
|
8442
|
+
this.rl.close();
|
|
8443
|
+
return;
|
|
8444
|
+
default:
|
|
8445
|
+
console.log(formatError(`Unknown command: ${command}`));
|
|
8446
|
+
console.log('Type "help" for available commands');
|
|
8447
|
+
}
|
|
8448
|
+
this.rl.prompt();
|
|
8449
|
+
}
|
|
8450
|
+
showHelp(command) {
|
|
8451
|
+
if (command) {
|
|
8452
|
+
switch (command.toLowerCase()) {
|
|
8453
|
+
case "stats":
|
|
8454
|
+
console.log(formatHeader("\nstats"));
|
|
8455
|
+
console.log("Show GEDCOM file statistics\n");
|
|
8456
|
+
console.log("Usage: stats [--json]");
|
|
8457
|
+
console.log("\nOptions:");
|
|
8458
|
+
console.log(" --json Output in JSON format");
|
|
8459
|
+
break;
|
|
8460
|
+
case "find":
|
|
8461
|
+
console.log(formatHeader("\nfind"));
|
|
8462
|
+
console.log("Search for individuals in the tree\n");
|
|
8463
|
+
console.log("Usage: find <query> [options]");
|
|
8464
|
+
console.log("\nArguments:");
|
|
8465
|
+
console.log(" <query> Search query (name or ID)");
|
|
8466
|
+
console.log("\nOptions:");
|
|
8467
|
+
console.log(" --birth-year <year> Filter by birth year");
|
|
8468
|
+
console.log(" --death-year <year> Filter by death year");
|
|
8469
|
+
console.log(
|
|
8470
|
+
" --json Output in JSON format"
|
|
8471
|
+
);
|
|
8472
|
+
break;
|
|
8473
|
+
case "select":
|
|
8474
|
+
console.log(formatHeader("\nselect"));
|
|
8475
|
+
console.log("Select an individual by ID or index\n");
|
|
8476
|
+
console.log("Usage: select <id|index>");
|
|
8477
|
+
console.log("\nArguments:");
|
|
8478
|
+
console.log(
|
|
8479
|
+
" <id|index> Individual ID (e.g., @I123@) or search result index (e.g., 1)"
|
|
8480
|
+
);
|
|
8481
|
+
break;
|
|
8482
|
+
case "show":
|
|
8483
|
+
console.log(formatHeader("\nshow"));
|
|
8484
|
+
console.log(
|
|
8485
|
+
"Show detailed information about an individual\n"
|
|
8486
|
+
);
|
|
8487
|
+
console.log("Usage: show [id]");
|
|
8488
|
+
console.log("\nArguments:");
|
|
8489
|
+
console.log(
|
|
8490
|
+
" [id] Optional individual ID. If not provided, shows currently selected person"
|
|
8491
|
+
);
|
|
8492
|
+
break;
|
|
8493
|
+
case "get":
|
|
8494
|
+
console.log(formatHeader("\nget"));
|
|
8495
|
+
console.log("Get a value from a GEDCOM record\n");
|
|
8496
|
+
console.log("Usage: get [id] [options]");
|
|
8497
|
+
console.log("\nArguments:");
|
|
8498
|
+
console.log(
|
|
8499
|
+
" [id] Record ID (e.g., @I123@, @F456@). If not provided, uses selected person"
|
|
8500
|
+
);
|
|
8501
|
+
console.log("\nOptions:");
|
|
8502
|
+
console.log(
|
|
8503
|
+
' --path, -p <path> Dot-separated path (e.g., "BIRT.PLAC", "NAME")'
|
|
8504
|
+
);
|
|
8505
|
+
console.log(" --json, -j Output in JSON format");
|
|
8506
|
+
console.log(
|
|
8507
|
+
" --raw, -r Output raw value only (no formatting)"
|
|
8508
|
+
);
|
|
8509
|
+
console.log("\nExamples:");
|
|
8510
|
+
console.log(' get --path "BIRT.PLAC"');
|
|
8511
|
+
console.log(' get @I123@ --path "NAME"');
|
|
8512
|
+
console.log(" get @I123@ --json");
|
|
8513
|
+
break;
|
|
8514
|
+
case "relatives":
|
|
8515
|
+
console.log(formatHeader("\nrelatives"));
|
|
8516
|
+
console.log(
|
|
8517
|
+
"Get ancestors and/or descendants of the selected individual\n"
|
|
8518
|
+
);
|
|
8519
|
+
console.log("Usage: relatives [options]");
|
|
8520
|
+
console.log("\nOptions:");
|
|
8521
|
+
console.log(" --ancestors, -a Include ancestors");
|
|
8522
|
+
console.log(" --descendants, -d Include descendants");
|
|
8523
|
+
console.log(
|
|
8524
|
+
" --tree, -t Include both ancestors and descendants"
|
|
8525
|
+
);
|
|
8526
|
+
console.log(
|
|
8527
|
+
" --depth <n> Limit depth (generations, default: 999)"
|
|
8528
|
+
);
|
|
8529
|
+
console.log(" --json, -j Output in JSON format");
|
|
8530
|
+
console.log(
|
|
8531
|
+
"\nNote: You must select an individual first using 'select'"
|
|
8532
|
+
);
|
|
8533
|
+
console.log("\nExamples:");
|
|
8534
|
+
console.log(" relatives --ancestors");
|
|
8535
|
+
console.log(" relatives --descendants --depth 3");
|
|
8536
|
+
console.log(" relatives --tree --json");
|
|
8537
|
+
break;
|
|
8538
|
+
case "validate":
|
|
8539
|
+
console.log(formatHeader("\nvalidate"));
|
|
8540
|
+
console.log("Validate the GEDCOM file\n");
|
|
8541
|
+
console.log("Usage: validate [options]");
|
|
8542
|
+
console.log("\nOptions:");
|
|
8543
|
+
console.log(" --json, -j Output in JSON format");
|
|
8544
|
+
console.log(" --strict, -s Enable strict validation");
|
|
8545
|
+
console.log("\nExamples:");
|
|
8546
|
+
console.log(" validate");
|
|
8547
|
+
console.log(" validate --strict --json");
|
|
8548
|
+
break;
|
|
8549
|
+
case "clear":
|
|
8550
|
+
console.log(formatHeader("\nclear"));
|
|
8551
|
+
console.log("Clear the screen\n");
|
|
8552
|
+
console.log("Usage: clear");
|
|
8553
|
+
break;
|
|
8554
|
+
case "exit":
|
|
8555
|
+
case "quit":
|
|
8556
|
+
console.log(formatHeader("\nexit"));
|
|
8557
|
+
console.log("Exit the REPL\n");
|
|
8558
|
+
console.log("Usage: exit");
|
|
8559
|
+
console.log("Alias: quit");
|
|
8560
|
+
break;
|
|
8561
|
+
default:
|
|
8562
|
+
console.log(formatError(`Unknown command: ${command}`));
|
|
8563
|
+
console.log('Type "help" for available commands');
|
|
8564
|
+
}
|
|
8565
|
+
console.log("");
|
|
8566
|
+
return;
|
|
8567
|
+
}
|
|
8568
|
+
console.log(formatHeader("\nAvailable Commands:"));
|
|
8569
|
+
console.log("");
|
|
8570
|
+
console.log(
|
|
8571
|
+
chalk.cyan(" stats [--json]") + " - Show tree statistics"
|
|
8572
|
+
);
|
|
8573
|
+
console.log(
|
|
8574
|
+
chalk.cyan(" find <query> [options]") + " - Search for individuals"
|
|
8575
|
+
);
|
|
8576
|
+
console.log(
|
|
8577
|
+
chalk.cyan(" select <id|index>") + " - Select an individual"
|
|
8578
|
+
);
|
|
8579
|
+
console.log(
|
|
8580
|
+
chalk.cyan(" show [id]") + " - Show details (current or specified person)"
|
|
8581
|
+
);
|
|
8582
|
+
console.log(
|
|
8583
|
+
chalk.cyan(" get [id] [options]") + " - Get a value from a record"
|
|
8584
|
+
);
|
|
8585
|
+
console.log(
|
|
8586
|
+
chalk.cyan(" relatives [options]") + " - Get ancestors/descendants of selected person"
|
|
8587
|
+
);
|
|
8588
|
+
console.log(
|
|
8589
|
+
chalk.cyan(" validate [options]") + " - Validate the GEDCOM file"
|
|
8590
|
+
);
|
|
8591
|
+
console.log(
|
|
8592
|
+
chalk.cyan(" clear") + " - Clear screen"
|
|
8593
|
+
);
|
|
8594
|
+
console.log(
|
|
8595
|
+
chalk.cyan(" help [command]") + " - Show this help or help for a specific command"
|
|
8596
|
+
);
|
|
8597
|
+
console.log(
|
|
8598
|
+
chalk.cyan(" exit") + " - Exit REPL"
|
|
8599
|
+
);
|
|
8600
|
+
console.log("");
|
|
8601
|
+
}
|
|
8602
|
+
handleFind(args) {
|
|
8603
|
+
if (args.length === 0) {
|
|
8604
|
+
console.log(formatError("Usage: find <query> [options]"));
|
|
8605
|
+
console.log('Type "help find" for more information');
|
|
8606
|
+
return;
|
|
8607
|
+
}
|
|
8608
|
+
const isJson = args.includes("--json");
|
|
8609
|
+
let birthYear;
|
|
8610
|
+
let deathYear;
|
|
8611
|
+
const birthYearIndex = args.indexOf("--birth-year");
|
|
8612
|
+
if (birthYearIndex !== -1 && birthYearIndex + 1 < args.length) {
|
|
8613
|
+
birthYear = args[birthYearIndex + 1];
|
|
8614
|
+
}
|
|
8615
|
+
const deathYearIndex = args.indexOf("--death-year");
|
|
8616
|
+
if (deathYearIndex !== -1 && deathYearIndex + 1 < args.length) {
|
|
8617
|
+
deathYear = args[deathYearIndex + 1];
|
|
8618
|
+
}
|
|
8619
|
+
const query = args.filter((a, i) => {
|
|
8620
|
+
if (a.startsWith("--")) return false;
|
|
8621
|
+
if (i > 0 && args[i - 1].startsWith("--")) return false;
|
|
8622
|
+
return true;
|
|
8623
|
+
}).join(" ");
|
|
8624
|
+
const results = findIndividuals(this.context.tree, {
|
|
8625
|
+
query: query || void 0,
|
|
8626
|
+
birthYear,
|
|
8627
|
+
deathYear
|
|
8628
|
+
});
|
|
8629
|
+
this.context.searchResults = results;
|
|
8630
|
+
formatFindResults(results, isJson);
|
|
8631
|
+
if (results.length > 0 && !isJson) {
|
|
8632
|
+
console.log(
|
|
8633
|
+
chalk.dim(
|
|
8634
|
+
'\nTip: Use "select <number>" to select an individual from results'
|
|
8635
|
+
)
|
|
8636
|
+
);
|
|
8637
|
+
}
|
|
8638
|
+
}
|
|
8639
|
+
handleSelect(args) {
|
|
8640
|
+
if (args.length === 0) {
|
|
8641
|
+
console.log(formatError("Usage: select <id|index>"));
|
|
8642
|
+
return;
|
|
8643
|
+
}
|
|
8644
|
+
const input = args[0];
|
|
8645
|
+
const individual = selectIndividual(
|
|
8646
|
+
this.context.tree,
|
|
8647
|
+
input,
|
|
8648
|
+
this.context.searchResults
|
|
8649
|
+
);
|
|
8650
|
+
formatSelectResult(individual, input);
|
|
8651
|
+
if (individual) {
|
|
8652
|
+
this.context.selectedPerson = individual;
|
|
8653
|
+
console.log(chalk.dim('Tip: Use "show" to see details'));
|
|
8654
|
+
}
|
|
8655
|
+
}
|
|
8656
|
+
handleShow(args) {
|
|
8657
|
+
let individual;
|
|
8658
|
+
if (args.length > 0) {
|
|
8659
|
+
individual = this.context.tree.indi(args[0]);
|
|
8660
|
+
if (!individual) {
|
|
8661
|
+
console.log(formatError(`Individual ${args[0]} not found`));
|
|
8662
|
+
return;
|
|
8663
|
+
}
|
|
8664
|
+
} else {
|
|
8665
|
+
individual = this.context.selectedPerson;
|
|
8666
|
+
if (!individual) {
|
|
8667
|
+
console.log(
|
|
8668
|
+
formatError(
|
|
8669
|
+
'No person selected. Use "select <id>" first or "show <id>"'
|
|
8670
|
+
)
|
|
8671
|
+
);
|
|
8672
|
+
return;
|
|
8673
|
+
}
|
|
8674
|
+
}
|
|
8675
|
+
showIndividual(this.context.tree, individual);
|
|
8676
|
+
}
|
|
8677
|
+
handleGet(args) {
|
|
8678
|
+
if (args.length === 0) {
|
|
8679
|
+
console.log(
|
|
8680
|
+
formatError("Usage: get <id> [--path <path>] [--json] [--raw]")
|
|
8681
|
+
);
|
|
8682
|
+
console.log('Type "help get" for more information');
|
|
8683
|
+
return;
|
|
8684
|
+
}
|
|
8685
|
+
let id;
|
|
8686
|
+
let path;
|
|
8687
|
+
let json = false;
|
|
8688
|
+
let raw = false;
|
|
8689
|
+
for (let i = 0; i < args.length; i++) {
|
|
8690
|
+
const arg = args[i];
|
|
8691
|
+
if (arg === "--path" || arg === "-p") {
|
|
8692
|
+
if (i + 1 < args.length) {
|
|
8693
|
+
path = args[i + 1];
|
|
8694
|
+
i++;
|
|
8695
|
+
}
|
|
8696
|
+
} else if (arg === "--json" || arg === "-j") {
|
|
8697
|
+
json = true;
|
|
8698
|
+
} else if (arg === "--raw" || arg === "-r") {
|
|
8699
|
+
raw = true;
|
|
8700
|
+
} else if (!arg.startsWith("-") && !id) {
|
|
8701
|
+
id = arg;
|
|
8702
|
+
}
|
|
8703
|
+
}
|
|
8704
|
+
if (!id) {
|
|
8705
|
+
if (this.context.selectedPerson) {
|
|
8706
|
+
id = this.context.selectedPerson.id;
|
|
8707
|
+
} else {
|
|
8708
|
+
console.log(
|
|
8709
|
+
formatError("No ID specified and no person selected")
|
|
8710
|
+
);
|
|
8711
|
+
return;
|
|
8712
|
+
}
|
|
8713
|
+
}
|
|
8714
|
+
try {
|
|
8715
|
+
const result = getValue(this.context.tree, id, path, json, raw);
|
|
8716
|
+
console.log(result);
|
|
8717
|
+
} catch (error) {
|
|
8718
|
+
console.log(formatError(error.message));
|
|
8719
|
+
}
|
|
8720
|
+
}
|
|
8721
|
+
handleRelatives(args) {
|
|
8722
|
+
if (!this.context.selectedPerson) {
|
|
8723
|
+
console.log(
|
|
8724
|
+
formatError('No person selected. Use "select <id>" first')
|
|
8725
|
+
);
|
|
8726
|
+
return;
|
|
8727
|
+
}
|
|
8728
|
+
const isJson = args.includes("--json") || args.includes("-j");
|
|
8729
|
+
const includeAncestors = args.includes("--ancestors") || args.includes("-a") || args.includes("--tree") || args.includes("-t");
|
|
8730
|
+
const includeDescendants = args.includes("--descendants") || args.includes("-d") || args.includes("--tree") || args.includes("-t");
|
|
8731
|
+
if (!includeAncestors && !includeDescendants) {
|
|
8732
|
+
console.log(
|
|
8733
|
+
formatError(
|
|
8734
|
+
"Must specify --ancestors, --descendants, or --tree"
|
|
8735
|
+
)
|
|
8736
|
+
);
|
|
8737
|
+
console.log('Type "help relatives" for more information');
|
|
8738
|
+
return;
|
|
8739
|
+
}
|
|
8740
|
+
let maxDepth = 999;
|
|
8741
|
+
const depthIndex = args.findIndex((arg) => arg === "--depth");
|
|
8742
|
+
if (depthIndex !== -1 && depthIndex + 1 < args.length) {
|
|
8743
|
+
maxDepth = parseInt(args[depthIndex + 1], 10);
|
|
8744
|
+
if (isNaN(maxDepth) || maxDepth < 1) {
|
|
8745
|
+
console.log(formatError("Invalid depth value"));
|
|
8746
|
+
return;
|
|
8747
|
+
}
|
|
8748
|
+
}
|
|
8749
|
+
const individual = this.context.selectedPerson;
|
|
8750
|
+
const relatives = /* @__PURE__ */ new Set();
|
|
8751
|
+
relatives.add(individual.id);
|
|
8752
|
+
if (includeAncestors) {
|
|
8753
|
+
const getAncestors = (indi, depth) => {
|
|
8754
|
+
if (depth > maxDepth) return;
|
|
8755
|
+
const parents = indi.getParents();
|
|
8756
|
+
parents?.forEach((parent) => {
|
|
8757
|
+
relatives.add(parent.id);
|
|
8758
|
+
getAncestors(parent, depth + 1);
|
|
8759
|
+
});
|
|
8760
|
+
};
|
|
8761
|
+
getAncestors(individual, 1);
|
|
8762
|
+
}
|
|
8763
|
+
if (includeDescendants) {
|
|
8764
|
+
const getDescendants = (indi, depth) => {
|
|
8765
|
+
if (depth > maxDepth) return;
|
|
8766
|
+
const children = indi.getChildren();
|
|
8767
|
+
children?.forEach((child) => {
|
|
8768
|
+
relatives.add(child.id);
|
|
8769
|
+
getDescendants(child, depth + 1);
|
|
8770
|
+
});
|
|
8771
|
+
};
|
|
8772
|
+
getDescendants(individual, 1);
|
|
8773
|
+
}
|
|
8774
|
+
const allRelatives = Array.from(relatives).map((relId) => this.context.tree.indi(relId)).filter((indi) => indi !== null);
|
|
8775
|
+
if (isJson) {
|
|
8776
|
+
const jsonData = allRelatives.map((indi) => ({
|
|
8777
|
+
id: indi.id,
|
|
8778
|
+
name: cleanGedcomName(indi.NAME?.toValue()),
|
|
8779
|
+
birthDate: indi.BIRT?.DATE?.toValue() || null,
|
|
8780
|
+
deathDate: indi.DEAT?.DATE?.toValue() || null
|
|
8781
|
+
}));
|
|
8782
|
+
console.log(
|
|
8783
|
+
formatJson({ count: jsonData.length, individuals: jsonData })
|
|
8784
|
+
);
|
|
8785
|
+
} else {
|
|
8786
|
+
console.log(
|
|
8787
|
+
formatHeader(`Found ${allRelatives.length} relative(s)
|
|
8788
|
+
`)
|
|
8789
|
+
);
|
|
8790
|
+
allRelatives.forEach((indi) => {
|
|
8791
|
+
const name = cleanGedcomName(indi.NAME?.toValue());
|
|
8792
|
+
const lifespan = formatLifespan(
|
|
8793
|
+
indi.BIRT?.DATE?.toValue(),
|
|
8794
|
+
indi.DEAT?.DATE?.toValue()
|
|
8795
|
+
);
|
|
8796
|
+
console.log(
|
|
8797
|
+
formatListItem(
|
|
8798
|
+
`${formatId(indi.id)} ${formatName(name)} ${lifespan}`
|
|
8799
|
+
)
|
|
8800
|
+
);
|
|
8801
|
+
});
|
|
8802
|
+
}
|
|
8803
|
+
}
|
|
8804
|
+
handleValidate(args) {
|
|
8805
|
+
const isJson = args.includes("--json") || args.includes("-j");
|
|
8806
|
+
const isStrict = args.includes("--strict") || args.includes("-s");
|
|
8807
|
+
const errors = [];
|
|
8808
|
+
const warnings = [];
|
|
8809
|
+
const header = this.context.tree.HEAD;
|
|
8810
|
+
const version = header?.GEDC?.VERS?.value || "Unknown";
|
|
8811
|
+
const individuals = this.context.tree.indis();
|
|
8812
|
+
const families = this.context.tree.fams();
|
|
8813
|
+
individuals.forEach((indi) => {
|
|
8814
|
+
if (!indi.NAME?.toValue()) {
|
|
8815
|
+
warnings.push(`Individual ${indi.id} is missing a name`);
|
|
8816
|
+
}
|
|
8817
|
+
});
|
|
8818
|
+
let missingBirthDates = 0;
|
|
8819
|
+
individuals.forEach((indi) => {
|
|
8820
|
+
if (!indi.BIRT?.DATE?.toValue()) {
|
|
8821
|
+
missingBirthDates++;
|
|
8822
|
+
}
|
|
8823
|
+
});
|
|
8824
|
+
let missingDeathDates = 0;
|
|
8825
|
+
individuals.forEach((indi) => {
|
|
8826
|
+
if (indi.DEAT && !indi.DEAT.DATE?.toValue()) {
|
|
8827
|
+
missingDeathDates++;
|
|
8828
|
+
}
|
|
8829
|
+
});
|
|
8830
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
8831
|
+
const duplicateIds = [];
|
|
8832
|
+
const checkDuplicates = (item) => {
|
|
8833
|
+
if (seenIds.has(item.id)) {
|
|
8834
|
+
duplicateIds.push(item.id);
|
|
8835
|
+
errors.push(`Duplicate ID found: ${item.id}`);
|
|
8836
|
+
}
|
|
8837
|
+
seenIds.add(item.id);
|
|
8838
|
+
};
|
|
8839
|
+
individuals.forEach(checkDuplicates);
|
|
8840
|
+
families.forEach(checkDuplicates);
|
|
8841
|
+
families.forEach((fam) => {
|
|
8842
|
+
const husb = fam.HUSB?.value;
|
|
8843
|
+
const wife = fam.WIFE?.value;
|
|
8844
|
+
if (!husb && !wife) {
|
|
8845
|
+
warnings.push(`Family ${fam.id} has no husband or wife`);
|
|
8846
|
+
}
|
|
8847
|
+
if (husb && !this.context.tree.indi(husb)) {
|
|
8848
|
+
errors.push(
|
|
8849
|
+
`Family ${fam.id} references non-existent husband ${husb}`
|
|
8850
|
+
);
|
|
8851
|
+
}
|
|
8852
|
+
if (wife && !this.context.tree.indi(wife)) {
|
|
8853
|
+
errors.push(
|
|
8854
|
+
`Family ${fam.id} references non-existent wife ${wife}`
|
|
8855
|
+
);
|
|
8856
|
+
}
|
|
8857
|
+
});
|
|
8858
|
+
if (isStrict) {
|
|
8859
|
+
individuals.forEach((indi) => {
|
|
8860
|
+
const birthDate = indi.BIRT?.DATE?.toValue();
|
|
8861
|
+
const deathDate = indi.DEAT?.DATE?.toValue();
|
|
8862
|
+
if (birthDate && birthDate.includes("INVALID")) {
|
|
8863
|
+
errors.push(`Invalid birth date format for ${indi.id}`);
|
|
8864
|
+
}
|
|
8865
|
+
if (deathDate && deathDate.includes("INVALID")) {
|
|
8866
|
+
errors.push(`Invalid death date format for ${indi.id}`);
|
|
8867
|
+
}
|
|
8868
|
+
});
|
|
8869
|
+
}
|
|
8870
|
+
const result = {
|
|
8871
|
+
valid: errors.length === 0,
|
|
8872
|
+
version,
|
|
8873
|
+
errors,
|
|
8874
|
+
warnings
|
|
8875
|
+
};
|
|
8876
|
+
if (isJson) {
|
|
8877
|
+
console.log(formatJson(result));
|
|
8878
|
+
} else {
|
|
8879
|
+
if (result.valid) {
|
|
8880
|
+
console.log(formatSuccess(`Valid GEDCOM ${version} file`));
|
|
8881
|
+
} else {
|
|
8882
|
+
console.log(
|
|
8883
|
+
formatError(
|
|
8884
|
+
`Invalid GEDCOM file - ${errors.length} error(s) found`
|
|
8885
|
+
)
|
|
8886
|
+
);
|
|
8887
|
+
}
|
|
8888
|
+
console.log();
|
|
8889
|
+
console.log(formatHeader("Validation Summary"));
|
|
8890
|
+
console.log(
|
|
8891
|
+
`${formatError("Errors:")} ${formatCount(errors.length)}`
|
|
8892
|
+
);
|
|
8893
|
+
console.log(
|
|
8894
|
+
`${formatWarning("Warnings:")} ${formatCount(warnings.length)}`
|
|
8895
|
+
);
|
|
8896
|
+
if (errors.length > 0) {
|
|
8897
|
+
console.log();
|
|
8898
|
+
console.log(formatHeader("Errors"));
|
|
8899
|
+
errors.slice(0, 10).forEach((error) => {
|
|
8900
|
+
console.log(formatListItem(formatError(error)));
|
|
8901
|
+
});
|
|
8902
|
+
if (errors.length > 10) {
|
|
8903
|
+
console.log(
|
|
8904
|
+
formatListItem(
|
|
8905
|
+
`... and ${errors.length - 10} more errors`
|
|
8906
|
+
)
|
|
8907
|
+
);
|
|
8908
|
+
}
|
|
8909
|
+
}
|
|
8910
|
+
if (warnings.length > 0) {
|
|
8911
|
+
console.log();
|
|
8912
|
+
console.log(formatHeader("Warnings"));
|
|
8913
|
+
if (missingBirthDates > 0) {
|
|
8914
|
+
console.log(
|
|
8915
|
+
formatListItem(
|
|
8916
|
+
formatWarning(
|
|
8917
|
+
`Missing birth dates: ${missingBirthDates} individuals`
|
|
8918
|
+
)
|
|
8919
|
+
)
|
|
8920
|
+
);
|
|
8921
|
+
}
|
|
8922
|
+
if (missingDeathDates > 0) {
|
|
8923
|
+
console.log(
|
|
8924
|
+
formatListItem(
|
|
8925
|
+
formatWarning(
|
|
8926
|
+
`Missing death dates: ${missingDeathDates} individuals`
|
|
8927
|
+
)
|
|
8928
|
+
)
|
|
8929
|
+
);
|
|
8930
|
+
}
|
|
8931
|
+
if (duplicateIds.length > 0) {
|
|
8932
|
+
console.log(
|
|
8933
|
+
formatListItem(
|
|
8934
|
+
formatWarning(
|
|
8935
|
+
`Duplicate IDs: ${duplicateIds.length}`
|
|
8936
|
+
)
|
|
8937
|
+
)
|
|
8938
|
+
);
|
|
8939
|
+
}
|
|
8940
|
+
const otherWarnings = warnings.filter(
|
|
8941
|
+
(w) => !w.includes("birth") && !w.includes("death")
|
|
8942
|
+
);
|
|
8943
|
+
otherWarnings.slice(0, 5).forEach((warning) => {
|
|
8944
|
+
console.log(formatListItem(formatWarning(warning)));
|
|
8945
|
+
});
|
|
8946
|
+
if (otherWarnings.length > 5) {
|
|
8947
|
+
console.log(
|
|
8948
|
+
formatListItem(
|
|
8949
|
+
`... and ${otherWarnings.length - 5} more warnings`
|
|
8950
|
+
)
|
|
8951
|
+
);
|
|
8952
|
+
}
|
|
8953
|
+
}
|
|
8954
|
+
}
|
|
8955
|
+
}
|
|
8956
|
+
start() {
|
|
8957
|
+
console.log(formatHeader("GEDCOM Interactive Explorer"));
|
|
8958
|
+
console.log("");
|
|
8959
|
+
console.log(chalk.dim('Type "help" for available commands'));
|
|
8960
|
+
console.log("");
|
|
8961
|
+
this.rl.prompt();
|
|
8962
|
+
}
|
|
8963
|
+
};
|
|
8964
|
+
|
|
8965
|
+
// src/cli/commands/open.ts
|
|
8966
|
+
function registerOpenCommand(program2) {
|
|
8967
|
+
program2.command("open <file>").description("Open GEDCOM file in interactive mode").action((file) => {
|
|
8968
|
+
try {
|
|
8969
|
+
const content = readGedcomFile(file);
|
|
8970
|
+
const { gedcom: tree } = parser_default.parse(content);
|
|
8971
|
+
const repl = new GedcomRepl(tree);
|
|
8972
|
+
repl.start();
|
|
8973
|
+
} catch (error) {
|
|
8974
|
+
handleError(error, "Failed to open GEDCOM file");
|
|
8975
|
+
}
|
|
8976
|
+
});
|
|
8977
|
+
}
|
|
7514
8978
|
function registerRelativesCommand(program2) {
|
|
7515
8979
|
program2.command("relatives <file> <id>").description("Get ancestors and/or descendants of an individual").option("-a, --ancestors", "Include ancestors").option("-d, --descendants", "Include descendants").option("-t, --tree", "Include both ancestors and descendants").option("--depth <n>", "Limit depth (generations)", "999").option("-o, --output <file>", "Save to new GEDCOM file").option("-j, --json", "Output in JSON format").action((file, id, options) => {
|
|
7516
8980
|
try {
|
|
@@ -7595,242 +9059,6 @@ function createSubsetGedcom(tree, individuals) {
|
|
|
7595
9059
|
return lines.join("\n");
|
|
7596
9060
|
}
|
|
7597
9061
|
|
|
7598
|
-
// src/cli/commands/show.ts
|
|
7599
|
-
function registerShowCommand(program2) {
|
|
7600
|
-
program2.command("show <file> <id>").description("Display detailed information about an individual").option("-j, --json", "Output in JSON format").option("-f, --format <format>", "Output format: text, json, markdown", "text").option("--include-events", "Include all life events").option("--include-sources", "Include sources").action((file, id, options) => {
|
|
7601
|
-
try {
|
|
7602
|
-
const content = readGedcomFile(file);
|
|
7603
|
-
const { gedcom: tree } = parser_default.parse(content);
|
|
7604
|
-
const individual = tree.indi(id);
|
|
7605
|
-
if (!individual) {
|
|
7606
|
-
console.error(formatError(`Individual ${id} not found`));
|
|
7607
|
-
process.exit(1);
|
|
7608
|
-
}
|
|
7609
|
-
const name = cleanGedcomName(individual.NAME?.toValue());
|
|
7610
|
-
const birthDate = individual.BIRT?.DATE?.toValue();
|
|
7611
|
-
const birthPlace = individual.BIRT?.PLAC?.value;
|
|
7612
|
-
const deathDate = individual.DEAT?.DATE?.toValue();
|
|
7613
|
-
const deathPlace = individual.DEAT?.PLAC?.value;
|
|
7614
|
-
const sex = individual.SEX?.value;
|
|
7615
|
-
const parents = individual.getParents();
|
|
7616
|
-
const father = parents?.find((p) => p.SEX?.value === "M");
|
|
7617
|
-
const mother = parents?.find((p) => p.SEX?.value === "F");
|
|
7618
|
-
const spouses = individual.getSpouses();
|
|
7619
|
-
const children = individual.getChildren();
|
|
7620
|
-
if (options.json || options.format === "json") {
|
|
7621
|
-
const jsonData = {
|
|
7622
|
-
id: individual.id,
|
|
7623
|
-
name,
|
|
7624
|
-
sex,
|
|
7625
|
-
birth: {
|
|
7626
|
-
date: birthDate || null,
|
|
7627
|
-
place: birthPlace || null
|
|
7628
|
-
},
|
|
7629
|
-
death: {
|
|
7630
|
-
date: deathDate || null,
|
|
7631
|
-
place: deathPlace || null
|
|
7632
|
-
},
|
|
7633
|
-
parents: {
|
|
7634
|
-
father: father ? {
|
|
7635
|
-
id: father.id,
|
|
7636
|
-
name: cleanGedcomName(father.NAME?.toValue())
|
|
7637
|
-
} : null,
|
|
7638
|
-
mother: mother ? {
|
|
7639
|
-
id: mother.id,
|
|
7640
|
-
name: cleanGedcomName(mother.NAME?.toValue())
|
|
7641
|
-
} : null
|
|
7642
|
-
},
|
|
7643
|
-
spouses: spouses.map((spouse) => ({
|
|
7644
|
-
id: spouse.id,
|
|
7645
|
-
name: cleanGedcomName(spouse.NAME?.toValue()),
|
|
7646
|
-
birthDate: spouse.BIRT?.DATE?.toValue() || null,
|
|
7647
|
-
deathDate: spouse.DEAT?.DATE?.toValue() || null
|
|
7648
|
-
})),
|
|
7649
|
-
children: children.map((child) => ({
|
|
7650
|
-
id: child.id,
|
|
7651
|
-
name: cleanGedcomName(child.NAME?.toValue()),
|
|
7652
|
-
birthDate: child.BIRT?.DATE?.toValue() || null,
|
|
7653
|
-
deathDate: child.DEAT?.DATE?.toValue() || null
|
|
7654
|
-
}))
|
|
7655
|
-
};
|
|
7656
|
-
console.log(formatJson(jsonData));
|
|
7657
|
-
} else if (options.format === "markdown") {
|
|
7658
|
-
console.log(`# ${formatId(individual.id)} ${name}
|
|
7659
|
-
`);
|
|
7660
|
-
if (birthDate || birthPlace) {
|
|
7661
|
-
console.log(`**Born:** ${birthDate || "?"}`);
|
|
7662
|
-
if (birthPlace) console.log(` in ${birthPlace}`);
|
|
7663
|
-
console.log();
|
|
7664
|
-
}
|
|
7665
|
-
if (deathDate || deathPlace) {
|
|
7666
|
-
console.log(`**Died:** ${deathDate || "?"}`);
|
|
7667
|
-
if (deathPlace) console.log(` in ${deathPlace}`);
|
|
7668
|
-
console.log();
|
|
7669
|
-
}
|
|
7670
|
-
if (father || mother) {
|
|
7671
|
-
console.log("## Parents\n");
|
|
7672
|
-
if (father) {
|
|
7673
|
-
const fatherName = cleanGedcomName(father.NAME?.toValue());
|
|
7674
|
-
const fatherLifespan = formatLifespan(
|
|
7675
|
-
father.BIRT?.DATE?.toValue(),
|
|
7676
|
-
father.DEAT?.DATE?.toValue()
|
|
7677
|
-
);
|
|
7678
|
-
console.log(`- **Father:** ${father.id} ${fatherName} ${fatherLifespan}`);
|
|
7679
|
-
}
|
|
7680
|
-
if (mother) {
|
|
7681
|
-
const motherName = cleanGedcomName(mother.NAME?.toValue());
|
|
7682
|
-
const motherLifespan = formatLifespan(
|
|
7683
|
-
mother.BIRT?.DATE?.toValue(),
|
|
7684
|
-
mother.DEAT?.DATE?.toValue()
|
|
7685
|
-
);
|
|
7686
|
-
console.log(`- **Mother:** ${mother.id} ${motherName} ${motherLifespan}`);
|
|
7687
|
-
}
|
|
7688
|
-
console.log();
|
|
7689
|
-
}
|
|
7690
|
-
if (spouses.length > 0) {
|
|
7691
|
-
console.log("## Spouses\n");
|
|
7692
|
-
spouses.forEach((spouse) => {
|
|
7693
|
-
const spouseName = cleanGedcomName(spouse.NAME?.toValue());
|
|
7694
|
-
const spouseLifespan = formatLifespan(
|
|
7695
|
-
spouse.BIRT?.DATE?.toValue(),
|
|
7696
|
-
spouse.DEAT?.DATE?.toValue()
|
|
7697
|
-
);
|
|
7698
|
-
console.log(`- ${spouse.id} ${spouseName} ${spouseLifespan}`);
|
|
7699
|
-
});
|
|
7700
|
-
console.log();
|
|
7701
|
-
}
|
|
7702
|
-
if (children.length > 0) {
|
|
7703
|
-
console.log("## Children\n");
|
|
7704
|
-
children.forEach((child) => {
|
|
7705
|
-
const childName = cleanGedcomName(child.NAME?.toValue());
|
|
7706
|
-
const childLifespan = formatLifespan(
|
|
7707
|
-
child.BIRT?.DATE?.toValue(),
|
|
7708
|
-
child.DEAT?.DATE?.toValue()
|
|
7709
|
-
);
|
|
7710
|
-
console.log(`- ${child.id} ${childName} ${childLifespan}`);
|
|
7711
|
-
});
|
|
7712
|
-
console.log();
|
|
7713
|
-
}
|
|
7714
|
-
} else {
|
|
7715
|
-
console.log(formatHeader(`${formatId(individual.id)} ${formatName(name)}
|
|
7716
|
-
`));
|
|
7717
|
-
if (sex) {
|
|
7718
|
-
console.log(`${formatLabel("Sex")} ${formatValue(sex === "M" ? "Male" : sex === "F" ? "Female" : sex)}`);
|
|
7719
|
-
}
|
|
7720
|
-
if (birthDate || birthPlace) {
|
|
7721
|
-
console.log(`${formatLabel("Born")} ${formatDate(birthDate)}`);
|
|
7722
|
-
if (birthPlace) {
|
|
7723
|
-
console.log(`${formatLabel("Birth Place")} ${formatPlace(birthPlace)}`);
|
|
7724
|
-
}
|
|
7725
|
-
}
|
|
7726
|
-
if (deathDate || deathPlace) {
|
|
7727
|
-
console.log(`${formatLabel("Died")} ${formatDate(deathDate)}`);
|
|
7728
|
-
if (deathPlace) {
|
|
7729
|
-
console.log(`${formatLabel("Death Place")} ${formatPlace(deathPlace)}`);
|
|
7730
|
-
}
|
|
7731
|
-
}
|
|
7732
|
-
if (father || mother) {
|
|
7733
|
-
console.log();
|
|
7734
|
-
console.log(formatHeader("Parents"));
|
|
7735
|
-
if (father) {
|
|
7736
|
-
const fatherName = cleanGedcomName(father.NAME?.toValue());
|
|
7737
|
-
const fatherLifespan = formatLifespan(
|
|
7738
|
-
father.BIRT?.DATE?.toValue(),
|
|
7739
|
-
father.DEAT?.DATE?.toValue()
|
|
7740
|
-
);
|
|
7741
|
-
console.log(formatListItem(`${formatLabel("Father")} ${formatId(father.id)} ${formatName(fatherName)} ${fatherLifespan}`));
|
|
7742
|
-
}
|
|
7743
|
-
if (mother) {
|
|
7744
|
-
const motherName = cleanGedcomName(mother.NAME?.toValue());
|
|
7745
|
-
const motherLifespan = formatLifespan(
|
|
7746
|
-
mother.BIRT?.DATE?.toValue(),
|
|
7747
|
-
mother.DEAT?.DATE?.toValue()
|
|
7748
|
-
);
|
|
7749
|
-
console.log(formatListItem(`${formatLabel("Mother")} ${formatId(mother.id)} ${formatName(motherName)} ${motherLifespan}`));
|
|
7750
|
-
}
|
|
7751
|
-
}
|
|
7752
|
-
if (spouses.length > 0) {
|
|
7753
|
-
console.log();
|
|
7754
|
-
console.log(formatHeader("Spouses"));
|
|
7755
|
-
spouses.forEach((spouse) => {
|
|
7756
|
-
const spouseName = cleanGedcomName(spouse.NAME?.toValue());
|
|
7757
|
-
const spouseLifespan = formatLifespan(
|
|
7758
|
-
spouse.BIRT?.DATE?.toValue(),
|
|
7759
|
-
spouse.DEAT?.DATE?.toValue()
|
|
7760
|
-
);
|
|
7761
|
-
console.log(formatListItem(`${formatId(spouse.id)} ${formatName(spouseName)} ${spouseLifespan}`));
|
|
7762
|
-
});
|
|
7763
|
-
}
|
|
7764
|
-
if (children.length > 0) {
|
|
7765
|
-
console.log();
|
|
7766
|
-
console.log(formatHeader("Children"));
|
|
7767
|
-
children.forEach((child) => {
|
|
7768
|
-
const childName = cleanGedcomName(child.NAME?.toValue());
|
|
7769
|
-
const childLifespan = formatLifespan(
|
|
7770
|
-
child.BIRT?.DATE?.toValue(),
|
|
7771
|
-
child.DEAT?.DATE?.toValue()
|
|
7772
|
-
);
|
|
7773
|
-
console.log(formatListItem(`${formatId(child.id)} ${formatName(childName)} ${childLifespan}`));
|
|
7774
|
-
});
|
|
7775
|
-
}
|
|
7776
|
-
}
|
|
7777
|
-
} catch (error) {
|
|
7778
|
-
handleError(error, "Failed to show individual details");
|
|
7779
|
-
}
|
|
7780
|
-
});
|
|
7781
|
-
}
|
|
7782
|
-
|
|
7783
|
-
// src/cli/commands/stats.ts
|
|
7784
|
-
function registerStatsCommand(program2) {
|
|
7785
|
-
program2.command("stats <file>").description("Generate statistics about a GEDCOM file").option("-j, --json", "Output in JSON format").action((file, options) => {
|
|
7786
|
-
try {
|
|
7787
|
-
const content = readGedcomFile(file);
|
|
7788
|
-
const { gedcom: tree } = parser_default.parse(content);
|
|
7789
|
-
const stats = tree.stats();
|
|
7790
|
-
if (options.json) {
|
|
7791
|
-
console.log(formatJson(stats));
|
|
7792
|
-
} else {
|
|
7793
|
-
console.log(formatHeader("GEDCOM File Statistics\n"));
|
|
7794
|
-
console.log(formatLabel("Total Individuals"));
|
|
7795
|
-
console.log(` ${formatCount(stats.totalIndividuals)}`);
|
|
7796
|
-
console.log(formatLabel("Total Families"));
|
|
7797
|
-
console.log(` ${formatCount(stats.totalFamilies)}`);
|
|
7798
|
-
console.log();
|
|
7799
|
-
console.log(formatLabel("By Gender"));
|
|
7800
|
-
console.log(` Males: ${formatCount(stats.byGender.males)}`);
|
|
7801
|
-
console.log(` Females: ${formatCount(stats.byGender.females)}`);
|
|
7802
|
-
console.log(` Unknown: ${formatCount(stats.byGender.unknown)}`);
|
|
7803
|
-
console.log();
|
|
7804
|
-
if (stats.dateRange.earliest && stats.dateRange.latest) {
|
|
7805
|
-
console.log(formatLabel("Date Range"));
|
|
7806
|
-
console.log(` ${stats.dateRange.earliest} - ${stats.dateRange.latest}`);
|
|
7807
|
-
console.log();
|
|
7808
|
-
}
|
|
7809
|
-
if (stats.averageLifespan) {
|
|
7810
|
-
console.log(formatLabel("Average Lifespan"));
|
|
7811
|
-
console.log(` ${stats.averageLifespan.toFixed(1)} years`);
|
|
7812
|
-
console.log();
|
|
7813
|
-
}
|
|
7814
|
-
if (stats.topSurnames.length > 0) {
|
|
7815
|
-
console.log(formatLabel("Most Common Surnames"));
|
|
7816
|
-
stats.topSurnames.forEach(({ surname, count }) => {
|
|
7817
|
-
console.log(` ${surname}: ${formatCount(count)}`);
|
|
7818
|
-
});
|
|
7819
|
-
console.log();
|
|
7820
|
-
}
|
|
7821
|
-
if (stats.topBirthPlaces.length > 0) {
|
|
7822
|
-
console.log(formatLabel("Most Common Birth Places"));
|
|
7823
|
-
stats.topBirthPlaces.slice(0, 5).forEach(({ place, count }) => {
|
|
7824
|
-
console.log(` ${place}: ${formatCount(count)}`);
|
|
7825
|
-
});
|
|
7826
|
-
}
|
|
7827
|
-
}
|
|
7828
|
-
} catch (error) {
|
|
7829
|
-
handleError(error, "Failed to generate statistics");
|
|
7830
|
-
}
|
|
7831
|
-
});
|
|
7832
|
-
}
|
|
7833
|
-
|
|
7834
9062
|
// src/cli/commands/validate.ts
|
|
7835
9063
|
function registerValidateCommand(program2) {
|
|
7836
9064
|
program2.command("validate <file>").description("Validate a GEDCOM file").option("-j, --json", "Output in JSON format").option("-s, --strict", "Enable strict validation").option("--fix", "Attempt to fix common issues (not implemented)").action((file, options) => {
|
|
@@ -7967,6 +9195,7 @@ var program = new Command();
|
|
|
7967
9195
|
program.name("gedcom-parser").description("CLI tool for parsing and manipulating GEDCOM files").version(packageJson.version);
|
|
7968
9196
|
registerInfoCommand(program);
|
|
7969
9197
|
registerFindCommand(program);
|
|
9198
|
+
registerSelectCommand(program);
|
|
7970
9199
|
registerShowCommand(program);
|
|
7971
9200
|
registerValidateCommand(program);
|
|
7972
9201
|
registerRelativesCommand(program);
|
|
@@ -7974,6 +9203,8 @@ registerExtractCommand(program);
|
|
|
7974
9203
|
registerStatsCommand(program);
|
|
7975
9204
|
registerMergeCommand(program);
|
|
7976
9205
|
registerConvertCommand(program);
|
|
9206
|
+
registerGetCommand(program);
|
|
9207
|
+
registerOpenCommand(program);
|
|
7977
9208
|
program.parse(process.argv);
|
|
7978
9209
|
if (!process.argv.slice(2).length) {
|
|
7979
9210
|
program.outputHelp();
|