Skip to content

Commit 4552703

Browse files
committed
Add bash completion script for projinfo
and install it in standard ${prefix}/share/bash-completion/completions directory Demo: ``` source ./scripts/projinfo-bash-completion.sh ``` ``` $ projinfo <TAB><TAB> EPSG: ESRI: IAU_2015: IGNF: NKG: NRCAN: OGC: PROJ: ``` ``` $ projinfo -<TAB><TAB> -3d --boundcrs-to-wgs84 --list-crs --searchpaths --accuracy --c-ify --main-db-path --show-superseded --allow-ellipsoidal-height-as-vertical-crs --crs-extent-use--grid-check -o --single-line --area --dump-db-structure --output-id --spatial-test --authority --hide-ballpark --pivot-crs --summary --aux-db-path --identify -q --bbox -k --remote-data ``` ``` $ projinfo -o <TAB><TAB> all PROJ PROJJSON SQL WKT1:ESRI WKT1:GDAL WKT2:2015 WKT2:2019 ``` ``` $ projinfo "NAD83(2011) <TAB><TAB> NAD83(2011) NAD83(2011) / New Mexico West NAD83(2011) / Adjusted Jackson (ftUS) NAD83(2011) / New Mexico West (ftUS) NAD83(2011) / Alabama East NAD83(2011) / New York Central [ ... snip ... ] ``` ``` $ projinfo EPSG:43<TAB><TAB> 4322 -- WGS 72 4324 -- WGS 72BE 4326 -- WGS 84 ```
1 parent 177f6f3 commit 4552703

File tree

6 files changed

+362
-1
lines changed

6 files changed

+362
-1
lines changed

CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,8 @@ if(BUILD_EXAMPLES)
415415
add_subdirectory(examples)
416416
endif()
417417

418+
add_subdirectory(scripts)
419+
418420
set(docfiles COPYING NEWS.md AUTHORS.md)
419421
install(FILES ${docfiles}
420422
DESTINATION ${CMAKE_INSTALL_DOCDIR})
@@ -459,7 +461,6 @@ set(CPACK_SOURCE_IGNORE_FILES
459461
/m4/libtool*
460462
/media/
461463
/schemas/
462-
/scripts/
463464
/test/fuzzers/
464465
/test/gigs/.*gie\\.failing
465466
/test/postinstall/

scripts/CMakeLists.txt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
find_package(PkgConfig QUIET)
2+
if (PKG_CONFIG_FOUND)
3+
pkg_check_modules(PC_BASH_COMPLETION QUIET bash-completion)
4+
if (PC_BASH_COMPLETION_FOUND)
5+
pkg_get_variable(BASH_COMPLETIONS_FULL_DIR bash-completion completionsdir)
6+
pkg_get_variable(BASH_COMPLETIONS_PREFIX bash-completion prefix)
7+
if (BASH_COMPLETIONS_FULL_DIR
8+
AND BASH_COMPLETIONS_PREFIX
9+
AND BASH_COMPLETIONS_FULL_DIR MATCHES "^${BASH_COMPLETIONS_PREFIX}/")
10+
string(REGEX REPLACE "^${BASH_COMPLETIONS_PREFIX}/" "" BASH_COMPLETIONS_DIR_DEFAULT ${BASH_COMPLETIONS_FULL_DIR})
11+
endif ()
12+
endif ()
13+
endif ()
14+
15+
if (NOT DEFINED BASH_COMPLETIONS_DIR_DEFAULT)
16+
include(GNUInstallDirs)
17+
set(BASH_COMPLETIONS_DIR_DEFAULT ${CMAKE_INSTALL_DATADIR}/bash-completion/completions)
18+
endif ()
19+
20+
set(BASH_COMPLETIONS_DIR
21+
"${BASH_COMPLETIONS_DIR_DEFAULT}"
22+
CACHE PATH "Installation sub-directory for bash completion scripts")
23+
24+
if (NOT BASH_COMPLETIONS_DIR STREQUAL "")
25+
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/install_bash_completions.cmake.in
26+
${CMAKE_CURRENT_BINARY_DIR}/install_bash_completions.cmake @ONLY)
27+
install(SCRIPT ${CMAKE_CURRENT_BINARY_DIR}/install_bash_completions.cmake)
28+
endif ()
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
set(PROGRAMS
2+
projinfo
3+
)
4+
5+
set(INSTALL_DIR "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/@BASH_COMPLETIONS_DIR@")
6+
7+
file(MAKE_DIRECTORY "${INSTALL_DIR}")
8+
9+
foreach (program IN LISTS PROGRAMS)
10+
message(STATUS "Installing ${INSTALL_DIR}/${program}")
11+
configure_file("@CMAKE_CURRENT_SOURCE_DIR@/${program}-bash-completion.sh" "${INSTALL_DIR}/${program}" COPYONLY)
12+
file(APPEND "@PROJECT_BINARY_DIR@/install_manifest_extra.txt" "${INSTALL_DIR}/${program}\n")
13+
endforeach ()
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#!/bin/bash
2+
3+
function_exists() {
4+
declare -f -F "$1" > /dev/null
5+
return $?
6+
}
7+
8+
# Checks that bash-completion is recent enough
9+
function_exists _get_comp_words_by_ref || return 0
10+
11+
_projinfo()
12+
{
13+
local cur prev
14+
COMPREPLY=()
15+
_get_comp_words_by_ref cur prev
16+
choices=$(projinfo completion ${COMP_LINE})
17+
if [[ "$cur" == "=" ]]; then
18+
mapfile -t COMPREPLY < <(compgen -W "$choices" --)
19+
elif [[ "$cur" == ":" ]]; then
20+
mapfile -t COMPREPLY < <(compgen -W "$choices" --)
21+
elif [[ "${cur: -1}" == "/" ]]; then
22+
mapfile -t COMPREPLY < <(compgen -W "$choices" --)
23+
elif [[ "${cur: -2}" == "/ " ]]; then
24+
mapfile -t COMPREPLY < <(compgen -W "$choices" --)
25+
elif [[ "${cur: -1}" == "+" ]]; then
26+
mapfile -t COMPREPLY < <(compgen -W "$choices" --)
27+
elif [[ "${cur: -2}" == "+ " ]]; then
28+
mapfile -t COMPREPLY < <(compgen -W "$choices" --)
29+
elif [[ "$cur" == "!" ]]; then
30+
mapfile -t COMPREPLY < <(compgen -W "$choices" -P "! " --)
31+
else
32+
mapfile -t COMPREPLY < <(compgen -W "$choices" -- "$cur")
33+
fi
34+
for element in "${COMPREPLY[@]}"; do
35+
if [[ $element == */ ]]; then
36+
# Do not add a space if one of the suggestion ends with slash
37+
compopt -o nospace
38+
break
39+
elif [[ $element == *= ]]; then
40+
# Do not add a space if one of the suggestion ends with equal
41+
compopt -o nospace
42+
break
43+
elif [[ $element == *: ]]; then
44+
# Do not add a space if one of the suggestion ends with colon
45+
compopt -o nospace
46+
break
47+
fi
48+
done
49+
}
50+
complete -o default -F _projinfo projinfo
51+

