Compare commits

..
115 changed files with 14675 additions and 24108 deletions

View file

@ -4,15 +4,15 @@ on: [push]
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v1
with:
submodules: "recursive"
- name: deps - name: deps
run: | run: sudo apt-get install protobuf-compiler libh2o-dev libcurl4-openssl-dev libssl-dev libprotobuf-dev libh2o-evloop-dev libwslay-dev libncurses5-dev libeigen3-dev libzstd-dev
sudo apt-get update - name: submodules
sudo apt-get install protobuf-compiler libh2o-dev libcurl4-openssl-dev libssl-dev libprotobuf-dev libh2o-evloop-dev libwslay-dev libncurses5-dev libeigen3-dev libzstd-dev libfec-dev libfmt-dev run: git submodule update --init --recursive
- name: config - name: config
run: echo WSLAY=-lwslay > Makefile.local run: echo WSLAY=-lwslay > Makefile.local
- name: make - name: make

View file

@ -1,32 +0,0 @@
name: Build and publish Docker image
on:
push:
branches: master
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Checkout submodules
run: git submodule update --init --recursive
- name: Set up docker qemu
id: qemu
uses: docker/setup-qemu-action@v4
- name: Set up docker buildx
id: buildx
uses: docker/setup-buildx-action@v4
- name: Login to docker registry
run: |
docker login --username ${{ secrets.DOCKER_USERNAME }} --password ${{ secrets.DOCKER_TOKEN }}
- name: Run buildx
run: |
docker buildx build \
--tag berthubert/galmon \
--platform linux/386,linux/amd64,linux/arm/v7,linux/arm64/v8 \
--output "type=registry" \
--build-arg MAKE_FLAGS=-j1 \
--file Dockerfile \
.

View file

