Skip to content

Commit

Permalink
gdal CLI: add Bash completion
Browse files Browse the repository at this point in the history
Examples:
```
$ gdal <TAB><TAB>
convert   info      pipeline  raster    vector
```

```
$ gdal r<TAB>
==>
$ gdal raster
```

```
$ gdal raster<TAB><TAB>
convert    edit       info       pipeline   reproject
```

```
$ gdal raster info -<TAB><TAB>
--approx-stats   -f               --help           --if             --json-usage     --min-max        --no-fl          --no-md          --oo             --stats
--checksum       --format         --hist           --input          --list-mdd       --mm             --no-gcp         --no-nodata      --open-option    --subdataset
--drivers        -h               -i               --input-format   --mdd            --no-ct          --no-mask        --of             --output-format  --version
```

```
$ gdal raster info --of <TAB><TAB>
json  text
```

```
$ gdal raster info --of=<TAB><TAB>
json  text
```

```
$ gdal raster info --of=j<TAB>
==>
$ gdal raster info --of=json
```

```
$ gdal raster convert --of <TAB><TAB>
AAIGrid          CALS             ERS              GSAG             ILWIS            KEA              MFF              OpenFileGDB      R                SQLite           WMTS
ADRG             COG              EXR              GSBG             ISCE             KMLSUPEROVERLAY  MFF2             PAux             Rasterlite       SRTMHGT          XPM
AVIF             CTable2          FIT              GTA              ISIS2            KRO              MRF              PCIDSK           RMF              Terragen         XYZ
BAG              DDS              FITS             GTiff            ISIS3            KTX2             netCDF           PCRaster         ROI_PAC          TileDB           Zarr
BASISU           DTED             GeoRaster        GTX              JP2ECW           LAN              NGW              PDF              RRASTER          USGSDEM          ZMap
BLX              ECW              GIF              HDF4Image        JP2KAK           LCP              NITF             PDS4             RST              VICAR
BMP              EHdr             GPKG             HEIF             JP2OpenJPEG      Leveller         NTv2             PNG              SAGA             VRT
BT               ELAS             GRIB             HF2              JPEG             MBTiles          NULL             PNM              SGI              WEBP
BYN              ENVI             GS7BG            HFA              JPEGXL           MEM              NWT_GRD          PostGISRaster    SIGDEM           WMS
```

```
$ gdal raster convert in.tif out.tif --co <TAB><TAB>
ALPHA=                           ENDIANNESS=                      JXL_EFFORT=                      PIXELTYPE=                       SOURCE_PRIMARIES_RED=            TIFFTAG_TRANSFERRANGE_BLACK=
BIGTIFF=                         GEOTIFF_KEYS_FLAVOR=             JXL_LOSSLESS=                    PREDICTOR=                       SOURCE_WHITEPOINT=               TIFFTAG_TRANSFERRANGE_WHITE=
BLOCKXSIZE=                      GEOTIFF_VERSION=                 LZMA_PRESET=                     PROFILE=                         SPARSE_OK=                       TILED=
[ ... snip ... ]
```

```
$ gdal raster convert in.tif out.tif --co COMP<TAB>
==>
$ gdal raster convert in.tif out.tif --co COMPRESS=
```

```
$ gdal raster convert in.tif out.tif --co COMPRESS=<TAB><TAB>
CCITTFAX3     CCITTRLE      JPEG          LERC          LERC_ZSTD     LZW           PACKBITS      ZSTD
CCITTFAX4     DEFLATE       JXL           LERC_DEFLATE  LZMA          NONE          WEBP
```

```
$ gdal raster convert in.tif out.tif --co TILED=<TAB><TAB>
NO   YES
```

```
$ gdal raster convert in.tif out.tif --co ZLEVEL=<TAB><TAB>
1   10  11  12  2   3   4   5   6   7   8   9
```

```
$ gdal raster convert --of COG --co <TAB><TAB>
ADD_ALPHA=             EXTENT=                JXL_LOSSLESS=          NUM_THREADS=           OVERVIEW_RESAMPLING=   RESAMPLING=            WARP_RESAMPLING=
ALIGNED_LEVELS=        GEOTIFF_VERSION=       LEVEL=                 OVERVIEW_COMPRESS=     OVERVIEWS=             SPARSE_OK=             ZOOM_LEVEL=
BIGTIFF=               JXL_ALPHA_DISTANCE=    MAX_Z_ERROR=           OVERVIEW_COUNT=        PREDICTOR=             STATISTICS=            ZOOM_LEVEL_STRATEGY=
BLOCKSIZE=             JXL_DISTANCE=          MAX_Z_ERROR_OVERVIEW=  OVERVIEW_PREDICTOR=    QUALITY=               TARGET_SRS=
COMPRESS=              JXL_EFFORT=            NBITS=                 OVERVIEW_QUALITY=      RES=                   TILING_SCHEME=
```

```
$ gdal --config <TAB><TAB>
... long list of known configuration options ...
```

