diff --git a/Makefile b/Makefile index a7ee214e..5e6c69ae 100644 --- a/Makefile +++ b/Makefile @@ -59,25 +59,25 @@ C = $(wildcard *.c) $(wildcard *.cpp) INCLUDES = -I/usr/local/include -I. -Iclipper2/include LIBS = -L/usr/local/lib -tippecanoe: geojson.o jsonpull/jsonpull.o tile.o pool.o mbtiles.o geometry.o projection.o memfile.o mvt.o serial.o main.o platform.o text.o dirtiles.o pmtiles_file.o plugin.o read_json.o write_json.o geobuf.o flatgeobuf.o evaluator.o geocsv.o csv.o geojson-loop.o json_logger.o visvalingam.o compression.o clip.o sort.o attribute.o thread.o shared_borders.o clipper2/src/clipper.engine.o +tippecanoe: geojson.o jsonpull/jsonpull.o tile.o pool.o mbtiles.o geometry.o projection.o memfile.o mvt.o serial.o main.o platform.o text.o dirtiles.o pmtiles_file.o plugin.o read_json.o write_json.o geobuf.o flatgeobuf.o evaluator.o geocsv.o csv.o geojson-loop.o json_logger.o visvalingam.o compression.o clip.o sort.o attribute.o thread.o shared_borders.o usage.o clipper2/src/clipper.engine.o $(CXX) $(PG) $(LIBS) $(FINAL_FLAGS) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -lm -lz -lsqlite3 -lpthread -tippecanoe-enumerate: enumerate.o +tippecanoe-enumerate: enumerate.o usage.o $(CXX) $(PG) $(LIBS) $(FINAL_FLAGS) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -lsqlite3 -tippecanoe-decode: decode.o projection.o mvt.o write_json.o text.o jsonpull/jsonpull.o dirtiles.o pmtiles_file.o +tippecanoe-decode: decode.o projection.o mvt.o write_json.o text.o jsonpull/jsonpull.o dirtiles.o pmtiles_file.o usage.o $(CXX) $(PG) $(LIBS) $(FINAL_FLAGS) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -lm -lz -lsqlite3 -tile-join: tile-join.o platform.o projection.o mbtiles.o mvt.o memfile.o dirtiles.o jsonpull/jsonpull.o text.o evaluator.o csv.o write_json.o pmtiles_file.o clip.o attribute.o thread.o read_json.o clipper2/src/clipper.engine.o +tile-join: tile-join.o platform.o projection.o mbtiles.o mvt.o memfile.o dirtiles.o jsonpull/jsonpull.o text.o evaluator.o csv.o write_json.o pmtiles_file.o clip.o attribute.o thread.o read_json.o usage.o clipper2/src/clipper.engine.o $(CXX) $(PG) $(LIBS) $(FINAL_FLAGS) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -lm -lz -lsqlite3 -lpthread -tippecanoe-json-tool: jsontool.o jsonpull/jsonpull.o csv.o text.o geojson-loop.o +tippecanoe-json-tool: jsontool.o jsonpull/jsonpull.o csv.o text.o geojson-loop.o usage.o $(CXX) $(PG) $(LIBS) $(FINAL_FLAGS) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -lm -lz -lsqlite3 -lpthread unit: unit.o text.o sort.o mvt.o projection.o clip.o attribute.o jsonpull/jsonpull.o evaluator.o read_json.o clipper2/src/clipper.engine.o $(CXX) $(PG) $(LIBS) $(FINAL_FLAGS) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -lm -lz -lsqlite3 -lpthread -tippecanoe-overzoom: overzoom.o mvt.o clip.o evaluator.o jsonpull/jsonpull.o text.o attribute.o read_json.o projection.o read_json.o clipper2/src/clipper.engine.o +tippecanoe-overzoom: overzoom.o mvt.o clip.o evaluator.o jsonpull/jsonpull.o text.o attribute.o read_json.o projection.o read_json.o usage.o clipper2/src/clipper.engine.o $(CXX) $(PG) $(LIBS) $(FINAL_FLAGS) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -lm -lz -lsqlite3 -lpthread -include $(wildcard *.d) diff --git a/README.md b/README.md index 5c6ffec5..0788e95c 100644 --- a/README.md +++ b/README.md @@ -569,6 +569,9 @@ the same layer, enclose them in an `all` expression so they will all be evaluate * `-Q` or `--no-progress-indicator`: Don't report progress, but still give warnings * `-U` _seconds_ or `--progress-interval=`_seconds_: Don't report progress more often than the specified number of _seconds_. * `-u` or `--json-progress`: like `-quiet` but logs progress as a JSON object. Use in combination with `-U`. + +### Version + * `-v` or `--version`: Report Tippecanoe's version number ### Filters diff --git a/decode.cpp b/decode.cpp index 534e6219..6bee8876 100644 --- a/decode.cpp +++ b/decode.cpp @@ -24,6 +24,7 @@ #include "dirtiles.hpp" #include "pmtiles_file.hpp" #include "errors.hpp" +#include "usage.hpp" int minzoom = 0; int maxzoom = 32; @@ -549,8 +550,42 @@ void decode(char *fname, int z, unsigned x, unsigned y, std::set co } } +static const struct option long_options[] = { + {"Tiles to decode", 0, 0, 0}, + {"minimum-zoom", required_argument, 0, 'Z'}, + {"maximum-zoom", required_argument, 0, 'z'}, + {"layer", required_argument, 0, 'l'}, + + {"Output format", 0, 0, 0}, + {"projection", required_argument, 0, 's'}, + {"fractional-coordinates", no_argument, 0, 'F'}, + {"integer-coordinates", no_argument, 0, 'I'}, + {"tag-layer-and-zoom", no_argument, 0, 'c'}, + {"stats", no_argument, 0, 'S'}, + + {"Filtering the output", 0, 0, 0}, + {"include", required_argument, 0, 'y'}, + {"exclude-metadata-row", required_argument, 0, 'x'}, + + {"Ignoring errors in the input", 0, 0, 0}, + {"force", no_argument, 0, 'f'}, + + {0, 0, 0, 0}, +}; + +// the options above, with the usage message headings removed +static struct option real_long_options[sizeof(long_options) / sizeof(long_options[0])]; + void usage(char **argv) { - fprintf(stderr, "Usage: %s [-s projection] [-Z minzoom] [-z maxzoom] [-l layer ...] file.mbtiles [zoom x y]\n", argv[0]); + static const char *const forms[] = { + "[options] tileset", + "[options] tileset zoom x y", + NULL, + }; + + print_usage(stderr, argv[0], forms, long_options, NULL); + fprintf(stderr, "\nThe tileset can be an .mbtiles or .pmtiles file or a directory of tiles,\n"); + fprintf(stderr, "or, if zoom/x/y is specified, a single .pbf tile.\n"); exit(EXIT_ARGS); } @@ -564,33 +599,10 @@ int main(int argc, char **argv) { std::set exclude_meta; int coordinate_mode = 0; - struct option long_options[] = { - {"projection", required_argument, 0, 's'}, - {"fractional-coordinates", no_argument, 0, 'F'}, - {"integer-coordinates", no_argument, 0, 'I'}, - {"maximum-zoom", required_argument, 0, 'z'}, - {"minimum-zoom", required_argument, 0, 'Z'}, - {"layer", required_argument, 0, 'l'}, - {"tag-layer-and-zoom", no_argument, 0, 'c'}, - {"stats", no_argument, 0, 'S'}, - {"force", no_argument, 0, 'f'}, - {"exclude-metadata-row", required_argument, 0, 'x'}, - {"include", required_argument, 0, 'y'}, - {0, 0, 0, 0}, - }; + strip_usage_headings(long_options, real_long_options); + std::string getopt_str = getopt_string(real_long_options); - std::string getopt_str; - for (size_t lo = 0; long_options[lo].name != NULL; lo++) { - if (long_options[lo].val > ' ') { - getopt_str.push_back(long_options[lo].val); - - if (long_options[lo].has_arg == required_argument) { - getopt_str.push_back(':'); - } - } - } - - while ((i = getopt_long(argc, argv, getopt_str.c_str(), long_options, NULL)) != -1) { + while ((i = getopt_long(argc, argv, getopt_str.c_str(), real_long_options, NULL)) != -1) { switch (i) { case 0: break; diff --git a/enumerate.cpp b/enumerate.cpp index 691e06ce..40ff506d 100644 --- a/enumerate.cpp +++ b/enumerate.cpp @@ -1,8 +1,11 @@ #include #include #include +#include +#include #include #include "errors.hpp" +#include "usage.hpp" void enumerate(char *fname) { sqlite3 *db; @@ -48,8 +51,20 @@ void enumerate(char *fname) { } } +// there are no options, but the table is still what the usage message +// and the getopt string are derived from, so that they will keep up +// with any options that are added later +static const struct option long_options[] = { + {0, 0, 0, 0}, +}; + void usage(char **argv) { - fprintf(stderr, "Usage: %s file.mbtiles ...\n", argv[0]); + static const char *const forms[] = { + "file.mbtiles ...", + NULL, + }; + + print_usage(stderr, argv[0], forms, long_options, NULL); exit(EXIT_ARGS); } @@ -58,7 +73,9 @@ int main(int argc, char **argv) { // extern char *optarg; int i; - while ((i = getopt(argc, argv, "")) != -1) { + std::string getopt_str = getopt_string(long_options); + + while ((i = getopt_long(argc, argv, getopt_str.c_str(), long_options, NULL)) != -1) { usage(argv); } diff --git a/jsontool.cpp b/jsontool.cpp index bb5341b8..5c4e0abe 100644 --- a/jsontool.cpp +++ b/jsontool.cpp @@ -13,6 +13,7 @@ #include "geojson-loop.hpp" #include "milo/dtoa_milo.h" #include "errors.hpp" +#include "usage.hpp" int fail = EXIT_SUCCESS; bool wrap = false; @@ -407,34 +408,45 @@ void process(FILE *fp, const char *fname) { json_end(jp); } +static const struct option long_options[] = { + {"Wrapping the output", 0, 0, 0}, + {"wrap", no_argument, 0, 'w'}, + + {"Sorting and joining", 0, 0, 0}, + {"extract", required_argument, 0, 'e'}, + {"csv", required_argument, 0, 'c'}, + {"empty-csv-columns-are-null", no_argument, &pe, 1}, + + {"", 0, 0, 0}, + {"prevent", required_argument, 0, 'p'}, + + {0, 0, 0, 0}, +}; + +// the options above, with the usage message headings removed +static struct option real_long_options[sizeof(long_options) / sizeof(long_options[0])]; + +void usage(char **argv) { + static const char *const forms[] = { + "[options] [file.json ...]", + NULL, + }; + + print_usage(stderr, argv[0], forms, long_options, NULL); + fprintf(stderr, "\nIf no files are named, the JSON is read from the standard input.\n"); + exit(EXIT_ARGS); +} + int main(int argc, char **argv) { const char *csv = NULL; - struct option long_options[] = { - {"wrap", no_argument, 0, 'w'}, - {"extract", required_argument, 0, 'e'}, - {"csv", required_argument, 0, 'c'}, - {"empty-csv-columns-are-null", no_argument, &pe, 1}, - {"prevent", required_argument, 0, 'p'}, - - {0, 0, 0, 0}, - }; - - std::string getopt_str; - for (size_t lo = 0; long_options[lo].name != NULL; lo++) { - if (long_options[lo].val > ' ') { - getopt_str.push_back(long_options[lo].val); - - if (long_options[lo].has_arg == required_argument) { - getopt_str.push_back(':'); - } - } - } + strip_usage_headings(long_options, real_long_options); + std::string getopt_str = getopt_string(real_long_options); extern int optind; int i; - while ((i = getopt_long(argc, argv, getopt_str.c_str(), long_options, NULL)) != -1) { + while ((i = getopt_long(argc, argv, getopt_str.c_str(), real_long_options, NULL)) != -1) { switch (i) { case 0: break; @@ -461,8 +473,7 @@ int main(int argc, char **argv) { break; default: - fprintf(stderr, "Unexpected option -%c\n", i); - exit(EXIT_ARGS); + usage(argv); } } diff --git a/main.cpp b/main.cpp index 1f8b8f83..3fb5467a 100644 --- a/main.cpp +++ b/main.cpp @@ -57,6 +57,7 @@ #include "geometry.hpp" #include "serial.hpp" #include "options.hpp" +#include "usage.hpp" #include "mvt.hpp" #include "dirtiles.hpp" #include "evaluator.hpp" @@ -2965,11 +2966,206 @@ void parse_json_source(const char *arg, struct source &src) { json_end(jp); } +static const struct option long_options_orig[] = { + {"Output tileset", 0, 0, 0}, + {"output", required_argument, 0, 'o'}, + {"output-to-directory", required_argument, 0, 'e'}, + {"force", no_argument, 0, 'f'}, + {"allow-existing", no_argument, 0, 'F'}, + + {"Tileset description and attribution", 0, 0, 0}, + {"name", required_argument, 0, 'n'}, + {"attribution", required_argument, 0, 'A'}, + {"description", required_argument, 0, 'N'}, + + {"Input files and layer names", 0, 0, 0}, + {"layer", required_argument, 0, 'l'}, + {"named-layer", required_argument, 0, 'L'}, + + {"Parallel processing of input", 0, 0, 0}, + {"read-parallel", no_argument, 0, 'P'}, + + {"Projection of input", 0, 0, 0}, + {"projection", required_argument, 0, 's'}, + + {"Zoom levels", 0, 0, 0}, + {"maximum-zoom", required_argument, 0, 'z'}, + {"minimum-zoom", required_argument, 0, 'Z'}, + {"smallest-maximum-zoom-guess", required_argument, 0, '~'}, + {"extend-zooms-if-still-dropping", no_argument, &additional[A_EXTEND_ZOOMS], 1}, + {"extend-zooms-if-still-dropping-maximum", required_argument, 0, '~'}, + {"generate-variable-depth-tile-pyramid", no_argument, &additional[A_VARIABLE_DEPTH_PYRAMID], 1}, + {"one-tile", required_argument, 0, 'R'}, + + {"Tile resolution", 0, 0, 0}, + {"full-detail", required_argument, 0, 'd'}, + {"low-detail", required_argument, 0, 'D'}, + {"minimum-detail", required_argument, 0, 'm'}, + {"extra-detail", required_argument, 0, '~'}, + + {"Filtering feature attributes", 0, 0, 0}, + {"exclude", required_argument, 0, 'x'}, + {"include", required_argument, 0, 'y'}, + {"exclude-all", no_argument, 0, 'X'}, + + {"Modifying feature attributes", 0, 0, 0}, + {"attribute-type", required_argument, 0, 'T'}, + {"attribute-description", required_argument, 0, 'Y'}, + {"accumulate-attribute", required_argument, 0, 'E'}, + {"empty-csv-columns-are-null", no_argument, &prevent[P_EMPTY_CSV_COLUMNS], 1}, + {"convert-stringified-ids-to-numbers", no_argument, &additional[A_CONVERT_NUMERIC_IDS], 1}, + {"use-attribute-for-id", required_argument, 0, '~'}, + {"single-precision", no_argument, &prevent[P_SINGLE_PRECISION], 1}, + {"set-attribute", required_argument, 0, '~'}, + {"maximum-string-attribute-length", required_argument, 0, '~'}, + + {"Filtering features by attributes", 0, 0, 0}, + {"feature-filter-file", required_argument, 0, 'J'}, + {"feature-filter", required_argument, 0, 'j'}, + {"unidecode-data", required_argument, 0, '~'}, + + {"Dropping a fixed fraction of features by zoom level", 0, 0, 0}, + {"drop-rate", required_argument, 0, 'r'}, + {"retain-points-multiplier", required_argument, 0, '~'}, + {"base-zoom", required_argument, 0, 'B'}, + {"drop-denser", required_argument, 0, '~'}, + {"limit-base-zoom-to-maximum-zoom", no_argument, &prevent[P_BASEZOOM_ABOVE_MAXZOOM], 1}, + {"drop-lines", no_argument, &additional[A_LINE_DROP], 1}, + {"drop-polygons", no_argument, &additional[A_POLYGON_DROP], 1}, + {"cluster-distance", required_argument, 0, 'K'}, + {"cluster-maxzoom", required_argument, 0, 'k'}, + {"preserve-point-density-threshold", required_argument, 0, '~'}, + {"preserve-multiplier-density-threshold", required_argument, 0, '~'}, + + {"Dropping or merging a fraction of features to keep under tile size limits", 0, 0, 0}, + {"drop-densest-as-needed", no_argument, &additional[A_DROP_DENSEST_AS_NEEDED], 1}, + {"drop-fraction-as-needed", no_argument, &additional[A_DROP_FRACTION_AS_NEEDED], 1}, + {"drop-smallest-as-needed", no_argument, &additional[A_DROP_SMALLEST_AS_NEEDED], 1}, + {"drop-by-attribute-as-needed", required_argument, 0, '~'}, + {"drop-by-attribute-order", required_argument, 0, '~'}, + {"coalesce-densest-as-needed", no_argument, &additional[A_COALESCE_DENSEST_AS_NEEDED], 1}, + {"coalesce-fraction-as-needed", no_argument, &additional[A_COALESCE_FRACTION_AS_NEEDED], 1}, + {"coalesce-smallest-as-needed", no_argument, &additional[A_COALESCE_SMALLEST_AS_NEEDED], 1}, + {"force-feature-limit", no_argument, &prevent[P_DYNAMIC_DROP], 1}, + {"cluster-densest-as-needed", no_argument, &additional[A_CLUSTER_DENSEST_AS_NEEDED], 1}, + {"keep-point-cluster-position", no_argument, &additional[A_KEEP_POINT_CLUSTER_POSITION], 1}, + + {"Dropping tightly overlapping features", 0, 0, 0}, + {"gamma", required_argument, 0, 'g'}, + {"increase-gamma-as-needed", no_argument, &additional[A_INCREASE_GAMMA_AS_NEEDED], 1}, + + {"Line and polygon simplification", 0, 0, 0}, + {"simplification", required_argument, 0, 'S'}, + {"no-line-simplification", no_argument, &prevent[P_SIMPLIFY], 1}, + {"simplify-only-low-zooms", no_argument, &prevent[P_SIMPLIFY_LOW], 1}, + {"simplification-at-maximum-zoom", required_argument, 0, '~'}, + {"no-tiny-polygon-reduction", no_argument, &prevent[P_TINY_POLYGON_REDUCTION], 1}, + {"no-tiny-polygon-reduction-at-maximum-zoom", no_argument, &prevent[P_TINY_POLYGON_REDUCTION_AT_MAXZOOM], 1}, + {"tiny-polygon-size", required_argument, 0, '~'}, + {"no-simplification-of-shared-nodes", no_argument, &prevent[P_SIMPLIFY_SHARED_NODES], 1}, + {"visvalingam", no_argument, &additional[A_VISVALINGAM], 1}, + + {"Attempts to improve shared polygon boundaries", 0, 0, 0}, + {"detect-shared-borders", no_argument, &additional[A_DETECT_SHARED_BORDERS], 1}, + {"grid-low-zooms", no_argument, &additional[A_GRID_LOW_ZOOMS], 1}, + + {"Controlling clipping to tile boundaries", 0, 0, 0}, + {"buffer", required_argument, 0, 'b'}, + {"no-clipping", no_argument, &prevent[P_CLIPPING], 1}, + {"no-duplication", no_argument, &prevent[P_DUPLICATION], 1}, + + {"Reordering features within each tile", 0, 0, 0}, + {"preserve-input-order", no_argument, &prevent[P_INPUT_ORDER], 1}, + {"reorder", no_argument, &additional[A_REORDER], 1}, + {"coalesce", no_argument, &additional[A_COALESCE], 1}, + {"reverse", no_argument, &additional[A_REVERSE], 1}, + {"hilbert", no_argument, &additional[A_HILBERT], 1}, + {"order-by", required_argument, 0, '~'}, + {"order-descending-by", required_argument, 0, '~'}, + {"order-smallest-first", no_argument, 0, '~'}, + {"order-largest-first", no_argument, 0, '~'}, + + {"Adding calculated attributes", 0, 0, 0}, + {"calculate-feature-density", no_argument, &additional[A_CALCULATE_FEATURE_DENSITY], 1}, + {"generate-ids", no_argument, &additional[A_GENERATE_IDS], 1}, + {"calculate-feature-index", no_argument, &additional[A_CALCULATE_INDEX], 1}, + + {"Trying to correct bad source geometry", 0, 0, 0}, + {"detect-longitude-wraparound", no_argument, &additional[A_DETECT_WRAPAROUND], 1}, + {"use-source-polygon-winding", no_argument, &prevent[P_USE_SOURCE_POLYGON_WINDING], 1}, + {"reverse-source-polygon-winding", no_argument, &prevent[P_REVERSE_SOURCE_POLYGON_WINDING], 1}, + {"clip-bounding-box", required_argument, 0, '~'}, + {"convert-polygons-to-label-points", no_argument, &additional[A_GENERATE_POLYGON_LABEL_POINTS], 1}, + + {"Filtering tile contents", 0, 0, 0}, + {"prefilter", required_argument, 0, 'C'}, + {"postfilter", required_argument, 0, 'c'}, + + {"Setting or disabling tile size limits", 0, 0, 0}, + {"maximum-tile-bytes", required_argument, 0, 'M'}, + {"maximum-tile-features", required_argument, 0, 'O'}, + {"limit-tile-feature-count", required_argument, 0, '~'}, + {"limit-tile-feature-count-at-maximum-zoom", required_argument, 0, '~'}, + {"no-feature-limit", no_argument, &prevent[P_FEATURE_LIMIT], 1}, + {"no-tile-size-limit", no_argument, &prevent[P_KILOBYTE_LIMIT], 1}, + {"no-tile-compression", no_argument, &prevent[P_TILE_COMPRESSION], 1}, + {"no-tile-stats", no_argument, &prevent[P_TILE_STATS], 1}, + {"tile-stats-attributes-limit", required_argument, 0, '~'}, + {"tile-stats-sample-values-limit", required_argument, 0, '~'}, + {"tile-stats-values-limit", required_argument, 0, '~'}, + + {"Temporary storage", 0, 0, 0}, + {"temporary-directory", required_argument, 0, 't'}, + + {"Progress indicator", 0, 0, 0}, + {"quiet", no_argument, 0, 'q'}, + {"no-progress-indicator", no_argument, 0, 'Q'}, + {"progress-interval", required_argument, 0, 'U'}, + {"json-progress", no_argument, 0, 'u'}, + + {"Version", 0, 0, 0}, + {"version", no_argument, 0, 'v'}, + + {"", 0, 0, 0}, + {"prevent", required_argument, 0, 'p'}, + {"additional", required_argument, 0, 'a'}, + {"check-polygons", no_argument, &additional[A_DEBUG_POLYGON], 1}, + {"no-polygon-splitting", no_argument, &prevent[P_POLYGON_SPLIT], 1}, + {"prefer-radix-sort", no_argument, &additional[A_PREFER_RADIX_SORT], 1}, + {"help", no_argument, 0, 'H'}, + + {0, 0, 0, 0}, +}; + +// the options above, with the usage message headings removed +static struct option long_options[sizeof(long_options_orig) / sizeof(long_options_orig[0])]; + +void usage(char **argv, int status) { + static const char *const forms[] = { + "[options] [file.json ...]", + NULL, + }; + static const struct usage_required_option required[] = { + {"output", "output.mbtiles", 1}, + {"output-to-directory", "directory", 1}, + {NULL, NULL, 0}, + }; + + print_usage(stderr, argv[0], forms, long_options_orig, required); + exit(status); +} + int main(int argc, char **argv) { #ifdef MTRACE mtrace(); #endif + if (argc == 1) { + // with no arguments at all, there is nothing to complain about + // specifically, so say in general what the arguments should be + usage(argv, EXIT_ARGS); + } + av = argv; init_cpus(); @@ -3017,197 +3213,9 @@ int main(int argc, char **argv) { additional[i] = 0; } - static struct option long_options_orig[] = { - {"Output tileset", 0, 0, 0}, - {"output", required_argument, 0, 'o'}, - {"output-to-directory", required_argument, 0, 'e'}, - {"force", no_argument, 0, 'f'}, - {"allow-existing", no_argument, 0, 'F'}, - - {"Tileset description and attribution", 0, 0, 0}, - {"name", required_argument, 0, 'n'}, - {"attribution", required_argument, 0, 'A'}, - {"description", required_argument, 0, 'N'}, - - {"Input files and layer names", 0, 0, 0}, - {"layer", required_argument, 0, 'l'}, - {"named-layer", required_argument, 0, 'L'}, - - {"Parallel processing of input", 0, 0, 0}, - {"read-parallel", no_argument, 0, 'P'}, - - {"Projection of input", 0, 0, 0}, - {"projection", required_argument, 0, 's'}, - - {"Zoom levels", 0, 0, 0}, - {"maximum-zoom", required_argument, 0, 'z'}, - {"minimum-zoom", required_argument, 0, 'Z'}, - {"smallest-maximum-zoom-guess", required_argument, 0, '~'}, - {"extend-zooms-if-still-dropping", no_argument, &additional[A_EXTEND_ZOOMS], 1}, - {"extend-zooms-if-still-dropping-maximum", required_argument, 0, '~'}, - {"generate-variable-depth-tile-pyramid", no_argument, &additional[A_VARIABLE_DEPTH_PYRAMID], 1}, - {"one-tile", required_argument, 0, 'R'}, - - {"Tile resolution", 0, 0, 0}, - {"full-detail", required_argument, 0, 'd'}, - {"low-detail", required_argument, 0, 'D'}, - {"minimum-detail", required_argument, 0, 'm'}, - {"extra-detail", required_argument, 0, '~'}, - - {"Filtering feature attributes", 0, 0, 0}, - {"exclude", required_argument, 0, 'x'}, - {"include", required_argument, 0, 'y'}, - {"exclude-all", no_argument, 0, 'X'}, - - {"Modifying feature attributes", 0, 0, 0}, - {"attribute-type", required_argument, 0, 'T'}, - {"attribute-description", required_argument, 0, 'Y'}, - {"accumulate-attribute", required_argument, 0, 'E'}, - {"empty-csv-columns-are-null", no_argument, &prevent[P_EMPTY_CSV_COLUMNS], 1}, - {"convert-stringified-ids-to-numbers", no_argument, &additional[A_CONVERT_NUMERIC_IDS], 1}, - {"use-attribute-for-id", required_argument, 0, '~'}, - {"single-precision", no_argument, &prevent[P_SINGLE_PRECISION], 1}, - {"set-attribute", required_argument, 0, '~'}, - {"maximum-string-attribute-length", required_argument, 0, '~'}, - - {"Filtering features by attributes", 0, 0, 0}, - {"feature-filter-file", required_argument, 0, 'J'}, - {"feature-filter", required_argument, 0, 'j'}, - {"unidecode-data", required_argument, 0, '~'}, - - {"Dropping a fixed fraction of features by zoom level", 0, 0, 0}, - {"drop-rate", required_argument, 0, 'r'}, - {"retain-points-multiplier", required_argument, 0, '~'}, - {"base-zoom", required_argument, 0, 'B'}, - {"drop-denser", required_argument, 0, '~'}, - {"limit-base-zoom-to-maximum-zoom", no_argument, &prevent[P_BASEZOOM_ABOVE_MAXZOOM], 1}, - {"drop-lines", no_argument, &additional[A_LINE_DROP], 1}, - {"drop-polygons", no_argument, &additional[A_POLYGON_DROP], 1}, - {"cluster-distance", required_argument, 0, 'K'}, - {"cluster-maxzoom", required_argument, 0, 'k'}, - {"preserve-point-density-threshold", required_argument, 0, '~'}, - {"preserve-multiplier-density-threshold", required_argument, 0, '~'}, - - {"Dropping or merging a fraction of features to keep under tile size limits", 0, 0, 0}, - {"drop-densest-as-needed", no_argument, &additional[A_DROP_DENSEST_AS_NEEDED], 1}, - {"drop-fraction-as-needed", no_argument, &additional[A_DROP_FRACTION_AS_NEEDED], 1}, - {"drop-smallest-as-needed", no_argument, &additional[A_DROP_SMALLEST_AS_NEEDED], 1}, - {"drop-by-attribute-as-needed", required_argument, 0, '~'}, - {"drop-by-attribute-order", required_argument, 0, '~'}, - {"coalesce-densest-as-needed", no_argument, &additional[A_COALESCE_DENSEST_AS_NEEDED], 1}, - {"coalesce-fraction-as-needed", no_argument, &additional[A_COALESCE_FRACTION_AS_NEEDED], 1}, - {"coalesce-smallest-as-needed", no_argument, &additional[A_COALESCE_SMALLEST_AS_NEEDED], 1}, - {"force-feature-limit", no_argument, &prevent[P_DYNAMIC_DROP], 1}, - {"cluster-densest-as-needed", no_argument, &additional[A_CLUSTER_DENSEST_AS_NEEDED], 1}, - {"keep-point-cluster-position", no_argument, &additional[A_KEEP_POINT_CLUSTER_POSITION], 1}, - - {"Dropping tightly overlapping features", 0, 0, 0}, - {"gamma", required_argument, 0, 'g'}, - {"increase-gamma-as-needed", no_argument, &additional[A_INCREASE_GAMMA_AS_NEEDED], 1}, - - {"Line and polygon simplification", 0, 0, 0}, - {"simplification", required_argument, 0, 'S'}, - {"no-line-simplification", no_argument, &prevent[P_SIMPLIFY], 1}, - {"simplify-only-low-zooms", no_argument, &prevent[P_SIMPLIFY_LOW], 1}, - {"simplification-at-maximum-zoom", required_argument, 0, '~'}, - {"no-tiny-polygon-reduction", no_argument, &prevent[P_TINY_POLYGON_REDUCTION], 1}, - {"no-tiny-polygon-reduction-at-maximum-zoom", no_argument, &prevent[P_TINY_POLYGON_REDUCTION_AT_MAXZOOM], 1}, - {"tiny-polygon-size", required_argument, 0, '~'}, - {"no-simplification-of-shared-nodes", no_argument, &prevent[P_SIMPLIFY_SHARED_NODES], 1}, - {"visvalingam", no_argument, &additional[A_VISVALINGAM], 1}, - - {"Attempts to improve shared polygon boundaries", 0, 0, 0}, - {"detect-shared-borders", no_argument, &additional[A_DETECT_SHARED_BORDERS], 1}, - {"grid-low-zooms", no_argument, &additional[A_GRID_LOW_ZOOMS], 1}, - - {"Controlling clipping to tile boundaries", 0, 0, 0}, - {"buffer", required_argument, 0, 'b'}, - {"no-clipping", no_argument, &prevent[P_CLIPPING], 1}, - {"no-duplication", no_argument, &prevent[P_DUPLICATION], 1}, - - {"Reordering features within each tile", 0, 0, 0}, - {"preserve-input-order", no_argument, &prevent[P_INPUT_ORDER], 1}, - {"reorder", no_argument, &additional[A_REORDER], 1}, - {"coalesce", no_argument, &additional[A_COALESCE], 1}, - {"reverse", no_argument, &additional[A_REVERSE], 1}, - {"hilbert", no_argument, &additional[A_HILBERT], 1}, - {"order-by", required_argument, 0, '~'}, - {"order-descending-by", required_argument, 0, '~'}, - {"order-smallest-first", no_argument, 0, '~'}, - {"order-largest-first", no_argument, 0, '~'}, - - {"Adding calculated attributes", 0, 0, 0}, - {"calculate-feature-density", no_argument, &additional[A_CALCULATE_FEATURE_DENSITY], 1}, - {"generate-ids", no_argument, &additional[A_GENERATE_IDS], 1}, - {"calculate-feature-index", no_argument, &additional[A_CALCULATE_INDEX], 1}, - - {"Trying to correct bad source geometry", 0, 0, 0}, - {"detect-longitude-wraparound", no_argument, &additional[A_DETECT_WRAPAROUND], 1}, - {"use-source-polygon-winding", no_argument, &prevent[P_USE_SOURCE_POLYGON_WINDING], 1}, - {"reverse-source-polygon-winding", no_argument, &prevent[P_REVERSE_SOURCE_POLYGON_WINDING], 1}, - {"clip-bounding-box", required_argument, 0, '~'}, - {"convert-polygons-to-label-points", no_argument, &additional[A_GENERATE_POLYGON_LABEL_POINTS], 1}, - - {"Filtering tile contents", 0, 0, 0}, - {"prefilter", required_argument, 0, 'C'}, - {"postfilter", required_argument, 0, 'c'}, - - {"Setting or disabling tile size limits", 0, 0, 0}, - {"maximum-tile-bytes", required_argument, 0, 'M'}, - {"maximum-tile-features", required_argument, 0, 'O'}, - {"limit-tile-feature-count", required_argument, 0, '~'}, - {"limit-tile-feature-count-at-maximum-zoom", required_argument, 0, '~'}, - {"no-feature-limit", no_argument, &prevent[P_FEATURE_LIMIT], 1}, - {"no-tile-size-limit", no_argument, &prevent[P_KILOBYTE_LIMIT], 1}, - {"no-tile-compression", no_argument, &prevent[P_TILE_COMPRESSION], 1}, - {"no-tile-stats", no_argument, &prevent[P_TILE_STATS], 1}, - {"tile-stats-attributes-limit", required_argument, 0, '~'}, - {"tile-stats-sample-values-limit", required_argument, 0, '~'}, - {"tile-stats-values-limit", required_argument, 0, '~'}, - - {"Temporary storage", 0, 0, 0}, - {"temporary-directory", required_argument, 0, 't'}, - - {"Progress indicator", 0, 0, 0}, - {"quiet", no_argument, 0, 'q'}, - {"no-progress-indicator", no_argument, 0, 'Q'}, - {"progress-interval", required_argument, 0, 'U'}, - {"json-progress", no_argument, 0, 'u'}, - {"version", no_argument, 0, 'v'}, - - {"", 0, 0, 0}, - {"prevent", required_argument, 0, 'p'}, - {"additional", required_argument, 0, 'a'}, - {"check-polygons", no_argument, &additional[A_DEBUG_POLYGON], 1}, - {"no-polygon-splitting", no_argument, &prevent[P_POLYGON_SPLIT], 1}, - {"prefer-radix-sort", no_argument, &additional[A_PREFER_RADIX_SORT], 1}, - {"help", no_argument, 0, 'H'}, - - {0, 0, 0, 0}, - }; - - static struct option long_options[sizeof(long_options_orig) / sizeof(long_options_orig[0])]; - static char getopt_str[sizeof(long_options_orig) / sizeof(long_options_orig[0]) * 2 + 1]; + strip_usage_headings(long_options_orig, long_options); { - size_t out = 0; - size_t cout = 0; - for (size_t lo = 0; long_options_orig[lo].name != NULL; lo++) { - if (long_options_orig[lo].val != 0) { - long_options[out++] = long_options_orig[lo]; - - if (long_options_orig[lo].val > ' ') { - getopt_str[cout++] = long_options_orig[lo].val; - - if (long_options_orig[lo].has_arg == required_argument) { - getopt_str[cout++] = ':'; - } - } - } - } - long_options[out] = {0, 0, 0, 0}; - getopt_str[cout] = '\0'; - for (size_t lo = 0; long_options[lo].name != NULL; lo++) { if (long_options[lo].flag != NULL) { if (*long_options[lo].flag != 0) { @@ -3226,9 +3234,10 @@ int main(int argc, char **argv) { } std::string commandline = format_commandline(argc, argv); + std::string getopt_str = getopt_string(long_options); int option_index = 0; - while ((i = getopt_long(argc, argv, getopt_str, long_options, &option_index)) != -1) { + while ((i = getopt_long(argc, argv, getopt_str.c_str(), long_options, &option_index)) != -1) { switch (i) { case 0: break; @@ -3652,43 +3661,11 @@ int main(int argc, char **argv) { set_attribute_accum(attribute_accum, optarg, argv); break; - default: { + default: if (i != 'H' && i != '?') { fprintf(stderr, "Unknown option -%c\n", i); } - int width = 7 + strlen(argv[0]); - fprintf(stderr, "Usage: %s [options] [file.json ...]", argv[0]); - for (size_t lo = 0; long_options_orig[lo].name != NULL && strlen(long_options_orig[lo].name) > 0; lo++) { - if (long_options_orig[lo].val == 0) { - fprintf(stderr, "\n %s\n ", long_options_orig[lo].name); - width = 8; - continue; - } - if (width + strlen(long_options_orig[lo].name) + 9 >= 80) { - fprintf(stderr, "\n "); - width = 8; - } - width += strlen(long_options_orig[lo].name) + 9; - if (strcmp(long_options_orig[lo].name, "output") == 0) { - fprintf(stderr, " --%s=output.mbtiles", long_options_orig[lo].name); - width += 9; - } else if (long_options_orig[lo].has_arg) { - fprintf(stderr, " [--%s=...]", long_options_orig[lo].name); - } else { - fprintf(stderr, " [--%s]", long_options_orig[lo].name); - } - } - if (width + 16 >= 80) { - fprintf(stderr, "\n "); - width = 8; - } - fprintf(stderr, "\n"); - if (i == 'H') { - exit(EXIT_SUCCESS); - } else { - exit(EXIT_ARGS); - } - } + usage(argv, i == 'H' ? EXIT_SUCCESS : EXIT_ARGS); } } diff --git a/overzoom.cpp b/overzoom.cpp index cdef1dd8..4347bd4f 100644 --- a/overzoom.cpp +++ b/overzoom.cpp @@ -11,6 +11,7 @@ #include "text.hpp" #include "read_json.hpp" #include "projection.hpp" +#include "usage.hpp" extern char *optarg; extern int optind; @@ -30,11 +31,58 @@ std::set keep; std::set exclude; std::vector exclude_prefix; +static const struct option long_options[] = { + {"Output tile", 0, 0, 0}, + {"output", required_argument, 0, 'o'}, + {"source-tile", required_argument, 0, 't'}, + {"no-tile-compression", no_argument, 0, 'd' & 0x1F}, + + {"Tile resolution", 0, 0, 0}, + {"full-detail", required_argument, 0, 'd'}, + {"buffer", required_argument, 0, 'b'}, + + {"Filtering feature attributes", 0, 0, 0}, + {"include", required_argument, 0, 'y'}, + {"exclude", required_argument, 0, 'x'}, + {"exclude-prefix", required_argument, 0, 'x' & 0x1F}, + + {"Modifying feature attributes", 0, 0, 0}, + {"accumulate-attribute", required_argument, 0, 'E'}, + {"unidecode-data", required_argument, 0, 'u' & 0x1F}, + + {"Filtering features", 0, 0, 0}, + {"feature-filter", required_argument, 0, 'j'}, + {"feature-filter-file", required_argument, 0, 'J'}, + {"filter-points-multiplier", no_argument, 0, 'm'}, + {"deduplicate-by-id", no_argument, 0, 'i' & 0x1F}, + + {"Line and polygon simplification", 0, 0, 0}, + {"line-simplification", required_argument, 0, 'S'}, + {"tiny-polygon-size", required_argument, 0, 's' & 0x1F}, + + {"Reordering features within the tile", 0, 0, 0}, + {"preserve-input-order", no_argument, 0, 'o' & 0x1F}, + + {0, 0, 0, 0}, +}; + +// the options above, with the usage message headings removed +static struct option real_long_options[sizeof(long_options) / sizeof(long_options[0])]; + void usage(char **argv) { - fprintf(stderr, "Usage: %s -o newtile.pbf.gz tile.pbf.gz oz/ox/oy nz/nx/ny\n", argv[0]); - fprintf(stderr, "to create tile nz/nx/ny from tile oz/ox/oy\n"); - fprintf(stderr, "Usage: %s -o newtile.pbf.gz -t nz/nx/ny tile.pbf.gz oz/ox/oy tile2.pbf.gz oz2/ox2/oy2\n", argv[0]); - fprintf(stderr, "to create tile nz/nx/ny from tiles oz/ox/oy and oz2/ox2/oy2\n"); + static const char *const forms[] = { + "[options] tile.pbf.gz oz/ox/oy nz/nx/ny", + "[options] --source-tile=nz/nx/ny tile.pbf.gz oz/ox/oy ...", + NULL, + }; + static const struct usage_required_option required[] = { + {"output", "newtile.pbf.gz", 0}, + {NULL, NULL, 0}, + }; + + print_usage(stderr, argv[0], forms, long_options, required); + fprintf(stderr, "\nThe tile nz/nx/ny is created from the tile or tiles oz/ox/oy that contain it.\n"); + fprintf(stderr, "In the second form, each source tile is named by a file name and a z/x/y pair.\n"); exit(EXIT_FAILURE); } @@ -67,41 +115,11 @@ int main(int argc, char **argv) { std::vector sources; - struct option long_options[] = { - {"include", required_argument, 0, 'y'}, - {"exclude", required_argument, 0, 'x'}, - {"exclude-prefix", required_argument, 0, 'x' & 0x1F}, - {"full-detail", required_argument, 0, 'd'}, - {"buffer", required_argument, 0, 'b'}, - {"output", required_argument, 0, 'o'}, - {"filter-points-multiplier", no_argument, 0, 'm'}, - {"feature-filter", required_argument, 0, 'j'}, - {"feature-filter-file", required_argument, 0, 'J'}, - {"preserve-input-order", no_argument, 0, 'o' & 0x1F}, - {"accumulate-attribute", required_argument, 0, 'E'}, - {"unidecode-data", required_argument, 0, 'u' & 0x1F}, - {"line-simplification", required_argument, 0, 'S'}, - {"tiny-polygon-size", required_argument, 0, 's' & 0x1F}, - {"source-tile", required_argument, 0, 't'}, - {"no-tile-compression", no_argument, 0, 'd' & 0x1F}, - {"deduplicate-by-id", no_argument, 0, 'i' & 0x1F}, - - {0, 0, 0, 0}, - }; - - std::string getopt_str; - for (size_t lo = 0; long_options[lo].name != NULL; lo++) { - if (long_options[lo].val > ' ') { - getopt_str.push_back(long_options[lo].val); - - if (long_options[lo].has_arg == required_argument) { - getopt_str.push_back(':'); - } - } - } + strip_usage_headings(long_options, real_long_options); + std::string getopt_str = getopt_string(real_long_options); int option_index = 0; - while ((i = getopt_long(argc, argv, getopt_str.c_str(), long_options, &option_index)) != -1) { + while ((i = getopt_long(argc, argv, getopt_str.c_str(), real_long_options, &option_index)) != -1) { switch (i) { case 'y': keep.insert(optarg); @@ -181,6 +199,11 @@ int main(int argc, char **argv) { std::vector its; int nz, nx, ny; + if (outfile == NULL) { + fprintf(stderr, "%s: must specify -o newtile.pbf.gz\n", argv[0]); + usage(argv); + } + if (outtile == NULL) { // single input if (argc - optind != 3) { fprintf(stderr, "Wrong number of arguments\n"); diff --git a/tile-join.cpp b/tile-join.cpp index 1b057577..581a1574 100644 --- a/tile-join.cpp +++ b/tile-join.cpp @@ -44,6 +44,7 @@ #include "geometry.hpp" #include "thread.hpp" #include "platform.hpp" +#include "usage.hpp" int pk = false; int pC = false; @@ -1253,8 +1254,83 @@ void decode(struct tileset_reader *readers, std::map ' ') { - getopt_str.push_back(long_options[lo].val); - - if (long_options[lo].has_arg == required_argument) { - getopt_str.push_back(':'); - } - } - } + strip_usage_headings(long_options, real_long_options); + std::string getopt_str = getopt_string(real_long_options); extern int optind; extern char *optarg; @@ -1357,7 +1384,7 @@ int main(int argc, char **argv) { std::string commandline = format_commandline(argc, argv); int option_index = 0; - while ((i = getopt_long(argc, argv, getopt_str.c_str(), long_options, &option_index)) != -1) { + while ((i = getopt_long(argc, argv, getopt_str.c_str(), real_long_options, &option_index)) != -1) { switch (i) { case 0: break; @@ -1502,7 +1529,7 @@ int main(int argc, char **argv) { break; case '~': { - const char *opt = long_options[option_index].name; + const char *opt = real_long_options[option_index].name; if (strcmp(opt, "tile-stats-attributes-limit") == 0) { max_tilestats_attributes = atoi(optarg); } else if (strcmp(opt, "tile-stats-sample-values-limit") == 0) { diff --git a/usage.cpp b/usage.cpp new file mode 100644 index 00000000..0f36f282 --- /dev/null +++ b/usage.cpp @@ -0,0 +1,133 @@ +#include +#include +#include +#include "usage.hpp" + +// Options are wrapped to fit within this many columns +#define USAGE_WIDTH 80 + +// The indentation of the continuation lines of the option list +#define USAGE_INDENT 8 + +std::string getopt_string(const struct option *long_options) { + std::string getopt_str; + + for (size_t lo = 0; long_options[lo].name != NULL; lo++) { + if (long_options[lo].val > ' ') { + getopt_str.push_back(long_options[lo].val); + + if (long_options[lo].has_arg == required_argument) { + getopt_str.push_back(':'); + } + } + } + + return getopt_str; +} + +void strip_usage_headings(const struct option *long_options, struct option *real_long_options) { + size_t out = 0; + + for (size_t lo = 0; long_options[lo].name != NULL; lo++) { + if (long_options[lo].val != 0) { + real_long_options[out++] = long_options[lo]; + } + } + + real_long_options[out] = {0, 0, 0, 0}; +} + +// The entry for `name` in the list of options that must be specified, +// or NULL if it is an optional option +static const struct usage_required_option *required_for(const char *name, const struct usage_required_option *required) { + for (size_t i = 0; required != NULL && required[i].name != NULL; i++) { + if (strcmp(required[i].name, name) == 0) { + return &required[i]; + } + } + + return NULL; +} + +// "--option", or "--option=placeholder" if the option takes an argument +static std::string option_text(const struct option *opt, const struct usage_required_option *req) { + std::string text = std::string("--") + opt->name; + + if (opt->has_arg != no_argument) { + text += "="; + text += (req != NULL && req->placeholder != NULL) ? req->placeholder : "..."; + } + + return text; +} + +// The alternatives that `req` belongs to, as "(--this=... | --that=...)" +static std::string alternation_text(const struct option *long_options, const struct usage_required_option *required, int alternation) { + std::string text; + size_t found = 0; + + for (size_t lo = 0; long_options[lo].name != NULL && long_options[lo].name[0] != '\0'; lo++) { + const struct usage_required_option *req = required_for(long_options[lo].name, required); + + if (req != NULL && req->alternation == alternation) { + if (found++ > 0) { + text += " | "; + } + + text += option_text(&long_options[lo], req); + } + } + + if (found > 1) { + text = "(" + text + ")"; + } + + return text; +} + +void print_usage(FILE *out, const char *program, const char *const *forms, + const struct option *long_options, + const struct usage_required_option *required) { + for (size_t f = 0; forms[f] != NULL; f++) { + const char *lead = (f == 0) ? "Usage: " : "\n or: "; + fprintf(out, "%s%s %s", lead, program, forms[f]); + } + + // whatever the forms took up, the option list starts on a line of its own + size_t width = USAGE_WIDTH; + std::set alternations_listed; + + for (size_t lo = 0; long_options[lo].name != NULL && long_options[lo].name[0] != '\0'; lo++) { + if (long_options[lo].val == 0) { + fprintf(out, "\n %s\n%*s", long_options[lo].name, USAGE_INDENT, ""); + width = USAGE_INDENT; + continue; + } + + const struct usage_required_option *req = required_for(long_options[lo].name, required); + std::string text; + + if (req == NULL) { + text = "[" + option_text(&long_options[lo], NULL) + "]"; + } else if (req->alternation == 0) { + text = option_text(&long_options[lo], req); + } else { + if (alternations_listed.count(req->alternation) != 0) { + continue; // already listed with the first of its alternatives + } + alternations_listed.insert(req->alternation); + + text = alternation_text(long_options, required, req->alternation); + } + + if (width + 1 + text.size() >= USAGE_WIDTH) { + fprintf(out, "\n%*s", USAGE_INDENT, ""); + width = USAGE_INDENT; + } + + fprintf(out, " %s", text.c_str()); + width += 1 + text.size(); + } + + fprintf(out, "\n"); +} diff --git a/usage.hpp b/usage.hpp new file mode 100644 index 00000000..d8e8d68d --- /dev/null +++ b/usage.hpp @@ -0,0 +1,52 @@ +#ifndef USAGE_HPP +#define USAGE_HPP + +#include +#include +#include + +// An option that must be specified rather than being optional, and the +// placeholder to show for its argument in the usage message. +// +// Options that share the same non-zero `alternation` are alternatives to +// each other: one of them must be specified, but not more than one, and +// they are listed together as `(--this=... | --that=...)`. +struct usage_required_option { + const char *name; + const char *placeholder; + int alternation; +}; + +// Returns the short option string to pass to getopt_long() for the +// options in `long_options`, so that the two can't disagree about +// which short options exist or take arguments. +std::string getopt_string(const struct option *long_options); + +// Copies `long_options` to `real_long_options`, leaving out the headings +// of the usage message, which are not real options and so must not be +// passed on to getopt_long(). The destination must be at least as large +// as the source. +void strip_usage_headings(const struct option *long_options, struct option *real_long_options); + +// Prints a usage message for `program` to `out`: +// +// Usage: program forms[0] +// or: program forms[1] +// [--some-option] [--another-option=...] ... +// +// where `forms` is a NULL-terminated list of the ways the non-option +// arguments can be given, and the list of options is derived from +// `long_options`, the same table that is passed to getopt_long(), so that +// the message stays in sync with the options that are really accepted. +// +// Options named in `required` (a list terminated by a NULL name, or NULL +// if there are none) are shown without brackets, using the placeholder +// given there for their argument, and grouped with any alternatives to +// them. An entry in `long_options` with no `val` is printed as a heading +// for the options that follow it, and an entry with an empty name ends +// the listing, hiding any options after it. +void print_usage(FILE *out, const char *program, const char *const *forms, + const struct option *long_options, + const struct usage_required_option *required); + +#endif