@ -1,41 +1,25 @@
# FROM ubuntu:disco
# First stage - builder
#
FROM debian:10-slim AS builder
ENV DEBIAN_FRONTEND=noninteractive ENV DEBIAN_FRONTEND noninteractive
ENV LC_ALL=C.UTF-8 ENV LC_ALL C.UTF-8
# This allows you to use a local Debian mirror # This allows you to use a local Ubuntu mirror
ARG APT_URL=http://archive.debian.org/debian ARG APT_URL=
ARG MAKE_FLAGS=-j2 ENV APT_URL ${APT_URL:-http://archive.ubuntu.com/ubuntu/}
RUN sed -i "s%http://archive.ubuntu.com/ubuntu/%${APT_URL}%" /etc/apt/sources.list
RUN sed -i "s%http://deb.debian.org/debian%${APT_URL}%" /etc/apt/sources.list
RUN apt-get update && apt-get -y upgrade \ # Update packages and install dependencies
&& apt-get install -y protobuf-compiler libh2o-dev libcurl4-openssl-dev \ RUN apt-get update && apt-get -y upgrade && apt-get -y clean
libssl-dev libprotobuf-dev libh2o-evloop-dev libwslay-dev \ RUN apt-get install -y protobuf-compiler libh2o-dev libcurl4-openssl-dev \
libeigen3-dev libzstd-dev libfmt-dev libncurses-dev libfmt-dev \ libssl-dev libprotobuf-dev libh2o-evloop-dev libwslay-dev libeigen3-dev libzstd-dev \
make gcc g++ git build-essential curl autoconf automake help2man make gcc g++ git build-essential curl autoconf automake libfmt-dev libncurses5-dev \
&& apt-get -y clean
# Build # Build
ADD . /galmon-src/ ARG MAKE_FLAGS=-j2
RUN cd /galmon-src/ \ ADD . /galmon/
&& make $MAKE_FLAGS \ WORKDIR /galmon/
&& prefix=/galmon make install RUN make $MAKE_FLAGS
ENV PATH=/galmon:${PATH}
#
# Second stage - contains just the binaries
#
FROM debian:10-slim
COPY --from=builder /etc/apt/sources.list /etc/apt/sources.list
RUN apt-get update && apt-get -y upgrade \
&& apt-get install -y libcurl4 libssl1.1 libprotobuf17 libh2o-evloop0.13 \
libncurses6 \
&& apt-get -y clean \
&& rm -rf /var/lib/apt/lists/*
COPY --from=builder /galmon/ /galmon/
ENV PATH=/galmon/bin:${PATH}
ENV LC_ALL=C.UTF-8
WORKDIR /galmon/bin

18
Dockerfile-pi Normal file
View file

@ -0,0 +1,18 @@
#FROM ubuntu:disco
FROM arm32v7/debian:unstable
ENV DEBIAN_FRONTEND noninteractive
ENV LC_ALL C.UTF-8
# Update packages and install dependencies
RUN apt-get update && apt-get -y upgrade && apt-get -y clean
RUN apt-get install -y protobuf-compiler libh2o-dev libcurl4-openssl-dev \
libssl-dev libprotobuf-dev libh2o-evloop-dev libwslay-dev libeigen3-dev libzstd-dev \
make clang-9 git build-essential curl autoconf automake libfmt-dev libncurses5-dev \
&& apt-get -y clean
# Build
ADD . /galmon/
WORKDIR /galmon/
RUN make
ENV PATH=/galmon:${PATH}

View file

@ -1,7 +1,7 @@
CFLAGS = -O3 -Wall -ggdb CFLAGS = -O3 -Wall -ggdb
CXXFLAGS:= -std=gnu++17 -Wall -O3 -ggdb -MMD -MP -fno-omit-frame-pointer -Iext/CLI11 \ CXXFLAGS:= -std=gnu++17 -Wall -O3 -ggdb -MMD -MP -fno-omit-frame-pointer -Iext/CLI11 \
-Iext/powerblog/ext/simplesocket -Iext/powerblog/ext/ \ -Iext/fmt-6.1.2/include/ -Iext/powerblog/ext/simplesocket -Iext/powerblog/ext/ \
-I/usr/local/opt/openssl/include/ \ -I/usr/local/opt/openssl/include/ \
-Iext/sgp4/libsgp4/ \ -Iext/sgp4/libsgp4/ \
-I/usr/local/include -I/usr/local/include
@ -21,10 +21,11 @@ else ifneq (,$(wildcard ubxsec.o))
EXTRADEP = ubxsec.o EXTRADEP = ubxsec.o
endif endif
CHEAT_ARG := $(shell ./update-git-hash-if-necessary) CHEAT_ARG := $(shell ./update-git-hash-if-necessary)
PROGRAMS = navparse ubxtool navnexus navcat navrecv navdump testrunner navdisplay tlecatch reporter sp3feed \ PROGRAMS = navparse ubxtool navnexus navcat navrecv navdump testrunner navdisplay tlecatch reporter sp3feed \
galmonmon rinreport rinjoin rtcmtool gndate septool navmerge galmonmon rinreport rtcmtool
all: navmon.pb.cc $(PROGRAMS) all: navmon.pb.cc $(PROGRAMS)
@ -39,12 +40,12 @@ navmon.pb.cc: navmon.proto
protoc --cpp_out=./ navmon.proto protoc --cpp_out=./ navmon.proto
H2OPP=ext/powerblog/h2o-pp.o
SIMPLESOCKETS=ext/powerblog/ext/simplesocket/swrappers.o ext/powerblog/ext/simplesocket/sclasses.o ext/powerblog/ext/simplesocket/comboaddress.o SIMPLESOCKETS=ext/powerblog/ext/simplesocket/swrappers.o ext/powerblog/ext/simplesocket/sclasses.o ext/powerblog/ext/simplesocket/comboaddress.o
clean: clean:
rm -f *~ *.o *.d ext/*/*.o ext/*/*.d $(PROGRAMS) navmon.pb.h navmon.pb.cc $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) $(SIMPLESOCKETS) rm -f *~ *.o *.d ext/*/*.o ext/*/*.d $(PROGRAMS) navmon.pb.h navmon.pb.cc $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) $(H2OPP) $(SIMPLESOCKETS)
rm -f ext/sgp4/libsgp4/*.d ext/powerblog/ext/simplesocket/*.d rm -f ext/fmt-6.1.2/src/format.[do] ext/sgp4/libsgp4/*.d ext/powerblog/ext/simplesocket/*.d
help2man: help2man:
$(INSTALL) -m 755 -d $(DESTDIR)$(prefix)/share/man/man1 $(INSTALL) -m 755 -d $(DESTDIR)$(prefix)/share/man/man1
@ -62,76 +63,69 @@ install: $(PROGRAMS) help2man
mkdir -p $(DESTDIR)$(prefix)$(htdocs)/galmon mkdir -p $(DESTDIR)$(prefix)$(htdocs)/galmon
cp -a html $(DESTDIR)$(prefix)$(htdocs)/galmon/ cp -a html $(DESTDIR)$(prefix)$(htdocs)/galmon/
decrypt: decrypt.o bits.o download-debian-package:
$(CXX) -std=gnu++17 $^ -o $@ -lfmt apt-key adv --fetch-keys https://ota.bike/public-package-signing-keys/86E7F51C04FBAAB0.asc
echo "deb https://ota.bike/debian/ buster main" > /etc/apt/sources.list.d/galmon.list
apt-get update && apt-get install -y galmon
navparse: navparse.o $(SIMPLESOCKETS) minicurl.o ubx.o bits.o navmon.pb.o gps.o ephemeris.o beidou.o glonass.o $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) tle.o navmon.o coverage.o osen.o trkmeas.o influxpush.o ${EXTRADEP} githash.o sbas.o rtcm.o galileo.o download-raspbian-package:
$(CXX) -std=gnu++17 $^ -o $@ -pthread -L/usr/local/lib -L/usr/local/opt/openssl/lib/ -lz -lcurl -lprotobuf -lfmt apt-key adv --fetch-keys https://ota.bike/public-package-signing-keys/86E7F51C04FBAAB0.asc
echo "deb https://ota.bike/raspbian/ buster main" > /etc/apt/sources.list.d/galmon.list
apt-get update && apt-get install -y galmon
reporter: reporter.o $(SIMPLESOCKETS) minicurl.o ubx.o bits.o navmon.pb.o gps.o ephemeris.o beidou.o glonass.o $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) tle.o navmon.o coverage.o osen.o githash.o influxpush.o decrypt: decrypt.o bits.o ext/fmt-6.1.2/src/format.o
$(CXX) -std=gnu++17 $^ -o $@ -pthread -L/usr/local/lib -lprotobuf -lcurl -lfmt $(CXX) -std=gnu++17 $^ -o $@
sp3feed: sp3feed.o $(SIMPLESOCKETS) minicurl.o ubx.o bits.o navmon.pb.o gps.o ephemeris.o beidou.o glonass.o $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) tle.o navmon.o coverage.o osen.o influxpush.o githash.o sp3.o navparse: navparse.o ext/fmt-6.1.2/src/format.o $(H2OPP) $(SIMPLESOCKETS) minicurl.o ubx.o bits.o navmon.pb.o gps.o ephemeris.o beidou.o glonass.o $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) tle.o navmon.o coverage.o osen.o trkmeas.o influxpush.o ${EXTRADEP} githash.o sbas.o rtcm.o
$(CXX) -std=gnu++17 $^ -o $@ -pthread -L/usr/local/lib -lprotobuf -lcurl -lfmt $(CXX) -std=gnu++17 $^ -o $@ -pthread -L/usr/local/lib -L/usr/local/opt/openssl/lib/ -lh2o-evloop -lssl -lcrypto -lz -lcurl -lprotobuf $(WSLAY)
reporter: reporter.o ext/fmt-6.1.2/src/format.o $(SIMPLESOCKETS) minicurl.o ubx.o bits.o navmon.pb.o gps.o ephemeris.o beidou.o glonass.o $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) tle.o navmon.o coverage.o osen.o githash.o influxpush.o
$(CXX) -std=gnu++17 $^ -o $@ -pthread -L/usr/local/lib -lprotobuf -lcurl
sp3feed: sp3feed.o ext/fmt-6.1.2/src/format.o $(SIMPLESOCKETS) minicurl.o ubx.o bits.o navmon.pb.o gps.o ephemeris.o beidou.o glonass.o $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) tle.o navmon.o coverage.o osen.o influxpush.o githash.o sp3.o
$(CXX) -std=gnu++17 $^ -o $@ -pthread -L/usr/local/lib -lprotobuf -lcurl
tracker: tracker.o $(SIMPLESOCKETS) minicurl.o ubx.o bits.o navmon.pb.o gps.o ephemeris.o beidou.o glonass.o $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) tle.o navmon.o coverage.o osen.o githash.o tracker: tracker.o ext/fmt-6.1.2/src/format.o $(SIMPLESOCKETS) minicurl.o ubx.o bits.o navmon.pb.o gps.o ephemeris.o beidou.o glonass.o $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) tle.o navmon.o coverage.o osen.o githash.o
$(CXX) -std=gnu++17 $^ -o $@ -pthread -L/usr/local/lib -lprotobuf -lcurl -lfmt $(CXX) -std=gnu++17 $^ -o $@ -pthread -L/usr/local/lib -lprotobuf -lcurl
galmonmon: galmonmon.o $(SIMPLESOCKETS) minicurl.o ubx.o bits.o navmon.pb.o gps.o ephemeris.o beidou.o glonass.o $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) tle.o navmon.o coverage.o osen.o githash.o galmonmon: galmonmon.o ext/fmt-6.1.2/src/format.o $(SIMPLESOCKETS) minicurl.o ubx.o bits.o navmon.pb.o gps.o ephemeris.o beidou.o glonass.o $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) tle.o navmon.o coverage.o osen.o githash.o
$(CXX) -std=gnu++17 $^ -o $@ -pthread -L/usr/local/lib -lprotobuf -lcurl -lfmt $(CXX) -std=gnu++17 $^ -o $@ -pthread -L/usr/local/lib -lprotobuf -lcurl
# rs.o fixhunter.o navdump: navdump.o ext/fmt-6.1.2/src/format.o bits.o navmon.pb.o gps.o ephemeris.o beidou.o glonass.o navmon.o $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) tle.o sp3.o osen.o trkmeas.o githash.o rinex.o sbas.o rtcm.o ${EXTRADEP}
navdump: navdump.o bits.o navmon.pb.o gps.o ephemeris.o beidou.o glonass.o navmon.o $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) tle.o sp3.o osen.o trkmeas.o githash.o rinex.o sbas.o rtcm.o galileo.o ${EXTRADEP} $(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf -lz
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf -lz -lfmt
# -lfec
navdisplay: navdisplay.o bits.o navmon.pb.o gps.o ephemeris.o beidou.o glonass.o ephemeris.o navmon.o osen.o githash.o navdisplay: navdisplay.o ext/fmt-6.1.2/src/format.o bits.o navmon.pb.o gps.o ephemeris.o beidou.o glonass.o ephemeris.o navmon.o osen.o githash.o
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf -lncurses -lfmt $(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf -lncurses
navnexus: navnexus.o $(SIMPLESOCKETS) bits.o navmon.pb.o storage.o githash.o navnexus: navnexus.o ext/fmt-6.1.2/src/format.o $(SIMPLESOCKETS) ubx.o bits.o navmon.pb.o storage.o githash.o
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf -lfmt $(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf
navcat: navcat.o $(SIMPLESOCKETS) ubx.o bits.o navmon.pb.o storage.o navmon.o githash.o navcat: navcat.o ext/fmt-6.1.2/src/format.o $(SIMPLESOCKETS) ubx.o bits.o navmon.pb.o storage.o navmon.o githash.o
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf -lfmt $(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf
navrecv: navrecv.o $(SIMPLESOCKETS) navmon.pb.o storage.o githash.o zstdwrap.o navmon.o navrecv: navrecv.o ext/fmt-6.1.2/src/format.o $(SIMPLESOCKETS) navmon.pb.o storage.o githash.o zstdwrap.o navmon.o
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf -lzstd -lfmt $(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf -lzstd
navmerge: navmerge.o $(SIMPLESOCKETS) navmon.pb.o storage.o githash.o zstdwrap.o navmon.o nmmsender.o
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf -lzstd -lfmt
tlecatch: tlecatch.o $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) githash.o tlecatch: tlecatch.o $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) githash.o
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf $(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf
rinreport: rinreport.o rinex.o githash.o navmon.o ephemeris.o osen.o rinreport: rinreport.o rinex.o githash.o navmon.o ext/fmt-6.1.2/src/format.o ephemeris.o osen.o
$(CXX) -std=gnu++17 $^ -o $@ -lz -pthread -lfmt $(CXX) -std=gnu++17 $^ -o $@ -lz -pthread
rinjoin: rinjoin.o rinex.o githash.o navmon.o ephemeris.o osen.o rtcmtool: rtcmtool.o navmon.pb.o githash.o ext/fmt-6.1.2/src/format.o bits.o nmmsender.o $(SIMPLESOCKETS) navmon.o rtcm.o zstdwrap.o
$(CXX) -std=gnu++17 $^ -o $@ -lz -pthread -lfmt $(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -lz -pthread -lprotobuf -lzstd
rtcmtool: rtcmtool.o navmon.pb.o githash.o bits.o nmmsender.o $(SIMPLESOCKETS) navmon.o rtcm.o zstdwrap.o ubxtool: navmon.pb.o ubxtool.o ubx.o bits.o ext/fmt-6.1.2/src/format.o galileo.o gps.o beidou.o navmon.o ephemeris.o $(SIMPLESOCKETS) osen.o githash.o nmmsender.o zstdwrap.o
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -lz -pthread -lprotobuf -lzstd -lfmt $(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -lprotobuf -pthread -lzstd
testrunner: navmon.pb.o testrunner.o ubx.o bits.o ext/fmt-6.1.2/src/format.o galileo.o gps.o beidou.o ephemeris.o sp3.o osen.o navmon.o rinex.o githash.o
ubxtool: navmon.pb.o ubxtool.o ubx.o bits.o galileo.o gps.o beidou.o navmon.o ephemeris.o $(SIMPLESOCKETS) osen.o githash.o nmmsender.o zstdwrap.o $(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -lprotobuf -lz
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -lprotobuf -pthread -lzstd -lfmt
septool: navmon.pb.o septool.o bits.o galileo.o gps.o beidou.o navmon.o ephemeris.o $(SIMPLESOCKETS) osen.o githash.o nmmsender.o zstdwrap.o
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -lprotobuf -pthread -lzstd -lfmt
testrunner: navmon.pb.o testrunner.o ubx.o bits.o galileo.o gps.o beidou.o ephemeris.o sp3.o osen.o navmon.o rinex.o githash.o
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -lprotobuf -lz -pthread -lfmt
gndate: gndate.o githash.o navmon.o
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -lfmt
check: testrunner check: testrunner
./testrunner ./testrunner

160
README.md
View file

@ -4,17 +4,14 @@ galileo/GPS/GLONASS/BeiDou open source monitoring. GPL3 licensed.
Live website: https://galmon.eu/ Live website: https://galmon.eu/
Multi-vendor, with support for U-blox 8 and 9 chipsets and many Septentrio Theoretically multi-vendor, although currently only the U-blox 8 and 9
devices. Navilock NL-8012U receiver works really well, as does the U-blox chipsets are supported. Navilock NL-8012U receiver works really well, as
evaluation kit for the 8MT. In addition, many stations have reported does the U-blox evaluation kit for the 8MT. In addition, many stations have
success with this very cheap [AliExpress sourced reported success with this very cheap [AliExpress sourced
device](https://www.aliexpress.com/item/32816656706.html). device](https://www.aliexpress.com/item/32816656706.html). The best and
most high-end receiver, which does all bands, all the time, is the Ublox
For ublox, there is good support for the F9P, several of us use the F9P, several of us use the
[ArdusimpleRTK2B](https://www.ardusimple.com/simplertk2b/) board. It adds [ArdusimpleRTK2B](https://www.ardusimple.com/simplertk2b/) board.
the Galileo E5b band.
Septentrio devices support even more bands.
An annotated presentation about our project aimed at GNSS professionals can An annotated presentation about our project aimed at GNSS professionals can
be found [here](https://berthub.eu/galileo/The%20galmon.eu%20project.pdf). be found [here](https://berthub.eu/galileo/The%20galmon.eu%20project.pdf).
@ -27,15 +24,14 @@ be found [here](https://berthub.eu/galileo/The%20galmon.eu%20project.pdf).
To deliver data to the project, please read To deliver data to the project, please read
[The Galmon GNSS Monitoring Project](https://berthub.eu/articles/posts/galmon-project/) [The Galmon GNSS Monitoring Project](https://berthub.eu/articles/posts/galmon-project/)
and consult the rules outlined in [the operator and consult the rules outlined in [the operator
guidelines](https://github.com/berthubert/galmon/blob/master/Operator.md). guidelines](https://github.com/ahupowerdns/galmon/blob/master/Operator.md).
Highlights Highlights
---------- ----------
* Support for Septentrio and U-blox.
* Processes raw frames/strings/words from GPS, GLONASS, BeiDou and Galileo * Processes raw frames/strings/words from GPS, GLONASS, BeiDou and Galileo
* All-band support (E1, E5a, E5b, B1I, B2I, Glonass L1, Glonass L2, GPS L1C/A) * All-band support (E1, E5b, B1I, B2I, Glonass L1, Glonass L2, GPS L1C/A)
so far. so far, GPS L2C and Galileo E5a pending).
* Calculate ephemeris positions * Calculate ephemeris positions
* Comparison of ephemerides to independent SP3 data to determine SISE * Comparison of ephemerides to independent SP3 data to determine SISE
* Globally, locally, worst user location * Globally, locally, worst user location
@ -53,7 +49,7 @@ Highlights
Data is made available as JSON, as a user-friendly website and as a Data is made available as JSON, as a user-friendly website and as a
time-series database. This time-series database is easily mated to the time-series database. This time-series database is easily mated to the
industry standard Matplotlib/Pandas/Jupyter combination (details industry standard Matplotlib/Pandas/Jupyter combination (details
[here](https://github.com/berthubert/galmon/blob/master/influxdb.md). [here](https://github.com/ahupowerdns/galmon/blob/master/influxdb.md).
There is also tooling to extract raw frames/strings/words from specific There is also tooling to extract raw frames/strings/words from specific
timeframes. timeframes.
@ -72,8 +68,8 @@ Goals:
Works on Linux (including Raspbian Buster on Pi Zero W), OSX and OpenBSD. Works on Linux (including Raspbian Buster on Pi Zero W), OSX and OpenBSD.
Build locally (Linux, Debian, Ubuntu) Build locally
------------------------------------- -------------
To get started, make sure you have a C++17 compiler (like g++ 8 or higher), To get started, make sure you have a C++17 compiler (like g++ 8 or higher),
git, protobuf-compiler. Then run 'make ubxtool navdump' to build the git, protobuf-compiler. Then run 'make ubxtool navdump' to build the
@ -82,64 +78,43 @@ receiver-only tools.
To build everything, including the webserver, try: To build everything, including the webserver, try:
``` ```
apt-get install protobuf-compiler libcurl4-openssl-dev libssl-dev libprotobuf-dev \ apt-get install protobuf-compiler libh2o-dev libcurl4-openssl-dev libssl-dev libprotobuf-dev \
libncurses5-dev libeigen3-dev libzstd-dev g++ libfmt-dev libh2o-evloop-dev libwslay-dev libncurses5-dev libeigen3-dev libzstd-dev
git clone https://github.com/berthubert/galmon.git --recursive git clone https://github.com/ahupowerdns/galmon.git --recursive
cd galmon cd galmon
make make
``` ```
Building on OSX If this doesn't succeed with an error about h2o, make sure you have this
library installed. If you get an error about 'wslay', do the following, and run make again:
```
echo WSLAY=-lwslay > Makefile.local
```
Build in Docker
--------------- ---------------
With thanks to a contributor from Prague. First make sure you've installed
brew, which you can get [here](https://brew.sh/). Then do: To build it in Docker:
``` ```
brew install protobuf lzlib zstd eigen git clone https://github.com/ahupowerdns/galmon.git --recursive
docker build -t galmon --build-arg MAKE_FLAGS=-j2 .
``` ```
And then: To run a container with a shell in there (this will also expose a port so you can view the UI too and assumes a ublox GPS device too - you may need to tweak as necessary):
```
git clone https://github.com/berthubert/galmon.git --recursive
cd galmon
make
```
Running in Docker
-----------------
We publish official Docker images for galmon on
[docker hub](https://hub.docker.com/r/berthubert/galmon) for multiple architectures.
To run a container with a shell in there (this will also expose a port so you
can view the UI too and assumes a ublox GPS device too -
you may need to tweak as necessary):
``` ```
docker run -it --rm --device=/dev/ttyACM0 -p 10000:10000 berthubert/galmon docker run -it --rm --device=/dev/ttyACM0 -p 10000:10000 galmon
``` ```
Running a daemonized docker container reporting data to a remote server
might look like:
```
docker run -d --restart=always --device=/dev/ttyACM0 --name=galmon berthubert/galmon ubxtool --wait --port /dev/ttyACM0 --gps --galileo --glonass --destination [server] --station [station-id] --owner [owner]
```
To make your docker container update automatically you could use a tool such as
[watchtower](https://containrrr.github.io/watchtower/).
Running Running
------- -------
On u-blox:
Once compiled, run for example `./ubxtool --wait --port /dev/ttyACM0 Once compiled, run for example `./ubxtool --wait --port /dev/ttyACM0
--station 1 --stdout --galileo | ./navparse --bind [::1]:10000` --station 1 --stdout --galileo | ./navparse --bind [::1]:10000`
For Septentrio, try: `nc 192.168.1.1 29000 | ./septool --station x --stdout |
./navparse --bind [::1]:10000`, assuming your Septentrio can be reached on
192.168.1.1.1 and you have defined an SBF stream on port 29000. For more
details, please see below.
Next up, browse to http://[::1]:10000 (or try http://localhost:10000/ and Next up, browse to http://[::1]:10000 (or try http://localhost:10000/ and
you should be in business. ubxtool changes (non-permanently) the you should be in business. ubxtool changes (non-permanently) the
configuration of your u-blox receiver so it emits the required frames for configuration of your u-blox receiver so it emits the required frames for
@ -174,13 +149,9 @@ to stdout, add `--stdout`.
Tooling: Tooling:
* ubxtool: can configure a u-blox 8/9/10 chipset, parses its output & will * ubxtool: can configure a u-blox 8 chipset, parses its output & will
convert it into a protbuf stream of GNSS NAV frames + metadata convert it into a protbuf stream of GNSS NAV frames + metadata
Adds 64-bit timestamps plus origin information to each message Adds 64-bit timestamps plus origin information to each message
* septool: ingests the Septentrio binary format (SBF) and converts it to our
protobuf format. Supports same protocol as ubxtool.
* rtcmtool: ingest ntripclient output, decodes RTCM messages and converts
them to our protobuf format
* xtool: if you have another chipset, build something that extracts NAV * xtool: if you have another chipset, build something that extracts NAV
frames & metadata. Not done yet. frames & metadata. Not done yet.
* navrecv: receives GNSS NAV frames and stores them on disk, split out per * navrecv: receives GNSS NAV frames and stores them on disk, split out per
@ -193,11 +164,6 @@ Tooling:
computations on ephemerides. computations on ephemerides.
* grafana dashboard: makes pretty graphs * grafana dashboard: makes pretty graphs
Per device notes
----------------
The "SparkFun GNSS L1/L5 Breakout - NEO-F10N, SMA" needs '-u1 -b 38400
--wait'.
Linux Systemd Linux Systemd
------------- -------------
First make sure 'ubxtool' has been compiled (run: make ubxtool). Then, as First make sure 'ubxtool' has been compiled (run: make ubxtool). Then, as
@ -256,47 +222,6 @@ This also works for `navparse` for the pretty website and influx storage, `nc 12
if you have an influxdb running on localhost with a galileo database in there. if you have an influxdb running on localhost with a galileo database in there.
The default URL is http://127.0.0.1:29599/ The default URL is http://127.0.0.1:29599/
Septentrio specifics
--------------------
Unlike `ubxtool`, our `septool` does not (re)configure your Septentrio
device. Instead, the tool expects Septentrio Binary Format (SBF) on input,
and that this stream includes at least the following messages:
* MeasEpoch
* PVTCartesian
We currently parse and understand:
* GALRawFNAV
* GALRawINAV
Support will be added soon for:
* GPSRawCA
* GPSRawL2C
* GPSRawL5
* GLORawCA
* BDSRaw
* BDSRawB1C
* BDSRawB2a
A typical invocation of `septool` looks like this:
```
nc 192.168.1.1 29000 | ./septool --station x --destination galmon-eu-server.example.com
```
Or to test, try:
```
nc 192.168.1.1 29000 | ./septool --station x --stdout | ./navdump
```
This is assuming that you can reach your Septentrio on 192.168.1.1 and that
you have defined a TCP server stream on port 29000.
Septool will also accept input from a serial port or basically anything that
can provide SBF. Please let us know if our tooling can make your life
easier.
Internals Internals
--------- ---------
The transport format consists of repeats of: The transport format consists of repeats of:
@ -321,10 +246,9 @@ Documents
* [GLONASS CDMA](http://russianspacesystems.ru/wp-content/uploads/2016/08/ICD-GLONASS-CDMA-General.-Edition-1.0-2016.pdf) * [GLONASS CDMA](http://russianspacesystems.ru/wp-content/uploads/2016/08/ICD-GLONASS-CDMA-General.-Edition-1.0-2016.pdf)
not actually relevant for the CDMA aspects, but has appendices on more not actually relevant for the CDMA aspects, but has appendices on more
precise orbit determinations. precise orbit determinations.
* [GPS](https://www.navcen.uscg.gov/sites/default/files/pdf/gps/IS-GPS-200N.pdf) * [GPS](https://www.gps.gov/technical/icwg/IS-GPS-200K.pdf)
* [U-blox 8 interface specification](https://www.u-blox.com/sites/default/files/products/documents/u-blox8-M8_ReceiverDescrProtSpec_%28UBX-13003221%29_Public.pdf) * [U-blox 8 interface specification](https://www.u-blox.com/sites/default/files/products/documents/u-blox8-M8_ReceiverDescrProtSpec_%28UBX-13003221%29_Public.pdf)
* [U-blox 9 interface specification](https://www.u-blox.com/sites/default/files/u-blox_ZED-F9P_InterfaceDescription_%28UBX-18010854%29.pdf) * [U-blox 9 interface specification](https://www.u-blox.com/sites/default/files/u-blox_ZED-F9P_InterfaceDescription_%28UBX-18010854%29.pdf)
* [U-blox 10 interface specification](https://content.u-blox.com/sites/default/files/documents/u-blox-F10-SPG-6.00_InterfaceDescription_UBX-23002975.pdf)
* [U-blox 9 integration manual](https://www.u-blox.com/sites/default/files/ZED-F9P_IntegrationManual_%28UBX-18010802%29.pdf) * [U-blox 9 integration manual](https://www.u-blox.com/sites/default/files/ZED-F9P_IntegrationManual_%28UBX-18010802%29.pdf)
Data sources Data sources
@ -344,26 +268,16 @@ The software can interpret SP3 files, good sources:
to have less of a delay than the ESA ESM series. to have less of a delay than the ESA ESM series.
* GBU = ultra rapid, still a few days delay, but much more recent. * GBU = ultra rapid, still a few days delay, but much more recent.
Uncompress and concatenate all downloaded files into 'all.sp3' and run
'navdump ' on collected protobuf, and it will output 'sp3.csv' with fit data.
To get SP3 GBM from GFZ Potsdam for GPS week number 2111: To get SP3 GBM from GFZ Potsdam for GPS week number 2111:
``` ```
WN=2111 WN=2111
lftp -c "mget ftp://ftp.gfz-potsdam.de/GNSS/products/mgnss/${WN}/gbm*sp3.Z" lftp -c "mget ftp://ftp.gfz-potsdam.de/GNSS/products/mgnss/${WN}/gbm*sp3.Z"
gunzip gbm*sp3.Z
``` ```
To feed data, use:
```
./sp3feed --sp3src=gbm --influxdb=galileo gbm*sp3
```
This will populate the sp3 tables in the database. A subsequent run of
`reporter`, while setting the `--sp3src` parameter, will provide SP3
deviation statistics, and fill out the `sp3delta` table in there, which
stores deviations from the SP3 provided position, per sp3src.
Further interesting (ephemeris) data is on http://mgex.igs.org/IGS_MGEX_Products.php
RTCM RTCM
---- ----

View file

@ -1,8 +1,6 @@
#include "beidou.hh" #include "beidou.hh"
#include "bits.hh" #include "bits.hh"
#include <iostream> #include <iostream>
#include <vector>
#include "navmon.hh"
using namespace std; using namespace std;
@ -31,7 +29,7 @@ static int checkbds(int bits)
return 1; return 1;
} }
std::vector<uint8_t> getCondensedBeidouMessage(const std::vector<uint8_t>& payload) std::basic_string<uint8_t> getCondensedBeidouMessage(std::basic_string_view<uint8_t> payload)
{ {
// payload consists of 32 bit words where we have to ignore the first 2 bits of every word // payload consists of 32 bit words where we have to ignore the first 2 bits of every word
@ -58,7 +56,7 @@ std::vector<uint8_t> getCondensedBeidouMessage(const std::vector<uint8_t>& paylo
setbitu(buffer, 26+22*(w-1), 22, getbitu(&payload[0], 2 + w*32, 22)); setbitu(buffer, 26+22*(w-1), 22, getbitu(&payload[0], 2 + w*32, 22));
} }
return makeVec(buffer, 28); return std::basic_string<uint8_t>(buffer, 28);
} }

View file

@ -4,10 +4,9 @@
#include "bits.hh" #include "bits.hh"
#include <math.h> #include <math.h>
#include <stdexcept> #include <stdexcept>
#include <vector>
#include "ephemeris.hh" #include "ephemeris.hh"
std::vector<uint8_t> getCondensedBeidouMessage(const std::vector<uint8_t>& payload); std::basic_string<uint8_t> getCondensedBeidouMessage(std::basic_string_view<uint8_t> payload);
int beidouBitconv(int their); int beidouBitconv(int their);
/* Geostationary, so D2, so not to be parsed by this parser: /* Geostationary, so D2, so not to be parsed by this parser:
@ -22,7 +21,7 @@ struct BeidouMessage : GPSLikeEphemeris
{ {
uint8_t strtype; uint8_t strtype;
std::vector<uint8_t> g_cond; std::basic_string_view<uint8_t> g_cond;
int bbitu(int bit, int len) int bbitu(int bit, int len)
{ {
return getbitu(&g_cond[0], beidouBitconv(bit), len); return getbitu(&g_cond[0], beidouBitconv(bit), len);
@ -35,7 +34,7 @@ struct BeidouMessage : GPSLikeEphemeris
int fraid{-1}, sow{-1}; // part of every message (thanks!) int fraid{-1}, sow{-1}; // part of every message (thanks!)
int parse(const std::vector<uint8_t>& cond, uint8_t* pageno) int parse(std::basic_string_view<uint8_t> cond, uint8_t* pageno)
{ {
g_cond = cond; g_cond = cond;
if(pageno) if(pageno)
@ -90,7 +89,7 @@ struct BeidouMessage : GPSLikeEphemeris
return {factor * cur, factor * trend}; return {factor * cur, factor * trend};
} }
void parse1(const std::vector<uint8_t>& cond) void parse1(std::basic_string_view<uint8_t> cond)
{ {
sath1 = bbitu(43,1); sath1 = bbitu(43,1);
aodc = bbitu(31+13, 5); aodc = bbitu(31+13, 5);
@ -125,7 +124,7 @@ struct BeidouMessage : GPSLikeEphemeris
return -1; return -1;
} }
void parse2(const std::vector<uint8_t>& cond) void parse2(std::basic_string_view<uint8_t> cond)
{ {
deltan = bbits(43, 16); deltan = bbits(43, 16);
cuc = bbits(67, 18); cuc = bbits(67, 18);
@ -152,7 +151,7 @@ struct BeidouMessage : GPSLikeEphemeris
double getOmega0() const { return ldexp(Omega0 * M_PI, -31); } // radians double getOmega0() const { return ldexp(Omega0 * M_PI, -31); } // radians
double getIdot() const { return ldexp(idot * M_PI, -43); } // radians/s double getIdot() const { return ldexp(idot * M_PI, -43); } // radians/s
double getOmega() const { return ldexp(omega * M_PI, -31); } // radians double getOmega() const { return ldexp(omega * M_PI, -31); } // radians
void parse3(const std::vector<uint8_t>& cond) void parse3(std::basic_string_view<uint8_t> cond)
{ {
t0eLSB = bbitu(43, 15); t0eLSB = bbitu(43, 15);
i0 = bbits(66, 32); i0 = bbits(66, 32);
@ -208,7 +207,7 @@ struct BeidouMessage : GPSLikeEphemeris
} alma; } alma;
// 4 is all almanac // 4 is all almanac
int parse4(const std::vector<uint8_t>& cond) int parse4(std::basic_string_view<uint8_t> cond)
{ {
alma.sqrtA = bbitu(51, 24); alma.sqrtA = bbitu(51, 24);
alma.a1 = bbits(91, 11); alma.a1 = bbits(91, 11);
@ -229,8 +228,7 @@ struct BeidouMessage : GPSLikeEphemeris
// 2^-30 2^-50 // 2^-30 2^-50
int a0gps, a1gps, a0gal, a1gal, a0glo, a1glo, a0utc, a1utc; int a0gps, a1gps, a0gal, a1gal, a0glo, a1glo, a0utc, a1utc;
int8_t deltaTLS, deltaTLSF; int8_t deltaTLS;
uint8_t wnLSF, dn;
// in Beidou the offset is a0utc + SOW * a1utc // in Beidou the offset is a0utc + SOW * a1utc
std::pair<double, double> getUTCOffset(int tow) const std::pair<double, double> getUTCOffset(int tow) const
@ -255,7 +253,7 @@ struct BeidouMessage : GPSLikeEphemeris
} }
int parse5(const std::vector<uint8_t>& cond) int parse5(std::basic_string_view<uint8_t> cond)
{ {
alma.pageno = bbitu(44, 7); alma.pageno = bbitu(44, 7);
if(alma.pageno == 9) { if(alma.pageno == 9) {
@ -270,9 +268,6 @@ struct BeidouMessage : GPSLikeEphemeris
a0utc = bbits(91, 32); a0utc = bbits(91, 32);
a1utc = bbits(131, 24); a1utc = bbits(131, 24);
deltaTLS = bbits(31+12+1+7, 8); deltaTLS = bbits(31+12+1+7, 8);
deltaTLSF = bbits(61+6, 8);
wnLSF = bbits(61+6+8, 8);
dn = bbits(151+12, 8);
} }
else { else {
alma.sqrtA = bbitu(51, 24); alma.sqrtA = bbitu(51, 24);

View file

@ -89,7 +89,7 @@ double getCoordinates(double tow, const T& iod, Point* p, bool quiet=true)
cerr << "sqrtA = "<< sqrtA << endl; cerr << "sqrtA = "<< sqrtA << endl;
cerr << "deltan = "<< deltan << endl; cerr << "deltan = "<< deltan << endl;
cerr << "t0e = "<< t0e << "( rel "<<(tow - t0e)<<")"<<endl; cerr << "t0e = "<< t0e << endl;
cerr << "m0 = "<< m0 << " ("<<todeg(m0)<<")"<<endl; cerr << "m0 = "<< m0 << " ("<<todeg(m0)<<")"<<endl;
cerr << "e = "<< e << endl; cerr << "e = "<< e << endl;
cerr << "omega = " << omega << " ("<<todeg(omega)<<")"<<endl; cerr << "omega = " << omega << " ("<<todeg(omega)<<")"<<endl;
@ -122,7 +122,7 @@ double getCoordinates(double tow, const T& iod, Point* p, bool quiet=true)
double M = m0 + n * tk; double M = m0 + n * tk;
if(!quiet) if(!quiet)
cerr << " M = m0 + n * tk = "<<m0 << " + " << n << " * " << tk << " = " <<M <<endl; cerr << " M = m0 + n * tk = "<<m0 << " + " << n << " * " << tk <<endl;
double E = M; double E = M;
double newE; double newE;
for(int k =0 ; k < 10; ++k) { for(int k =0 ; k < 10; ++k) {

File diff suppressed because it is too large Load diff

View file

@ -1,36 +1,16 @@
// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once #pragma once
// Distributed under the 3-Clause BSD License. See accompanying
// file LICENSE or https://github.com/CLIUtils/CLI11 for details.
// CLI Library includes // CLI Library includes
// Order is important for combiner script // Order is important for combiner script
#include "Version.hpp" #include "CLI/Error.hpp"
#include "CLI/TypeTools.hpp"
#include "Macros.hpp" #include "CLI/StringTools.hpp"
#include "CLI/Split.hpp"
#include "StringTools.hpp" #include "CLI/Ini.hpp"
#include "CLI/Validators.hpp"
#include "Error.hpp" #include "CLI/Option.hpp"
#include "CLI/App.hpp"
#include "TypeTools.hpp"
#include "Split.hpp"
#include "ConfigFwd.hpp"
#include "Validators.hpp"
#include "FormatterFwd.hpp"
#include "Option.hpp"
#include "App.hpp"
#include "Config.hpp"
#include "Formatter.hpp"

View file

@ -1,346 +0,0 @@
// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
#include <algorithm>
#include <fstream>
#include <iostream>
#include <string>
#include <utility>
#include <vector>
#include "App.hpp"
#include "ConfigFwd.hpp"
#include "StringTools.hpp"
namespace CLI {
namespace detail {
inline std::string convert_arg_for_ini(const std::string &arg) {
if(arg.empty()) {
return std::string(2, '"');
}
// some specifically supported strings
if(arg == "true" || arg == "false" || arg == "nan" || arg == "inf") {
return arg;
}
// floating point conversion can convert some hex codes, but don't try that here
if(arg.compare(0, 2, "0x") != 0 && arg.compare(0, 2, "0X") != 0) {
double val;
if(detail::lexical_cast(arg, val)) {
return arg;
}
}
// just quote a single non numeric character
if(arg.size() == 1) {
return std::string("'") + arg + '\'';
}
// handle hex, binary or octal arguments
if(arg.front() == '0') {
if(arg[1] == 'x') {
if(std::all_of(arg.begin() + 2, arg.end(), [](char x) {
return (x >= '0' && x <= '9') || (x >= 'A' && x <= 'F') || (x >= 'a' && x <= 'f');
})) {
return arg;
}
} else if(arg[1] == 'o') {
if(std::all_of(arg.begin() + 2, arg.end(), [](char x) { return (x >= '0' && x <= '7'); })) {
return arg;
}
} else if(arg[1] == 'b') {
if(std::all_of(arg.begin() + 2, arg.end(), [](char x) { return (x == '0' || x == '1'); })) {
return arg;
}
}
}
if(arg.find_first_of('"') == std::string::npos) {
return std::string("\"") + arg + '"';
} else {
return std::string("'") + arg + '\'';
}
}
/// Comma separated join, adds quotes if needed
inline std::string
ini_join(const std::vector<std::string> &args, char sepChar = ',', char arrayStart = '[', char arrayEnd = ']') {
std::string joined;
if(args.size() > 1 && arrayStart != '\0') {
joined.push_back(arrayStart);
}
std::size_t start = 0;
for(const auto &arg : args) {
if(start++ > 0) {
joined.push_back(sepChar);
if(isspace(sepChar) == 0) {
joined.push_back(' ');
}
}
joined.append(convert_arg_for_ini(arg));
}
if(args.size() > 1 && arrayEnd != '\0') {
joined.push_back(arrayEnd);
}
return joined;
}
inline std::vector<std::string> generate_parents(const std::string &section, std::string &name) {
std::vector<std::string> parents;
if(detail::to_lower(section) != "default") {
if(section.find('.') != std::string::npos) {
parents = detail::split(section, '.');
} else {
parents = {section};
}
}
if(name.find('.') != std::string::npos) {
std::vector<std::string> plist = detail::split(name, '.');
name = plist.back();
detail::remove_quotes(name);
plist.pop_back();
parents.insert(parents.end(), plist.begin(), plist.end());
}
// clean up quotes on the parents
for(auto &parent : parents) {
detail::remove_quotes(parent);
}
return parents;
}
/// assuming non default segments do a check on the close and open of the segments in a configItem structure
inline void checkParentSegments(std::vector<ConfigItem> &output, const std::string &currentSection) {
std::string estring;
auto parents = detail::generate_parents(currentSection, estring);
if(!output.empty() && output.back().name == "--") {
std::size_t msize = (parents.size() > 1U) ? parents.size() : 2;
while(output.back().parents.size() >= msize) {
output.push_back(output.back());
output.back().parents.pop_back();
}
if(parents.size() > 1) {
std::size_t common = 0;
std::size_t mpair = (std::min)(output.back().parents.size(), parents.size() - 1);
for(std::size_t ii = 0; ii < mpair; ++ii) {
if(output.back().parents[ii] != parents[ii]) {
break;
}
++common;
}
if(common == mpair) {
output.pop_back();
} else {
while(output.back().parents.size() > common + 1) {
output.push_back(output.back());
output.back().parents.pop_back();
}
}
for(std::size_t ii = common; ii < parents.size() - 1; ++ii) {
output.emplace_back();
output.back().parents.assign(parents.begin(), parents.begin() + static_cast<std::ptrdiff_t>(ii) + 1);
output.back().name = "++";
}
}
} else if(parents.size() > 1) {
for(std::size_t ii = 0; ii < parents.size() - 1; ++ii) {
output.emplace_back();
output.back().parents.assign(parents.begin(), parents.begin() + static_cast<std::ptrdiff_t>(ii) + 1);
output.back().name = "++";
}
}
// insert a section end which is just an empty items_buffer
output.emplace_back();
output.back().parents = std::move(parents);
output.back().name = "++";
}
} // namespace detail
inline std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) const {
std::string line;
std::string section = "default";
std::vector<ConfigItem> output;
bool defaultArray = (arrayStart == '\0' || arrayStart == ' ') && arrayStart == arrayEnd;
char aStart = (defaultArray) ? '[' : arrayStart;
char aEnd = (defaultArray) ? ']' : arrayEnd;
char aSep = (defaultArray && arraySeparator == ' ') ? ',' : arraySeparator;
while(getline(input, line)) {
std::vector<std::string> items_buffer;
std::string name;
detail::trim(line);
std::size_t len = line.length();
if(len > 1 && line.front() == '[' && line.back() == ']') {
if(section != "default") {
// insert a section end which is just an empty items_buffer
output.emplace_back();
output.back().parents = detail::generate_parents(section, name);
output.back().name = "--";
}
section = line.substr(1, len - 2);
// deal with double brackets for TOML
if(section.size() > 1 && section.front() == '[' && section.back() == ']') {
section = section.substr(1, section.size() - 2);
}
if(detail::to_lower(section) == "default") {
section = "default";
} else {
detail::checkParentSegments(output, section);
}
continue;
}
if(len == 0) {
continue;
}
// comment lines
if(line.front() == ';' || line.front() == '#' || line.front() == commentChar) {
continue;
}
// Find = in string, split and recombine
auto pos = line.find(valueDelimiter);
if(pos != std::string::npos) {
name = detail::trim_copy(line.substr(0, pos));
std::string item = detail::trim_copy(line.substr(pos + 1));
if(item.size() > 1 && item.front() == aStart && item.back() == aEnd) {
items_buffer = detail::split_up(item.substr(1, item.length() - 2), aSep);
} else if(defaultArray && item.find_first_of(aSep) != std::string::npos) {
items_buffer = detail::split_up(item, aSep);
} else if(defaultArray && item.find_first_of(' ') != std::string::npos) {
items_buffer = detail::split_up(item);
} else {
items_buffer = {item};
}
} else {
name = detail::trim_copy(line);
items_buffer = {"true"};
}
if(name.find('.') == std::string::npos) {
detail::remove_quotes(name);
}
// clean up quotes on the items
for(auto &it : items_buffer) {
detail::remove_quotes(it);
}
std::vector<std::string> parents = detail::generate_parents(section, name);
if(!output.empty() && name == output.back().name && parents == output.back().parents) {
output.back().inputs.insert(output.back().inputs.end(), items_buffer.begin(), items_buffer.end());
} else {
output.emplace_back();
output.back().parents = std::move(parents);
output.back().name = std::move(name);
output.back().inputs = std::move(items_buffer);
}
}
if(section != "default") {
// insert a section end which is just an empty items_buffer
std::string ename;
output.emplace_back();
output.back().parents = detail::generate_parents(section, ename);
output.back().name = "--";
while(output.back().parents.size() > 1) {
output.push_back(output.back());
output.back().parents.pop_back();
}
}
return output;
}
inline std::string
ConfigBase::to_config(const App *app, bool default_also, bool write_description, std::string prefix) const {
std::stringstream out;
std::string commentLead;
commentLead.push_back(commentChar);
commentLead.push_back(' ');
std::vector<std::string> groups = app->get_groups();
bool defaultUsed = false;
groups.insert(groups.begin(), std::string("Options"));
if(write_description) {
out << commentLead << app->get_description() << '\n';
}
for(auto &group : groups) {
if(group == "Options" || group.empty()) {
if(defaultUsed) {
continue;
}
defaultUsed = true;
}
if(write_description && group != "Options" && !group.empty()) {
out << '\n' << commentLead << group << " Options\n";
}
for(const Option *opt : app->get_options({})) {
// Only process option with a long-name and configurable
if(!opt->get_lnames().empty() && opt->get_configurable()) {
if(opt->get_group() != group) {
if(!(group == "Options" && opt->get_group().empty())) {
continue;
}
}
std::string name = prefix + opt->get_lnames()[0];
std::string value = detail::ini_join(opt->reduced_results(), arraySeparator, arrayStart, arrayEnd);
if(value.empty() && default_also) {
if(!opt->get_default_str().empty()) {
value = detail::convert_arg_for_ini(opt->get_default_str());
} else if(opt->get_expected_min() == 0) {
value = "false";
}
}
if(!value.empty()) {
if(write_description && opt->has_description()) {
out << '\n';
out << commentLead << detail::fix_newlines(commentLead, opt->get_description()) << '\n';
}
out << name << valueDelimiter << value << '\n';
}
}
}
}
auto subcommands = app->get_subcommands({});
for(const App *subcom : subcommands) {
if(subcom->get_name().empty()) {
if(write_description && !subcom->get_group().empty()) {
out << '\n' << commentLead << subcom->get_group() << " Options\n";
}
out << to_config(subcom, default_also, write_description, prefix);
}
}
for(const App *subcom : subcommands) {
if(!subcom->get_name().empty()) {
if(subcom->get_configurable() && app->got_subcommand(subcom)) {
if(!prefix.empty() || app->get_parent() == nullptr) {
out << '[' << prefix << subcom->get_name() << "]\n";
} else {
std::string subname = app->get_name() + "." + subcom->get_name();
auto p = app->get_parent();
while(p->get_parent() != nullptr) {
subname = p->get_name() + "." + subname;
p = p->get_parent();
}
out << '[' << subname << "]\n";
}
out << to_config(subcom, default_also, write_description, "");
} else {
out << to_config(subcom, default_also, write_description, prefix + subcom->get_name() + ".");
}
}
}
return out.str();
}
} // namespace CLI

View file

@ -1,131 +0,0 @@
// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
#include <algorithm>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
#include "Error.hpp"
#include "StringTools.hpp"
namespace CLI {
class App;
/// Holds values to load into Options
struct ConfigItem {
/// This is the list of parents
std::vector<std::string> parents{};
/// This is the name
std::string name{};
/// Listing of inputs
std::vector<std::string> inputs{};
/// The list of parents and name joined by "."
std::string fullname() const {
std::vector<std::string> tmp = parents;
tmp.emplace_back(name);
return detail::join(tmp, ".");
}
};
/// This class provides a converter for configuration files.
class Config {
protected:
std::vector<ConfigItem> items{};
public:
/// Convert an app into a configuration
virtual std::string to_config(const App *, bool, bool, std::string) const = 0;
/// Convert a configuration into an app
virtual std::vector<ConfigItem> from_config(std::istream &) const = 0;
/// Get a flag value
virtual std::string to_flag(const ConfigItem &item) const {
if(item.inputs.size() == 1) {
return item.inputs.at(0);
}
throw ConversionError::TooManyInputsFlag(item.fullname());
}
/// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure
std::vector<ConfigItem> from_file(const std::string &name) {
std::ifstream input{name};
if(!input.good())
throw FileError::Missing(name);
return from_config(input);
}
/// Virtual destructor
virtual ~Config() = default;
};
/// This converter works with INI/TOML files; to write proper TOML files use ConfigTOML
class ConfigBase : public Config {
protected:
/// the character used for comments
char commentChar = ';';
/// the character used to start an array '\0' is a default to not use
char arrayStart = '\0';
/// the character used to end an array '\0' is a default to not use
char arrayEnd = '\0';
/// the character used to separate elements in an array
char arraySeparator = ' ';
/// the character used separate the name from the value
char valueDelimiter = '=';
public:
std::string
to_config(const App * /*app*/, bool default_also, bool write_description, std::string prefix) const override;
std::vector<ConfigItem> from_config(std::istream &input) const override;
/// Specify the configuration for comment characters
ConfigBase *comment(char cchar) {
commentChar = cchar;
return this;
}
/// Specify the start and end characters for an array
ConfigBase *arrayBounds(char aStart, char aEnd) {
arrayStart = aStart;
arrayEnd = aEnd;
return this;
}
/// Specify the delimiter character for an array
ConfigBase *arrayDelimiter(char aSep) {
arraySeparator = aSep;
return this;
}
/// Specify the delimiter between a name and value
ConfigBase *valueSeparator(char vSep) {
valueDelimiter = vSep;
return this;
}
};
/// the default Config is the INI file format
using ConfigINI = ConfigBase;
/// ConfigTOML generates a TOML compliant output
class ConfigTOML : public ConfigINI {
public:
ConfigTOML() {
commentChar = '#';
arrayStart = '[';
arrayEnd = ']';
arraySeparator = ',';
valueDelimiter = '=';
}
};
} // namespace CLI

