@treeviz/gedcom-parser 1.0.23 → 2.0.1
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 +424 -61
- package/dist/cli/index.js +1825 -449
- package/dist/constants/index.d.ts +1 -1
- package/dist/constants/index.js +423 -60
- package/dist/factories/index.d.ts +2 -2
- package/dist/factories/index.js +423 -60
- package/dist/{index-CSjQRlxq.d.ts → index-B3Po1Kaw.d.ts} +145 -107
- package/dist/index.d.ts +3 -3
- package/dist/index.js +461 -65
- package/dist/interfaces/index.d.ts +1 -1
- package/dist/kinship-translator/index.d.ts +19 -19
- package/dist/kinship-translator/index.js +423 -60
- package/dist/place-parser-BLwBjtXS.d.ts +40 -0
- package/dist/{place-translator-BYX8180A.d.ts → place-translator-DPMyrsnu.d.ts} +24 -19
- 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 +459 -63
- package/package.json +1 -1
- package/dist/cli/index.d.ts +0 -1
- package/dist/place-parser-Dl5iva3h.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) {
|
|
@@ -1977,7 +2212,7 @@ var Indi = class extends Common {
|
|
|
1977
2212
|
title,
|
|
1978
2213
|
url,
|
|
1979
2214
|
contentType: type,
|
|
1980
|
-
downloadName: `${this.id.replaceAll("@", "")}_${this.toNaturalName()
|
|
2215
|
+
downloadName: `${this.id.replaceAll("@", "")}_${this.toNaturalName()?.replaceAll(" ", "-") || ""}_${(title || key.replaceAll("@", "").toString()).replaceAll(" ", "-")}`
|
|
1981
2216
|
};
|
|
1982
2217
|
}
|
|
1983
2218
|
})
|
|
@@ -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?.merge(
|
|
2244
|
+
objeList?.merge(fam?.get("MARR.OBJE"));
|
|
2009
2245
|
});
|
|
2010
2246
|
objeList?.forEach((o, index) => {
|
|
2011
2247
|
if (!o) {
|
|
@@ -2031,7 +2267,7 @@ var Indi = class extends Common {
|
|
|
2031
2267
|
title,
|
|
2032
2268
|
url,
|
|
2033
2269
|
contentType: type,
|
|
2034
|
-
downloadName: `${this.id.replaceAll("@", "")}_${this.toNaturalName()
|
|
2270
|
+
downloadName: `${this.id.replaceAll("@", "")}_${this.toNaturalName()?.replaceAll(" ", "-") || ""}_${(title || key.replaceAll("@", "").toString()).replaceAll(" ", "-")}`
|
|
2035
2271
|
};
|
|
2036
2272
|
}
|
|
2037
2273
|
});
|
|
@@ -2123,6 +2359,85 @@ 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
|
+
const rfn = this.get("RFN")?.toValue();
|
|
2371
|
+
const geniId = rfn?.replace(/^geni:/, "") || "unknown";
|
|
2372
|
+
objeList?.forEach((obje, index) => {
|
|
2373
|
+
if (!obje) {
|
|
2374
|
+
return;
|
|
2375
|
+
}
|
|
2376
|
+
const key = `@O${index}@`;
|
|
2377
|
+
const isPrimary = obje?.get("_PRIM")?.toValue() === "Y";
|
|
2378
|
+
const url = obje?.get("FILE")?.toValue();
|
|
2379
|
+
const title = obje?.get("TITL")?.toValue() ?? "";
|
|
2380
|
+
const type = obje?.get("FORM")?.toValue() ?? "raw";
|
|
2381
|
+
if (url) {
|
|
2382
|
+
const urlMatch = url.match(/\/([^/]+)\?hash=/);
|
|
2383
|
+
const imgId = urlMatch?.[1] || `img-${index}-${Date.now().toString(36)}`;
|
|
2384
|
+
const id = `geni-${geniId}-${imgId}`;
|
|
2385
|
+
list[id] = {
|
|
2386
|
+
isPrimary,
|
|
2387
|
+
key,
|
|
2388
|
+
id,
|
|
2389
|
+
tree: geniId,
|
|
2390
|
+
imgId,
|
|
2391
|
+
person: this.id,
|
|
2392
|
+
title,
|
|
2393
|
+
url,
|
|
2394
|
+
contentType: type,
|
|
2395
|
+
downloadName: `${this.id.replaceAll("@", "")}_${this.toNaturalName()?.replaceAll(" ", "-") || ""}_${(title || key.replaceAll("@", "").toString()).replaceAll(" ", "-")}`
|
|
2396
|
+
};
|
|
2397
|
+
}
|
|
2398
|
+
});
|
|
2399
|
+
return list;
|
|
2400
|
+
}
|
|
2401
|
+
universalMedia() {
|
|
2402
|
+
const list = {};
|
|
2403
|
+
if (!this.id) {
|
|
2404
|
+
return list;
|
|
2405
|
+
}
|
|
2406
|
+
const objeList = this.get("OBJE")?.toList().copy();
|
|
2407
|
+
if (!objeList || objeList.length === 0) {
|
|
2408
|
+
return list;
|
|
2409
|
+
}
|
|
2410
|
+
const rfn = this.get("RFN")?.toValue();
|
|
2411
|
+
const treeId = this.getUniversalTreeId() || rfn || "universal";
|
|
2412
|
+
objeList.forEach((obje, index) => {
|
|
2413
|
+
if (!obje) {
|
|
2414
|
+
return;
|
|
2415
|
+
}
|
|
2416
|
+
const key = `@O${index}@`;
|
|
2417
|
+
obje.standardizeMedia();
|
|
2418
|
+
const isPrimary = obje?.get("_PRIM")?.toValue() === "Y";
|
|
2419
|
+
const url = obje?.get("FILE")?.toValue();
|
|
2420
|
+
const title = obje?.get("TITL")?.toValue() ?? "";
|
|
2421
|
+
const type = obje?.get("FORM")?.toValue() ?? "raw";
|
|
2422
|
+
if (url) {
|
|
2423
|
+
const imgId = `media-${index}-${url.split("/").pop()?.split("?")[0]?.substring(0, 20) || Date.now().toString(36)}`;
|
|
2424
|
+
const id = `${treeId}-${this.id}-${imgId}`;
|
|
2425
|
+
list[id] = {
|
|
2426
|
+
isPrimary,
|
|
2427
|
+
key,
|
|
2428
|
+
id,
|
|
2429
|
+
tree: treeId,
|
|
2430
|
+
imgId,
|
|
2431
|
+
person: this.id,
|
|
2432
|
+
title,
|
|
2433
|
+
url,
|
|
2434
|
+
contentType: type,
|
|
2435
|
+
downloadName: `${this.id.replaceAll("@", "")}_${this.toNaturalName()?.replaceAll(" ", "-") || ""}_${(title || key.replaceAll("@", "").toString()).replaceAll(" ", "-")}`
|
|
2436
|
+
};
|
|
2437
|
+
}
|
|
2438
|
+
});
|
|
2439
|
+
return list;
|
|
2440
|
+
}
|
|
2126
2441
|
async multimedia(namespace) {
|
|
2127
2442
|
if (this?.isAncestry()) {
|
|
2128
2443
|
return await this.ancestryMedia(namespace);
|
|
@@ -2130,11 +2445,30 @@ var Indi = class extends Common {
|
|
|
2130
2445
|
if (this?.isMyHeritage()) {
|
|
2131
2446
|
return this.myheritageMedia();
|
|
2132
2447
|
}
|
|
2133
|
-
|
|
2448
|
+
if (this?.isGeni()) {
|
|
2449
|
+
return this.geniMedia();
|
|
2450
|
+
}
|
|
2451
|
+
return this.universalMedia();
|
|
2134
2452
|
}
|
|
2135
2453
|
async getProfilePicture(namespace, onlyPrimary = true) {
|
|
2454
|
+
if (!this.id) {
|
|
2455
|
+
return void 0;
|
|
2456
|
+
}
|
|
2457
|
+
const cacheKey = this.id;
|
|
2458
|
+
const cached = profilePictureCache(
|
|
2459
|
+
this._gedcom,
|
|
2460
|
+
cacheKey
|
|
2461
|
+
);
|
|
2462
|
+
if (cached !== void 0) {
|
|
2463
|
+
return cached;
|
|
2464
|
+
}
|
|
2136
2465
|
const mediaList = await this.multimedia(namespace);
|
|
2137
2466
|
if (!mediaList) {
|
|
2467
|
+
profilePictureCache(
|
|
2468
|
+
this._gedcom,
|
|
2469
|
+
cacheKey,
|
|
2470
|
+
void 0
|
|
2471
|
+
);
|
|
2138
2472
|
return void 0;
|
|
2139
2473
|
}
|
|
2140
2474
|
const mediaArray = Object.values(mediaList);
|
|
@@ -2142,27 +2476,41 @@ var Indi = class extends Common {
|
|
|
2142
2476
|
(media) => media.isPrimary && isImageFormat(media.contentType || getFileExtension(media.url))
|
|
2143
2477
|
);
|
|
2144
2478
|
if (primaryMedia) {
|
|
2145
|
-
|
|
2479
|
+
const result = {
|
|
2146
2480
|
file: primaryMedia.url,
|
|
2147
2481
|
form: primaryMedia.contentType,
|
|
2148
2482
|
title: primaryMedia.title,
|
|
2149
2483
|
isPrimary: true
|
|
2150
2484
|
};
|
|
2151
|
-
|
|
2152
|
-
|
|
2485
|
+
profilePictureCache(this._gedcom, cacheKey, result);
|
|
2486
|
+
return result;
|
|
2487
|
+
}
|
|
2488
|
+
if (onlyPrimary) {
|
|
2489
|
+
profilePictureCache(
|
|
2490
|
+
this._gedcom,
|
|
2491
|
+
cacheKey,
|
|
2492
|
+
void 0
|
|
2493
|
+
);
|
|
2153
2494
|
return void 0;
|
|
2154
2495
|
}
|
|
2155
2496
|
const secondaryMedia = mediaArray.find(
|
|
2156
2497
|
(media) => isImageFormat(media.contentType || getFileExtension(media.url))
|
|
2157
2498
|
);
|
|
2158
2499
|
if (secondaryMedia) {
|
|
2159
|
-
|
|
2500
|
+
const result = {
|
|
2160
2501
|
file: secondaryMedia.url,
|
|
2161
2502
|
form: secondaryMedia.contentType,
|
|
2162
2503
|
title: secondaryMedia.title,
|
|
2163
2504
|
isPrimary: false
|
|
2164
2505
|
};
|
|
2506
|
+
profilePictureCache(this._gedcom, cacheKey, result);
|
|
2507
|
+
return result;
|
|
2165
2508
|
}
|
|
2509
|
+
profilePictureCache(
|
|
2510
|
+
this._gedcom,
|
|
2511
|
+
cacheKey,
|
|
2512
|
+
void 0
|
|
2513
|
+
);
|
|
2166
2514
|
return void 0;
|
|
2167
2515
|
}
|
|
2168
2516
|
link(poolId) {
|
|
@@ -2530,7 +2878,7 @@ var Indi = class extends Common {
|
|
|
2530
2878
|
return;
|
|
2531
2879
|
}
|
|
2532
2880
|
const cacheKey = `${this.id}|${usedIndi.id}`;
|
|
2533
|
-
const cache = pathCache(cacheKey);
|
|
2881
|
+
const cache = pathCache(this._gedcom, cacheKey);
|
|
2534
2882
|
if (cache) {
|
|
2535
2883
|
return cache;
|
|
2536
2884
|
}
|
|
@@ -2575,7 +2923,7 @@ var Indi = class extends Common {
|
|
|
2575
2923
|
if (breakOnNext) {
|
|
2576
2924
|
return void 0;
|
|
2577
2925
|
}
|
|
2578
|
-
pathCache(cacheKey, path2);
|
|
2926
|
+
pathCache(this._gedcom, cacheKey, path2);
|
|
2579
2927
|
return path2;
|
|
2580
2928
|
}
|
|
2581
2929
|
visited.append(indi);
|
|
@@ -2745,7 +3093,7 @@ var Indi = class extends Common {
|
|
|
2745
3093
|
}
|
|
2746
3094
|
getRelativesOnDegree(degree = 0) {
|
|
2747
3095
|
this.id = this.id || `@I${Math.random()}@`;
|
|
2748
|
-
const cache = relativesOnDegreeCache(this.id, degree);
|
|
3096
|
+
const cache = relativesOnDegreeCache(this._gedcom, this.id, degree);
|
|
2749
3097
|
if (cache) {
|
|
2750
3098
|
return cache;
|
|
2751
3099
|
}
|
|
@@ -2753,6 +3101,7 @@ var Indi = class extends Common {
|
|
|
2753
3101
|
const excludes = persons;
|
|
2754
3102
|
if (!Math.abs(degree)) {
|
|
2755
3103
|
return relativesOnDegreeCache(
|
|
3104
|
+
this._gedcom,
|
|
2756
3105
|
this.id,
|
|
2757
3106
|
degree,
|
|
2758
3107
|
persons.except(this)
|
|
@@ -2763,11 +3112,11 @@ var Indi = class extends Common {
|
|
|
2763
3112
|
excludes.merge(persons);
|
|
2764
3113
|
persons = this.getRelativesOnLevel(validDegree).getRelativesOnDegree(-validDegree).copy().exclude(excludes);
|
|
2765
3114
|
}
|
|
2766
|
-
return relativesOnDegreeCache(this.id, degree, persons);
|
|
3115
|
+
return relativesOnDegreeCache(this._gedcom, this.id, degree, persons);
|
|
2767
3116
|
}
|
|
2768
3117
|
getRelativesOnLevel(level = 0, filter) {
|
|
2769
3118
|
this.id = this.id || `@I${Math.random()}@`;
|
|
2770
|
-
const cache = relativesOnLevelCache(this.id, level);
|
|
3119
|
+
const cache = relativesOnLevelCache(this._gedcom, this.id, level);
|
|
2771
3120
|
if (cache) {
|
|
2772
3121
|
return cache;
|
|
2773
3122
|
}
|
|
@@ -2778,7 +3127,7 @@ var Indi = class extends Common {
|
|
|
2778
3127
|
};
|
|
2779
3128
|
let families = this.get(config.key)?.toValueList();
|
|
2780
3129
|
if (!families) {
|
|
2781
|
-
return relativesOnLevelCache(this.id, level, persons);
|
|
3130
|
+
return relativesOnLevelCache(this._gedcom, this.id, level, persons);
|
|
2782
3131
|
}
|
|
2783
3132
|
if (filter) {
|
|
2784
3133
|
families = families.filter(filter);
|
|
@@ -2789,7 +3138,12 @@ var Indi = class extends Common {
|
|
|
2789
3138
|
persons = this.toFamilies(families).getParents();
|
|
2790
3139
|
}
|
|
2791
3140
|
if (level >= -1 && level <= 1) {
|
|
2792
|
-
return relativesOnLevelCache(
|
|
3141
|
+
return relativesOnLevelCache(
|
|
3142
|
+
this._gedcom,
|
|
3143
|
+
this.id,
|
|
3144
|
+
level,
|
|
3145
|
+
persons.except(this)
|
|
3146
|
+
);
|
|
2793
3147
|
}
|
|
2794
3148
|
for (let i = 1; i < Math.abs(level); i++) {
|
|
2795
3149
|
if (config.isAscendant) {
|
|
@@ -2798,7 +3152,12 @@ var Indi = class extends Common {
|
|
|
2798
3152
|
persons = persons.getParents();
|
|
2799
3153
|
}
|
|
2800
3154
|
}
|
|
2801
|
-
return relativesOnLevelCache(
|
|
3155
|
+
return relativesOnLevelCache(
|
|
3156
|
+
this._gedcom,
|
|
3157
|
+
this.id,
|
|
3158
|
+
level,
|
|
3159
|
+
persons.except(this)
|
|
3160
|
+
);
|
|
2802
3161
|
}
|
|
2803
3162
|
getAscendants(level = 0, filter) {
|
|
2804
3163
|
if (!level) {
|
|
@@ -2837,7 +3196,12 @@ var Indi = class extends Common {
|
|
|
2837
3196
|
}
|
|
2838
3197
|
currentGen++;
|
|
2839
3198
|
generations[currentGen] = descentants;
|
|
2840
|
-
relativesOnLevelCache(
|
|
3199
|
+
relativesOnLevelCache(
|
|
3200
|
+
this._gedcom,
|
|
3201
|
+
this.id,
|
|
3202
|
+
-currentGen,
|
|
3203
|
+
descentants
|
|
3204
|
+
);
|
|
2841
3205
|
descentants && relatives.merge(descentants);
|
|
2842
3206
|
}
|
|
2843
3207
|
return { relatives, generations };
|
|
@@ -2870,7 +3234,7 @@ var Indi = class extends Common {
|
|
|
2870
3234
|
}
|
|
2871
3235
|
currentGen++;
|
|
2872
3236
|
generations[currentGen] = parents;
|
|
2873
|
-
relativesOnLevelCache(this.id, currentGen, parents);
|
|
3237
|
+
relativesOnLevelCache(this._gedcom, this.id, currentGen, parents);
|
|
2874
3238
|
parents && relatives.merge(parents);
|
|
2875
3239
|
}
|
|
2876
3240
|
return { relatives, generations };
|
|
@@ -5881,7 +6245,7 @@ var Families = class _Families extends List {
|
|
|
5881
6245
|
|
|
5882
6246
|
// package.json
|
|
5883
6247
|
var package_default = {
|
|
5884
|
-
version: "
|
|
6248
|
+
version: "2.0.1"};
|
|
5885
6249
|
|
|
5886
6250
|
// src/utils/get-product-details.ts
|
|
5887
6251
|
var isDevelopment = () => {
|
|
@@ -6872,7 +7236,7 @@ var GedcomTree = {
|
|
|
6872
7236
|
return this.parseHierarchy(content, options);
|
|
6873
7237
|
},
|
|
6874
7238
|
parseHierarchy: function(content, options) {
|
|
6875
|
-
const { settings } = options ?? {};
|
|
7239
|
+
const { settings, filename = "" } = options ?? {};
|
|
6876
7240
|
const { linkedPersons = "skip", linkingKey } = settings ?? {};
|
|
6877
7241
|
const gedcom = createGedCom();
|
|
6878
7242
|
gedcom.removeValue();
|
|
@@ -6907,6 +7271,25 @@ var GedcomTree = {
|
|
|
6907
7271
|
if (lineMatch) {
|
|
6908
7272
|
const lineIndent = Number(lineMatch?.groups?.indent ?? 0);
|
|
6909
7273
|
const lineValue = lineMatch?.groups?.value ?? "";
|
|
7274
|
+
const lineType = lineMatch?.groups?.type ?? "";
|
|
7275
|
+
const linesAcc = acc;
|
|
7276
|
+
if (lineIndent === 0 && lineType === "HEAD") {
|
|
7277
|
+
acc.push(line);
|
|
7278
|
+
linesAcc._inHead = true;
|
|
7279
|
+
linesAcc._hasFile = false;
|
|
7280
|
+
return acc;
|
|
7281
|
+
}
|
|
7282
|
+
if (lineIndent === 0 && linesAcc._inHead) {
|
|
7283
|
+
if (filename && !linesAcc._hasFile) {
|
|
7284
|
+
acc.push(`1 FILE ${filename}`);
|
|
7285
|
+
}
|
|
7286
|
+
linesAcc._inHead = false;
|
|
7287
|
+
acc.push(line);
|
|
7288
|
+
return acc;
|
|
7289
|
+
}
|
|
7290
|
+
if (linesAcc._inHead && lineIndent === 1 && lineType === "FILE") {
|
|
7291
|
+
linesAcc._hasFile = true;
|
|
7292
|
+
}
|
|
6910
7293
|
if (lineIndent > 0 && lineIndent > prevLineIndent && lineValue && isId(lineValue)) {
|
|
6911
7294
|
const refLines = lineValue.split(/,\s*/).map((id) => line.replace(lineValue, id));
|
|
6912
7295
|
if (refLines.length > 1) {
|
|
@@ -7121,12 +7504,6 @@ function formatDate(date) {
|
|
|
7121
7504
|
}
|
|
7122
7505
|
return chalk.white(date);
|
|
7123
7506
|
}
|
|
7124
|
-
function formatPlace(place) {
|
|
7125
|
-
if (!place) {
|
|
7126
|
-
return chalk.gray("(unknown)");
|
|
7127
|
-
}
|
|
7128
|
-
return chalk.white(place);
|
|
7129
|
-
}
|
|
7130
7507
|
function formatName(name) {
|
|
7131
7508
|
if (!name) {
|
|
7132
7509
|
return chalk.gray("(unnamed)");
|
|
@@ -7174,14 +7551,17 @@ function formatLifespan(birthDate, deathDate) {
|
|
|
7174
7551
|
|
|
7175
7552
|
// src/cli/commands/convert.ts
|
|
7176
7553
|
function registerConvertCommand(program2) {
|
|
7177
|
-
program2.command("convert <file>").description("Convert GEDCOM to another format").requiredOption(
|
|
7554
|
+
program2.command("convert <file>").description("Convert GEDCOM to another format").requiredOption(
|
|
7555
|
+
"-f, --format <format>",
|
|
7556
|
+
"Output format: json, csv, markdown"
|
|
7557
|
+
).option("-o, --output <file>", "Output file path").action((file, options) => {
|
|
7178
7558
|
try {
|
|
7179
7559
|
const content = readGedcomFile(file);
|
|
7180
7560
|
const { gedcom: tree } = parser_default.parse(content);
|
|
7181
7561
|
const individuals = tree.indis();
|
|
7182
7562
|
let outputContent = "";
|
|
7183
7563
|
if (options.format === "json") {
|
|
7184
|
-
const jsonData = individuals
|
|
7564
|
+
const jsonData = individuals?.map((indi) => ({
|
|
7185
7565
|
id: indi.id,
|
|
7186
7566
|
name: cleanGedcomName(indi.NAME?.toValue()),
|
|
7187
7567
|
sex: indi.SEX?.value || null,
|
|
@@ -7193,8 +7573,10 @@ function registerConvertCommand(program2) {
|
|
|
7193
7573
|
outputContent = formatJson(jsonData);
|
|
7194
7574
|
} else if (options.format === "csv") {
|
|
7195
7575
|
const lines = [];
|
|
7196
|
-
lines.push(
|
|
7197
|
-
|
|
7576
|
+
lines.push(
|
|
7577
|
+
"ID,Name,Sex,Birth Date,Birth Place,Death Date,Death Place"
|
|
7578
|
+
);
|
|
7579
|
+
individuals?.forEach((indi) => {
|
|
7198
7580
|
const csvEscape = (str) => {
|
|
7199
7581
|
if (!str) return "";
|
|
7200
7582
|
if (str.includes(",") || str.includes('"')) {
|
|
@@ -7202,15 +7584,19 @@ function registerConvertCommand(program2) {
|
|
|
7202
7584
|
}
|
|
7203
7585
|
return str;
|
|
7204
7586
|
};
|
|
7205
|
-
lines.push(
|
|
7206
|
-
|
|
7207
|
-
|
|
7208
|
-
|
|
7209
|
-
|
|
7210
|
-
|
|
7211
|
-
|
|
7212
|
-
|
|
7213
|
-
|
|
7587
|
+
lines.push(
|
|
7588
|
+
[
|
|
7589
|
+
csvEscape(indi.id),
|
|
7590
|
+
csvEscape(
|
|
7591
|
+
cleanGedcomName(indi.NAME?.toValue())
|
|
7592
|
+
),
|
|
7593
|
+
csvEscape(indi.SEX?.value),
|
|
7594
|
+
csvEscape(indi.BIRT?.DATE?.toValue()),
|
|
7595
|
+
csvEscape(indi.BIRT?.PLAC?.value),
|
|
7596
|
+
csvEscape(indi.DEAT?.DATE?.toValue()),
|
|
7597
|
+
csvEscape(indi.DEAT?.PLAC?.value)
|
|
7598
|
+
].join(",")
|
|
7599
|
+
);
|
|
7214
7600
|
});
|
|
7215
7601
|
outputContent = lines.join("\n");
|
|
7216
7602
|
} else if (options.format === "markdown") {
|
|
@@ -7218,12 +7604,14 @@ function registerConvertCommand(program2) {
|
|
|
7218
7604
|
lines.push("# GEDCOM Individuals\n");
|
|
7219
7605
|
lines.push("| ID | Name | Sex | Birth | Death |");
|
|
7220
7606
|
lines.push("|----|------|-----|-------|-------|");
|
|
7221
|
-
individuals
|
|
7607
|
+
individuals?.forEach((indi) => {
|
|
7222
7608
|
const name = cleanGedcomName(indi.NAME?.toValue()) || "?";
|
|
7223
7609
|
const sex = indi.SEX?.value || "?";
|
|
7224
7610
|
const birth = indi.BIRT?.DATE?.toValue() || "?";
|
|
7225
7611
|
const death = indi.DEAT?.DATE?.toValue() || "?";
|
|
7226
|
-
lines.push(
|
|
7612
|
+
lines.push(
|
|
7613
|
+
`| ${indi.id} | ${name} | ${sex} | ${birth} | ${death} |`
|
|
7614
|
+
);
|
|
7227
7615
|
});
|
|
7228
7616
|
outputContent = lines.join("\n");
|
|
7229
7617
|
} else {
|
|
@@ -7232,7 +7620,11 @@ function registerConvertCommand(program2) {
|
|
|
7232
7620
|
}
|
|
7233
7621
|
if (options.output) {
|
|
7234
7622
|
writeFileSync(options.output, outputContent, "utf-8");
|
|
7235
|
-
console.log(
|
|
7623
|
+
console.log(
|
|
7624
|
+
formatSuccess(
|
|
7625
|
+
`Converted to ${options.format} and saved to ${options.output}`
|
|
7626
|
+
)
|
|
7627
|
+
);
|
|
7236
7628
|
} else {
|
|
7237
7629
|
console.log(outputContent);
|
|
7238
7630
|
}
|
|
@@ -7242,49 +7634,73 @@ function registerConvertCommand(program2) {
|
|
|
7242
7634
|
});
|
|
7243
7635
|
}
|
|
7244
7636
|
function registerExtractCommand(program2) {
|
|
7245
|
-
program2.command("extract <file>").description("Extract a subset of individuals to a new GEDCOM file").requiredOption("-o, --output <file>", "Output file path (required)").option("--surname <name>", "Filter by surname").option(
|
|
7637
|
+
program2.command("extract <file>").description("Extract a subset of individuals to a new GEDCOM file").requiredOption("-o, --output <file>", "Output file path (required)").option("--surname <name>", "Filter by surname").option(
|
|
7638
|
+
"--birth-after <year>",
|
|
7639
|
+
"Include individuals born after this year"
|
|
7640
|
+
).option(
|
|
7641
|
+
"--birth-before <year>",
|
|
7642
|
+
"Include individuals born before this year"
|
|
7643
|
+
).option(
|
|
7644
|
+
"--death-after <year>",
|
|
7645
|
+
"Include individuals who died after this year"
|
|
7646
|
+
).option(
|
|
7647
|
+
"--death-before <year>",
|
|
7648
|
+
"Include individuals who died before this year"
|
|
7649
|
+
).action((file, options) => {
|
|
7246
7650
|
try {
|
|
7247
7651
|
const content = readGedcomFile(file);
|
|
7248
7652
|
const { gedcom: tree } = parser_default.parse(content);
|
|
7249
7653
|
const individuals = tree.indis();
|
|
7250
7654
|
const results = [];
|
|
7251
|
-
individuals
|
|
7655
|
+
individuals?.forEach((indi) => {
|
|
7252
7656
|
let matches = true;
|
|
7253
7657
|
if (options.surname && matches) {
|
|
7254
7658
|
const searchSurname = options.surname.toLowerCase();
|
|
7255
|
-
const name = cleanGedcomName(
|
|
7659
|
+
const name = cleanGedcomName(
|
|
7660
|
+
indi.NAME?.toValue()
|
|
7661
|
+
).toLowerCase();
|
|
7256
7662
|
matches = name.includes(searchSurname);
|
|
7257
7663
|
}
|
|
7258
7664
|
if (options.birthAfter && matches) {
|
|
7259
7665
|
const year = parseInt(options.birthAfter, 10);
|
|
7260
7666
|
const birthDate = indi.BIRT?.DATE?.toValue();
|
|
7261
7667
|
const match = birthDate?.match(/\d{4}/);
|
|
7262
|
-
matches =
|
|
7668
|
+
matches = Boolean(
|
|
7669
|
+
match && parseInt(match[0], 10) > year
|
|
7670
|
+
);
|
|
7263
7671
|
}
|
|
7264
7672
|
if (options.birthBefore && matches) {
|
|
7265
7673
|
const year = parseInt(options.birthBefore, 10);
|
|
7266
7674
|
const birthDate = indi.BIRT?.DATE?.toValue();
|
|
7267
7675
|
const match = birthDate?.match(/\d{4}/);
|
|
7268
|
-
matches =
|
|
7676
|
+
matches = Boolean(
|
|
7677
|
+
match && parseInt(match[0], 10) < year
|
|
7678
|
+
);
|
|
7269
7679
|
}
|
|
7270
7680
|
if (options.deathAfter && matches) {
|
|
7271
7681
|
const year = parseInt(options.deathAfter, 10);
|
|
7272
7682
|
const deathDate = indi.DEAT?.DATE?.toValue();
|
|
7273
7683
|
const match = deathDate?.match(/\d{4}/);
|
|
7274
|
-
matches =
|
|
7684
|
+
matches = Boolean(
|
|
7685
|
+
match && parseInt(match[0], 10) > year
|
|
7686
|
+
);
|
|
7275
7687
|
}
|
|
7276
7688
|
if (options.deathBefore && matches) {
|
|
7277
7689
|
const year = parseInt(options.deathBefore, 10);
|
|
7278
7690
|
const deathDate = indi.DEAT?.DATE?.toValue();
|
|
7279
7691
|
const match = deathDate?.match(/\d{4}/);
|
|
7280
|
-
matches =
|
|
7692
|
+
matches = Boolean(
|
|
7693
|
+
match && parseInt(match[0], 10) < year
|
|
7694
|
+
);
|
|
7281
7695
|
}
|
|
7282
7696
|
if (matches) {
|
|
7283
7697
|
results.push(indi);
|
|
7284
7698
|
}
|
|
7285
7699
|
});
|
|
7286
7700
|
if (results.length === 0) {
|
|
7287
|
-
console.log(
|
|
7701
|
+
console.log(
|
|
7702
|
+
formatError("No individuals match the criteria")
|
|
7703
|
+
);
|
|
7288
7704
|
process.exit(1);
|
|
7289
7705
|
}
|
|
7290
7706
|
const lines = [];
|
|
@@ -7296,12 +7712,18 @@ function registerExtractCommand(program2) {
|
|
|
7296
7712
|
results.forEach((indi) => {
|
|
7297
7713
|
const raw = indi.raw();
|
|
7298
7714
|
if (raw) {
|
|
7299
|
-
lines.push(
|
|
7715
|
+
lines.push(
|
|
7716
|
+
...raw.split("\n").filter((line) => line.trim())
|
|
7717
|
+
);
|
|
7300
7718
|
}
|
|
7301
7719
|
});
|
|
7302
7720
|
lines.push("0 TRLR");
|
|
7303
7721
|
writeFileSync(options.output, lines.join("\n"), "utf-8");
|
|
7304
|
-
console.log(
|
|
7722
|
+
console.log(
|
|
7723
|
+
formatSuccess(
|
|
7724
|
+
`Extracted ${results.length} individuals to ${options.output}`
|
|
7725
|
+
)
|
|
7726
|
+
);
|
|
7305
7727
|
} catch (error) {
|
|
7306
7728
|
handleError(error, "Failed to extract individuals");
|
|
7307
7729
|
}
|
|
@@ -7309,88 +7731,310 @@ function registerExtractCommand(program2) {
|
|
|
7309
7731
|
}
|
|
7310
7732
|
|
|
7311
7733
|
// src/cli/commands/find.ts
|
|
7312
|
-
function
|
|
7313
|
-
|
|
7314
|
-
|
|
7315
|
-
|
|
7316
|
-
|
|
7317
|
-
|
|
7318
|
-
|
|
7319
|
-
|
|
7320
|
-
|
|
7321
|
-
|
|
7322
|
-
|
|
7323
|
-
|
|
7324
|
-
|
|
7325
|
-
|
|
7326
|
-
|
|
7327
|
-
|
|
7328
|
-
|
|
7329
|
-
|
|
7734
|
+
function findIndividuals(tree, options) {
|
|
7735
|
+
const individuals = tree.indis();
|
|
7736
|
+
const results = [];
|
|
7737
|
+
individuals?.forEach((indi) => {
|
|
7738
|
+
let matches = true;
|
|
7739
|
+
if (options.id && indi.id !== options.id) {
|
|
7740
|
+
matches = false;
|
|
7741
|
+
}
|
|
7742
|
+
if ((options.name || options.query) && matches) {
|
|
7743
|
+
const searchName = (options.name || options.query || "").toLowerCase();
|
|
7744
|
+
const name = cleanGedcomName(indi?.toNaturalName()).toLowerCase();
|
|
7745
|
+
if (!name.includes(searchName)) {
|
|
7746
|
+
matches = false;
|
|
7747
|
+
}
|
|
7748
|
+
}
|
|
7749
|
+
if (options.birthYear && matches) {
|
|
7750
|
+
const year = options.birthYear;
|
|
7751
|
+
const birthDate = indi.BIRT?.DATE?.toValue();
|
|
7752
|
+
if (!birthDate?.includes(String(year))) {
|
|
7753
|
+
matches = false;
|
|
7754
|
+
}
|
|
7755
|
+
}
|
|
7756
|
+
if (options.deathYear && matches) {
|
|
7757
|
+
const year = options.deathYear;
|
|
7758
|
+
const deathDate = indi.DEAT?.DATE?.toValue();
|
|
7759
|
+
if (!deathDate?.includes(String(year))) {
|
|
7760
|
+
matches = false;
|
|
7761
|
+
}
|
|
7762
|
+
}
|
|
7763
|
+
if (matches) {
|
|
7764
|
+
results.push(indi);
|
|
7765
|
+
}
|
|
7766
|
+
});
|
|
7767
|
+
return results;
|
|
7768
|
+
}
|
|
7769
|
+
function formatFindResults(results, json = false) {
|
|
7770
|
+
if (json) {
|
|
7771
|
+
const jsonResults = results.map((indi) => ({
|
|
7772
|
+
id: indi.id,
|
|
7773
|
+
name: cleanGedcomName(indi.NAME?.toValue()),
|
|
7774
|
+
birthDate: indi.BIRT?.DATE?.toValue() || null,
|
|
7775
|
+
birthPlace: indi.BIRT?.PLAC?.value || null,
|
|
7776
|
+
deathDate: indi.DEAT?.DATE?.toValue() || null,
|
|
7777
|
+
deathPlace: indi.DEAT?.PLAC?.value || null,
|
|
7778
|
+
sex: indi.SEX?.value || null
|
|
7779
|
+
}));
|
|
7780
|
+
console.log(
|
|
7781
|
+
formatJson({ count: jsonResults.length, individuals: jsonResults })
|
|
7782
|
+
);
|
|
7783
|
+
} else {
|
|
7784
|
+
if (results.length === 0) {
|
|
7785
|
+
console.log(
|
|
7786
|
+
formatWarning("No individuals found matching the criteria")
|
|
7787
|
+
);
|
|
7788
|
+
} else {
|
|
7789
|
+
console.log(
|
|
7790
|
+
formatHeader(`Found ${results.length} individual(s)
|
|
7791
|
+
`)
|
|
7792
|
+
);
|
|
7793
|
+
results.forEach((indi) => {
|
|
7794
|
+
const name = cleanGedcomName(indi.NAME?.toValue());
|
|
7795
|
+
const birthDate = indi.BIRT?.DATE?.toValue();
|
|
7796
|
+
const deathDate = indi.DEAT?.DATE?.toValue();
|
|
7797
|
+
const lifespan = formatLifespan(birthDate, deathDate);
|
|
7798
|
+
console.log(
|
|
7799
|
+
formatListItem(
|
|
7800
|
+
`${formatId(indi.id ?? "")} ${formatName(name)} ${lifespan}`
|
|
7801
|
+
)
|
|
7802
|
+
);
|
|
7803
|
+
const birthPlace = indi.BIRT?.PLAC?.value;
|
|
7804
|
+
if (birthPlace) {
|
|
7805
|
+
console.log(
|
|
7806
|
+
formatListItem(
|
|
7807
|
+
`Birth: ${formatDate(birthDate)} in ${birthPlace}`,
|
|
7808
|
+
1
|
|
7809
|
+
)
|
|
7810
|
+
);
|
|
7330
7811
|
}
|
|
7331
|
-
|
|
7332
|
-
|
|
7333
|
-
|
|
7334
|
-
|
|
7335
|
-
|
|
7336
|
-
|
|
7812
|
+
const deathPlace = indi.DEAT?.PLAC?.value;
|
|
7813
|
+
if (deathPlace) {
|
|
7814
|
+
console.log(
|
|
7815
|
+
formatListItem(
|
|
7816
|
+
`Death: ${formatDate(deathDate)} in ${deathPlace}`,
|
|
7817
|
+
1
|
|
7818
|
+
)
|
|
7819
|
+
);
|
|
7337
7820
|
}
|
|
7338
|
-
|
|
7339
|
-
|
|
7340
|
-
|
|
7341
|
-
|
|
7342
|
-
|
|
7821
|
+
console.log();
|
|
7822
|
+
});
|
|
7823
|
+
}
|
|
7824
|
+
}
|
|
7825
|
+
}
|
|
7826
|
+
function registerFindCommand(program2) {
|
|
7827
|
+
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(
|
|
7828
|
+
(file, query, options) => {
|
|
7829
|
+
try {
|
|
7830
|
+
const content = readGedcomFile(file);
|
|
7831
|
+
const { gedcom: tree } = parser_default.parse(content);
|
|
7832
|
+
const results = findIndividuals(tree, {
|
|
7833
|
+
...options,
|
|
7834
|
+
query
|
|
7835
|
+
});
|
|
7836
|
+
formatFindResults(results, options.json);
|
|
7837
|
+
} catch (error) {
|
|
7838
|
+
handleError(error, "Failed to search GEDCOM file");
|
|
7839
|
+
}
|
|
7840
|
+
}
|
|
7841
|
+
);
|
|
7842
|
+
}
|
|
7843
|
+
|
|
7844
|
+
// src/cli/commands/get.ts
|
|
7845
|
+
function getValueByPath(record, path) {
|
|
7846
|
+
return record.get(path);
|
|
7847
|
+
}
|
|
7848
|
+
function formatOutput(value, options) {
|
|
7849
|
+
if (value === void 0 || value === null) {
|
|
7850
|
+
return "(not found)";
|
|
7851
|
+
}
|
|
7852
|
+
if (value instanceof List) {
|
|
7853
|
+
if (options.json) {
|
|
7854
|
+
const items2 = [];
|
|
7855
|
+
value.forEach((item) => {
|
|
7856
|
+
if (item) {
|
|
7857
|
+
try {
|
|
7858
|
+
items2.push(JSON.parse(item.toJson()));
|
|
7859
|
+
} catch {
|
|
7860
|
+
items2.push(item.toValue());
|
|
7343
7861
|
}
|
|
7344
7862
|
}
|
|
7345
|
-
|
|
7346
|
-
|
|
7863
|
+
});
|
|
7864
|
+
return JSON.stringify(items2, null, 2);
|
|
7865
|
+
}
|
|
7866
|
+
if (options.raw) {
|
|
7867
|
+
const items2 = [];
|
|
7868
|
+
value.forEach((item) => {
|
|
7869
|
+
if (item) {
|
|
7870
|
+
items2.push(item.toGedcom());
|
|
7347
7871
|
}
|
|
7348
7872
|
});
|
|
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
|
-
|
|
7383
|
-
|
|
7384
|
-
|
|
7385
|
-
|
|
7873
|
+
return items2.join("\n");
|
|
7874
|
+
}
|
|
7875
|
+
const items = [];
|
|
7876
|
+
let index = 0;
|
|
7877
|
+
value.forEach((item) => {
|
|
7878
|
+
if (item) {
|
|
7879
|
+
const exportedValue = item.exportValue();
|
|
7880
|
+
items.push(
|
|
7881
|
+
`[${index}] ${exportedValue || item.toValue() || "(empty)"}`
|
|
7882
|
+
);
|
|
7883
|
+
}
|
|
7884
|
+
index++;
|
|
7885
|
+
});
|
|
7886
|
+
return items.join("\n");
|
|
7887
|
+
}
|
|
7888
|
+
if (value instanceof Common) {
|
|
7889
|
+
if (options.json) {
|
|
7890
|
+
return value.toJson();
|
|
7891
|
+
}
|
|
7892
|
+
if (options.raw) {
|
|
7893
|
+
return value.toValue() || "(empty)";
|
|
7894
|
+
}
|
|
7895
|
+
return value.exportValue() || value.toValue() || "(empty)";
|
|
7896
|
+
}
|
|
7897
|
+
return String(value);
|
|
7898
|
+
}
|
|
7899
|
+
function getValue(tree, id, path, json = false, raw = false) {
|
|
7900
|
+
let record = null;
|
|
7901
|
+
let recordType = "";
|
|
7902
|
+
if (id.startsWith("@I")) {
|
|
7903
|
+
record = tree.indi(id);
|
|
7904
|
+
recordType = "Individual";
|
|
7905
|
+
} else if (id.startsWith("@F")) {
|
|
7906
|
+
record = tree.fam(id);
|
|
7907
|
+
recordType = "Family";
|
|
7908
|
+
} else if (id.startsWith("@S")) {
|
|
7909
|
+
record = tree.sour(id);
|
|
7910
|
+
recordType = "Source";
|
|
7911
|
+
} else {
|
|
7912
|
+
record = tree.indi(id);
|
|
7913
|
+
recordType = "Individual";
|
|
7914
|
+
}
|
|
7915
|
+
if (!record) {
|
|
7916
|
+
throw new Error(`Record ${id} not found`);
|
|
7917
|
+
}
|
|
7918
|
+
if (!path) {
|
|
7919
|
+
if (json) {
|
|
7920
|
+
const simplified = {
|
|
7921
|
+
id: record.id || id,
|
|
7922
|
+
type: recordType
|
|
7923
|
+
};
|
|
7924
|
+
if (recordType === "Individual") {
|
|
7925
|
+
if (record.NAME) {
|
|
7926
|
+
simplified.name = record.toNaturalName();
|
|
7927
|
+
}
|
|
7928
|
+
if (record.BIRT) {
|
|
7929
|
+
simplified.birth = {
|
|
7930
|
+
date: record.BIRT.DATE?.toValue(),
|
|
7931
|
+
place: record.BIRT.PLAC?.value
|
|
7932
|
+
};
|
|
7933
|
+
}
|
|
7934
|
+
if (record.DEAT) {
|
|
7935
|
+
simplified.death = {
|
|
7936
|
+
date: record.DEAT.DATE?.toValue(),
|
|
7937
|
+
place: record.DEAT.PLAC?.value
|
|
7938
|
+
};
|
|
7939
|
+
}
|
|
7940
|
+
if (record.SEX) {
|
|
7941
|
+
simplified.sex = record.SEX.value;
|
|
7386
7942
|
}
|
|
7387
7943
|
}
|
|
7388
|
-
|
|
7389
|
-
|
|
7944
|
+
return formatJson(simplified);
|
|
7945
|
+
} else {
|
|
7946
|
+
let result = `${recordType}: ${id}`;
|
|
7947
|
+
if (recordType === "Individual" && record.NAME) {
|
|
7948
|
+
result += `
|
|
7949
|
+
Name: ${record.NAME.toValue()}`;
|
|
7950
|
+
}
|
|
7951
|
+
return result;
|
|
7390
7952
|
}
|
|
7391
|
-
}
|
|
7953
|
+
}
|
|
7954
|
+
const value = getValueByPath(record, path);
|
|
7955
|
+
if (value === void 0) {
|
|
7956
|
+
throw new Error(`Path "${path}" not found in ${recordType} ${id}`);
|
|
7957
|
+
}
|
|
7958
|
+
return formatOutput(value, { json, raw });
|
|
7392
7959
|
}
|
|
7393
|
-
|
|
7960
|
+
function registerGetCommand(program2) {
|
|
7961
|
+
program2.command("get <file> <id>").description("Get a value from a GEDCOM record").option(
|
|
7962
|
+
"-p, --path <path>",
|
|
7963
|
+
'Dot-separated path to the value (e.g., "BIRT.PLAC", "NAME")'
|
|
7964
|
+
).option("-j, --json", "Output in JSON format").option("-r, --raw", "Output raw value only (no formatting)").action((file, id, options) => {
|
|
7965
|
+
try {
|
|
7966
|
+
const content = readGedcomFile(file);
|
|
7967
|
+
const { gedcom: tree } = parser_default.parse(content);
|
|
7968
|
+
let record = null;
|
|
7969
|
+
let recordType = "";
|
|
7970
|
+
if (id.startsWith("@I")) {
|
|
7971
|
+
record = tree.indi(id);
|
|
7972
|
+
recordType = "Individual";
|
|
7973
|
+
} else if (id.startsWith("@F")) {
|
|
7974
|
+
record = tree.fam(id);
|
|
7975
|
+
recordType = "Family";
|
|
7976
|
+
} else if (id.startsWith("@S")) {
|
|
7977
|
+
record = tree.sour(id);
|
|
7978
|
+
recordType = "Source";
|
|
7979
|
+
} else {
|
|
7980
|
+
record = tree.indi(id);
|
|
7981
|
+
recordType = "Individual";
|
|
7982
|
+
}
|
|
7983
|
+
if (!record) {
|
|
7984
|
+
console.error(formatError(`Record ${id} not found`));
|
|
7985
|
+
process.exit(1);
|
|
7986
|
+
}
|
|
7987
|
+
if (!options.path) {
|
|
7988
|
+
if (options.json) {
|
|
7989
|
+
const simplified = {
|
|
7990
|
+
id: record.id || id,
|
|
7991
|
+
type: recordType
|
|
7992
|
+
};
|
|
7993
|
+
if (recordType === "Individual") {
|
|
7994
|
+
if (record.NAME) {
|
|
7995
|
+
simplified.name = record.NAME.toValue();
|
|
7996
|
+
}
|
|
7997
|
+
if (record.BIRT) {
|
|
7998
|
+
simplified.birth = {
|
|
7999
|
+
date: record.BIRT.DATE?.toValue(),
|
|
8000
|
+
place: record.BIRT.PLAC?.value
|
|
8001
|
+
};
|
|
8002
|
+
}
|
|
8003
|
+
if (record.DEAT) {
|
|
8004
|
+
simplified.death = {
|
|
8005
|
+
date: record.DEAT.DATE?.toValue(),
|
|
8006
|
+
place: record.DEAT.PLAC?.value
|
|
8007
|
+
};
|
|
8008
|
+
}
|
|
8009
|
+
if (record.SEX) {
|
|
8010
|
+
simplified.sex = record.SEX.value;
|
|
8011
|
+
}
|
|
8012
|
+
}
|
|
8013
|
+
console.log(formatJson(simplified));
|
|
8014
|
+
} else {
|
|
8015
|
+
console.log(`${recordType}: ${id}`);
|
|
8016
|
+
if (recordType === "Individual" && record.NAME) {
|
|
8017
|
+
console.log(`Name: ${record.NAME.toValue()}`);
|
|
8018
|
+
}
|
|
8019
|
+
}
|
|
8020
|
+
return;
|
|
8021
|
+
}
|
|
8022
|
+
const value = getValueByPath(record, options.path);
|
|
8023
|
+
if (value === void 0) {
|
|
8024
|
+
console.error(
|
|
8025
|
+
formatError(
|
|
8026
|
+
`Path "${options.path}" not found in ${recordType} ${id}`
|
|
8027
|
+
)
|
|
8028
|
+
);
|
|
8029
|
+
process.exit(1);
|
|
8030
|
+
}
|
|
8031
|
+
console.log(formatOutput(value, options));
|
|
8032
|
+
} catch (error) {
|
|
8033
|
+
handleError(error);
|
|
8034
|
+
}
|
|
8035
|
+
});
|
|
8036
|
+
}
|
|
8037
|
+
|
|
7394
8038
|
// src/cli/commands/info.ts
|
|
7395
8039
|
function registerInfoCommand(program2) {
|
|
7396
8040
|
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) => {
|
|
@@ -7418,37 +8062,60 @@ function registerInfoCommand(program2) {
|
|
|
7418
8062
|
if (options.json) {
|
|
7419
8063
|
console.log(formatJson(info));
|
|
7420
8064
|
} else {
|
|
7421
|
-
console.log(
|
|
8065
|
+
console.log(
|
|
8066
|
+
formatSuccess("GEDCOM file parsed successfully\n")
|
|
8067
|
+
);
|
|
7422
8068
|
console.log(formatHeader("File Information"));
|
|
7423
8069
|
console.log(`${formatLabel("File")} ${formatValue(file)}`);
|
|
7424
|
-
console.log(
|
|
8070
|
+
console.log(
|
|
8071
|
+
`${formatLabel("GEDCOM Version")} ${formatValue(version)}`
|
|
8072
|
+
);
|
|
7425
8073
|
console.log();
|
|
7426
8074
|
console.log(formatHeader("Statistics"));
|
|
7427
|
-
console.log(
|
|
7428
|
-
|
|
7429
|
-
|
|
7430
|
-
console.log(
|
|
7431
|
-
|
|
7432
|
-
|
|
8075
|
+
console.log(
|
|
8076
|
+
`${formatLabel("Individuals")} ${formatCount(individuals?.length || 0)}`
|
|
8077
|
+
);
|
|
8078
|
+
console.log(
|
|
8079
|
+
`${formatLabel("Families")} ${formatCount(families?.length || 0)}`
|
|
8080
|
+
);
|
|
8081
|
+
console.log(
|
|
8082
|
+
`${formatLabel("Sources")} ${formatCount(sources?.length || 0)}`
|
|
8083
|
+
);
|
|
8084
|
+
console.log(
|
|
8085
|
+
`${formatLabel("Repositories")} ${formatCount(repos?.length || 0)}`
|
|
8086
|
+
);
|
|
8087
|
+
console.log(
|
|
8088
|
+
`${formatLabel("Media Objects")} ${formatCount(objes?.length || 0)}`
|
|
8089
|
+
);
|
|
8090
|
+
console.log(
|
|
8091
|
+
`${formatLabel("Submitters")} ${formatCount(submitters?.length || 0)}`
|
|
8092
|
+
);
|
|
7433
8093
|
if (options.verbose) {
|
|
7434
8094
|
console.log();
|
|
7435
8095
|
console.log(formatHeader("Additional Details"));
|
|
7436
8096
|
const surnames = /* @__PURE__ */ new Map();
|
|
7437
|
-
individuals
|
|
8097
|
+
individuals?.forEach((indi) => {
|
|
7438
8098
|
const name = indi.NAME?.toValue();
|
|
7439
8099
|
if (name) {
|
|
7440
8100
|
const match = name.match(/\/(.+?)\//);
|
|
7441
8101
|
if (match) {
|
|
7442
8102
|
const surname = match[1];
|
|
7443
|
-
surnames.set(
|
|
8103
|
+
surnames.set(
|
|
8104
|
+
surname,
|
|
8105
|
+
(surnames.get(surname) || 0) + 1
|
|
8106
|
+
);
|
|
7444
8107
|
}
|
|
7445
8108
|
}
|
|
7446
8109
|
});
|
|
7447
8110
|
const topSurnames = Array.from(surnames.entries()).sort((a, b) => b[1] - a[1]).slice(0, 5);
|
|
7448
8111
|
if (topSurnames.length > 0) {
|
|
7449
|
-
console.log(
|
|
8112
|
+
console.log(
|
|
8113
|
+
`${formatLabel("Most Common Surnames")}`
|
|
8114
|
+
);
|
|
7450
8115
|
topSurnames.forEach(([surname, count]) => {
|
|
7451
|
-
console.log(
|
|
8116
|
+
console.log(
|
|
8117
|
+
` - ${surname}: ${formatCount(count)}`
|
|
8118
|
+
);
|
|
7452
8119
|
});
|
|
7453
8120
|
}
|
|
7454
8121
|
}
|
|
@@ -7514,12 +8181,882 @@ function registerMergeCommand(program2) {
|
|
|
7514
8181
|
}
|
|
7515
8182
|
});
|
|
7516
8183
|
}
|
|
8184
|
+
|
|
8185
|
+
// src/cli/commands/select.ts
|
|
8186
|
+
function selectIndividual(tree, input, searchResults) {
|
|
8187
|
+
if (/^\d+$/.test(input) && searchResults && searchResults.length > 0) {
|
|
8188
|
+
const index = parseInt(input) - 1;
|
|
8189
|
+
if (index >= 0 && index < searchResults.length) {
|
|
8190
|
+
return searchResults[index];
|
|
8191
|
+
}
|
|
8192
|
+
return void 0;
|
|
8193
|
+
}
|
|
8194
|
+
return tree.indi(input);
|
|
8195
|
+
}
|
|
8196
|
+
function formatSelectResult(individual, input) {
|
|
8197
|
+
if (individual) {
|
|
8198
|
+
const name = cleanGedcomName(individual.NAME?.toValue());
|
|
8199
|
+
console.log(
|
|
8200
|
+
formatSuccess(
|
|
8201
|
+
`Selected: ${formatId(individual.id)} ${formatName(name)}`
|
|
8202
|
+
)
|
|
8203
|
+
);
|
|
8204
|
+
} else {
|
|
8205
|
+
console.log(formatError(`Individual ${input} not found`));
|
|
8206
|
+
}
|
|
8207
|
+
}
|
|
8208
|
+
function registerSelectCommand(program2) {
|
|
8209
|
+
program2.command("select <file> <id>").description("Select an individual by ID").action((file, id) => {
|
|
8210
|
+
try {
|
|
8211
|
+
const content = readGedcomFile(file);
|
|
8212
|
+
const { gedcom: tree } = parser_default.parse(content);
|
|
8213
|
+
const individual = selectIndividual(tree, id);
|
|
8214
|
+
formatSelectResult(individual, id);
|
|
8215
|
+
if (!individual) {
|
|
8216
|
+
process.exit(1);
|
|
8217
|
+
}
|
|
8218
|
+
} catch (error) {
|
|
8219
|
+
handleError(error, "Failed to select individual");
|
|
8220
|
+
}
|
|
8221
|
+
});
|
|
8222
|
+
}
|
|
8223
|
+
|
|
8224
|
+
// src/cli/commands/show.ts
|
|
8225
|
+
function showIndividual(tree, individual) {
|
|
8226
|
+
const name = cleanGedcomName(individual.NAME?.toValue());
|
|
8227
|
+
console.log(
|
|
8228
|
+
formatHeader(`
|
|
8229
|
+
${formatId(individual.id ?? "")} ${formatName(name)}`)
|
|
8230
|
+
);
|
|
8231
|
+
console.log("");
|
|
8232
|
+
if (individual.SEX?.value) {
|
|
8233
|
+
console.log(formatListItem(`Sex: ${individual.SEX.value}`));
|
|
8234
|
+
}
|
|
8235
|
+
if (individual.BIRT) {
|
|
8236
|
+
const date = individual.BIRT.DATE?.toValue();
|
|
8237
|
+
const place = individual.BIRT.PLAC?.value;
|
|
8238
|
+
console.log(
|
|
8239
|
+
formatListItem(
|
|
8240
|
+
`Birth: ${date || "?"}${place ? ` at ${place}` : ""}`
|
|
8241
|
+
)
|
|
8242
|
+
);
|
|
8243
|
+
}
|
|
8244
|
+
if (individual.DEAT) {
|
|
8245
|
+
const date = individual.DEAT.DATE?.toValue();
|
|
8246
|
+
const place = individual.DEAT.PLAC?.value;
|
|
8247
|
+
console.log(
|
|
8248
|
+
formatListItem(
|
|
8249
|
+
`Death: ${date || "?"}${place ? ` at ${place}` : ""}`
|
|
8250
|
+
)
|
|
8251
|
+
);
|
|
8252
|
+
}
|
|
8253
|
+
const parentFams = individual.FAMC;
|
|
8254
|
+
if (parentFams) {
|
|
8255
|
+
const parentList = parentFams instanceof List ? parentFams.values() : [parentFams];
|
|
8256
|
+
console.log(formatListItem("\nParents:"));
|
|
8257
|
+
parentList.forEach((famRef) => {
|
|
8258
|
+
if (famRef) {
|
|
8259
|
+
const fam = tree.fam(famRef.value);
|
|
8260
|
+
if (fam) {
|
|
8261
|
+
const father = fam.HUSB ? tree.indi(fam.HUSB.value) : null;
|
|
8262
|
+
const mother = fam.WIFE ? tree.indi(fam.WIFE.value) : null;
|
|
8263
|
+
if (father) {
|
|
8264
|
+
const fatherName = cleanGedcomName(
|
|
8265
|
+
father.NAME?.toValue()
|
|
8266
|
+
);
|
|
8267
|
+
console.log(
|
|
8268
|
+
formatListItem(
|
|
8269
|
+
` ${formatId(father.id)} ${formatName(fatherName)}`
|
|
8270
|
+
)
|
|
8271
|
+
);
|
|
8272
|
+
}
|
|
8273
|
+
if (mother) {
|
|
8274
|
+
const motherName = cleanGedcomName(
|
|
8275
|
+
mother.NAME?.toValue()
|
|
8276
|
+
);
|
|
8277
|
+
console.log(
|
|
8278
|
+
formatListItem(
|
|
8279
|
+
` ${formatId(mother.id)} ${formatName(motherName)}`
|
|
8280
|
+
)
|
|
8281
|
+
);
|
|
8282
|
+
}
|
|
8283
|
+
}
|
|
8284
|
+
}
|
|
8285
|
+
});
|
|
8286
|
+
}
|
|
8287
|
+
const spouseFams = individual.FAMS;
|
|
8288
|
+
if (spouseFams) {
|
|
8289
|
+
const famList = spouseFams instanceof List ? spouseFams.values() : [spouseFams];
|
|
8290
|
+
console.log(formatListItem("\nSpouses:"));
|
|
8291
|
+
famList.forEach((famRef) => {
|
|
8292
|
+
if (famRef) {
|
|
8293
|
+
const fam = tree.fam(famRef.value);
|
|
8294
|
+
if (fam) {
|
|
8295
|
+
const spouseRef = fam.HUSB?.value === individual.id ? fam.WIFE : fam.HUSB;
|
|
8296
|
+
if (spouseRef) {
|
|
8297
|
+
const spouse = tree.indi(spouseRef.value);
|
|
8298
|
+
if (spouse) {
|
|
8299
|
+
const spouseName = cleanGedcomName(
|
|
8300
|
+
spouse.NAME?.toValue()
|
|
8301
|
+
);
|
|
8302
|
+
console.log(
|
|
8303
|
+
formatListItem(
|
|
8304
|
+
` ${formatId(spouse.id)} ${formatName(spouseName)}`
|
|
8305
|
+
)
|
|
8306
|
+
);
|
|
8307
|
+
}
|
|
8308
|
+
}
|
|
8309
|
+
}
|
|
8310
|
+
}
|
|
8311
|
+
});
|
|
8312
|
+
}
|
|
8313
|
+
if (spouseFams) {
|
|
8314
|
+
const famList = spouseFams instanceof List ? spouseFams.values() : [spouseFams];
|
|
8315
|
+
let hasChildren = false;
|
|
8316
|
+
famList.forEach((famRef) => {
|
|
8317
|
+
if (famRef) {
|
|
8318
|
+
const fam = tree.fam(famRef.value);
|
|
8319
|
+
if (fam && fam.CHIL) {
|
|
8320
|
+
if (!hasChildren) {
|
|
8321
|
+
console.log(formatListItem("\nChildren:"));
|
|
8322
|
+
hasChildren = true;
|
|
8323
|
+
}
|
|
8324
|
+
const children = fam.CHIL instanceof List ? fam.CHIL.values() : [fam.CHIL];
|
|
8325
|
+
children.forEach((childRef) => {
|
|
8326
|
+
if (childRef) {
|
|
8327
|
+
const child = tree.indi(childRef.value);
|
|
8328
|
+
if (child) {
|
|
8329
|
+
const childName = cleanGedcomName(
|
|
8330
|
+
child.NAME?.toValue()
|
|
8331
|
+
);
|
|
8332
|
+
console.log(
|
|
8333
|
+
formatListItem(
|
|
8334
|
+
` ${formatId(child.id ?? "")} ${formatName(childName)}`
|
|
8335
|
+
)
|
|
8336
|
+
);
|
|
8337
|
+
}
|
|
8338
|
+
}
|
|
8339
|
+
});
|
|
8340
|
+
}
|
|
8341
|
+
}
|
|
8342
|
+
});
|
|
8343
|
+
}
|
|
8344
|
+
console.log("");
|
|
8345
|
+
}
|
|
8346
|
+
function registerShowCommand(program2) {
|
|
8347
|
+
program2.command("show <file> <id>").description("Display detailed information about an individual").action((file, id) => {
|
|
8348
|
+
try {
|
|
8349
|
+
const content = readGedcomFile(file);
|
|
8350
|
+
const { gedcom: tree } = parser_default.parse(content);
|
|
8351
|
+
const individual = tree.indi(id);
|
|
8352
|
+
if (!individual) {
|
|
8353
|
+
console.error(formatError(`Individual ${id} not found`));
|
|
8354
|
+
process.exit(1);
|
|
8355
|
+
}
|
|
8356
|
+
showIndividual(tree, individual);
|
|
8357
|
+
} catch (error) {
|
|
8358
|
+
handleError(error, "Failed to show individual");
|
|
8359
|
+
}
|
|
8360
|
+
});
|
|
8361
|
+
}
|
|
8362
|
+
|
|
8363
|
+
// src/cli/commands/stats.ts
|
|
8364
|
+
function displayStats(tree, json = false) {
|
|
8365
|
+
const stats = tree.stats();
|
|
8366
|
+
if (json) {
|
|
8367
|
+
console.log(formatJson(stats));
|
|
8368
|
+
} else {
|
|
8369
|
+
console.log(formatHeader("GEDCOM File Statistics\n"));
|
|
8370
|
+
console.log(formatLabel("Total Individuals"));
|
|
8371
|
+
console.log(` ${formatCount(stats.totalIndividuals)}`);
|
|
8372
|
+
console.log(formatLabel("Total Families"));
|
|
8373
|
+
console.log(` ${formatCount(stats.totalFamilies)}`);
|
|
8374
|
+
console.log();
|
|
8375
|
+
console.log(formatLabel("By Gender"));
|
|
8376
|
+
console.log(` Males: ${formatCount(stats.byGender.males)}`);
|
|
8377
|
+
console.log(` Females: ${formatCount(stats.byGender.females)}`);
|
|
8378
|
+
console.log(` Unknown: ${formatCount(stats.byGender.unknown)}`);
|
|
8379
|
+
console.log();
|
|
8380
|
+
if (stats.dateRange.earliest && stats.dateRange.latest) {
|
|
8381
|
+
console.log(formatLabel("Date Range"));
|
|
8382
|
+
console.log(
|
|
8383
|
+
` ${stats.dateRange.earliest} - ${stats.dateRange.latest}`
|
|
8384
|
+
);
|
|
8385
|
+
console.log();
|
|
8386
|
+
}
|
|
8387
|
+
if (stats.averageLifespan) {
|
|
8388
|
+
console.log(formatLabel("Average Lifespan"));
|
|
8389
|
+
console.log(` ${stats.averageLifespan.toFixed(1)} years`);
|
|
8390
|
+
console.log();
|
|
8391
|
+
}
|
|
8392
|
+
if (stats.topSurnames.length > 0) {
|
|
8393
|
+
console.log(formatLabel("Most Common Surnames"));
|
|
8394
|
+
stats.topSurnames.forEach(({ surname, count }) => {
|
|
8395
|
+
console.log(` ${surname}: ${formatCount(count)}`);
|
|
8396
|
+
});
|
|
8397
|
+
console.log();
|
|
8398
|
+
}
|
|
8399
|
+
if (stats.topBirthPlaces.length > 0) {
|
|
8400
|
+
console.log(formatLabel("Most Common Birth Places"));
|
|
8401
|
+
stats.topBirthPlaces.slice(0, 5).forEach(({ place, count }) => {
|
|
8402
|
+
console.log(` ${place}: ${formatCount(count)}`);
|
|
8403
|
+
});
|
|
8404
|
+
}
|
|
8405
|
+
}
|
|
8406
|
+
}
|
|
8407
|
+
function registerStatsCommand(program2) {
|
|
8408
|
+
program2.command("stats <file>").description("Generate statistics about a GEDCOM file").option("-j, --json", "Output in JSON format").action((file, options) => {
|
|
8409
|
+
try {
|
|
8410
|
+
const content = readGedcomFile(file);
|
|
8411
|
+
const { gedcom: tree } = parser_default.parse(content);
|
|
8412
|
+
displayStats(tree, options.json);
|
|
8413
|
+
} catch (error) {
|
|
8414
|
+
handleError(error, "Failed to generate statistics");
|
|
8415
|
+
}
|
|
8416
|
+
});
|
|
8417
|
+
}
|
|
8418
|
+
|
|
8419
|
+
// src/cli/repl.ts
|
|
8420
|
+
function parseArgs(line) {
|
|
8421
|
+
const args = [];
|
|
8422
|
+
let current = "";
|
|
8423
|
+
let inQuotes = false;
|
|
8424
|
+
let quoteChar = "";
|
|
8425
|
+
for (let i = 0; i < line.length; i++) {
|
|
8426
|
+
const char = line[i];
|
|
8427
|
+
if ((char === '"' || char === "'") && !inQuotes) {
|
|
8428
|
+
inQuotes = true;
|
|
8429
|
+
quoteChar = char;
|
|
8430
|
+
} else if (char === quoteChar && inQuotes) {
|
|
8431
|
+
inQuotes = false;
|
|
8432
|
+
quoteChar = "";
|
|
8433
|
+
} else if (char === " " && !inQuotes) {
|
|
8434
|
+
if (current) {
|
|
8435
|
+
args.push(current);
|
|
8436
|
+
current = "";
|
|
8437
|
+
}
|
|
8438
|
+
} else {
|
|
8439
|
+
current += char;
|
|
8440
|
+
}
|
|
8441
|
+
}
|
|
8442
|
+
if (current) {
|
|
8443
|
+
args.push(current);
|
|
8444
|
+
}
|
|
8445
|
+
return args;
|
|
8446
|
+
}
|
|
8447
|
+
var GedcomRepl = class {
|
|
8448
|
+
constructor(tree) {
|
|
8449
|
+
this.context = { tree };
|
|
8450
|
+
this.rl = readline.createInterface({
|
|
8451
|
+
input: process.stdin,
|
|
8452
|
+
output: process.stdout,
|
|
8453
|
+
prompt: chalk.blue("gedcom> ")
|
|
8454
|
+
});
|
|
8455
|
+
this.setupHandlers();
|
|
8456
|
+
}
|
|
8457
|
+
setupHandlers() {
|
|
8458
|
+
this.rl.on("line", (line) => {
|
|
8459
|
+
this.handleCommand(line.trim());
|
|
8460
|
+
});
|
|
8461
|
+
this.rl.on("close", () => {
|
|
8462
|
+
console.log("\nGoodbye!");
|
|
8463
|
+
process.exit(0);
|
|
8464
|
+
});
|
|
8465
|
+
}
|
|
8466
|
+
handleCommand(line) {
|
|
8467
|
+
if (!line) {
|
|
8468
|
+
this.rl.prompt();
|
|
8469
|
+
return;
|
|
8470
|
+
}
|
|
8471
|
+
const args = parseArgs(line);
|
|
8472
|
+
const command = args[0];
|
|
8473
|
+
const commandArgs = args.slice(1);
|
|
8474
|
+
switch (command.toLowerCase()) {
|
|
8475
|
+
case "help":
|
|
8476
|
+
this.showHelp(commandArgs[0]);
|
|
8477
|
+
break;
|
|
8478
|
+
case "stats":
|
|
8479
|
+
displayStats(this.context.tree, commandArgs.includes("--json"));
|
|
8480
|
+
break;
|
|
8481
|
+
case "find":
|
|
8482
|
+
this.handleFind(commandArgs);
|
|
8483
|
+
break;
|
|
8484
|
+
case "select":
|
|
8485
|
+
this.handleSelect(commandArgs);
|
|
8486
|
+
break;
|
|
8487
|
+
case "show":
|
|
8488
|
+
this.handleShow(commandArgs);
|
|
8489
|
+
break;
|
|
8490
|
+
case "get":
|
|
8491
|
+
this.handleGet(commandArgs);
|
|
8492
|
+
break;
|
|
8493
|
+
case "relatives":
|
|
8494
|
+
this.handleRelatives(commandArgs);
|
|
8495
|
+
break;
|
|
8496
|
+
case "validate":
|
|
8497
|
+
this.handleValidate(commandArgs);
|
|
8498
|
+
break;
|
|
8499
|
+
case "clear":
|
|
8500
|
+
console.clear();
|
|
8501
|
+
break;
|
|
8502
|
+
case "exit":
|
|
8503
|
+
case "quit":
|
|
8504
|
+
this.rl.close();
|
|
8505
|
+
return;
|
|
8506
|
+
default:
|
|
8507
|
+
console.log(formatError(`Unknown command: ${command}`));
|
|
8508
|
+
console.log('Type "help" for available commands');
|
|
8509
|
+
}
|
|
8510
|
+
this.rl.prompt();
|
|
8511
|
+
}
|
|
8512
|
+
showHelp(command) {
|
|
8513
|
+
if (command) {
|
|
8514
|
+
switch (command.toLowerCase()) {
|
|
8515
|
+
case "stats":
|
|
8516
|
+
console.log(formatHeader("\nstats"));
|
|
8517
|
+
console.log("Show GEDCOM file statistics\n");
|
|
8518
|
+
console.log("Usage: stats [--json]");
|
|
8519
|
+
console.log("\nOptions:");
|
|
8520
|
+
console.log(" --json Output in JSON format");
|
|
8521
|
+
break;
|
|
8522
|
+
case "find":
|
|
8523
|
+
console.log(formatHeader("\nfind"));
|
|
8524
|
+
console.log("Search for individuals in the tree\n");
|
|
8525
|
+
console.log("Usage: find <query> [options]");
|
|
8526
|
+
console.log("\nArguments:");
|
|
8527
|
+
console.log(" <query> Search query (name or ID)");
|
|
8528
|
+
console.log("\nOptions:");
|
|
8529
|
+
console.log(" --birth-year <year> Filter by birth year");
|
|
8530
|
+
console.log(" --death-year <year> Filter by death year");
|
|
8531
|
+
console.log(
|
|
8532
|
+
" --json Output in JSON format"
|
|
8533
|
+
);
|
|
8534
|
+
break;
|
|
8535
|
+
case "select":
|
|
8536
|
+
console.log(formatHeader("\nselect"));
|
|
8537
|
+
console.log("Select an individual by ID or index\n");
|
|
8538
|
+
console.log("Usage: select <id|index>");
|
|
8539
|
+
console.log("\nArguments:");
|
|
8540
|
+
console.log(
|
|
8541
|
+
" <id|index> Individual ID (e.g., @I123@) or search result index (e.g., 1)"
|
|
8542
|
+
);
|
|
8543
|
+
break;
|
|
8544
|
+
case "show":
|
|
8545
|
+
console.log(formatHeader("\nshow"));
|
|
8546
|
+
console.log(
|
|
8547
|
+
"Show detailed information about an individual\n"
|
|
8548
|
+
);
|
|
8549
|
+
console.log("Usage: show [id]");
|
|
8550
|
+
console.log("\nArguments:");
|
|
8551
|
+
console.log(
|
|
8552
|
+
" [id] Optional individual ID. If not provided, shows currently selected person"
|
|
8553
|
+
);
|
|
8554
|
+
break;
|
|
8555
|
+
case "get":
|
|
8556
|
+
console.log(formatHeader("\nget"));
|
|
8557
|
+
console.log("Get a value from a GEDCOM record\n");
|
|
8558
|
+
console.log("Usage: get [id] [options]");
|
|
8559
|
+
console.log("\nArguments:");
|
|
8560
|
+
console.log(
|
|
8561
|
+
" [id] Record ID (e.g., @I123@, @F456@). If not provided, uses selected person"
|
|
8562
|
+
);
|
|
8563
|
+
console.log("\nOptions:");
|
|
8564
|
+
console.log(
|
|
8565
|
+
' --path, -p <path> Dot-separated path (e.g., "BIRT.PLAC", "NAME")'
|
|
8566
|
+
);
|
|
8567
|
+
console.log(" --json, -j Output in JSON format");
|
|
8568
|
+
console.log(
|
|
8569
|
+
" --raw, -r Output raw value only (no formatting)"
|
|
8570
|
+
);
|
|
8571
|
+
console.log("\nExamples:");
|
|
8572
|
+
console.log(' get --path "BIRT.PLAC"');
|
|
8573
|
+
console.log(' get @I123@ --path "NAME"');
|
|
8574
|
+
console.log(" get @I123@ --json");
|
|
8575
|
+
break;
|
|
8576
|
+
case "relatives":
|
|
8577
|
+
console.log(formatHeader("\nrelatives"));
|
|
8578
|
+
console.log(
|
|
8579
|
+
"Get ancestors and/or descendants of the selected individual\n"
|
|
8580
|
+
);
|
|
8581
|
+
console.log("Usage: relatives [options]");
|
|
8582
|
+
console.log("\nOptions:");
|
|
8583
|
+
console.log(" --ancestors, -a Include ancestors");
|
|
8584
|
+
console.log(" --descendants, -d Include descendants");
|
|
8585
|
+
console.log(
|
|
8586
|
+
" --tree, -t Include both ancestors and descendants"
|
|
8587
|
+
);
|
|
8588
|
+
console.log(
|
|
8589
|
+
" --depth <n> Limit depth (generations, default: 999)"
|
|
8590
|
+
);
|
|
8591
|
+
console.log(" --json, -j Output in JSON format");
|
|
8592
|
+
console.log(
|
|
8593
|
+
"\nNote: You must select an individual first using 'select'"
|
|
8594
|
+
);
|
|
8595
|
+
console.log("\nExamples:");
|
|
8596
|
+
console.log(" relatives --ancestors");
|
|
8597
|
+
console.log(" relatives --descendants --depth 3");
|
|
8598
|
+
console.log(" relatives --tree --json");
|
|
8599
|
+
break;
|
|
8600
|
+
case "validate":
|
|
8601
|
+
console.log(formatHeader("\nvalidate"));
|
|
8602
|
+
console.log("Validate the GEDCOM file\n");
|
|
8603
|
+
console.log("Usage: validate [options]");
|
|
8604
|
+
console.log("\nOptions:");
|
|
8605
|
+
console.log(" --json, -j Output in JSON format");
|
|
8606
|
+
console.log(" --strict, -s Enable strict validation");
|
|
8607
|
+
console.log("\nExamples:");
|
|
8608
|
+
console.log(" validate");
|
|
8609
|
+
console.log(" validate --strict --json");
|
|
8610
|
+
break;
|
|
8611
|
+
case "clear":
|
|
8612
|
+
console.log(formatHeader("\nclear"));
|
|
8613
|
+
console.log("Clear the screen\n");
|
|
8614
|
+
console.log("Usage: clear");
|
|
8615
|
+
break;
|
|
8616
|
+
case "exit":
|
|
8617
|
+
case "quit":
|
|
8618
|
+
console.log(formatHeader("\nexit"));
|
|
8619
|
+
console.log("Exit the REPL\n");
|
|
8620
|
+
console.log("Usage: exit");
|
|
8621
|
+
console.log("Alias: quit");
|
|
8622
|
+
break;
|
|
8623
|
+
default:
|
|
8624
|
+
console.log(formatError(`Unknown command: ${command}`));
|
|
8625
|
+
console.log('Type "help" for available commands');
|
|
8626
|
+
}
|
|
8627
|
+
console.log("");
|
|
8628
|
+
return;
|
|
8629
|
+
}
|
|
8630
|
+
console.log(formatHeader("\nAvailable Commands:"));
|
|
8631
|
+
console.log("");
|
|
8632
|
+
console.log(
|
|
8633
|
+
chalk.cyan(" stats [--json]") + " - Show tree statistics"
|
|
8634
|
+
);
|
|
8635
|
+
console.log(
|
|
8636
|
+
chalk.cyan(" find <query> [options]") + " - Search for individuals"
|
|
8637
|
+
);
|
|
8638
|
+
console.log(
|
|
8639
|
+
chalk.cyan(" select <id|index>") + " - Select an individual"
|
|
8640
|
+
);
|
|
8641
|
+
console.log(
|
|
8642
|
+
chalk.cyan(" show [id]") + " - Show details (current or specified person)"
|
|
8643
|
+
);
|
|
8644
|
+
console.log(
|
|
8645
|
+
chalk.cyan(" get [id] [options]") + " - Get a value from a record"
|
|
8646
|
+
);
|
|
8647
|
+
console.log(
|
|
8648
|
+
chalk.cyan(" relatives [options]") + " - Get ancestors/descendants of selected person"
|
|
8649
|
+
);
|
|
8650
|
+
console.log(
|
|
8651
|
+
chalk.cyan(" validate [options]") + " - Validate the GEDCOM file"
|
|
8652
|
+
);
|
|
8653
|
+
console.log(
|
|
8654
|
+
chalk.cyan(" clear") + " - Clear screen"
|
|
8655
|
+
);
|
|
8656
|
+
console.log(
|
|
8657
|
+
chalk.cyan(" help [command]") + " - Show this help or help for a specific command"
|
|
8658
|
+
);
|
|
8659
|
+
console.log(
|
|
8660
|
+
chalk.cyan(" exit") + " - Exit REPL"
|
|
8661
|
+
);
|
|
8662
|
+
console.log("");
|
|
8663
|
+
}
|
|
8664
|
+
handleFind(args) {
|
|
8665
|
+
if (args.length === 0) {
|
|
8666
|
+
console.log(formatError("Usage: find <query> [options]"));
|
|
8667
|
+
console.log('Type "help find" for more information');
|
|
8668
|
+
return;
|
|
8669
|
+
}
|
|
8670
|
+
const isJson = args.includes("--json");
|
|
8671
|
+
let birthYear;
|
|
8672
|
+
let deathYear;
|
|
8673
|
+
const birthYearIndex = args.indexOf("--birth-year");
|
|
8674
|
+
if (birthYearIndex !== -1 && birthYearIndex + 1 < args.length) {
|
|
8675
|
+
birthYear = args[birthYearIndex + 1];
|
|
8676
|
+
}
|
|
8677
|
+
const deathYearIndex = args.indexOf("--death-year");
|
|
8678
|
+
if (deathYearIndex !== -1 && deathYearIndex + 1 < args.length) {
|
|
8679
|
+
deathYear = args[deathYearIndex + 1];
|
|
8680
|
+
}
|
|
8681
|
+
const query = args.filter((a, i) => {
|
|
8682
|
+
if (a.startsWith("--")) return false;
|
|
8683
|
+
if (i > 0 && args[i - 1].startsWith("--")) return false;
|
|
8684
|
+
return true;
|
|
8685
|
+
}).join(" ");
|
|
8686
|
+
const results = findIndividuals(this.context.tree, {
|
|
8687
|
+
query: query || void 0,
|
|
8688
|
+
birthYear,
|
|
8689
|
+
deathYear
|
|
8690
|
+
});
|
|
8691
|
+
this.context.searchResults = results;
|
|
8692
|
+
formatFindResults(results, isJson);
|
|
8693
|
+
if (results.length > 0 && !isJson) {
|
|
8694
|
+
console.log(
|
|
8695
|
+
chalk.dim(
|
|
8696
|
+
'\nTip: Use "select <number>" to select an individual from results'
|
|
8697
|
+
)
|
|
8698
|
+
);
|
|
8699
|
+
}
|
|
8700
|
+
}
|
|
8701
|
+
handleSelect(args) {
|
|
8702
|
+
if (args.length === 0) {
|
|
8703
|
+
console.log(formatError("Usage: select <id|index>"));
|
|
8704
|
+
return;
|
|
8705
|
+
}
|
|
8706
|
+
const input = args[0];
|
|
8707
|
+
const individual = selectIndividual(
|
|
8708
|
+
this.context.tree,
|
|
8709
|
+
input,
|
|
8710
|
+
this.context.searchResults
|
|
8711
|
+
);
|
|
8712
|
+
formatSelectResult(individual, input);
|
|
8713
|
+
if (individual) {
|
|
8714
|
+
this.context.selectedPerson = individual;
|
|
8715
|
+
console.log(chalk.dim('Tip: Use "show" to see details'));
|
|
8716
|
+
}
|
|
8717
|
+
}
|
|
8718
|
+
handleShow(args) {
|
|
8719
|
+
let individual;
|
|
8720
|
+
if (args.length > 0) {
|
|
8721
|
+
individual = this.context.tree.indi(args[0]);
|
|
8722
|
+
if (!individual) {
|
|
8723
|
+
console.log(formatError(`Individual ${args[0]} not found`));
|
|
8724
|
+
return;
|
|
8725
|
+
}
|
|
8726
|
+
} else {
|
|
8727
|
+
individual = this.context.selectedPerson;
|
|
8728
|
+
if (!individual) {
|
|
8729
|
+
console.log(
|
|
8730
|
+
formatError(
|
|
8731
|
+
'No person selected. Use "select <id>" first or "show <id>"'
|
|
8732
|
+
)
|
|
8733
|
+
);
|
|
8734
|
+
return;
|
|
8735
|
+
}
|
|
8736
|
+
}
|
|
8737
|
+
showIndividual(this.context.tree, individual);
|
|
8738
|
+
}
|
|
8739
|
+
handleGet(args) {
|
|
8740
|
+
if (args.length === 0) {
|
|
8741
|
+
console.log(
|
|
8742
|
+
formatError("Usage: get <id> [--path <path>] [--json] [--raw]")
|
|
8743
|
+
);
|
|
8744
|
+
console.log('Type "help get" for more information');
|
|
8745
|
+
return;
|
|
8746
|
+
}
|
|
8747
|
+
let id;
|
|
8748
|
+
let path;
|
|
8749
|
+
let json = false;
|
|
8750
|
+
let raw = false;
|
|
8751
|
+
for (let i = 0; i < args.length; i++) {
|
|
8752
|
+
const arg = args[i];
|
|
8753
|
+
if (arg === "--path" || arg === "-p") {
|
|
8754
|
+
if (i + 1 < args.length) {
|
|
8755
|
+
path = args[i + 1];
|
|
8756
|
+
i++;
|
|
8757
|
+
}
|
|
8758
|
+
} else if (arg === "--json" || arg === "-j") {
|
|
8759
|
+
json = true;
|
|
8760
|
+
} else if (arg === "--raw" || arg === "-r") {
|
|
8761
|
+
raw = true;
|
|
8762
|
+
} else if (!arg.startsWith("-") && !id) {
|
|
8763
|
+
id = arg;
|
|
8764
|
+
}
|
|
8765
|
+
}
|
|
8766
|
+
if (!id) {
|
|
8767
|
+
if (this.context.selectedPerson) {
|
|
8768
|
+
id = this.context.selectedPerson.id;
|
|
8769
|
+
} else {
|
|
8770
|
+
console.log(
|
|
8771
|
+
formatError("No ID specified and no person selected")
|
|
8772
|
+
);
|
|
8773
|
+
return;
|
|
8774
|
+
}
|
|
8775
|
+
}
|
|
8776
|
+
try {
|
|
8777
|
+
const result = getValue(
|
|
8778
|
+
this.context.tree,
|
|
8779
|
+
id || "",
|
|
8780
|
+
path,
|
|
8781
|
+
json,
|
|
8782
|
+
raw
|
|
8783
|
+
);
|
|
8784
|
+
console.log(result);
|
|
8785
|
+
} catch (error) {
|
|
8786
|
+
console.log(formatError(error.message));
|
|
8787
|
+
}
|
|
8788
|
+
}
|
|
8789
|
+
handleRelatives(args) {
|
|
8790
|
+
if (!this.context.selectedPerson) {
|
|
8791
|
+
console.log(
|
|
8792
|
+
formatError('No person selected. Use "select <id>" first')
|
|
8793
|
+
);
|
|
8794
|
+
return;
|
|
8795
|
+
}
|
|
8796
|
+
const isJson = args.includes("--json") || args.includes("-j");
|
|
8797
|
+
const includeAncestors = args.includes("--ancestors") || args.includes("-a") || args.includes("--tree") || args.includes("-t");
|
|
8798
|
+
const includeDescendants = args.includes("--descendants") || args.includes("-d") || args.includes("--tree") || args.includes("-t");
|
|
8799
|
+
if (!includeAncestors && !includeDescendants) {
|
|
8800
|
+
console.log(
|
|
8801
|
+
formatError(
|
|
8802
|
+
"Must specify --ancestors, --descendants, or --tree"
|
|
8803
|
+
)
|
|
8804
|
+
);
|
|
8805
|
+
console.log('Type "help relatives" for more information');
|
|
8806
|
+
return;
|
|
8807
|
+
}
|
|
8808
|
+
let maxDepth = 999;
|
|
8809
|
+
const depthIndex = args.findIndex((arg) => arg === "--depth");
|
|
8810
|
+
if (depthIndex !== -1 && depthIndex + 1 < args.length) {
|
|
8811
|
+
maxDepth = parseInt(args[depthIndex + 1], 10);
|
|
8812
|
+
if (isNaN(maxDepth) || maxDepth < 1) {
|
|
8813
|
+
console.log(formatError("Invalid depth value"));
|
|
8814
|
+
return;
|
|
8815
|
+
}
|
|
8816
|
+
}
|
|
8817
|
+
const individual = this.context.selectedPerson;
|
|
8818
|
+
const relatives = /* @__PURE__ */ new Set();
|
|
8819
|
+
individual?.id && relatives.add(individual.id);
|
|
8820
|
+
if (includeAncestors) {
|
|
8821
|
+
const getAncestors = (indi, depth) => {
|
|
8822
|
+
if (depth > maxDepth) return;
|
|
8823
|
+
const parents = indi.getParents();
|
|
8824
|
+
parents?.forEach((parent) => {
|
|
8825
|
+
if (!parent?.id) return;
|
|
8826
|
+
relatives.add(parent.id);
|
|
8827
|
+
getAncestors(parent, depth + 1);
|
|
8828
|
+
});
|
|
8829
|
+
};
|
|
8830
|
+
getAncestors(individual, 1);
|
|
8831
|
+
}
|
|
8832
|
+
if (includeDescendants) {
|
|
8833
|
+
const getDescendants = (indi, depth) => {
|
|
8834
|
+
if (depth > maxDepth) return;
|
|
8835
|
+
const children = indi.getChildren();
|
|
8836
|
+
children?.forEach((child) => {
|
|
8837
|
+
if (!child?.id) return;
|
|
8838
|
+
relatives.add(child.id);
|
|
8839
|
+
getDescendants(child, depth + 1);
|
|
8840
|
+
});
|
|
8841
|
+
};
|
|
8842
|
+
getDescendants(individual, 1);
|
|
8843
|
+
}
|
|
8844
|
+
const allRelatives = Array.from(relatives).map((relId) => this.context.tree.indi(relId)).filter((indi) => indi !== null);
|
|
8845
|
+
if (isJson) {
|
|
8846
|
+
const jsonData = allRelatives.map(
|
|
8847
|
+
(indi) => indi && {
|
|
8848
|
+
id: indi.id,
|
|
8849
|
+
name: cleanGedcomName(indi.NAME?.toValue()),
|
|
8850
|
+
birthDate: indi.BIRT?.DATE?.toValue() || null,
|
|
8851
|
+
deathDate: indi.DEAT?.DATE?.toValue() || null
|
|
8852
|
+
}
|
|
8853
|
+
).filter(Boolean);
|
|
8854
|
+
console.log(
|
|
8855
|
+
formatJson({ count: jsonData.length, individuals: jsonData })
|
|
8856
|
+
);
|
|
8857
|
+
} else {
|
|
8858
|
+
console.log(
|
|
8859
|
+
formatHeader(`Found ${allRelatives.length} relative(s)
|
|
8860
|
+
`)
|
|
8861
|
+
);
|
|
8862
|
+
allRelatives.forEach((indi) => {
|
|
8863
|
+
if (!indi) return;
|
|
8864
|
+
const name = cleanGedcomName(indi.NAME?.toValue());
|
|
8865
|
+
const lifespan = formatLifespan(
|
|
8866
|
+
indi.BIRT?.DATE?.toValue(),
|
|
8867
|
+
indi.DEAT?.DATE?.toValue()
|
|
8868
|
+
);
|
|
8869
|
+
console.log(
|
|
8870
|
+
formatListItem(
|
|
8871
|
+
`${formatId(indi.id ?? "")} ${formatName(name)} ${lifespan}`
|
|
8872
|
+
)
|
|
8873
|
+
);
|
|
8874
|
+
});
|
|
8875
|
+
}
|
|
8876
|
+
}
|
|
8877
|
+
handleValidate(args) {
|
|
8878
|
+
const isJson = args.includes("--json") || args.includes("-j");
|
|
8879
|
+
const isStrict = args.includes("--strict") || args.includes("-s");
|
|
8880
|
+
const errors = [];
|
|
8881
|
+
const warnings = [];
|
|
8882
|
+
const header = this.context.tree.HEAD;
|
|
8883
|
+
const version = header?.GEDC?.VERS?.value || "Unknown";
|
|
8884
|
+
const individuals = this.context.tree.indis();
|
|
8885
|
+
const families = this.context.tree.fams();
|
|
8886
|
+
individuals?.forEach((indi) => {
|
|
8887
|
+
if (!indi.NAME?.toValue()) {
|
|
8888
|
+
warnings.push(`Individual ${indi.id} is missing a name`);
|
|
8889
|
+
}
|
|
8890
|
+
});
|
|
8891
|
+
let missingBirthDates = 0;
|
|
8892
|
+
individuals?.forEach((indi) => {
|
|
8893
|
+
if (!indi.BIRT?.DATE?.toValue()) {
|
|
8894
|
+
missingBirthDates++;
|
|
8895
|
+
}
|
|
8896
|
+
});
|
|
8897
|
+
let missingDeathDates = 0;
|
|
8898
|
+
individuals?.forEach((indi) => {
|
|
8899
|
+
if (indi.DEAT && !indi.DEAT.DATE?.toValue()) {
|
|
8900
|
+
missingDeathDates++;
|
|
8901
|
+
}
|
|
8902
|
+
});
|
|
8903
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
8904
|
+
const duplicateIds = [];
|
|
8905
|
+
const checkDuplicates = (item) => {
|
|
8906
|
+
if (!item.id) {
|
|
8907
|
+
return;
|
|
8908
|
+
}
|
|
8909
|
+
if (seenIds.has(item.id)) {
|
|
8910
|
+
duplicateIds.push(item.id);
|
|
8911
|
+
errors.push(`Duplicate ID found: ${item.id}`);
|
|
8912
|
+
}
|
|
8913
|
+
seenIds.add(item.id);
|
|
8914
|
+
};
|
|
8915
|
+
individuals?.forEach(checkDuplicates);
|
|
8916
|
+
families?.forEach(checkDuplicates);
|
|
8917
|
+
families?.forEach((fam) => {
|
|
8918
|
+
const husb = fam.HUSB?.value;
|
|
8919
|
+
const wife = fam.WIFE?.value;
|
|
8920
|
+
if (!husb && !wife) {
|
|
8921
|
+
warnings.push(`Family ${fam.id} has no husband or wife`);
|
|
8922
|
+
}
|
|
8923
|
+
if (husb && !this.context.tree.indi(husb)) {
|
|
8924
|
+
errors.push(
|
|
8925
|
+
`Family ${fam.id} references non-existent husband ${husb}`
|
|
8926
|
+
);
|
|
8927
|
+
}
|
|
8928
|
+
if (wife && !this.context.tree.indi(wife)) {
|
|
8929
|
+
errors.push(
|
|
8930
|
+
`Family ${fam.id} references non-existent wife ${wife}`
|
|
8931
|
+
);
|
|
8932
|
+
}
|
|
8933
|
+
});
|
|
8934
|
+
if (isStrict) {
|
|
8935
|
+
individuals?.forEach((indi) => {
|
|
8936
|
+
const birthDate = indi.BIRT?.DATE?.toValue();
|
|
8937
|
+
const deathDate = indi.DEAT?.DATE?.toValue();
|
|
8938
|
+
if (birthDate && birthDate.includes("INVALID")) {
|
|
8939
|
+
errors.push(`Invalid birth date format for ${indi.id}`);
|
|
8940
|
+
}
|
|
8941
|
+
if (deathDate && deathDate.includes("INVALID")) {
|
|
8942
|
+
errors.push(`Invalid death date format for ${indi.id}`);
|
|
8943
|
+
}
|
|
8944
|
+
});
|
|
8945
|
+
}
|
|
8946
|
+
const result = {
|
|
8947
|
+
valid: errors.length === 0,
|
|
8948
|
+
version,
|
|
8949
|
+
errors,
|
|
8950
|
+
warnings
|
|
8951
|
+
};
|
|
8952
|
+
if (isJson) {
|
|
8953
|
+
console.log(formatJson(result));
|
|
8954
|
+
} else {
|
|
8955
|
+
if (result.valid) {
|
|
8956
|
+
console.log(formatSuccess(`Valid GEDCOM ${version} file`));
|
|
8957
|
+
} else {
|
|
8958
|
+
console.log(
|
|
8959
|
+
formatError(
|
|
8960
|
+
`Invalid GEDCOM file - ${errors.length} error(s) found`
|
|
8961
|
+
)
|
|
8962
|
+
);
|
|
8963
|
+
}
|
|
8964
|
+
console.log();
|
|
8965
|
+
console.log(formatHeader("Validation Summary"));
|
|
8966
|
+
console.log(
|
|
8967
|
+
`${formatError("Errors:")} ${formatCount(errors.length)}`
|
|
8968
|
+
);
|
|
8969
|
+
console.log(
|
|
8970
|
+
`${formatWarning("Warnings:")} ${formatCount(warnings.length)}`
|
|
8971
|
+
);
|
|
8972
|
+
if (errors.length > 0) {
|
|
8973
|
+
console.log();
|
|
8974
|
+
console.log(formatHeader("Errors"));
|
|
8975
|
+
errors.slice(0, 10).forEach((error) => {
|
|
8976
|
+
console.log(formatListItem(formatError(error)));
|
|
8977
|
+
});
|
|
8978
|
+
if (errors.length > 10) {
|
|
8979
|
+
console.log(
|
|
8980
|
+
formatListItem(
|
|
8981
|
+
`... and ${errors.length - 10} more errors`
|
|
8982
|
+
)
|
|
8983
|
+
);
|
|
8984
|
+
}
|
|
8985
|
+
}
|
|
8986
|
+
if (warnings.length > 0) {
|
|
8987
|
+
console.log();
|
|
8988
|
+
console.log(formatHeader("Warnings"));
|
|
8989
|
+
if (missingBirthDates > 0) {
|
|
8990
|
+
console.log(
|
|
8991
|
+
formatListItem(
|
|
8992
|
+
formatWarning(
|
|
8993
|
+
`Missing birth dates: ${missingBirthDates} individuals`
|
|
8994
|
+
)
|
|
8995
|
+
)
|
|
8996
|
+
);
|
|
8997
|
+
}
|
|
8998
|
+
if (missingDeathDates > 0) {
|
|
8999
|
+
console.log(
|
|
9000
|
+
formatListItem(
|
|
9001
|
+
formatWarning(
|
|
9002
|
+
`Missing death dates: ${missingDeathDates} individuals`
|
|
9003
|
+
)
|
|
9004
|
+
)
|
|
9005
|
+
);
|
|
9006
|
+
}
|
|
9007
|
+
if (duplicateIds.length > 0) {
|
|
9008
|
+
console.log(
|
|
9009
|
+
formatListItem(
|
|
9010
|
+
formatWarning(
|
|
9011
|
+
`Duplicate IDs: ${duplicateIds.length}`
|
|
9012
|
+
)
|
|
9013
|
+
)
|
|
9014
|
+
);
|
|
9015
|
+
}
|
|
9016
|
+
const otherWarnings = warnings.filter(
|
|
9017
|
+
(w) => !w.includes("birth") && !w.includes("death")
|
|
9018
|
+
);
|
|
9019
|
+
otherWarnings.slice(0, 5).forEach((warning) => {
|
|
9020
|
+
console.log(formatListItem(formatWarning(warning)));
|
|
9021
|
+
});
|
|
9022
|
+
if (otherWarnings.length > 5) {
|
|
9023
|
+
console.log(
|
|
9024
|
+
formatListItem(
|
|
9025
|
+
`... and ${otherWarnings.length - 5} more warnings`
|
|
9026
|
+
)
|
|
9027
|
+
);
|
|
9028
|
+
}
|
|
9029
|
+
}
|
|
9030
|
+
}
|
|
9031
|
+
}
|
|
9032
|
+
start() {
|
|
9033
|
+
console.log(formatHeader("GEDCOM Interactive Explorer"));
|
|
9034
|
+
console.log("");
|
|
9035
|
+
console.log(chalk.dim('Type "help" for available commands'));
|
|
9036
|
+
console.log("");
|
|
9037
|
+
this.rl.prompt();
|
|
9038
|
+
}
|
|
9039
|
+
};
|
|
9040
|
+
|
|
9041
|
+
// src/cli/commands/open.ts
|
|
9042
|
+
function registerOpenCommand(program2) {
|
|
9043
|
+
program2.command("open <file>").description("Open GEDCOM file in interactive mode").action((file) => {
|
|
9044
|
+
try {
|
|
9045
|
+
const content = readGedcomFile(file);
|
|
9046
|
+
const { gedcom: tree } = parser_default.parse(content);
|
|
9047
|
+
const repl = new GedcomRepl(tree);
|
|
9048
|
+
repl.start();
|
|
9049
|
+
} catch (error) {
|
|
9050
|
+
handleError(error, "Failed to open GEDCOM file");
|
|
9051
|
+
}
|
|
9052
|
+
});
|
|
9053
|
+
}
|
|
7517
9054
|
function registerRelativesCommand(program2) {
|
|
7518
9055
|
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) => {
|
|
7519
9056
|
try {
|
|
7520
9057
|
const content = readGedcomFile(file);
|
|
7521
9058
|
const { gedcom: tree } = parser_default.parse(content);
|
|
7522
|
-
const individual = tree.indi(id);
|
|
9059
|
+
const individual = id && tree.indi(id);
|
|
7523
9060
|
if (!individual) {
|
|
7524
9061
|
console.error(formatError(`Individual ${id} not found`));
|
|
7525
9062
|
process.exit(1);
|
|
@@ -7534,6 +9071,7 @@ function registerRelativesCommand(program2) {
|
|
|
7534
9071
|
if (depth > maxDepth) return;
|
|
7535
9072
|
const parents = indi.getParents();
|
|
7536
9073
|
parents?.forEach((parent) => {
|
|
9074
|
+
if (!parent?.id) return;
|
|
7537
9075
|
relatives.add(parent.id);
|
|
7538
9076
|
getAncestors(parent, depth + 1);
|
|
7539
9077
|
});
|
|
@@ -7545,6 +9083,7 @@ function registerRelativesCommand(program2) {
|
|
|
7545
9083
|
if (depth > maxDepth) return;
|
|
7546
9084
|
const children = indi.getChildren();
|
|
7547
9085
|
children?.forEach((child) => {
|
|
9086
|
+
if (!child?.id) return;
|
|
7548
9087
|
relatives.add(child.id);
|
|
7549
9088
|
getDescendants(child, depth + 1);
|
|
7550
9089
|
});
|
|
@@ -7555,25 +9094,45 @@ function registerRelativesCommand(program2) {
|
|
|
7555
9094
|
if (options.output) {
|
|
7556
9095
|
const newContent = createSubsetGedcom(tree, allRelatives);
|
|
7557
9096
|
writeFileSync(options.output, newContent, "utf-8");
|
|
7558
|
-
console.log(
|
|
9097
|
+
console.log(
|
|
9098
|
+
formatSuccess(
|
|
9099
|
+
`Saved ${allRelatives.length} individuals to ${options.output}`
|
|
9100
|
+
)
|
|
9101
|
+
);
|
|
7559
9102
|
} else if (options.json) {
|
|
7560
|
-
const jsonData = allRelatives.map(
|
|
7561
|
-
|
|
7562
|
-
|
|
7563
|
-
|
|
7564
|
-
|
|
7565
|
-
|
|
7566
|
-
|
|
9103
|
+
const jsonData = allRelatives.map(
|
|
9104
|
+
(indi) => indi && {
|
|
9105
|
+
id: indi.id,
|
|
9106
|
+
name: cleanGedcomName(indi.NAME?.toValue()),
|
|
9107
|
+
birthDate: indi.BIRT?.DATE?.toValue() || null,
|
|
9108
|
+
deathDate: indi.DEAT?.DATE?.toValue() || null
|
|
9109
|
+
}
|
|
9110
|
+
).filter(Boolean);
|
|
9111
|
+
console.log(
|
|
9112
|
+
formatJson({
|
|
9113
|
+
count: jsonData.length,
|
|
9114
|
+
individuals: jsonData
|
|
9115
|
+
})
|
|
9116
|
+
);
|
|
7567
9117
|
} else {
|
|
7568
|
-
console.log(
|
|
7569
|
-
|
|
9118
|
+
console.log(
|
|
9119
|
+
formatHeader(
|
|
9120
|
+
`Found ${allRelatives.length} relative(s)
|
|
9121
|
+
`
|
|
9122
|
+
)
|
|
9123
|
+
);
|
|
7570
9124
|
allRelatives.forEach((indi) => {
|
|
9125
|
+
if (!indi) return;
|
|
7571
9126
|
const name = cleanGedcomName(indi.NAME?.toValue());
|
|
7572
9127
|
const lifespan = formatLifespan(
|
|
7573
9128
|
indi.BIRT?.DATE?.toValue(),
|
|
7574
9129
|
indi.DEAT?.DATE?.toValue()
|
|
7575
9130
|
);
|
|
7576
|
-
console.log(
|
|
9131
|
+
console.log(
|
|
9132
|
+
formatListItem(
|
|
9133
|
+
`${formatId(indi.id ?? "")} ${formatName(name)} ${lifespan}`
|
|
9134
|
+
)
|
|
9135
|
+
);
|
|
7577
9136
|
});
|
|
7578
9137
|
}
|
|
7579
9138
|
} catch (error) {
|
|
@@ -7591,249 +9150,15 @@ function createSubsetGedcom(tree, individuals) {
|
|
|
7591
9150
|
individuals.forEach((indi) => {
|
|
7592
9151
|
const raw = indi.raw();
|
|
7593
9152
|
if (raw) {
|
|
7594
|
-
lines.push(
|
|
9153
|
+
lines.push(
|
|
9154
|
+
...raw.split("\n").filter((line) => line.trim())
|
|
9155
|
+
);
|
|
7595
9156
|
}
|
|
7596
9157
|
});
|
|
7597
9158
|
lines.push("0 TRLR");
|
|
7598
9159
|
return lines.join("\n");
|
|
7599
9160
|
}
|
|
7600
9161
|
|
|
7601
|
-
// src/cli/commands/show.ts
|
|
7602
|
-
function registerShowCommand(program2) {
|
|
7603
|
-
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) => {
|
|
7604
|
-
try {
|
|
7605
|
-
const content = readGedcomFile(file);
|
|
7606
|
-
const { gedcom: tree } = parser_default.parse(content);
|
|
7607
|
-
const individual = tree.indi(id);
|
|
7608
|
-
if (!individual) {
|
|
7609
|
-
console.error(formatError(`Individual ${id} not found`));
|
|
7610
|
-
process.exit(1);
|
|
7611
|
-
}
|
|
7612
|
-
const name = cleanGedcomName(individual.NAME?.toValue());
|
|
7613
|
-
const birthDate = individual.BIRT?.DATE?.toValue();
|
|
7614
|
-
const birthPlace = individual.BIRT?.PLAC?.value;
|
|
7615
|
-
const deathDate = individual.DEAT?.DATE?.toValue();
|
|
7616
|
-
const deathPlace = individual.DEAT?.PLAC?.value;
|
|
7617
|
-
const sex = individual.SEX?.value;
|
|
7618
|
-
const parents = individual.getParents();
|
|
7619
|
-
const father = parents?.find((p) => p.SEX?.value === "M");
|
|
7620
|
-
const mother = parents?.find((p) => p.SEX?.value === "F");
|
|
7621
|
-
const spouses = individual.getSpouses();
|
|
7622
|
-
const children = individual.getChildren();
|
|
7623
|
-
if (options.json || options.format === "json") {
|
|
7624
|
-
const jsonData = {
|
|
7625
|
-
id: individual.id,
|
|
7626
|
-
name,
|
|
7627
|
-
sex,
|
|
7628
|
-
birth: {
|
|
7629
|
-
date: birthDate || null,
|
|
7630
|
-
place: birthPlace || null
|
|
7631
|
-
},
|
|
7632
|
-
death: {
|
|
7633
|
-
date: deathDate || null,
|
|
7634
|
-
place: deathPlace || null
|
|
7635
|
-
},
|
|
7636
|
-
parents: {
|
|
7637
|
-
father: father ? {
|
|
7638
|
-
id: father.id,
|
|
7639
|
-
name: cleanGedcomName(father.NAME?.toValue())
|
|
7640
|
-
} : null,
|
|
7641
|
-
mother: mother ? {
|
|
7642
|
-
id: mother.id,
|
|
7643
|
-
name: cleanGedcomName(mother.NAME?.toValue())
|
|
7644
|
-
} : null
|
|
7645
|
-
},
|
|
7646
|
-
spouses: spouses.map((spouse) => ({
|
|
7647
|
-
id: spouse.id,
|
|
7648
|
-
name: cleanGedcomName(spouse.NAME?.toValue()),
|
|
7649
|
-
birthDate: spouse.BIRT?.DATE?.toValue() || null,
|
|
7650
|
-
deathDate: spouse.DEAT?.DATE?.toValue() || null
|
|
7651
|
-
})),
|
|
7652
|
-
children: children.map((child) => ({
|
|
7653
|
-
id: child.id,
|
|
7654
|
-
name: cleanGedcomName(child.NAME?.toValue()),
|
|
7655
|
-
birthDate: child.BIRT?.DATE?.toValue() || null,
|
|
7656
|
-
deathDate: child.DEAT?.DATE?.toValue() || null
|
|
7657
|
-
}))
|
|
7658
|
-
};
|
|
7659
|
-
console.log(formatJson(jsonData));
|
|
7660
|
-
} else if (options.format === "markdown") {
|
|
7661
|
-
console.log(`# ${formatId(individual.id)} ${name}
|
|
7662
|
-
`);
|
|
7663
|
-
if (birthDate || birthPlace) {
|
|
7664
|
-
console.log(`**Born:** ${birthDate || "?"}`);
|
|
7665
|
-
if (birthPlace) console.log(` in ${birthPlace}`);
|
|
7666
|
-
console.log();
|
|
7667
|
-
}
|
|
7668
|
-
if (deathDate || deathPlace) {
|
|
7669
|
-
console.log(`**Died:** ${deathDate || "?"}`);
|
|
7670
|
-
if (deathPlace) console.log(` in ${deathPlace}`);
|
|
7671
|
-
console.log();
|
|
7672
|
-
}
|
|
7673
|
-
if (father || mother) {
|
|
7674
|
-
console.log("## Parents\n");
|
|
7675
|
-
if (father) {
|
|
7676
|
-
const fatherName = cleanGedcomName(father.NAME?.toValue());
|
|
7677
|
-
const fatherLifespan = formatLifespan(
|
|
7678
|
-
father.BIRT?.DATE?.toValue(),
|
|
7679
|
-
father.DEAT?.DATE?.toValue()
|
|
7680
|
-
);
|
|
7681
|
-
console.log(`- **Father:** ${father.id} ${fatherName} ${fatherLifespan}`);
|
|
7682
|
-
}
|
|
7683
|
-
if (mother) {
|
|
7684
|
-
const motherName = cleanGedcomName(mother.NAME?.toValue());
|
|
7685
|
-
const motherLifespan = formatLifespan(
|
|
7686
|
-
mother.BIRT?.DATE?.toValue(),
|
|
7687
|
-
mother.DEAT?.DATE?.toValue()
|
|
7688
|
-
);
|
|
7689
|
-
console.log(`- **Mother:** ${mother.id} ${motherName} ${motherLifespan}`);
|
|
7690
|
-
}
|
|
7691
|
-
console.log();
|
|
7692
|
-
}
|
|
7693
|
-
if (spouses.length > 0) {
|
|
7694
|
-
console.log("## Spouses\n");
|
|
7695
|
-
spouses.forEach((spouse) => {
|
|
7696
|
-
const spouseName = cleanGedcomName(spouse.NAME?.toValue());
|
|
7697
|
-
const spouseLifespan = formatLifespan(
|
|
7698
|
-
spouse.BIRT?.DATE?.toValue(),
|
|
7699
|
-
spouse.DEAT?.DATE?.toValue()
|
|
7700
|
-
);
|
|
7701
|
-
console.log(`- ${spouse.id} ${spouseName} ${spouseLifespan}`);
|
|
7702
|
-
});
|
|
7703
|
-
console.log();
|
|
7704
|
-
}
|
|
7705
|
-
if (children.length > 0) {
|
|
7706
|
-
console.log("## Children\n");
|
|
7707
|
-
children.forEach((child) => {
|
|
7708
|
-
const childName = cleanGedcomName(child.NAME?.toValue());
|
|
7709
|
-
const childLifespan = formatLifespan(
|
|
7710
|
-
child.BIRT?.DATE?.toValue(),
|
|
7711
|
-
child.DEAT?.DATE?.toValue()
|
|
7712
|
-
);
|
|
7713
|
-
console.log(`- ${child.id} ${childName} ${childLifespan}`);
|
|
7714
|
-
});
|
|
7715
|
-
console.log();
|
|
7716
|
-
}
|
|
7717
|
-
} else {
|
|
7718
|
-
console.log(formatHeader(`${formatId(individual.id)} ${formatName(name)}
|
|
7719
|
-
`));
|
|
7720
|
-
if (sex) {
|
|
7721
|
-
console.log(`${formatLabel("Sex")} ${formatValue(sex === "M" ? "Male" : sex === "F" ? "Female" : sex)}`);
|
|
7722
|
-
}
|
|
7723
|
-
if (birthDate || birthPlace) {
|
|
7724
|
-
console.log(`${formatLabel("Born")} ${formatDate(birthDate)}`);
|
|
7725
|
-
if (birthPlace) {
|
|
7726
|
-
console.log(`${formatLabel("Birth Place")} ${formatPlace(birthPlace)}`);
|
|
7727
|
-
}
|
|
7728
|
-
}
|
|
7729
|
-
if (deathDate || deathPlace) {
|
|
7730
|
-
console.log(`${formatLabel("Died")} ${formatDate(deathDate)}`);
|
|
7731
|
-
if (deathPlace) {
|
|
7732
|
-
console.log(`${formatLabel("Death Place")} ${formatPlace(deathPlace)}`);
|
|
7733
|
-
}
|
|
7734
|
-
}
|
|
7735
|
-
if (father || mother) {
|
|
7736
|
-
console.log();
|
|
7737
|
-
console.log(formatHeader("Parents"));
|
|
7738
|
-
if (father) {
|
|
7739
|
-
const fatherName = cleanGedcomName(father.NAME?.toValue());
|
|
7740
|
-
const fatherLifespan = formatLifespan(
|
|
7741
|
-
father.BIRT?.DATE?.toValue(),
|
|
7742
|
-
father.DEAT?.DATE?.toValue()
|
|
7743
|
-
);
|
|
7744
|
-
console.log(formatListItem(`${formatLabel("Father")} ${formatId(father.id)} ${formatName(fatherName)} ${fatherLifespan}`));
|
|
7745
|
-
}
|
|
7746
|
-
if (mother) {
|
|
7747
|
-
const motherName = cleanGedcomName(mother.NAME?.toValue());
|
|
7748
|
-
const motherLifespan = formatLifespan(
|
|
7749
|
-
mother.BIRT?.DATE?.toValue(),
|
|
7750
|
-
mother.DEAT?.DATE?.toValue()
|
|
7751
|
-
);
|
|
7752
|
-
console.log(formatListItem(`${formatLabel("Mother")} ${formatId(mother.id)} ${formatName(motherName)} ${motherLifespan}`));
|
|
7753
|
-
}
|
|
7754
|
-
}
|
|
7755
|
-
if (spouses.length > 0) {
|
|
7756
|
-
console.log();
|
|
7757
|
-
console.log(formatHeader("Spouses"));
|
|
7758
|
-
spouses.forEach((spouse) => {
|
|
7759
|
-
const spouseName = cleanGedcomName(spouse.NAME?.toValue());
|
|
7760
|
-
const spouseLifespan = formatLifespan(
|
|
7761
|
-
spouse.BIRT?.DATE?.toValue(),
|
|
7762
|
-
spouse.DEAT?.DATE?.toValue()
|
|
7763
|
-
);
|
|
7764
|
-
console.log(formatListItem(`${formatId(spouse.id)} ${formatName(spouseName)} ${spouseLifespan}`));
|
|
7765
|
-
});
|
|
7766
|
-
}
|
|
7767
|
-
if (children.length > 0) {
|
|
7768
|
-
console.log();
|
|
7769
|
-
console.log(formatHeader("Children"));
|
|
7770
|
-
children.forEach((child) => {
|
|
7771
|
-
const childName = cleanGedcomName(child.NAME?.toValue());
|
|
7772
|
-
const childLifespan = formatLifespan(
|
|
7773
|
-
child.BIRT?.DATE?.toValue(),
|
|
7774
|
-
child.DEAT?.DATE?.toValue()
|
|
7775
|
-
);
|
|
7776
|
-
console.log(formatListItem(`${formatId(child.id)} ${formatName(childName)} ${childLifespan}`));
|
|
7777
|
-
});
|
|
7778
|
-
}
|
|
7779
|
-
}
|
|
7780
|
-
} catch (error) {
|
|
7781
|
-
handleError(error, "Failed to show individual details");
|
|
7782
|
-
}
|
|
7783
|
-
});
|
|
7784
|
-
}
|
|
7785
|
-
|
|
7786
|
-
// src/cli/commands/stats.ts
|
|
7787
|
-
function registerStatsCommand(program2) {
|
|
7788
|
-
program2.command("stats <file>").description("Generate statistics about a GEDCOM file").option("-j, --json", "Output in JSON format").action((file, options) => {
|
|
7789
|
-
try {
|
|
7790
|
-
const content = readGedcomFile(file);
|
|
7791
|
-
const { gedcom: tree } = parser_default.parse(content);
|
|
7792
|
-
const stats = tree.stats();
|
|
7793
|
-
if (options.json) {
|
|
7794
|
-
console.log(formatJson(stats));
|
|
7795
|
-
} else {
|
|
7796
|
-
console.log(formatHeader("GEDCOM File Statistics\n"));
|
|
7797
|
-
console.log(formatLabel("Total Individuals"));
|
|
7798
|
-
console.log(` ${formatCount(stats.totalIndividuals)}`);
|
|
7799
|
-
console.log(formatLabel("Total Families"));
|
|
7800
|
-
console.log(` ${formatCount(stats.totalFamilies)}`);
|
|
7801
|
-
console.log();
|
|
7802
|
-
console.log(formatLabel("By Gender"));
|
|
7803
|
-
console.log(` Males: ${formatCount(stats.byGender.males)}`);
|
|
7804
|
-
console.log(` Females: ${formatCount(stats.byGender.females)}`);
|
|
7805
|
-
console.log(` Unknown: ${formatCount(stats.byGender.unknown)}`);
|
|
7806
|
-
console.log();
|
|
7807
|
-
if (stats.dateRange.earliest && stats.dateRange.latest) {
|
|
7808
|
-
console.log(formatLabel("Date Range"));
|
|
7809
|
-
console.log(` ${stats.dateRange.earliest} - ${stats.dateRange.latest}`);
|
|
7810
|
-
console.log();
|
|
7811
|
-
}
|
|
7812
|
-
if (stats.averageLifespan) {
|
|
7813
|
-
console.log(formatLabel("Average Lifespan"));
|
|
7814
|
-
console.log(` ${stats.averageLifespan.toFixed(1)} years`);
|
|
7815
|
-
console.log();
|
|
7816
|
-
}
|
|
7817
|
-
if (stats.topSurnames.length > 0) {
|
|
7818
|
-
console.log(formatLabel("Most Common Surnames"));
|
|
7819
|
-
stats.topSurnames.forEach(({ surname, count }) => {
|
|
7820
|
-
console.log(` ${surname}: ${formatCount(count)}`);
|
|
7821
|
-
});
|
|
7822
|
-
console.log();
|
|
7823
|
-
}
|
|
7824
|
-
if (stats.topBirthPlaces.length > 0) {
|
|
7825
|
-
console.log(formatLabel("Most Common Birth Places"));
|
|
7826
|
-
stats.topBirthPlaces.slice(0, 5).forEach(({ place, count }) => {
|
|
7827
|
-
console.log(` ${place}: ${formatCount(count)}`);
|
|
7828
|
-
});
|
|
7829
|
-
}
|
|
7830
|
-
}
|
|
7831
|
-
} catch (error) {
|
|
7832
|
-
handleError(error, "Failed to generate statistics");
|
|
7833
|
-
}
|
|
7834
|
-
});
|
|
7835
|
-
}
|
|
7836
|
-
|
|
7837
9162
|
// src/cli/commands/validate.ts
|
|
7838
9163
|
function registerValidateCommand(program2) {
|
|
7839
9164
|
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) => {
|
|
@@ -7847,20 +9172,22 @@ function registerValidateCommand(program2) {
|
|
|
7847
9172
|
const individuals = tree.indis();
|
|
7848
9173
|
const families = tree.fams();
|
|
7849
9174
|
let missingNames = 0;
|
|
7850
|
-
individuals
|
|
9175
|
+
individuals?.forEach((indi) => {
|
|
7851
9176
|
if (!indi.NAME?.toValue()) {
|
|
7852
9177
|
missingNames++;
|
|
7853
|
-
warnings.push(
|
|
9178
|
+
warnings.push(
|
|
9179
|
+
`Individual ${indi.id} is missing a name`
|
|
9180
|
+
);
|
|
7854
9181
|
}
|
|
7855
9182
|
});
|
|
7856
9183
|
let missingBirthDates = 0;
|
|
7857
|
-
individuals
|
|
9184
|
+
individuals?.forEach((indi) => {
|
|
7858
9185
|
if (!indi.BIRT?.DATE?.toValue()) {
|
|
7859
9186
|
missingBirthDates++;
|
|
7860
9187
|
}
|
|
7861
9188
|
});
|
|
7862
9189
|
let missingDeathDates = 0;
|
|
7863
|
-
individuals
|
|
9190
|
+
individuals?.forEach((indi) => {
|
|
7864
9191
|
if (indi.DEAT && !indi.DEAT.DATE?.toValue()) {
|
|
7865
9192
|
missingDeathDates++;
|
|
7866
9193
|
}
|
|
@@ -7874,22 +9201,28 @@ function registerValidateCommand(program2) {
|
|
|
7874
9201
|
}
|
|
7875
9202
|
seenIds.add(item.id);
|
|
7876
9203
|
};
|
|
7877
|
-
individuals
|
|
7878
|
-
families
|
|
7879
|
-
families
|
|
9204
|
+
individuals?.forEach(checkDuplicates);
|
|
9205
|
+
families?.forEach(checkDuplicates);
|
|
9206
|
+
families?.forEach((fam) => {
|
|
7880
9207
|
const husb = fam.HUSB?.value;
|
|
7881
9208
|
const wife = fam.WIFE?.value;
|
|
7882
9209
|
if (!husb && !wife) {
|
|
7883
|
-
warnings.push(
|
|
9210
|
+
warnings.push(
|
|
9211
|
+
`Family ${fam.id} has no husband or wife`
|
|
9212
|
+
);
|
|
7884
9213
|
}
|
|
7885
9214
|
if (husb && !tree.indi(husb)) {
|
|
7886
|
-
errors.push(
|
|
9215
|
+
errors.push(
|
|
9216
|
+
`Family ${fam.id} references non-existent husband ${husb}`
|
|
9217
|
+
);
|
|
7887
9218
|
}
|
|
7888
9219
|
if (wife && !tree.indi(wife)) {
|
|
7889
|
-
errors.push(
|
|
9220
|
+
errors.push(
|
|
9221
|
+
`Family ${fam.id} references non-existent wife ${wife}`
|
|
9222
|
+
);
|
|
7890
9223
|
}
|
|
7891
9224
|
});
|
|
7892
|
-
individuals
|
|
9225
|
+
individuals?.forEach((indi) => {
|
|
7893
9226
|
const birthDate = indi.BIRT?.DATE?.toValue();
|
|
7894
9227
|
const deathDate = indi.DEAT?.DATE?.toValue();
|
|
7895
9228
|
if (birthDate && birthDate.includes("INVALID")) {
|
|
@@ -7909,14 +9242,24 @@ function registerValidateCommand(program2) {
|
|
|
7909
9242
|
console.log(formatJson(result));
|
|
7910
9243
|
} else {
|
|
7911
9244
|
if (result.valid) {
|
|
7912
|
-
console.log(
|
|
9245
|
+
console.log(
|
|
9246
|
+
formatSuccess(`Valid GEDCOM ${version} file`)
|
|
9247
|
+
);
|
|
7913
9248
|
} else {
|
|
7914
|
-
console.log(
|
|
9249
|
+
console.log(
|
|
9250
|
+
formatError(
|
|
9251
|
+
`Invalid GEDCOM file - ${errors.length} error(s) found`
|
|
9252
|
+
)
|
|
9253
|
+
);
|
|
7915
9254
|
}
|
|
7916
9255
|
console.log();
|
|
7917
9256
|
console.log(formatHeader("Validation Summary"));
|
|
7918
|
-
console.log(
|
|
7919
|
-
|
|
9257
|
+
console.log(
|
|
9258
|
+
`${formatError("Errors:")} ${formatCount(errors.length)}`
|
|
9259
|
+
);
|
|
9260
|
+
console.log(
|
|
9261
|
+
`${formatWarning("Warnings:")} ${formatCount(warnings.length)}`
|
|
9262
|
+
);
|
|
7920
9263
|
if (errors.length > 0) {
|
|
7921
9264
|
console.log();
|
|
7922
9265
|
console.log(formatHeader("Errors"));
|
|
@@ -7924,32 +9267,62 @@ function registerValidateCommand(program2) {
|
|
|
7924
9267
|
console.log(formatListItem(formatError(error)));
|
|
7925
9268
|
});
|
|
7926
9269
|
if (errors.length > 10) {
|
|
7927
|
-
console.log(
|
|
9270
|
+
console.log(
|
|
9271
|
+
formatListItem(
|
|
9272
|
+
`... and ${errors.length - 10} more errors`
|
|
9273
|
+
)
|
|
9274
|
+
);
|
|
7928
9275
|
}
|
|
7929
9276
|
}
|
|
7930
9277
|
if (warnings.length > 0) {
|
|
7931
9278
|
console.log();
|
|
7932
9279
|
console.log(formatHeader("Warnings"));
|
|
7933
9280
|
if (missingBirthDates > 0) {
|
|
7934
|
-
console.log(
|
|
9281
|
+
console.log(
|
|
9282
|
+
formatListItem(
|
|
9283
|
+
formatWarning(
|
|
9284
|
+
`Missing birth dates: ${missingBirthDates} individuals`
|
|
9285
|
+
)
|
|
9286
|
+
)
|
|
9287
|
+
);
|
|
7935
9288
|
}
|
|
7936
9289
|
if (missingDeathDates > 0) {
|
|
7937
|
-
console.log(
|
|
9290
|
+
console.log(
|
|
9291
|
+
formatListItem(
|
|
9292
|
+
formatWarning(
|
|
9293
|
+
`Missing death dates: ${missingDeathDates} individuals`
|
|
9294
|
+
)
|
|
9295
|
+
)
|
|
9296
|
+
);
|
|
7938
9297
|
}
|
|
7939
9298
|
if (duplicateIds.length > 0) {
|
|
7940
|
-
console.log(
|
|
9299
|
+
console.log(
|
|
9300
|
+
formatListItem(
|
|
9301
|
+
formatWarning(
|
|
9302
|
+
`Duplicate IDs: ${duplicateIds.length}`
|
|
9303
|
+
)
|
|
9304
|
+
)
|
|
9305
|
+
);
|
|
7941
9306
|
}
|
|
7942
|
-
const otherWarnings = warnings.filter(
|
|
9307
|
+
const otherWarnings = warnings.filter(
|
|
9308
|
+
(w) => !w.includes("birth") && !w.includes("death")
|
|
9309
|
+
);
|
|
7943
9310
|
otherWarnings.slice(0, 5).forEach((warning) => {
|
|
7944
9311
|
console.log(formatListItem(formatWarning(warning)));
|
|
7945
9312
|
});
|
|
7946
9313
|
if (otherWarnings.length > 5) {
|
|
7947
|
-
console.log(
|
|
9314
|
+
console.log(
|
|
9315
|
+
formatListItem(
|
|
9316
|
+
`... and ${otherWarnings.length - 5} more warnings`
|
|
9317
|
+
)
|
|
9318
|
+
);
|
|
7948
9319
|
}
|
|
7949
9320
|
}
|
|
7950
9321
|
if (options.fix) {
|
|
7951
9322
|
console.log();
|
|
7952
|
-
console.log(
|
|
9323
|
+
console.log(
|
|
9324
|
+
formatWarning("Fix option is not yet implemented")
|
|
9325
|
+
);
|
|
7953
9326
|
}
|
|
7954
9327
|
}
|
|
7955
9328
|
if (!result.valid) {
|
|
@@ -7970,6 +9343,7 @@ var program = new Command();
|
|
|
7970
9343
|
program.name("gedcom-parser").description("CLI tool for parsing and manipulating GEDCOM files").version(packageJson.version);
|
|
7971
9344
|
registerInfoCommand(program);
|
|
7972
9345
|
registerFindCommand(program);
|
|
9346
|
+
registerSelectCommand(program);
|
|
7973
9347
|
registerShowCommand(program);
|
|
7974
9348
|
registerValidateCommand(program);
|
|
7975
9349
|
registerRelativesCommand(program);
|
|
@@ -7977,6 +9351,8 @@ registerExtractCommand(program);
|
|
|
7977
9351
|
registerStatsCommand(program);
|
|
7978
9352
|
registerMergeCommand(program);
|
|
7979
9353
|
registerConvertCommand(program);
|
|
9354
|
+
registerGetCommand(program);
|
|
9355
|
+
registerOpenCommand(program);
|
|
7980
9356
|
program.parse(process.argv);
|
|
7981
9357
|
if (!process.argv.slice(2).length) {
|
|
7982
9358
|
program.outputHelp();
|