```
$ gdal --config AWS_<TAB><TAB>
AWS_ACCESS_KEY_ID=                       AWS_DEFAULT_REGION=                      AWS_REQUEST_PAYER=                       AWS_STS_ENDPOINT=
AWS_CONFIG_FILE=                         AWS_HTTPS=                               AWS_ROLE_ARN=                            AWS_STS_REGION=
AWS_CONTAINER_AUTHORIZATION_TOKEN=       AWS_MAX_KEYS=                            AWS_ROLE_SESSION_NAME=                   AWS_STS_REGIONAL_ENDPOINTS=
AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE=  AWS_NO_SIGN_REQUEST=                     AWS_S3_ENDPOINT=                         AWS_TIMESTAMP=
AWS_CONTAINER_CREDENTIALS_FULL_URI=      AWS_PROFILE=                             AWS_SECRET_ACCESS_KEY=                   AWS_VIRTUAL_HOSTING=
AWS_DEFAULT_PROFILE=                     AWS_REGION=                              AWS_SESSION_TOKEN=                       AWS_WEB_IDENTITY_TOKEN_FILE=
```

Last but not least: autocompletion of VSI files
```
$ gdal raster info /vsis3/my_bucket/b<TAB><TAB>
/vsis3/my_bucket/byte.tif      /vsis3/my_bucket/byte2.tif
```
  • Loading branch information
rouault committed Jan 1, 2025
1 parent d0a945c commit a3b32dc
Show file tree
Hide file tree
Showing 13 changed files with 1,198 additions and 291 deletions.
276 changes: 273 additions & 3 deletions apps/gdal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,267 @@

#include <cassert>

// #define DEBUG_COMPLETION

/************************************************************************/
/* EmitCompletion() */
/************************************************************************/