src/apps/projinfo.cpp

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,6 +1053,237 @@ static void outputOperations(
10531053

10541054
// ---------------------------------------------------------------------------
10551055

1056+
static void suggestCompletion(const std::vector<std::string> &args) {
1057+
#ifdef DEBUG_COMPLETION
1058+
for (const auto &arg : args)
1059+
fprintf(stderr, "'%s' ", arg.c_str());
1060+
fprintf(stderr, "\n");
1061+
#endif
1062+
1063+
for (const std::string &arg : args) {
1064+
// Shouldn't happen, unless someone really tries to actively crash us
1065+
if (arg.empty())
1066+
return;
1067+
}
1068+
1069+
bool first = true;
1070+
if (args.empty()) {
1071+
try {
1072+
auto dbContext = DatabaseContext::create();
1073+
for (const auto &authName : dbContext->getAuthorities()) {
1074+
if (!first)
1075+
printf(" ");
1076+
first = false;
1077+
printf("%s:", authName.c_str());
1078+
}
1079+
printf("\n");
1080+
} catch (const std::exception &) {
1081+
}
1082+
return;
1083+
} else if (args.size() == 1 && args[0].front() != '-' &&
1084+
args[0].find(':') == std::string::npos) {
1085+
try {
1086+
auto dbContext = DatabaseContext::create();
1087+
for (const auto &authName : dbContext->getAuthorities()) {
1088+
if (starts_with(authName, args[0])) {
1089+
if (!first)
1090+
printf(" ");
1091+
first = false;
1092+
printf("%s:", authName.c_str());
1093+
}
1094+
}
1095+
} catch (const std::exception &) {
1096+
}
1097+
}
1098+
1099+
const auto isOption = [&args](const char *opt) {
1100+
return args.back() == opt ||
1101+
(args.size() >= 2 && args[args.size() - 2] == opt);
1102+
};
1103+
1104+
if (isOption("-k")) {
1105+
printf("crs operation datum ensemble ellipsoid\n");
1106+
return;
1107+
}
1108+
1109+
if (isOption("-o")) {
1110+
if (starts_with(args.back(), "WKT1:"))
1111+
printf("GDAL ESRI\n");
1112+
else if (starts_with(args.back(), "WKT2:"))
1113+
printf("2019 2015\n");
1114+
else
1115+
printf("all PROJ WKT2:2019 WKT2:2015 WKT1:GDAL WKT1:ESRI PROJJSON "
1116+
"SQL\n");
1117+
return;
1118+
}
1119+
1120+
if (isOption("--spatial-test")) {
1121+
printf("contains intersects\n");
1122+
return;
1123+
}
1124+
1125+
if (isOption("--crs-extent-use")) {
1126+
printf("none both intersection smallest\n");
1127+
return;
1128+
}
1129+
1130+
if (isOption("--grid-check")) {
1131+
printf("none discard_missing sort known_available\n");
1132+
return;
1133+
}
1134+
1135+
if (isOption("--pivot-crs")) {
1136+
if (args.back().back() == ':')
1137+
return;
1138+
printf("always if_no_direct_transformation never");
1139+
try {
1140+
auto dbContext = DatabaseContext::create();
1141+
for (const auto &authName : dbContext->getAuthorities()) {
1142+
printf(" %s:", authName.c_str());
1143+
}
1144+
printf("\n");
1145+
} catch (const std::exception &) {
1146+
}
1147+
return;
1148+
}
1149+
1150+
if (args.back()[0] == '-') {
1151+
const char *const knownOptions[] = {
1152+
"-o",
1153+
"-k",
1154+
"--summary",
1155+
"-q",
1156+
"--area",
1157+
"--bbox",
1158+
"--spatial-test",
1159+
"--crs-extent-use",
1160+
"--grid-check",
1161+
"--pivot-crs",
1162+
"--show-superseded",
1163+
"--hide-ballpark",
1164+
"--accuracy",
1165+
"--allow-ellipsoidal-height-as-vertical-crs",
1166+
"--boundcrs-to-wgs84",
1167+
"--authority",
1168+
"--main-db-path",
1169+
"--aux-db-path",
1170+
"--identify",
1171+
"--3d",
1172+
"--output-id",
1173+
"--c-ify",
1174+
"--single-line",
1175+
"--searchpaths",
1176+
"--remote-data",
1177+
"--list-crs",
1178+
"--dump-db-structure",
1179+
"-s",
1180+
"--s_epoch",
1181+
"-t",
1182+
"--t_epoch",
1183+
};
1184+
1185+
for (const char *opt : knownOptions) {
1186+
if (args.back() == opt)
1187+
return;
1188+
}
1189+
for (const char *opt : knownOptions) {
1190+
if (!first)
1191+
printf(" ");
1192+
first = false;
1193+
printf("%s", opt);
1194+
}
1195+
printf("\n");
1196+
return;
1197+
}
1198+
1199+
std::string lastArg = args.back();
1200+
for (size_t i = args.size(); i >= 1;) {
1201+
--i;
1202+
if (args[i].size() >= 2 && args[i].back() == '"') {
1203+
break;
1204+
}
1205+
if (args[i].size() >= 2 && args[i][0] == '"') {
1206+
lastArg = args[i].substr(1);
1207+
++i;
1208+
for (; i < args.size(); ++i) {
1209+
lastArg += " ";
1210+
lastArg += args[i];
1211+
}
1212+
break;
1213+
}
1214+
}
1215+
#ifdef DEBUG_COMPLETION
1216+
fprintf(stderr, "lastArg='%s'\n", lastArg.c_str());
1217+
#endif
1218+
1219+
try {
1220+
auto dbContext = DatabaseContext::create();
1221+
const auto columnPos = args.back().find(':');
1222+
if (columnPos != std::string::npos) {
1223+
const auto authName = args.back().substr(0, columnPos);
1224+
const auto codeStart = columnPos + 1 < args.back().size()
1225+
? args.back().substr(columnPos + 1)
1226+
: std::string();
1227+
auto factory = AuthorityFactory::create(dbContext, authName);
1228+
const auto list = factory->getCRSInfoList();
1229+
1230+
std::vector<std::string> res;
1231+
std::string code;
1232+
for (const auto &info : list) {
1233+
if (!info.deprecated &&
1234+
(codeStart.empty() || starts_with(info.code, codeStart))) {
1235+
if (res.empty())
1236+
code = info.code;
1237+
res.push_back(std::string(info.code).append(" -- ").append(
1238+
info.name));
1239+
}
1240+
}
1241+
if (res.size() == 1) {
1242+
// If there is a single match, remove the name from the
1243+
// suggestion.
1244+
res.clear();
1245+
res.push_back(code);
1246+
}
1247+
for (const auto &val : res) {
1248+
if (!first)
1249+
printf(" ");
1250+
first = false;
1251+
printf("%s", replaceAll(val, " ", "\\ ").c_str());
1252+
}
1253+
printf("\n");
1254+
return;
1255+
}
1256+
1257+
for (const char *authName : {"EPSG", ""}) {
1258+
auto factory =
1259+
AuthorityFactory::create(dbContext, std::string(authName));
1260+
const auto list = factory->getCRSInfoList();
1261+
for (const auto &info : list) {
1262+
if (!info.deprecated && starts_with(info.name, lastArg)) {
1263+
if (!first)
1264+
printf(" ");
1265+
first = false;
1266+
std::string val = info.name;
1267+
if (args.back() == "+" || args.back() == "/") {
1268+
const auto pos = val.find(args.back()[0]);
1269+
if (pos != std::string::npos && pos + 1 < val.size() &&
1270+
val[pos + 1] == ' ')
1271+
val = val.substr(pos + 2);
1272+
}
1273+
printf("%s", replaceAll(val, " ", "\\ ").c_str());
1274+
}
1275+
}
1276+
if (!first) {
1277+
printf("\n");
1278+
break;
1279+
}
1280+
}
1281+
} catch (const std::exception &) {
1282+
}
1283+
}
1284+
1285+
// ---------------------------------------------------------------------------
1286+
10561287
int main(int argc, char **argv) {
10571288

10581289
pj_stderr_proj_lib_deprecation_warning();
@@ -1062,6 +1293,11 @@ int main(int argc, char **argv) {
10621293
usage();
10631294
}
10641295

1296+
if (argc >= 3 && strcmp(argv[1], "completion") == 0) {
1297+
suggestCompletion(std::vector<std::string>(argv + 3, argv + argc));
1298+
return 0;
1299+
}
1300+
10651301
std::vector<std::string> positional_args;
10661302
std::string sourceCRSStr;
10671303
std::string sourceEpoch;

test/cli/test_projinfo.yaml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1890,3 +1890,35 @@ tests:
18901890
out: |
18911891
Candidate operations found: 1
18921892
unknown id, Conversion from WGS 84 (G1150) (geog2D) to WGS 84 (G1150) (geocentric) + WGS 84 (G1150) to WGS 84 (G1762) (1) + WGS 84 (G1762) to WGS 84 (G2139) (1) + WGS 84 (G2139) to WGS 84 (G2296) (1) + Conversion from WGS 84 (G2296) (geocentric) to WGS 84 (G2296) (geog2D), 0.04 m, World
1893+
- args: completion projinfo -
1894+
out: |
1895+
-o -k --summary -q --area --bbox --spatial-test --crs-extent-use --grid-check --pivot-crs --show-superseded --hide-ballpark --accuracy --allow-ellipsoidal-height-as-vertical-crs --boundcrs-to-wgs84 --authority --main-db-path --aux-db-path --identify --3d --output-id --c-ify --single-line --searchpaths --remote-data --list-crs --dump-db-structure -s --s_epoch -t --t_epoch
1896+
- args: completion projinfo -o
1897+
out: all PROJ WKT2:2019 WKT2:2015 WKT1:GDAL WKT1:ESRI PROJJSON SQL
1898+
- args: completion projinfo -o "WKT2:"
1899+
out: 2019 2015
1900+
- args: completion projinfo -o "WKT1:"
1901+
out: GDAL ESRI
1902+
- args: completion projinfo --pivot-crs
1903+
out: |
1904+
always if_no_direct_transformation never EPSG: ESRI: IAU_2015: IGNF: NKG: NRCAN: OGC: PROJ:
1905+
- args: completion projinfo
1906+
out: |
1907+
EPSG: ESRI: IAU_2015: IGNF: NKG: NRCAN: OGC: PROJ:
1908+
- args: completion projinfo NKG
1909+
out: "NKG:"
1910+
- args: completion projinfo "OGC:"
1911+
out: |
1912+
CRS27\ --\ NAD27\ (CRS27) CRS83\ --\ NAD83\ (CRS83) CRS84\ --\ WGS\ 84\ (CRS84) CRS84h\ --\ WGS\ 84\ longitude-latitude-height
1913+
- args: completion projinfo EPSG:432
1914+
out: |
1915+
4322\ --\ WGS\ 72 4324\ --\ WGS\ 72BE 4326\ --\ WGS\ 84
1916+
- args: completion projinfo EPSG:4326
1917+
out: |
1918+
4326
1919+
- args: completion projinfo EGM
1920+
out: |
1921+
EGM2008\ height EGM96\ height EGM84\ height
1922+
- args: completion projinfo "\"RGF93" v1 "/"
1923+
out: |
1924+
Lambert-93 CC42 CC43 CC44 CC45 CC46 CC47 CC48 CC49 CC50 Lambert-93\ +\ NGF-IGN69\ height Lambert-93\ +\ NGF-IGN78\ height

0 commit comments

Comments
 (0)