View file

@ -1,38 +1,14 @@
// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once #pragma once
// Distributed under the 3-Clause BSD License. See accompanying
// file LICENSE or https://github.com/CLIUtils/CLI11 for details.
#include <exception> #include <exception>
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
#include <utility>
#include <vector>
// CLI library includes
#include "StringTools.hpp"
namespace CLI { namespace CLI {
// Use one of these on all error classes.
// These are temporary and are undef'd at the end of this file.
#define CLI11_ERROR_DEF(parent, name) \
protected: \
name(std::string ename, std::string msg, int exit_code) : parent(std::move(ename), std::move(msg), exit_code) {} \
name(std::string ename, std::string msg, ExitCodes exit_code) \
: parent(std::move(ename), std::move(msg), exit_code) {} \
\
public: \
name(std::string msg, ExitCodes exit_code) : parent(#name, std::move(msg), exit_code) {} \
name(std::string msg, int exit_code) : parent(#name, std::move(msg), exit_code) {}
// This is added after the one above if a class is used directly and builds its own message
#define CLI11_ERROR_SIMPLE(name) \
explicit name(std::string msg) : name(#name, msg, ExitCodes::name) {}
/// These codes are part of every error in CLI. They can be obtained from e using e.exit_code or as a quick shortcut, /// These codes are part of every error in CLI. They can be obtained from e using e.exit_code or as a quick shortcut,
/// int values from e.get_error_code(). /// int values from e.get_error_code().
enum class ExitCodes { enum class ExitCodes {
@ -40,19 +16,18 @@ enum class ExitCodes {
IncorrectConstruction = 100, IncorrectConstruction = 100,
BadNameString, BadNameString,
OptionAlreadyAdded, OptionAlreadyAdded,
FileError, File,
ConversionError, Conversion,
ValidationError, Validation,
RequiredError, Required,
RequiresError, Requires,
ExcludesError, Excludes,
ExtrasError, Extras,
ConfigError, ExtrasINI,
InvalidError, Invalid,
HorribleError, Horrible,
OptionNotFound, OptionNotFound,
ArgumentMismatch, BaseClass = 255
BaseClass = 127
}; };
// Error definitions // Error definitions
@ -64,277 +39,127 @@ enum class ExitCodes {
/// @{ /// @{
/// All errors derive from this one /// All errors derive from this one
class Error : public std::runtime_error { struct Error : public std::runtime_error {
int actual_exit_code; int exit_code;
std::string error_name{"Error"}; bool print_help;
int get_exit_code() const { return exit_code; }
public: Error(std::string parent, std::string name, ExitCodes exit_code = ExitCodes::BaseClass, bool print_help = true)
int get_exit_code() const { return actual_exit_code; } : runtime_error(parent + ": " + name), exit_code(static_cast<int>(exit_code)), print_help(print_help) {}
Error(std::string parent,
std::string get_name() const { return error_name; } std::string name,
int exit_code = static_cast<int>(ExitCodes::BaseClass),
Error(std::string name, std::string msg, int exit_code = static_cast<int>(ExitCodes::BaseClass)) bool print_help = true)
: runtime_error(msg), actual_exit_code(exit_code), error_name(std::move(name)) {} : runtime_error(parent + ": " + name), exit_code(exit_code), print_help(print_help) {}
Error(std::string name, std::string msg, ExitCodes exit_code) : Error(name, msg, static_cast<int>(exit_code)) {}
}; };
// Note: Using Error::Error constructors does not work on GCC 4.7
/// Construction errors (not in parsing) /// Construction errors (not in parsing)
class ConstructionError : public Error { struct ConstructionError : public Error {
CLI11_ERROR_DEF(Error, ConstructionError) // Using Error::Error constructors seem to not work on GCC 4.7
ConstructionError(std::string parent,
std::string name,
ExitCodes exit_code = ExitCodes::BaseClass,
bool print_help = true)
: Error(parent, name, exit_code, print_help) {}
}; };
/// Thrown when an option is set to conflicting values (non-vector and multi args, for example) /// Thrown when an option is set to conflicting values (non-vector and multi args, for example)
class IncorrectConstruction : public ConstructionError { struct IncorrectConstruction : public ConstructionError {
CLI11_ERROR_DEF(ConstructionError, IncorrectConstruction) IncorrectConstruction(std::string name)
CLI11_ERROR_SIMPLE(IncorrectConstruction) : ConstructionError("IncorrectConstruction", name, ExitCodes::IncorrectConstruction) {}
static IncorrectConstruction PositionalFlag(std::string name) {
return IncorrectConstruction(name + ": Flags cannot be positional");
}
static IncorrectConstruction Set0Opt(std::string name) {
return IncorrectConstruction(name + ": Cannot set 0 expected, use a flag instead");
}
static IncorrectConstruction SetFlag(std::string name) {
return IncorrectConstruction(name + ": Cannot set an expected number for flags");
}
static IncorrectConstruction ChangeNotVector(std::string name) {
return IncorrectConstruction(name + ": You can only change the expected arguments for vectors");
}
static IncorrectConstruction AfterMultiOpt(std::string name) {
return IncorrectConstruction(
name + ": You can't change expected arguments after you've changed the multi option policy!");
}
static IncorrectConstruction MissingOption(std::string name) {
return IncorrectConstruction("Option " + name + " is not defined");
}
static IncorrectConstruction MultiOptionPolicy(std::string name) {
return IncorrectConstruction(name + ": multi_option_policy only works for flags and exact value options");
}
}; };
/// Thrown on construction of a bad name /// Thrown on construction of a bad name
class BadNameString : public ConstructionError { struct BadNameString : public ConstructionError {
CLI11_ERROR_DEF(ConstructionError, BadNameString) BadNameString(std::string name) : ConstructionError("BadNameString", name, ExitCodes::BadNameString) {}
CLI11_ERROR_SIMPLE(BadNameString)
static BadNameString OneCharName(std::string name) { return BadNameString("Invalid one char name: " + name); }
static BadNameString BadLongName(std::string name) { return BadNameString("Bad long name: " + name); }
static BadNameString DashesOnly(std::string name) {
return BadNameString("Must have a name, not just dashes: " + name);
}
static BadNameString MultiPositionalNames(std::string name) {
return BadNameString("Only one positional name allowed, remove: " + name);
}
}; };
/// Thrown when an option already exists /// Thrown when an option already exists
class OptionAlreadyAdded : public ConstructionError { struct OptionAlreadyAdded : public ConstructionError {
CLI11_ERROR_DEF(ConstructionError, OptionAlreadyAdded) OptionAlreadyAdded(std::string name)
explicit OptionAlreadyAdded(std::string name) : ConstructionError("OptionAlreadyAdded", name, ExitCodes::OptionAlreadyAdded) {}
: OptionAlreadyAdded(name + " is already added", ExitCodes::OptionAlreadyAdded) {}
static OptionAlreadyAdded Requires(std::string name, std::string other) {
return OptionAlreadyAdded(name + " requires " + other, ExitCodes::OptionAlreadyAdded);
}
static OptionAlreadyAdded Excludes(std::string name, std::string other) {
return OptionAlreadyAdded(name + " excludes " + other, ExitCodes::OptionAlreadyAdded);
}
}; };
// Parsing errors // Parsing errors
/// Anything that can error in Parse /// Anything that can error in Parse
class ParseError : public Error { struct ParseError : public Error {
CLI11_ERROR_DEF(Error, ParseError) ParseError(std::string parent, std::string name, ExitCodes exit_code = ExitCodes::BaseClass, bool print_help = true)
: Error(parent, name, exit_code, print_help) {}
}; };
// Not really "errors" // Not really "errors"
/// This is a successful completion on parsing, supposed to exit /// This is a successful completion on parsing, supposed to exit
class Success : public ParseError { struct Success : public ParseError {
CLI11_ERROR_DEF(ParseError, Success) Success() : ParseError("Success", "Successfully completed, should be caught and quit", ExitCodes::Success, false) {}
Success() : Success("Successfully completed, should be caught and quit", ExitCodes::Success) {}
}; };
/// -h or --help on command line /// -h or --help on command line
class CallForHelp : public ParseError { struct CallForHelp : public ParseError {
CLI11_ERROR_DEF(ParseError, CallForHelp) CallForHelp()
CallForHelp() : CallForHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} : ParseError("CallForHelp", "This should be caught in your main function, see examples", ExitCodes::Success) {}
};
/// Usually something like --help-all on command line
class CallForAllHelp : public ParseError {
CLI11_ERROR_DEF(ParseError, CallForAllHelp)
CallForAllHelp()
: CallForAllHelp("This should be caught in your main function, see examples", ExitCodes::Success) {}
};
/// Does not output a diagnostic in CLI11_PARSE, but allows to return from main() with a specific error code.
class RuntimeError : public ParseError {
CLI11_ERROR_DEF(ParseError, RuntimeError)
explicit RuntimeError(int exit_code = 1) : RuntimeError("Runtime error", exit_code) {}
}; };
/// Thrown when parsing an INI file and it is missing /// Thrown when parsing an INI file and it is missing
class FileError : public ParseError { struct FileError : public ParseError {
CLI11_ERROR_DEF(ParseError, FileError) FileError(std::string name) : ParseError("FileError", name, ExitCodes::File) {}
CLI11_ERROR_SIMPLE(FileError)
static FileError Missing(std::string name) { return FileError(name + " was not readable (missing?)"); }
}; };
/// Thrown when conversion call back fails, such as when an int fails to coerce to a string /// Thrown when conversion call back fails, such as when an int fails to coerse to a string
class ConversionError : public ParseError { struct ConversionError : public ParseError {
CLI11_ERROR_DEF(ParseError, ConversionError) ConversionError(std::string name) : ParseError("ConversionError", name, ExitCodes::Conversion) {}
CLI11_ERROR_SIMPLE(ConversionError)
ConversionError(std::string member, std::string name)
: ConversionError("The value " + member + " is not an allowed value for " + name) {}
ConversionError(std::string name, std::vector<std::string> results)
: ConversionError("Could not convert: " + name + " = " + detail::join(results)) {}
static ConversionError TooManyInputsFlag(std::string name) {
return ConversionError(name + ": too many inputs for a flag");
}
static ConversionError TrueFalse(std::string name) {
return ConversionError(name + ": Should be true/false or a number");
}
}; };
/// Thrown when validation of results fails /// Thrown when validation of results fails
class ValidationError : public ParseError { struct ValidationError : public ParseError {
CLI11_ERROR_DEF(ParseError, ValidationError) ValidationError(std::string name) : ParseError("ValidationError", name, ExitCodes::Validation) {}
CLI11_ERROR_SIMPLE(ValidationError)
explicit ValidationError(std::string name, std::string msg) : ValidationError(name + ": " + msg) {}
}; };
/// Thrown when a required option is missing /// Thrown when a required option is missing
class RequiredError : public ParseError { struct RequiredError : public ParseError {
CLI11_ERROR_DEF(ParseError, RequiredError) RequiredError(std::string name) : ParseError("RequiredError", name, ExitCodes::Required) {}
explicit RequiredError(std::string name) : RequiredError(name + " is required", ExitCodes::RequiredError) {}
static RequiredError Subcommand(std::size_t min_subcom) {
if(min_subcom == 1) {
return RequiredError("A subcommand");
}
return RequiredError("Requires at least " + std::to_string(min_subcom) + " subcommands",
ExitCodes::RequiredError);
}
static RequiredError
Option(std::size_t min_option, std::size_t max_option, std::size_t used, const std::string &option_list) {
if((min_option == 1) && (max_option == 1) && (used == 0))
return RequiredError("Exactly 1 option from [" + option_list + "]");
if((min_option == 1) && (max_option == 1) && (used > 1)) {
return RequiredError("Exactly 1 option from [" + option_list + "] is required and " + std::to_string(used) +
" were given",
ExitCodes::RequiredError);
}
if((min_option == 1) && (used == 0))
return RequiredError("At least 1 option from [" + option_list + "]");
if(used < min_option) {
return RequiredError("Requires at least " + std::to_string(min_option) + " options used and only " +
std::to_string(used) + "were given from [" + option_list + "]",
ExitCodes::RequiredError);
}
if(max_option == 1)
return RequiredError("Requires at most 1 options be given from [" + option_list + "]",
ExitCodes::RequiredError);
return RequiredError("Requires at most " + std::to_string(max_option) + " options be used and " +
std::to_string(used) + "were given from [" + option_list + "]",
ExitCodes::RequiredError);
}
};
/// Thrown when the wrong number of arguments has been received
class ArgumentMismatch : public ParseError {
CLI11_ERROR_DEF(ParseError, ArgumentMismatch)
CLI11_ERROR_SIMPLE(ArgumentMismatch)
ArgumentMismatch(std::string name, int expected, std::size_t received)
: ArgumentMismatch(expected > 0 ? ("Expected exactly " + std::to_string(expected) + " arguments to " + name +
", got " + std::to_string(received))
: ("Expected at least " + std::to_string(-expected) + " arguments to " + name +
", got " + std::to_string(received)),
ExitCodes::ArgumentMismatch) {}
static ArgumentMismatch AtLeast(std::string name, int num, std::size_t received) {
return ArgumentMismatch(name + ": At least " + std::to_string(num) + " required but received " +
std::to_string(received));
}
static ArgumentMismatch AtMost(std::string name, int num, std::size_t received) {
return ArgumentMismatch(name + ": At Most " + std::to_string(num) + " required but received " +
std::to_string(received));
}
static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) {
return ArgumentMismatch(name + ": " + std::to_string(num) + " required " + type + " missing");
}
static ArgumentMismatch FlagOverride(std::string name) {
return ArgumentMismatch(name + " was given a disallowed flag override");
}
}; };
/// Thrown when a requires option is missing /// Thrown when a requires option is missing
class RequiresError : public ParseError { struct RequiresError : public ParseError {
CLI11_ERROR_DEF(ParseError, RequiresError) RequiresError(std::string name, std::string subname)
RequiresError(std::string curname, std::string subname) : ParseError("RequiresError", name + " requires " + subname, ExitCodes::Requires) {}
: RequiresError(curname + " requires " + subname, ExitCodes::RequiresError) {}
}; };
/// Thrown when an excludes option is present /// Thrown when a exludes option is present
class ExcludesError : public ParseError { struct ExcludesError : public ParseError {
CLI11_ERROR_DEF(ParseError, ExcludesError) ExcludesError(std::string name, std::string subname)
ExcludesError(std::string curname, std::string subname) : ParseError("ExcludesError", name + " excludes " + subname, ExitCodes::Excludes) {}
: ExcludesError(curname + " excludes " + subname, ExitCodes::ExcludesError) {}
}; };
/// Thrown when too many positionals or options are found /// Thrown when too many positionals or options are found
class ExtrasError : public ParseError { struct ExtrasError : public ParseError {
CLI11_ERROR_DEF(ParseError, ExtrasError) ExtrasError(std::string name) : ParseError("ExtrasError", name, ExitCodes::Extras) {}
explicit ExtrasError(std::vector<std::string> args)
: ExtrasError((args.size() > 1 ? "The following arguments were not expected: "
: "The following argument was not expected: ") +
detail::rjoin(args, " "),
ExitCodes::ExtrasError) {}
ExtrasError(const std::string &name, std::vector<std::string> args)
: ExtrasError(name,
(args.size() > 1 ? "The following arguments were not expected: "
: "The following argument was not expected: ") +
detail::rjoin(args, " "),
ExitCodes::ExtrasError) {}
}; };
/// Thrown when extra values are found in an INI file /// Thrown when extra values are found in an INI file
class ConfigError : public ParseError { struct ExtrasINIError : public ParseError {
CLI11_ERROR_DEF(ParseError, ConfigError) ExtrasINIError(std::string name) : ParseError("ExtrasINIError", name, ExitCodes::ExtrasINI) {}
CLI11_ERROR_SIMPLE(ConfigError)
static ConfigError Extras(std::string item) { return ConfigError("INI was not able to parse " + item); }
static ConfigError NotConfigurable(std::string item) {
return ConfigError(item + ": This option is not allowed in a configuration file");
}
}; };
/// Thrown when validation fails before parsing /// Thrown when validation fails before parsing
class InvalidError : public ParseError { struct InvalidError : public ParseError {
CLI11_ERROR_DEF(ParseError, InvalidError) InvalidError(std::string name) : ParseError("InvalidError", name, ExitCodes::Invalid) {}
explicit InvalidError(std::string name)
: InvalidError(name + ": Too many positional arguments with unlimited expected args", ExitCodes::InvalidError) {
}
}; };
/// This is just a safety check to verify selection and parsing match - you should not ever see it /// This is just a safety check to verify selection and parsing match
/// Strings are directly added to this error, but again, it should never be seen. struct HorribleError : public ParseError {
class HorribleError : public ParseError { HorribleError(std::string name)
CLI11_ERROR_DEF(ParseError, HorribleError) : ParseError("HorribleError", "(You should never see this error) " + name, ExitCodes::Horrible) {}
CLI11_ERROR_SIMPLE(HorribleError)
}; };
// After parsing // After parsing
/// Thrown when counting a non-existent option /// Thrown when counting a non-existent option
class OptionNotFound : public Error { struct OptionNotFound : public Error {
CLI11_ERROR_DEF(Error, OptionNotFound) OptionNotFound(std::string name) : Error("OptionNotFound", name, ExitCodes::OptionNotFound) {}
explicit OptionNotFound(std::string name) : OptionNotFound(name + " not found", ExitCodes::OptionNotFound) {}
}; };
#undef CLI11_ERROR_DEF
#undef CLI11_ERROR_SIMPLE
/// @} /// @}
} // namespace CLI } // namespace CLI

View file

@ -1,281 +0,0 @@
// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
#include <algorithm>
#include <string>
#include <vector>
#include "App.hpp"
#include "FormatterFwd.hpp"
namespace CLI {
inline std::string
Formatter::make_group(std::string group, bool is_positional, std::vector<const Option *> opts) const {
std::stringstream out;
out << "\n" << group << ":\n";
for(const Option *opt : opts) {
out << make_option(opt, is_positional);
}
return out.str();
}
inline std::string Formatter::make_positionals(const App *app) const {
std::vector<const Option *> opts =
app->get_options([](const Option *opt) { return !opt->get_group().empty() && opt->get_positional(); });
if(opts.empty())
return std::string();
return make_group(get_label("Positionals"), true, opts);
}
inline std::string Formatter::make_groups(const App *app, AppFormatMode mode) const {
std::stringstream out;
std::vector<std::string> groups = app->get_groups();
// Options
for(const std::string &group : groups) {
std::vector<const Option *> opts = app->get_options([app, mode, &group](const Option *opt) {
return opt->get_group() == group // Must be in the right group
&& opt->nonpositional() // Must not be a positional
&& (mode != AppFormatMode::Sub // If mode is Sub, then
|| (app->get_help_ptr() != opt // Ignore help pointer
&& app->get_help_all_ptr() != opt)); // Ignore help all pointer
});
if(!group.empty() && !opts.empty()) {
out << make_group(group, false, opts);
if(group != groups.back())
out << "\n";
}
}
return out.str();
}
inline std::string Formatter::make_description(const App *app) const {
std::string desc = app->get_description();
auto min_options = app->get_require_option_min();
auto max_options = app->get_require_option_max();
if(app->get_required()) {
desc += " REQUIRED ";
}
if((max_options == min_options) && (min_options > 0)) {
if(min_options == 1) {
desc += " \n[Exactly 1 of the following options is required]";
} else {
desc += " \n[Exactly " + std::to_string(min_options) + "options from the following list are required]";
}
} else if(max_options > 0) {
if(min_options > 0) {
desc += " \n[Between " + std::to_string(min_options) + " and " + std::to_string(max_options) +
" of the follow options are required]";
} else {
desc += " \n[At most " + std::to_string(max_options) + " of the following options are allowed]";
}
} else if(min_options > 0) {
desc += " \n[At least " + std::to_string(min_options) + " of the following options are required]";
}
return (!desc.empty()) ? desc + "\n" : std::string{};
}
inline std::string Formatter::make_usage(const App *app, std::string name) const {
std::stringstream out;
out << get_label("Usage") << ":" << (name.empty() ? "" : " ") << name;
std::vector<std::string> groups = app->get_groups();
// Print an Options badge if any options exist
std::vector<const Option *> non_pos_options =
app->get_options([](const Option *opt) { return opt->nonpositional(); });
if(!non_pos_options.empty())
out << " [" << get_label("OPTIONS") << "]";
// Positionals need to be listed here
std::vector<const Option *> positionals = app->get_options([](const Option *opt) { return opt->get_positional(); });
// Print out positionals if any are left
if(!positionals.empty()) {
// Convert to help names
std::vector<std::string> positional_names(positionals.size());
std::transform(positionals.begin(), positionals.end(), positional_names.begin(), [this](const Option *opt) {
return make_option_usage(opt);
});
out << " " << detail::join(positional_names, " ");
}
// Add a marker if subcommands are expected or optional
if(!app->get_subcommands(
[](const CLI::App *subc) { return ((!subc->get_disabled()) && (!subc->get_name().empty())); })
.empty()) {
out << " " << (app->get_require_subcommand_min() == 0 ? "[" : "")
<< get_label(app->get_require_subcommand_max() < 2 || app->get_require_subcommand_min() > 1 ? "SUBCOMMAND"
: "SUBCOMMANDS")
<< (app->get_require_subcommand_min() == 0 ? "]" : "");
}
out << std::endl;
return out.str();
}
inline std::string Formatter::make_footer(const App *app) const {
std::string footer = app->get_footer();
if(footer.empty()) {
return std::string{};
}
return footer + "\n";
}
inline std::string Formatter::make_help(const App *app, std::string name, AppFormatMode mode) const {
// This immediately forwards to the make_expanded method. This is done this way so that subcommands can
// have overridden formatters
if(mode == AppFormatMode::Sub)
return make_expanded(app);
std::stringstream out;
if((app->get_name().empty()) && (app->get_parent() != nullptr)) {
if(app->get_group() != "Subcommands") {
out << app->get_group() << ':';
}
}
out << make_description(app);
out << make_usage(app, name);
out << make_positionals(app);
out << make_groups(app, mode);
out << make_subcommands(app, mode);
out << '\n' << make_footer(app);
return out.str();
}
inline std::string Formatter::make_subcommands(const App *app, AppFormatMode mode) const {
std::stringstream out;
std::vector<const App *> subcommands = app->get_subcommands({});
// Make a list in definition order of the groups seen
std::vector<std::string> subcmd_groups_seen;
for(const App *com : subcommands) {
if(com->get_name().empty()) {
if(!com->get_group().empty()) {
out << make_expanded(com);
}
continue;
}
std::string group_key = com->get_group();
if(!group_key.empty() &&
std::find_if(subcmd_groups_seen.begin(), subcmd_groups_seen.end(), [&group_key](std::string a) {
return detail::to_lower(a) == detail::to_lower(group_key);
}) == subcmd_groups_seen.end())
subcmd_groups_seen.push_back(group_key);
}
// For each group, filter out and print subcommands
for(const std::string &group : subcmd_groups_seen) {
out << "\n" << group << ":\n";
std::vector<const App *> subcommands_group = app->get_subcommands(
[&group](const App *sub_app) { return detail::to_lower(sub_app->get_group()) == detail::to_lower(group); });
for(const App *new_com : subcommands_group) {
if(new_com->get_name().empty())
continue;
if(mode != AppFormatMode::All) {
out << make_subcommand(new_com);
} else {
out << new_com->help(new_com->get_name(), AppFormatMode::Sub);
out << "\n";
}
}
}
return out.str();
}
inline std::string Formatter::make_subcommand(const App *sub) const {
std::stringstream out;
detail::format_help(out, sub->get_name(), sub->get_description(), column_width_);
return out.str();
}
inline std::string Formatter::make_expanded(const App *sub) const {
std::stringstream out;
out << sub->get_display_name() << "\n";
out << make_description(sub);
out << make_positionals(sub);
out << make_groups(sub, AppFormatMode::Sub);
out << make_subcommands(sub, AppFormatMode::Sub);
// Drop blank spaces
std::string tmp = detail::find_and_replace(out.str(), "\n\n", "\n");
tmp = tmp.substr(0, tmp.size() - 1); // Remove the final '\n'
// Indent all but the first line (the name)
return detail::find_and_replace(tmp, "\n", "\n ") + "\n";
}
inline std::string Formatter::make_option_name(const Option *opt, bool is_positional) const {
if(is_positional)
return opt->get_name(true, false);
return opt->get_name(false, true);
}
inline std::string Formatter::make_option_opts(const Option *opt) const {
std::stringstream out;
if(opt->get_type_size() != 0) {
if(!opt->get_type_name().empty())
out << " " << get_label(opt->get_type_name());
if(!opt->get_default_str().empty())
out << "=" << opt->get_default_str();
if(opt->get_expected_max() == detail::expected_max_vector_size)
out << " ...";
else if(opt->get_expected_min() > 1)
out << " x " << opt->get_expected();
if(opt->get_required())
out << " " << get_label("REQUIRED");
}
if(!opt->get_envname().empty())
out << " (" << get_label("Env") << ":" << opt->get_envname() << ")";
if(!opt->get_needs().empty()) {
out << " " << get_label("Needs") << ":";
for(const Option *op : opt->get_needs())
out << " " << op->get_name();
}
if(!opt->get_excludes().empty()) {
out << " " << get_label("Excludes") << ":";
for(const Option *op : opt->get_excludes())
out << " " << op->get_name();
}
return out.str();
}
inline std::string Formatter::make_option_desc(const Option *opt) const { return opt->get_description(); }
inline std::string Formatter::make_option_usage(const Option *opt) const {
// Note that these are positionals usages
std::stringstream out;
out << make_option_name(opt, true);
if(opt->get_expected_max() >= detail::expected_max_vector_size)
out << "...";
else if(opt->get_expected_max() > 1)
out << "(" << opt->get_expected() << "x)";
return opt->get_required() ? out.str() : "[" + out.str() + "]";
}
} // namespace CLI

View file

@ -1,180 +0,0 @@
// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
#include <map>
#include <string>
#include <utility>
#include <vector>
#include "StringTools.hpp"
namespace CLI {
class Option;
class App;
/// This enum signifies the type of help requested
///
/// This is passed in by App; all user classes must accept this as
/// the second argument.
enum class AppFormatMode {
Normal, ///< The normal, detailed help
All, ///< A fully expanded help
Sub, ///< Used when printed as part of expanded subcommand
};
/// This is the minimum requirements to run a formatter.
///
/// A user can subclass this is if they do not care at all
/// about the structure in CLI::Formatter.
class FormatterBase {
protected:
/// @name Options
///@{
/// The width of the first column
std::size_t column_width_{30};
/// @brief The required help printout labels (user changeable)
/// Values are Needs, Excludes, etc.
std::map<std::string, std::string> labels_{};
///@}
/// @name Basic
///@{
public:
FormatterBase() = default;
FormatterBase(const FormatterBase &) = default;
FormatterBase(FormatterBase &&) = default;
/// Adding a destructor in this form to work around bug in GCC 4.7
virtual ~FormatterBase() noexcept {} // NOLINT(modernize-use-equals-default)
/// This is the key method that puts together help
virtual std::string make_help(const App *, std::string, AppFormatMode) const = 0;
///@}
/// @name Setters
///@{
/// Set the "REQUIRED" label
void label(std::string key, std::string val) { labels_[key] = val; }
/// Set the column width
void column_width(std::size_t val) { column_width_ = val; }
///@}
/// @name Getters
///@{
/// Get the current value of a name (REQUIRED, etc.)
std::string get_label(std::string key) const {
if(labels_.find(key) == labels_.end())
return key;
else
return labels_.at(key);
}
/// Get the current column width
std::size_t get_column_width() const { return column_width_; }
///@}
};
/// This is a specialty override for lambda functions
class FormatterLambda final : public FormatterBase {
using funct_t = std::function<std::string(const App *, std::string, AppFormatMode)>;
/// The lambda to hold and run
funct_t lambda_;
public:
/// Create a FormatterLambda with a lambda function
explicit FormatterLambda(funct_t funct) : lambda_(std::move(funct)) {}
/// Adding a destructor (mostly to make GCC 4.7 happy)
~FormatterLambda() noexcept override {} // NOLINT(modernize-use-equals-default)
/// This will simply call the lambda function
std::string make_help(const App *app, std::string name, AppFormatMode mode) const override {
return lambda_(app, name, mode);
}
};
/// This is the default Formatter for CLI11. It pretty prints help output, and is broken into quite a few
/// overridable methods, to be highly customizable with minimal effort.
class Formatter : public FormatterBase {
public:
Formatter() = default;
Formatter(const Formatter &) = default;
Formatter(Formatter &&) = default;
/// @name Overridables
///@{
/// This prints out a group of options with title
///
virtual std::string make_group(std::string group, bool is_positional, std::vector<const Option *> opts) const;
/// This prints out just the positionals "group"
virtual std::string make_positionals(const App *app) const;
/// This prints out all the groups of options
std::string make_groups(const App *app, AppFormatMode mode) const;
/// This prints out all the subcommands
virtual std::string make_subcommands(const App *app, AppFormatMode mode) const;
/// This prints out a subcommand
virtual std::string make_subcommand(const App *sub) const;
/// This prints out a subcommand in help-all
virtual std::string make_expanded(const App *sub) const;
/// This prints out all the groups of options
virtual std::string make_footer(const App *app) const;
/// This displays the description line
virtual std::string make_description(const App *app) const;
/// This displays the usage line
virtual std::string make_usage(const App *app, std::string name) const;
/// This puts everything together
std::string make_help(const App * /*app*/, std::string, AppFormatMode) const override;
///@}
/// @name Options
///@{
/// This prints out an option help line, either positional or optional form
virtual std::string make_option(const Option *opt, bool is_positional) const {
std::stringstream out;
detail::format_help(
out, make_option_name(opt, is_positional) + make_option_opts(opt), make_option_desc(opt), column_width_);
return out.str();
}
/// @brief This is the name part of an option, Default: left column
virtual std::string make_option_name(const Option *, bool) const;
/// @brief This is the options part of the name, Default: combined into left column
virtual std::string make_option_opts(const Option *) const;
/// @brief This is the description. Default: Right column, on new line if left column too large
virtual std::string make_option_desc(const Option *) const;
/// @brief This is used to print the name on the USAGE line
virtual std::string make_option_usage(const Option *opt) const;
///@}
};
} // namespace CLI

115
ext/CLI11/CLI/Ini.hpp Normal file
View file

@ -0,0 +1,115 @@
#pragma once
// Distributed under the 3-Clause BSD License. See accompanying
// file LICENSE or https://github.com/CLIUtils/CLI11 for details.
#include <algorithm>
#include <fstream>
#include <iostream>
#include <string>
#include "CLI/StringTools.hpp"
namespace CLI {
namespace detail {
inline std::string inijoin(std::vector<std::string> args) {
std::ostringstream s;
size_t start = 0;
for(const auto &arg : args) {
if(start++ > 0)
s << " ";
auto it = std::find_if(arg.begin(), arg.end(), [](char ch) { return std::isspace<char>(ch, std::locale()); });
if(it == arg.end())
s << arg;
else if(arg.find(R"(")") == std::string::npos)
s << R"(")" << arg << R"(")";
else
s << R"(')" << arg << R"(')";
}
return s.str();
}
struct ini_ret_t {
/// This is the full name with dots
std::string fullname;
/// Listing of inputs
std::vector<std::string> inputs;
/// Current parent level
size_t level = 0;
/// Return parent or empty string, based on level
///
/// Level 0, a.b.c would return a
/// Level 1, a.b.c could return b
std::string parent() const {
std::vector<std::string> plist = detail::split(fullname, '.');
if(plist.size() > (level + 1))
return plist[level];
else
return "";
}
/// Return name
std::string name() const {
std::vector<std::string> plist = detail::split(fullname, '.');
return plist.at(plist.size() - 1);
}
};
/// Internal parsing function
inline std::vector<ini_ret_t> parse_ini(std::istream &input) {
std::string name, line;
std::string section = "default";
std::vector<ini_ret_t> output;
while(getline(input, line)) {
std::vector<std::string> items;
detail::trim(line);
size_t len = line.length();
if(len > 1 && line[0] == '[' && line[len - 1] == ']') {
section = line.substr(1, len - 2);
} else if(len > 0 && line[0] != ';') {
output.emplace_back();
ini_ret_t &out = output.back();
// Find = in string, split and recombine
auto pos = line.find("=");
if(pos != std::string::npos) {
name = detail::trim_copy(line.substr(0, pos));
std::string item = detail::trim_copy(line.substr(pos + 1));
items = detail::split_up(item);
} else {
name = detail::trim_copy(line);
items = {"ON"};
}
if(detail::to_lower(section) == "default")
out.fullname = name;
else
out.fullname = section + "." + name;
out.inputs.insert(std::end(out.inputs), std::begin(items), std::end(items));
}
}
return output;
}
/// Parse an INI file, throw an error (ParseError:INIParseError or FileError) on failure
inline std::vector<ini_ret_t> parse_ini(const std::string &name) {
std::ifstream input{name};
if(!input.good())
throw FileError(name);
return parse_ini(input);
}
} // namespace detail
} // namespace CLI

View file

@ -1,44 +0,0 @@
// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
// [CLI11:verbatim]
// The following version macro is very similar to the one in PyBind11
#if !(defined(_MSC_VER) && __cplusplus == 199711L) && !defined(__INTEL_COMPILER)
#if __cplusplus >= 201402L
#define CLI11_CPP14
#if __cplusplus >= 201703L
#define CLI11_CPP17
#if __cplusplus > 201703L
#define CLI11_CPP20
#endif
#endif
#endif
#elif defined(_MSC_VER) && __cplusplus == 199711L
// MSVC sets _MSVC_LANG rather than __cplusplus (supposedly until the standard is fully implemented)
// Unless you use the /Zc:__cplusplus flag on Visual Studio 2017 15.7 Preview 3 or newer
#if _MSVC_LANG >= 201402L
#define CLI11_CPP14
#if _MSVC_LANG > 201402L && _MSC_VER >= 1910
#define CLI11_CPP17
#if __MSVC_LANG > 201703L && _MSC_VER >= 1910
#define CLI11_CPP20
#endif
#endif
#endif
#endif
#if defined(CLI11_CPP14)
#define CLI11_DEPRECATED(reason) [[deprecated(reason)]]
#elif defined(_MSC_VER)
#define CLI11_DEPRECATED(reason) __declspec(deprecated(reason))
#else
#define CLI11_DEPRECATED(reason) __attribute__((deprecated(reason)))
#endif
// [CLI11:verbatim]

File diff suppressed because it is too large Load diff

View file

@ -1,18 +1,14 @@
// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once #pragma once
// Distributed under the 3-Clause BSD License. See accompanying
// file LICENSE or https://github.com/CLIUtils/CLI11 for details.
#include <string> #include <string>
#include <tuple> #include <tuple>
#include <utility>
#include <vector> #include <vector>
#include "Error.hpp" #include "CLI/Error.hpp"
#include "StringTools.hpp" #include "CLI/StringTools.hpp"
namespace CLI { namespace CLI {
namespace detail { namespace detail {
@ -23,14 +19,14 @@ inline bool split_short(const std::string &current, std::string &name, std::stri
name = current.substr(1, 1); name = current.substr(1, 1);
rest = current.substr(2); rest = current.substr(2);
return true; return true;
} } else
return false; return false;
} }
// Returns false if not a long option. Otherwise, sets opt name and other side of = and returns true // Returns false if not a long option. Otherwise, sets opt name and other side of = and returns true
inline bool split_long(const std::string &current, std::string &name, std::string &value) { inline bool split_long(const std::string &current, std::string &name, std::string &value) {
if(current.size() > 2 && current.substr(0, 2) == "--" && valid_first_char(current[2])) { if(current.size() > 2 && current.substr(0, 2) == "--" && valid_first_char(current[2])) {
auto loc = current.find_first_of('='); auto loc = current.find("=");
if(loc != std::string::npos) { if(loc != std::string::npos) {
name = current.substr(2, loc - 2); name = current.substr(2, loc - 2);
value = current.substr(loc + 1); value = current.substr(loc + 1);
@ -39,62 +35,19 @@ inline bool split_long(const std::string &current, std::string &name, std::strin
value = ""; value = "";
} }
return true; return true;
} } else
return false;
}
// Returns false if not a windows style option. Otherwise, sets opt name and value and returns true
inline bool split_windows_style(const std::string &current, std::string &name, std::string &value) {
if(current.size() > 1 && current[0] == '/' && valid_first_char(current[1])) {
auto loc = current.find_first_of(':');
if(loc != std::string::npos) {
name = current.substr(1, loc - 1);
value = current.substr(loc + 1);
} else {
name = current.substr(1);
value = "";
}
return true;
}
return false; return false;
} }
// Splits a string into multiple long and short names // Splits a string into multiple long and short names
inline std::vector<std::string> split_names(std::string current) { inline std::vector<std::string> split_names(std::string current) {
std::vector<std::string> output; std::vector<std::string> output;
std::size_t val; size_t val;
while((val = current.find(",")) != std::string::npos) { while((val = current.find(",")) != std::string::npos) {
output.push_back(trim_copy(current.substr(0, val))); output.push_back(current.substr(0, val));
current = current.substr(val + 1); current = current.substr(val + 1);
} }
output.push_back(trim_copy(current)); output.push_back(current);
return output;
}
/// extract default flag values either {def} or starting with a !
inline std::vector<std::pair<std::string, std::string>> get_default_flag_values(const std::string &str) {
std::vector<std::string> flags = split_names(str);
flags.erase(std::remove_if(flags.begin(),
flags.end(),
[](const std::string &name) {
return ((name.empty()) || (!(((name.find_first_of('{') != std::string::npos) &&
(name.back() == '}')) ||
(name[0] == '!'))));
}),
flags.end());
std::vector<std::pair<std::string, std::string>> output;
output.reserve(flags.size());
for(auto &flag : flags) {
auto def_start = flag.find_first_of('{');
std::string defval = "false";
if((def_start != std::string::npos) && (flag.back() == '}')) {
defval = flag.substr(def_start + 1);
defval.pop_back();
flag.erase(def_start, std::string::npos);
}
flag.erase(0, flag.find_first_not_of("-!"));
output.emplace_back(flag, defval);
}
return output; return output;
} }
@ -107,25 +60,24 @@ get_names(const std::vector<std::string> &input) {
std::string pos_name; std::string pos_name;
for(std::string name : input) { for(std::string name : input) {
if(name.length() == 0) { if(name.length() == 0)
continue; continue;
} else if(name.length() > 1 && name[0] == '-' && name[1] != '-') {
if(name.length() > 1 && name[0] == '-' && name[1] != '-') {
if(name.length() == 2 && valid_first_char(name[1])) if(name.length() == 2 && valid_first_char(name[1]))
short_names.emplace_back(1, name[1]); short_names.emplace_back(1, name[1]);
else else
throw BadNameString::OneCharName(name); throw BadNameString("Invalid one char name: " + name);
} else if(name.length() > 2 && name.substr(0, 2) == "--") { } else if(name.length() > 2 && name.substr(0, 2) == "--") {
name = name.substr(2); name = name.substr(2);
if(valid_name_string(name)) if(valid_name_string(name))
long_names.push_back(name); long_names.push_back(name);
else else
throw BadNameString::BadLongName(name); throw BadNameString("Bad long name: " + name);
} else if(name == "-" || name == "--") { } else if(name == "-" || name == "--") {
throw BadNameString::DashesOnly(name); throw BadNameString("Must have a name, not just dashes");
} else { } else {
if(pos_name.length() > 0) if(pos_name.length() > 0)
throw BadNameString::MultiPositionalNames(name); throw BadNameString("Only one positional name allowed, remove: " + name);
pos_name = name; pos_name = name;
} }
} }

Some files were not shown because too many files have changed in this diff Show more