/** Return on stdout a space-separated list of choices for bash completion */
static void EmitCompletion(std::unique_ptr<GDALAlgorithm> rootAlg, int argc,
const char *const *argv)
{
#ifdef DEBUG_COMPLETION
for (int i = 0; i < argc; ++i)
fprintf(stderr, "arg[%d]='%s'\n", i, argv[i]);
#endif

// Get inner-most algorithm
auto curAlg = std::move(rootAlg);
while (argc >= 1 && argv[0][0] != '-')
{
auto subAlg = curAlg->InstantiateSubAlgorithm(argv[0]);
if (!subAlg)
break;
++argv;
--argc;
curAlg = std::move(subAlg);
}

std::string currentOption;
std::string osKey;
bool currentWordIsPartialOrUnknownOption = false;
std::string currentWord;
bool hasCurrentWord = false;
constexpr const char *CURWORD = "curword=";
if (argc >= 1 && strncmp(argv[argc - 1], CURWORD, strlen(CURWORD)) == 0)
{
hasCurrentWord = true;
currentWord = argv[argc - 1] + strlen(CURWORD);
}

const auto IsKeyValueOption = [](const char *pszStr)
{
return strcmp(pszStr, "--co") == 0 ||
strcmp(pszStr, "--creation-option") == 0 ||
strcmp(pszStr, "--oo") == 0 ||
strcmp(pszStr, "--open-option") == 0;
};

// Deal with "gdal ... --option=<TAB>"
if (argc >= 2 && currentWord == "=" && argv[argc - 2][0] == '-' &&
argv[argc - 2][1] == '-')
{
if (curAlg->GetArg(argv[argc - 2]))
{
currentOption = argv[argc - 2];
}
--argc;
}
// Deal with "gdal raster convert in.tif out.tif --co COMPRESS=<TAB><TAB>"
else if (argc >= 3 && currentWord == "=" &&
IsKeyValueOption(argv[argc - 3]))
{
if (curAlg->GetArg(argv[argc - 3]))
{
osKey = argv[argc - 2];
currentOption = argv[argc - 3];
}
argc -= 2;
}
// Deal with "gdal raster convert in.tif out.tif --co=COMPRESS=<TAB>"
else if (argc >= 4 && currentWord == "=" &&
strcmp(argv[argc - 3], "=") == 0 &&
IsKeyValueOption(argv[argc - 4]))
{
if (curAlg->GetArg(argv[argc - 4]))
{
osKey = argv[argc - 2];
currentOption = argv[argc - 4];
}
argc -= 3;
}
// Deal with "gdal raster convert in.tif out.tif --co COMPRESS=NO<TAB>"
else if (argc >= 4 && currentWord != "=" &&
strcmp(argv[argc - 2], "=") == 0 &&
IsKeyValueOption(argv[argc - 4]))
{
if (curAlg->GetArg(argv[argc - 4]))
{
osKey = argv[argc - 3];
currentOption = argv[argc - 4];
}
argc -= 3;
}
// Deal with "gdal raster convert in.tif out.tif --co=COMPRESS=NO<TAB>"
else if (argc >= 5 && currentWord != "=" &&
strcmp(argv[argc - 2], "=") == 0 &&
strcmp(argv[argc - 4], "=") == 0 &&
IsKeyValueOption(argv[argc - 5]))
{
if (curAlg->GetArg(argv[argc - 5]))
{
osKey = argv[argc - 3];
currentOption = argv[argc - 5];
}
argc -= 3;
}
else if (!currentWord.empty() && currentWord[0] == '-')
{
// Deal with "gdal ... -<something><TAB>"
currentWordIsPartialOrUnknownOption = true;
--argc;
}
else if (hasCurrentWord)
{
--argc;
if (argc >= 1 && strcmp(argv[argc - 1], "=") == 0)
{
// Deal with "gdal ... --option=<something><TAB>"
--argc;
}
}

std::string ret;
const auto addSpace = [&ret]()
{
if (!ret.empty())
ret += " ";
};

if (argc >= 1 && strcmp(argv[argc - 1], "--config") == 0)
{
CPLStringList aosConfigOptions(CPLGetKnownConfigOptions());
for (const char *pszOpt : cpl::Iterate(aosConfigOptions))
{
addSpace();
ret += pszOpt;
ret += '=';
}
printf("%s", ret.c_str());
return;
}
else if (argc >= 2 && strcmp(argv[argc - 2], "--config") == 0 &&
currentWord == "=")
{
// Do not try to autocomplete "gdal --config FOO=<TAB>"
return;
}
else if (argc >= 3 && strcmp(argv[argc - 3], "--config") == 0 &&
strcmp(argv[argc - 2], "=") == 0 && currentWord == "=")
{
// Do not try to autocomplete "gdal --config=FOO=<TAB>"
return;
}

// If the algorithm has a auto-completion method, use it
const std::vector<std::string> args(argv, argv + argc);
std::vector<std::string> autoCompleteChoices =
curAlg->GetAutoCompleteChoices(args, currentWord);
if (!autoCompleteChoices.empty())
{
for (const auto &osChoice : autoCompleteChoices)
{
addSpace();
ret += osChoice;
}
}
else
{
if (!currentWordIsPartialOrUnknownOption && currentOption.empty() &&
argc >= 1 && argv[argc - 1][0] == '-')
currentOption = argv[argc - 1];

if (currentWordIsPartialOrUnknownOption)
{
// List available options
for (const auto &arg : curAlg->GetArgs())
{
if (arg->IsHiddenForCLI())
continue;
if (!arg->GetShortName().empty())
{
addSpace();
ret += "-";
ret += arg->GetShortName();
}
for (const std::string &alias : arg->GetAliases())
{
addSpace();
ret += "--";
ret += alias;
}
if (!arg->GetName().empty())
{
addSpace();
ret += "--";
ret += arg->GetName();
}
}
}
else if (!currentOption.empty())
{
// List possible choices for current option
auto arg = curAlg->GetArg(currentOption);
if (arg)
{
std::vector<std::string> choices = arg->GetChoices();
if (choices.empty())
{
{
CPLErrorStateBackuper oErrorQuieter(
CPLQuietErrorHandler);
curAlg->SetParseForAutoCompletion();
CPL_IGNORE_RET_VAL(
curAlg->ParseCommandLineArguments(args));
}
if (osKey.empty())
{
choices = arg->GetAutoCompleteChoices(currentWord);
}
else
{
choices = arg->GetAutoCompleteChoices(osKey);
}
}
for (const auto &choice : choices)
{
addSpace();
ret += CPLString(choice).replaceAll(" ", "\\ ");
}
}
}
else if (STARTS_WITH(currentWord.c_str(), "/vsi"))
{
auto arg = curAlg->GetArg("input");
if (arg)
{
for (const auto &choice :
arg->GetAutoCompleteChoices(currentWord))
{
addSpace();
ret += CPLString(choice).replaceAll(" ", "\\ ");
}
}
}
else
{
// List possible sub-algorithms
for (const auto &osName : curAlg->GetSubAlgorithmNames())
{
addSpace();
ret += osName;
}
}
}

#ifdef DEBUG_COMPLETION
fprintf(stderr, "ret = '%s'\n", ret.c_str());
#endif
if (!ret.empty())
printf("%s", ret.c_str());
}

/************************************************************************/
/* main() */
/************************************************************************/
Expand All @@ -31,14 +292,23 @@ MAIN_START(argc, argv)
/* -------------------------------------------------------------------- */

GDALAllRegister();

auto alg = GDALGlobalAlgorithmRegistry::GetSingleton().Instantiate(
GDALGlobalAlgorithmRegistry::ROOT_ALG_NAME);
assert(alg);

if (argc >= 2 && strcmp(argv[1], "completion") == 0)
{
// Process lines like "gdal completion raster curword=conv"
EmitCompletion(std::move(alg), argc - 2, argv + 2);
return 0;
}

argc = GDALGeneralCmdLineProcessor(
argc, &argv, GDAL_OF_RASTER | GDAL_OF_VECTOR | GDAL_OF_MULTIDIM_RASTER);
if (argc < 1)
return (-argc);

auto alg = GDALGlobalAlgorithmRegistry::GetSingleton().Instantiate(
GDALGlobalAlgorithmRegistry::ROOT_ALG_NAME);
assert(alg);
alg->SetCallPath(std::vector<std::string>{argv[0]});
std::vector<std::string> args;
for (int i = 1; i < argc; ++i)
Expand Down
Loading

0 comments on commit a3b32dc

Please sign in to comment.