mirror of
https://github.com/berthubert/galmon.git
synced 2026-11-09 08:39:14 -05:00
Compare commits
115 changed files with 14675 additions and 24108 deletions
12
.github/workflows/ccpp.yml
vendored
12
.github/workflows/ccpp.yml
vendored
|
|
@ -4,15 +4,15 @@ on: [push]
|
|||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: "recursive"
|
||||
- uses: actions/checkout@v1
|
||||
- name: deps
|
||||
run: |
|
||||
sudo apt-get update
|
||||
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: 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
|
||||
- name: submodules
|
||||
run: git submodule update --init --recursive
|
||||
- name: config
|
||||
run: echo WSLAY=-lwslay > Makefile.local
|
||||
- name: make
|
||||
|
|
|
|||
32
.github/workflows/docker.yml
vendored
32
.github/workflows/docker.yml
vendored
|
|
@ -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 \
|
||||
.
|
||||
54
Dockerfile
54
Dockerfile
|
|
@ -1,41 +1,25 @@
|
|||
#
|
||||
# First stage - builder
|
||||
#
|
||||
FROM debian:10-slim AS builder
|
||||
FROM ubuntu:disco
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
ENV LC_ALL=C.UTF-8
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
ENV LC_ALL C.UTF-8
|
||||
|
||||
# This allows you to use a local Debian mirror
|
||||
ARG APT_URL=http://archive.debian.org/debian
|
||||
ARG MAKE_FLAGS=-j2
|
||||
# This allows you to use a local Ubuntu mirror
|
||||
ARG APT_URL=
|
||||
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 \
|
||||
&& apt-get install -y protobuf-compiler libh2o-dev libcurl4-openssl-dev \
|
||||
libssl-dev libprotobuf-dev libh2o-evloop-dev libwslay-dev \
|
||||
libeigen3-dev libzstd-dev libfmt-dev libncurses-dev libfmt-dev \
|
||||
make gcc g++ git build-essential curl autoconf automake help2man
|
||||
|
||||
# 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 gcc g++ git build-essential curl autoconf automake libfmt-dev libncurses5-dev \
|
||||
&& apt-get -y clean
|
||||
|
||||
# Build
|
||||
ADD . /galmon-src/
|
||||
RUN cd /galmon-src/ \
|
||||
&& make $MAKE_FLAGS \
|
||||
&& prefix=/galmon make install
|
||||
ARG MAKE_FLAGS=-j2
|
||||
ADD . /galmon/
|
||||
WORKDIR /galmon/
|
||||
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
18
Dockerfile-pi
Normal 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}
|
||||
98
Makefile
98
Makefile
|
|
@ -1,7 +1,7 @@
|
|||
CFLAGS = -O3 -Wall -ggdb
|
||||
|
||||
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/ \
|
||||
-Iext/sgp4/libsgp4/ \
|
||||
-I/usr/local/include
|
||||
|
|
@ -21,10 +21,11 @@ else ifneq (,$(wildcard ubxsec.o))
|
|||
EXTRADEP = ubxsec.o
|
||||
endif
|
||||
|
||||
|
||||
CHEAT_ARG := $(shell ./update-git-hash-if-necessary)
|
||||
|
||||
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)
|
||||
|
||||
|
|
@ -39,12 +40,12 @@ navmon.pb.cc: 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
|
||||
|
||||
|
||||
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 ext/sgp4/libsgp4/*.d ext/powerblog/ext/simplesocket/*.d
|
||||
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/fmt-6.1.2/src/format.[do] ext/sgp4/libsgp4/*.d ext/powerblog/ext/simplesocket/*.d
|
||||
|
||||
help2man:
|
||||
$(INSTALL) -m 755 -d $(DESTDIR)$(prefix)/share/man/man1
|
||||
|
|
@ -62,76 +63,69 @@ install: $(PROGRAMS) help2man
|
|||
mkdir -p $(DESTDIR)$(prefix)$(htdocs)/galmon
|
||||
cp -a html $(DESTDIR)$(prefix)$(htdocs)/galmon/
|
||||
|
||||
decrypt: decrypt.o bits.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -lfmt
|
||||
download-debian-package:
|
||||
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
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -pthread -L/usr/local/lib -L/usr/local/opt/openssl/lib/ -lz -lcurl -lprotobuf -lfmt
|
||||
download-raspbian-package:
|
||||
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
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -pthread -L/usr/local/lib -lprotobuf -lcurl -lfmt
|
||||
decrypt: decrypt.o bits.o ext/fmt-6.1.2/src/format.o
|
||||
$(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
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -pthread -L/usr/local/lib -lprotobuf -lcurl -lfmt
|
||||
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 -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
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -pthread -L/usr/local/lib -lprotobuf -lcurl -lfmt
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -pthread -L/usr/local/lib -lprotobuf -lcurl -lfmt
|
||||
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
|
||||
|
||||
|
||||
# rs.o fixhunter.o
|
||||
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 -lfmt
|
||||
# -lfec
|
||||
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}
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf -lz
|
||||
|
||||
navdisplay: navdisplay.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
|
||||
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
|
||||
|
||||
|
||||
navnexus: navnexus.o $(SIMPLESOCKETS) bits.o navmon.pb.o storage.o githash.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf -lfmt
|
||||
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
|
||||
|
||||
navcat: navcat.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
|
||||
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
|
||||
|
||||
|
||||
navrecv: navrecv.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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
tlecatch: tlecatch.o $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) githash.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf
|
||||
|
||||
rinreport: rinreport.o rinex.o githash.o navmon.o ephemeris.o osen.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -lz -pthread -lfmt
|
||||
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
|
||||
|
||||
rinjoin: rinjoin.o rinex.o githash.o navmon.o ephemeris.o osen.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -lz -pthread -lfmt
|
||||
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 $@ -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
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -lz -pthread -lprotobuf -lzstd -lfmt
|
||||
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 -lprotobuf -pthread -lzstd
|
||||
|
||||
|
||||
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 -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
|
||||
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
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -lprotobuf -lz
|
||||
|
||||
check: testrunner
|
||||
./testrunner
|
||||
|
|
|
|||
160
README.md
160
README.md
|
|
@ -4,17 +4,14 @@ galileo/GPS/GLONASS/BeiDou open source monitoring. GPL3 licensed.
|
|||
|
||||
Live website: https://galmon.eu/
|
||||
|
||||
Multi-vendor, with support for U-blox 8 and 9 chipsets and many Septentrio
|
||||
devices. Navilock NL-8012U receiver works really well, as does the U-blox
|
||||
evaluation kit for the 8MT. In addition, many stations have reported
|
||||
success with this very cheap [AliExpress sourced
|
||||
device](https://www.aliexpress.com/item/32816656706.html).
|
||||
|
||||
For ublox, there is good support for the F9P, several of us use the
|
||||
[ArdusimpleRTK2B](https://www.ardusimple.com/simplertk2b/) board. It adds
|
||||
the Galileo E5b band.
|
||||
|
||||
Septentrio devices support even more bands.
|
||||
Theoretically multi-vendor, although currently only the U-blox 8 and 9
|
||||
chipsets are supported. Navilock NL-8012U receiver works really well, as
|
||||
does the U-blox evaluation kit for the 8MT. In addition, many stations have
|
||||
reported success with this very cheap [AliExpress sourced
|
||||
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
|
||||
F9P, several of us use the
|
||||
[ArdusimpleRTK2B](https://www.ardusimple.com/simplertk2b/) board.
|
||||
|
||||
An annotated presentation about our project aimed at GNSS professionals can
|
||||
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
|
||||
[The Galmon GNSS Monitoring Project](https://berthub.eu/articles/posts/galmon-project/)
|
||||
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
|
||||
----------
|
||||
|
||||
* Support for Septentrio and U-blox.
|
||||
* 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)
|
||||
so far.
|
||||
* All-band support (E1, E5b, B1I, B2I, Glonass L1, Glonass L2, GPS L1C/A)
|
||||
so far, GPS L2C and Galileo E5a pending).
|
||||
* Calculate ephemeris positions
|
||||
* Comparison of ephemerides to independent SP3 data to determine SISE
|
||||
* Globally, locally, worst user location
|
||||
|
|
@ -53,7 +49,7 @@ Highlights
|
|||
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
|
||||
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
|
||||
timeframes.
|
||||
|
|
@ -72,8 +68,8 @@ Goals:
|
|||
|
||||
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),
|
||||
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:
|
||||
|
||||
```
|
||||
apt-get install protobuf-compiler libcurl4-openssl-dev libssl-dev libprotobuf-dev \
|
||||
libncurses5-dev libeigen3-dev libzstd-dev g++ libfmt-dev
|
||||
git clone https://github.com/berthubert/galmon.git --recursive
|
||||
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
|
||||
git clone https://github.com/ahupowerdns/galmon.git --recursive
|
||||
cd galmon
|
||||
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:
|
||||
```
|
||||
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):
|
||||
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
|
||||
-------
|
||||
On u-blox:
|
||||
|
||||
Once compiled, run for example `./ubxtool --wait --port /dev/ttyACM0
|
||||
--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
|
||||
you should be in business. ubxtool changes (non-permanently) the
|
||||
configuration of your u-blox receiver so it emits the required frames for
|
||||
|
|
@ -174,13 +149,9 @@ to stdout, add `--stdout`.
|
|||
|
||||
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
|
||||
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
|
||||
frames & metadata. Not done yet.
|
||||
* navrecv: receives GNSS NAV frames and stores them on disk, split out per
|
||||
|
|
@ -193,11 +164,6 @@ Tooling:
|
|||
computations on ephemerides.
|
||||
* grafana dashboard: makes pretty graphs
|
||||
|
||||
Per device notes
|
||||
----------------
|
||||
The "SparkFun GNSS L1/L5 Breakout - NEO-F10N, SMA" needs '-u1 -b 38400
|
||||
--wait'.
|
||||
|
||||
Linux Systemd
|
||||
-------------
|
||||
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.
|
||||
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
|
||||
---------
|
||||
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)
|
||||
not actually relevant for the CDMA aspects, but has appendices on more
|
||||
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 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)
|
||||
|
||||
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.
|
||||
* 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:
|
||||
|
||||
```
|
||||
WN=2111
|
||||
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
|
||||
----
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
#include "beidou.hh"
|
||||
#include "bits.hh"
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include "navmon.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
|
@ -31,7 +29,7 @@ static int checkbds(int bits)
|
|||
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
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
||||
return makeVec(buffer, 28);
|
||||
return std::basic_string<uint8_t>(buffer, 28);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
23
beidou.hh
23
beidou.hh
|
|
@ -4,10 +4,9 @@
|
|||
#include "bits.hh"
|
||||
#include <math.h>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
#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);
|
||||
|
||||
/* Geostationary, so D2, so not to be parsed by this parser:
|
||||
|
|
@ -22,7 +21,7 @@ struct BeidouMessage : GPSLikeEphemeris
|
|||
{
|
||||
uint8_t strtype;
|
||||
|
||||
std::vector<uint8_t> g_cond;
|
||||
std::basic_string_view<uint8_t> g_cond;
|
||||
int bbitu(int bit, int 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 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;
|
||||
if(pageno)
|
||||
|
|
@ -90,7 +89,7 @@ struct BeidouMessage : GPSLikeEphemeris
|
|||
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);
|
||||
aodc = bbitu(31+13, 5);
|
||||
|
|
@ -125,7 +124,7 @@ struct BeidouMessage : GPSLikeEphemeris
|
|||
return -1;
|
||||
}
|
||||
|
||||
void parse2(const std::vector<uint8_t>& cond)
|
||||
void parse2(std::basic_string_view<uint8_t> cond)
|
||||
{
|
||||
deltan = bbits(43, 16);
|
||||
cuc = bbits(67, 18);
|
||||
|
|
@ -152,7 +151,7 @@ struct BeidouMessage : GPSLikeEphemeris
|
|||
double getOmega0() const { return ldexp(Omega0 * M_PI, -31); } // radians
|
||||
double getIdot() const { return ldexp(idot * M_PI, -43); } // radians/s
|
||||
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);
|
||||
i0 = bbits(66, 32);
|
||||
|
|
@ -208,7 +207,7 @@ struct BeidouMessage : GPSLikeEphemeris
|
|||
} alma;
|
||||
|
||||
// 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.a1 = bbits(91, 11);
|
||||
|
|
@ -229,8 +228,7 @@ struct BeidouMessage : GPSLikeEphemeris
|
|||
|
||||
// 2^-30 2^-50
|
||||
int a0gps, a1gps, a0gal, a1gal, a0glo, a1glo, a0utc, a1utc;
|
||||
int8_t deltaTLS, deltaTLSF;
|
||||
uint8_t wnLSF, dn;
|
||||
int8_t deltaTLS;
|
||||
|
||||
// in Beidou the offset is a0utc + SOW * a1utc
|
||||
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);
|
||||
if(alma.pageno == 9) {
|
||||
|
|
@ -270,9 +268,6 @@ struct BeidouMessage : GPSLikeEphemeris
|
|||
a0utc = bbits(91, 32);
|
||||
a1utc = bbits(131, 24);
|
||||
deltaTLS = bbits(31+12+1+7, 8);
|
||||
deltaTLSF = bbits(61+6, 8);
|
||||
wnLSF = bbits(61+6+8, 8);
|
||||
dn = bbits(151+12, 8);
|
||||
}
|
||||
else {
|
||||
alma.sqrtA = bbitu(51, 24);
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ double getCoordinates(double tow, const T& iod, Point* p, bool quiet=true)
|
|||
|
||||
cerr << "sqrtA = "<< sqrtA << endl;
|
||||
cerr << "deltan = "<< deltan << endl;
|
||||
cerr << "t0e = "<< t0e << "( rel "<<(tow - t0e)<<")"<<endl;
|
||||
cerr << "t0e = "<< t0e << endl;
|
||||
cerr << "m0 = "<< m0 << " ("<<todeg(m0)<<")"<<endl;
|
||||
cerr << "e = "<< e << 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;
|
||||
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 newE;
|
||||
for(int k =0 ; k < 10; ++k) {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -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
|
||||
|
||||
// Distributed under the 3-Clause BSD License. See accompanying
|
||||
// file LICENSE or https://github.com/CLIUtils/CLI11 for details.
|
||||
|
||||
// CLI Library includes
|
||||
// Order is important for combiner script
|
||||
|
||||
#include "Version.hpp"
|
||||
|
||||
#include "Macros.hpp"
|
||||
|
||||
#include "StringTools.hpp"
|
||||
|
||||
#include "Error.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"
|
||||
#include "CLI/Error.hpp"
|
||||
#include "CLI/TypeTools.hpp"
|
||||
#include "CLI/StringTools.hpp"
|
||||
#include "CLI/Split.hpp"
|
||||
#include "CLI/Ini.hpp"
|
||||
#include "CLI/Validators.hpp"
|
||||
#include "CLI/Option.hpp"
|
||||
#include "CLI/App.hpp"
|
||||
|
|
|
|||
|
|
@ -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 §ion, 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 ¤tSection) {
|
||||
|
||||
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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
// Distributed under the 3-Clause BSD License. See accompanying
|
||||
// file LICENSE or https://github.com/CLIUtils/CLI11 for details.
|
||||
|
||||
#include <exception>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
// CLI library includes
|
||||
#include "StringTools.hpp"
|
||||
|
||||
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,
|
||||
/// int values from e.get_error_code().
|
||||
enum class ExitCodes {
|
||||
|
|
@ -40,19 +16,18 @@ enum class ExitCodes {
|
|||
IncorrectConstruction = 100,
|
||||
BadNameString,
|
||||
OptionAlreadyAdded,
|
||||
FileError,
|
||||
ConversionError,
|
||||
ValidationError,
|
||||
RequiredError,
|
||||
RequiresError,
|
||||
ExcludesError,
|
||||
ExtrasError,
|
||||
ConfigError,
|
||||
InvalidError,
|
||||
HorribleError,
|
||||
File,
|
||||
Conversion,
|
||||
Validation,
|
||||
Required,
|
||||
Requires,
|
||||
Excludes,
|
||||
Extras,
|
||||
ExtrasINI,
|
||||
Invalid,
|
||||
Horrible,
|
||||
OptionNotFound,
|
||||
ArgumentMismatch,
|
||||
BaseClass = 127
|
||||
BaseClass = 255
|
||||
};
|
||||
|
||||
// Error definitions
|
||||
|
|
@ -64,277 +39,127 @@ enum class ExitCodes {
|
|||
/// @{
|
||||
|
||||
/// All errors derive from this one
|
||||
class Error : public std::runtime_error {
|
||||
int actual_exit_code;
|
||||
std::string error_name{"Error"};
|
||||
|
||||
public:
|
||||
int get_exit_code() const { return actual_exit_code; }
|
||||
|
||||
std::string get_name() const { return error_name; }
|
||||
|
||||
Error(std::string name, std::string msg, int exit_code = static_cast<int>(ExitCodes::BaseClass))
|
||||
: runtime_error(msg), actual_exit_code(exit_code), error_name(std::move(name)) {}
|
||||
|
||||
Error(std::string name, std::string msg, ExitCodes exit_code) : Error(name, msg, static_cast<int>(exit_code)) {}
|
||||
struct Error : public std::runtime_error {
|
||||
int exit_code;
|
||||
bool print_help;
|
||||
int get_exit_code() const { return exit_code; }
|
||||
Error(std::string parent, std::string name, ExitCodes exit_code = ExitCodes::BaseClass, bool print_help = true)
|
||||
: runtime_error(parent + ": " + name), exit_code(static_cast<int>(exit_code)), print_help(print_help) {}
|
||||
Error(std::string parent,
|
||||
std::string name,
|
||||
int exit_code = static_cast<int>(ExitCodes::BaseClass),
|
||||
bool print_help = true)
|
||||
: runtime_error(parent + ": " + name), exit_code(exit_code), print_help(print_help) {}
|
||||
};
|
||||
|
||||
// Note: Using Error::Error constructors does not work on GCC 4.7
|
||||
|
||||
/// Construction errors (not in parsing)
|
||||
class ConstructionError : public Error {
|
||||
CLI11_ERROR_DEF(Error, ConstructionError)
|
||||
struct ConstructionError : public Error {
|
||||
// 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)
|
||||
class IncorrectConstruction : public ConstructionError {
|
||||
CLI11_ERROR_DEF(ConstructionError, IncorrectConstruction)
|
||||
CLI11_ERROR_SIMPLE(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");
|
||||
}
|
||||
struct IncorrectConstruction : public ConstructionError {
|
||||
IncorrectConstruction(std::string name)
|
||||
: ConstructionError("IncorrectConstruction", name, ExitCodes::IncorrectConstruction) {}
|
||||
};
|
||||
|
||||
/// Thrown on construction of a bad name
|
||||
class BadNameString : public ConstructionError {
|
||||
CLI11_ERROR_DEF(ConstructionError, 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);
|
||||
}
|
||||
struct BadNameString : public ConstructionError {
|
||||
BadNameString(std::string name) : ConstructionError("BadNameString", name, ExitCodes::BadNameString) {}
|
||||
};
|
||||
|
||||
/// Thrown when an option already exists
|
||||
class OptionAlreadyAdded : public ConstructionError {
|
||||
CLI11_ERROR_DEF(ConstructionError, OptionAlreadyAdded)
|
||||
explicit OptionAlreadyAdded(std::string name)
|
||||
: 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);
|
||||
}
|
||||
struct OptionAlreadyAdded : public ConstructionError {
|
||||
OptionAlreadyAdded(std::string name)
|
||||
: ConstructionError("OptionAlreadyAdded", name, ExitCodes::OptionAlreadyAdded) {}
|
||||
};
|
||||
|
||||
// Parsing errors
|
||||
|
||||
/// Anything that can error in Parse
|
||||
class ParseError : public Error {
|
||||
CLI11_ERROR_DEF(Error, ParseError)
|
||||
struct ParseError : public Error {
|
||||
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"
|
||||
|
||||
/// This is a successful completion on parsing, supposed to exit
|
||||
class Success : public ParseError {
|
||||
CLI11_ERROR_DEF(ParseError, Success)
|
||||
Success() : Success("Successfully completed, should be caught and quit", ExitCodes::Success) {}
|
||||
struct Success : public ParseError {
|
||||
Success() : ParseError("Success", "Successfully completed, should be caught and quit", ExitCodes::Success, false) {}
|
||||
};
|
||||
|
||||
/// -h or --help on command line
|
||||
class CallForHelp : public ParseError {
|
||||
CLI11_ERROR_DEF(ParseError, CallForHelp)
|
||||
CallForHelp() : 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) {}
|
||||
struct CallForHelp : public ParseError {
|
||||
CallForHelp()
|
||||
: ParseError("CallForHelp", "This should be caught in your main function, see examples", ExitCodes::Success) {}
|
||||
};
|
||||
|
||||
/// Thrown when parsing an INI file and it is missing
|
||||
class FileError : public ParseError {
|
||||
CLI11_ERROR_DEF(ParseError, FileError)
|
||||
CLI11_ERROR_SIMPLE(FileError)
|
||||
static FileError Missing(std::string name) { return FileError(name + " was not readable (missing?)"); }
|
||||
struct FileError : public ParseError {
|
||||
FileError(std::string name) : ParseError("FileError", name, ExitCodes::File) {}
|
||||
};
|
||||
|
||||
/// Thrown when conversion call back fails, such as when an int fails to coerce to a string
|
||||
class ConversionError : public ParseError {
|
||||
CLI11_ERROR_DEF(ParseError, ConversionError)
|
||||
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 conversion call back fails, such as when an int fails to coerse to a string
|
||||
struct ConversionError : public ParseError {
|
||||
ConversionError(std::string name) : ParseError("ConversionError", name, ExitCodes::Conversion) {}
|
||||
};
|
||||
|
||||
/// Thrown when validation of results fails
|
||||
class ValidationError : public ParseError {
|
||||
CLI11_ERROR_DEF(ParseError, ValidationError)
|
||||
CLI11_ERROR_SIMPLE(ValidationError)
|
||||
explicit ValidationError(std::string name, std::string msg) : ValidationError(name + ": " + msg) {}
|
||||
struct ValidationError : public ParseError {
|
||||
ValidationError(std::string name) : ParseError("ValidationError", name, ExitCodes::Validation) {}
|
||||
};
|
||||
|
||||
/// Thrown when a required option is missing
|
||||
class RequiredError : public ParseError {
|
||||
CLI11_ERROR_DEF(ParseError, RequiredError)
|
||||
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");
|
||||
}
|
||||
struct RequiredError : public ParseError {
|
||||
RequiredError(std::string name) : ParseError("RequiredError", name, ExitCodes::Required) {}
|
||||
};
|
||||
|
||||
/// Thrown when a requires option is missing
|
||||
class RequiresError : public ParseError {
|
||||
CLI11_ERROR_DEF(ParseError, RequiresError)
|
||||
RequiresError(std::string curname, std::string subname)
|
||||
: RequiresError(curname + " requires " + subname, ExitCodes::RequiresError) {}
|
||||
struct RequiresError : public ParseError {
|
||||
RequiresError(std::string name, std::string subname)
|
||||
: ParseError("RequiresError", name + " requires " + subname, ExitCodes::Requires) {}
|
||||
};
|
||||
|
||||
/// Thrown when an excludes option is present
|
||||
class ExcludesError : public ParseError {
|
||||
CLI11_ERROR_DEF(ParseError, ExcludesError)
|
||||
ExcludesError(std::string curname, std::string subname)
|
||||
: ExcludesError(curname + " excludes " + subname, ExitCodes::ExcludesError) {}
|
||||
/// Thrown when a exludes option is present
|
||||
struct ExcludesError : public ParseError {
|
||||
ExcludesError(std::string name, std::string subname)
|
||||
: ParseError("ExcludesError", name + " excludes " + subname, ExitCodes::Excludes) {}
|
||||
};
|
||||
|
||||
/// Thrown when too many positionals or options are found
|
||||
class ExtrasError : public ParseError {
|
||||
CLI11_ERROR_DEF(ParseError, ExtrasError)
|
||||
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) {}
|
||||
struct ExtrasError : public ParseError {
|
||||
ExtrasError(std::string name) : ParseError("ExtrasError", name, ExitCodes::Extras) {}
|
||||
};
|
||||
|
||||
/// Thrown when extra values are found in an INI file
|
||||
class ConfigError : public ParseError {
|
||||
CLI11_ERROR_DEF(ParseError, ConfigError)
|
||||
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");
|
||||
}
|
||||
struct ExtrasINIError : public ParseError {
|
||||
ExtrasINIError(std::string name) : ParseError("ExtrasINIError", name, ExitCodes::ExtrasINI) {}
|
||||
};
|
||||
|
||||
/// Thrown when validation fails before parsing
|
||||
class InvalidError : public ParseError {
|
||||
CLI11_ERROR_DEF(ParseError, InvalidError)
|
||||
explicit InvalidError(std::string name)
|
||||
: InvalidError(name + ": Too many positional arguments with unlimited expected args", ExitCodes::InvalidError) {
|
||||
}
|
||||
struct InvalidError : public ParseError {
|
||||
InvalidError(std::string name) : ParseError("InvalidError", name, ExitCodes::Invalid) {}
|
||||
};
|
||||
|
||||
/// This is just a safety check to verify selection and parsing match - you should not ever see it
|
||||
/// Strings are directly added to this error, but again, it should never be seen.
|
||||
class HorribleError : public ParseError {
|
||||
CLI11_ERROR_DEF(ParseError, HorribleError)
|
||||
CLI11_ERROR_SIMPLE(HorribleError)
|
||||
/// This is just a safety check to verify selection and parsing match
|
||||
struct HorribleError : public ParseError {
|
||||
HorribleError(std::string name)
|
||||
: ParseError("HorribleError", "(You should never see this error) " + name, ExitCodes::Horrible) {}
|
||||
};
|
||||
|
||||
// After parsing
|
||||
|
||||
/// Thrown when counting a non-existent option
|
||||
class OptionNotFound : public Error {
|
||||
CLI11_ERROR_DEF(Error, OptionNotFound)
|
||||
explicit OptionNotFound(std::string name) : OptionNotFound(name + " not found", ExitCodes::OptionNotFound) {}
|
||||
struct OptionNotFound : public Error {
|
||||
OptionNotFound(std::string name) : Error("OptionNotFound", name, ExitCodes::OptionNotFound) {}
|
||||
};
|
||||
|
||||
#undef CLI11_ERROR_DEF
|
||||
#undef CLI11_ERROR_SIMPLE
|
||||
|
||||
/// @}
|
||||
|
||||
} // namespace CLI
|
||||
} // namespace CLI
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
115
ext/CLI11/CLI/Ini.hpp
Normal 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
|
||||
|
|
@ -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
|
|
@ -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
|
||||
|
||||
// Distributed under the 3-Clause BSD License. See accompanying
|
||||
// file LICENSE or https://github.com/CLIUtils/CLI11 for details.
|
||||
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "Error.hpp"
|
||||
#include "StringTools.hpp"
|
||||
#include "CLI/Error.hpp"
|
||||
#include "CLI/StringTools.hpp"
|
||||
|
||||
namespace CLI {
|
||||
namespace detail {
|
||||
|
|
@ -23,14 +19,14 @@ inline bool split_short(const std::string ¤t, std::string &name, std::stri
|
|||
name = current.substr(1, 1);
|
||||
rest = current.substr(2);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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 ¤t, std::string &name, std::string &value) {
|
||||
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) {
|
||||
name = current.substr(2, loc - 2);
|
||||
value = current.substr(loc + 1);
|
||||
|
|
@ -39,62 +35,19 @@ inline bool split_long(const std::string ¤t, std::string &name, std::strin
|
|||
value = "";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
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 ¤t, 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;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
// Splits a string into multiple long and short names
|
||||
inline std::vector<std::string> split_names(std::string current) {
|
||||
std::vector<std::string> output;
|
||||
std::size_t val;
|
||||
size_t val;
|
||||
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);
|
||||
}
|
||||
output.push_back(trim_copy(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);
|
||||
}
|
||||
output.push_back(current);
|
||||
return output;
|
||||
}
|
||||
|
||||
|
|
@ -107,25 +60,24 @@ get_names(const std::vector<std::string> &input) {
|
|||
std::string pos_name;
|
||||
|
||||
for(std::string name : input) {
|
||||
if(name.length() == 0) {
|
||||
if(name.length() == 0)
|
||||
continue;
|
||||
}
|
||||
if(name.length() > 1 && name[0] == '-' && name[1] != '-') {
|
||||
else if(name.length() > 1 && name[0] == '-' && name[1] != '-') {
|
||||
if(name.length() == 2 && valid_first_char(name[1]))
|
||||
short_names.emplace_back(1, name[1]);
|
||||
else
|
||||
throw BadNameString::OneCharName(name);
|
||||
throw BadNameString("Invalid one char name: " + name);
|
||||
} else if(name.length() > 2 && name.substr(0, 2) == "--") {
|
||||
name = name.substr(2);
|
||||
if(valid_name_string(name))
|
||||
long_names.push_back(name);
|
||||
else
|
||||
throw BadNameString::BadLongName(name);
|
||||
throw BadNameString("Bad long name: " + name);
|
||||
} else if(name == "-" || name == "--") {
|
||||
throw BadNameString::DashesOnly(name);
|
||||
throw BadNameString("Must have a name, not just dashes");
|
||||
} else {
|
||||
if(pos_name.length() > 0)
|
||||
throw BadNameString::MultiPositionalNames(name);
|
||||
throw BadNameString("Only one positional name allowed, remove: " + name);
|
||||
pos_name = name;
|
||||
}
|
||||
}
|
||||
|
|
@ -134,5 +86,5 @@ get_names(const std::vector<std::string> &input) {
|
|||
short_names, long_names, pos_name);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace CLI
|
||||
} // namespace detail
|
||||
} // namespace CLI
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue