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=
```

```
$ gdal raster reproject --dst-crs
EPSG:      ESRI:      IAU_2015:  IGNF:      NKG:       OGC:
```

```
$ gdal raster reproject --dst-crs EP<TAB>
==>
$ gdal raster reproject --dst-crs EPSG:
```

```
$ gdal raster reproject --dst-crs EPSG:<TAB>
10150 -- MSL UK & Ireland VORF08 depth
10151 -- CD UK & Ireland VORF08 depth
10156 -- ETRS89 + MSL UK & Ireland VORF08 depth
10157 -- ETRS89 + CD UK & Ireland VORF08 depth
[ ... snip ... ]
```

```
$ gdal raster reproject --dst-crs EPSG:432<TAB>
4322 -- WGS 72                  4324 -- WGS 72BE                4326 -- WGS 84                  4327 -- WGS 84 (geographic 3D)  4328 -- WGS 84 (geocentric)     4329 -- WGS 84 (3D)
```

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 2, 2025
1 parent 9335401 commit 5963dfb
Show file tree
Hide file tree
Showing 13 changed files with 1,579 additions and 344 deletions.
88 changes: 85 additions & 3 deletions apps/gdal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,96 @@

#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,
const std::vector<std::string> &argsIn)
{
#ifdef DEBUG_COMPLETION
for (size_t i = 0; i < argsIn.size(); ++i)
fprintf(stderr, "arg[%d]='%s'\n", static_cast<int>(i),
argsIn[i].c_str());
#endif

std::vector<std::string> args = argsIn;

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

if (!args.empty() &&
(args.back() == "--config" ||
STARTS_WITH(args.back().c_str(), "--config=") ||
(args.size() >= 2 && args[args.size() - 2] == "--config")))
{
if (args.back() == "--config=" || args.back().back() != '=')
{
CPLStringList aosConfigOptions(CPLGetKnownConfigOptions());
for (const char *pszOpt : cpl::Iterate(aosConfigOptions))
{
addSpace();
ret += pszOpt;
ret += '=';
}
printf("%s", ret.c_str());
}
return;
}

// Get inner-most algorithm
bool showAllOptions = true;
auto curAlg = std::move(rootAlg);
while (!args.empty() && !args.front().empty() && args.front()[0] != '-')
{
auto subAlg = curAlg->InstantiateSubAlgorithm(args.front());
if (!subAlg)
break;
showAllOptions = false;
args.erase(args.begin());
curAlg = std::move(subAlg);
}

for (const auto &choice : curAlg->GetAutoComplete(args, showAllOptions))
{
addSpace();
ret += CPLString(choice).replaceAll(" ", "\\ ");
}

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

/************************************************************************/
/* main() */
/************************************************************************/

MAIN_START(argc, argv)
{
auto alg = GDALGlobalAlgorithmRegistry::GetSingleton().Instantiate(
GDALGlobalAlgorithmRegistry::ROOT_ALG_NAME);
assert(alg);

if (argc >= 3 && strcmp(argv[1], "completion") == 0)
{
GDALAllRegister();

// Process lines like "gdal completion gdal raster"
EmitCompletion(std::move(alg),
std::vector<std::string>(argv + 3, argv + argc));
return 0;
}

EarlySetConfigOptions(argc, argv);

/* -------------------------------------------------------------------- */
Expand All @@ -31,14 +115,12 @@ MAIN_START(argc, argv)
/* -------------------------------------------------------------------- */

GDALAllRegister();

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
238 changes: 238 additions & 0 deletions apps/gdalalg_abstract_pipeline.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
/******************************************************************************
*
* Project: GDAL
* Purpose: gdal "raster/vector pipeline" subcommand
* Author: Even Rouault <even dot rouault at spatialys.com>
*
******************************************************************************
* Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
*
* SPDX-License-Identifier: MIT
****************************************************************************/

#ifndef GDALALG_ABSTRACT_PIPELINE_INCLUDED
#define GDALALG_ABSTRACT_PIPELINE_INCLUDED

//! @cond Doxygen_Suppress

#include "cpl_conv.h"
#include "cpl_json.h"
#include "gdalalgorithm.h"

template <class StepAlgorithm>
class GDALAbstractPipelineAlgorithm CPL_NON_FINAL : public StepAlgorithm
{
public:
std::vector<std::string> GetAutoComplete(std::vector<std::string> &args,
bool /* showAllOptions*/) override;

bool Finalize() override;

std::string GetUsageAsJSON() const override;

/* cppcheck-suppress functionStatic */
void SetDataset(GDALDataset *)
{
}

protected:
GDALAbstractPipelineAlgorithm(const std::string &name,
const std::string &description,
const std::string &helpURL,
bool standaloneStep)
: StepAlgorithm(name, description, helpURL, standaloneStep)
{
}

virtual GDALArgDatasetValue &GetOutputDataset() = 0;

std::string m_pipeline{};

std::unique_ptr<StepAlgorithm> GetStepAlg(const std::string &name) const;

GDALAlgorithmRegistry m_stepRegistry{};
std::vector<std::unique_ptr<StepAlgorithm>> m_steps{};

private:
bool RunStep(GDALProgressFunc pfnProgress, void *pProgressData) override;
};

/************************************************************************/
/* GDALAbstractPipelineAlgorithm::GetStepAlg() */
/************************************************************************/

template <class StepAlgorithm>
std::unique_ptr<StepAlgorithm>
GDALAbstractPipelineAlgorithm<StepAlgorithm>::GetStepAlg(
const std::string &name) const
{
auto alg = m_stepRegistry.Instantiate(name);
return std::unique_ptr<StepAlgorithm>(
cpl::down_cast<StepAlgorithm *>(alg.release()));
}

/************************************************************************/
/* GDALAbstractPipelineAlgorithm::GetAutoComplete() */
/************************************************************************/

template <class StepAlgorithm>
std::vector<std::string>
GDALAbstractPipelineAlgorithm<StepAlgorithm>::GetAutoComplete(
std::vector<std::string> &args, bool /* showAllOptions*/)
{
std::vector<std::string> ret;
if (args.size() <= 1)
{
ret.push_back("read");
}
else if (args.back() == "!" ||
(args.size() >= 2 && args[args.size() - 2] == "!"))
{
for (const std::string &name : m_stepRegistry.GetNames())
{
if (name != "read")
{
ret.push_back(name);
}
}
}
else
{
std::string lastStep = "read";
std::vector<std::string> lastArgs;
for (size_t i = 1; i < args.size(); ++i)
{
lastArgs.push_back(args[i]);
if (i + 1 < args.size() && args[i] == "!")
{
++i;
lastArgs.clear();
lastStep = args[i];
}
}

auto curAlg = GetStepAlg(lastStep);
if (curAlg)
{
ret =
curAlg->GetAutoComplete(lastArgs, /* showAllOptions = */ false);
}
}
return ret;
}

/************************************************************************/
/* GDALAbstractPipelineAlgorithm::RunStep() */
/************************************************************************/

template <class StepAlgorithm>
bool GDALAbstractPipelineAlgorithm<StepAlgorithm>::RunStep(
GDALProgressFunc pfnProgress, void *pProgressData)
{
if (m_steps.empty())
{
// If invoked programmatically, not from the command line.

if (m_pipeline.empty())
{
StepAlgorithm::ReportError(CE_Failure, CPLE_AppDefined,
"'pipeline' argument not set");
return false;
}

const CPLStringList aosTokens(CSLTokenizeString(m_pipeline.c_str()));
if (!this->ParseCommandLineArguments(aosTokens))
return false;
}

GDALDataset *poCurDS = nullptr;
for (size_t i = 0; i < m_steps.size(); ++i)
{
auto &step = m_steps[i];
if (i > 0)
{
if (step->m_inputDataset.GetDatasetRef())
{
// Shouldn't happen
StepAlgorithm::ReportError(
CE_Failure, CPLE_AppDefined,
"Step nr %d (%s) has already an input dataset",
static_cast<int>(i), step->GetName().c_str());
return false;
}
step->m_inputDataset.Set(poCurDS);
}
if (i + 1 < m_steps.size() && step->m_outputDataset.GetDatasetRef())
{
// Shouldn't happen
StepAlgorithm::ReportError(
CE_Failure, CPLE_AppDefined,
"Step nr %d (%s) has already an output dataset",
static_cast<int>(i), step->GetName().c_str());
return false;
}
if (!step->Run(i < m_steps.size() - 1 ? nullptr : pfnProgress,
i < m_steps.size() - 1 ? nullptr : pProgressData))
{
return false;
}
poCurDS = step->m_outputDataset.GetDatasetRef();
if (!poCurDS)
{
StepAlgorithm::ReportError(
CE_Failure, CPLE_AppDefined,
"Step nr %d (%s) failed to produce an output dataset",
static_cast<int>(i), step->GetName().c_str());
return false;
}
}

if (!GetOutputDataset().GetDatasetRef())
{
GetOutputDataset().Set(poCurDS);
}

return true;
}

/************************************************************************/
/* GDALAbstractPipelineAlgorithm::Finalize() */
/************************************************************************/

template <class StepAlgorithm>
bool GDALAbstractPipelineAlgorithm<StepAlgorithm>::Finalize()
{
bool ret = GDALAlgorithm::Finalize();
for (auto &step : m_steps)
{
ret = step->Finalize() && ret;
}
return ret;
}

/************************************************************************/
/* GDALAbstractPipelineAlgorithm::GetUsageAsJSON() */
/************************************************************************/

template <class StepAlgorithm>
std::string GDALAbstractPipelineAlgorithm<StepAlgorithm>::GetUsageAsJSON() const
{
CPLJSONDocument oDoc;
oDoc.LoadMemory(GDALAlgorithm::GetUsageAsJSON());

CPLJSONArray jPipelineSteps;
for (const std::string &name : m_stepRegistry.GetNames())
{
auto alg = GetStepAlg(name);
CPLJSONDocument oStepDoc;
oStepDoc.LoadMemory(alg->GetUsageAsJSON());
jPipelineSteps.Add(oStepDoc.GetRoot());
}
oDoc.GetRoot().Add("pipeline_algorithms", jPipelineSteps);

return oDoc.SaveAsString();
}

//! @endcond

#endif
Loading

0 comments on commit 5963dfb

Please sign in to comment.