Compare commits
No commits in common. "spacecruft" and "hzcorr" have entirely different histories.
spacecruft
...
hzcorr
|
@ -4,15 +4,15 @@ on: [push]
|
|||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
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
|
||||
run: sudo apt-get install protobuf-compiler libh2o-dev libcurl4-openssl-dev libssl-dev libprotobuf-dev libh2o-evloop-dev libwslay-dev
|
||||
- name: submodules
|
||||
run: git submodule update --init --recursive
|
||||
- name: config
|
||||
run: echo WSLAY=-lwslay > Makefile.local
|
||||
- name: make
|
||||
|
|
|
@ -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@v2
|
||||
- name: Checkout submodules
|
||||
run: git submodule update --init --recursive
|
||||
- name: Set up docker buildx
|
||||
id: buildx
|
||||
uses: crazy-max/ghaction-docker-buildx@v3
|
||||
with:
|
||||
buildx-version: latest
|
||||
qemu-version: latest
|
||||
- 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 \
|
||||
.
|
|
@ -1,24 +1,5 @@
|
|||
# - created by protoc
|
||||
navmon.pb.cc
|
||||
navmon.pb.h
|
||||
# - created by Makefile
|
||||
navcat
|
||||
navdisplay
|
||||
navdump
|
||||
navnexus
|
||||
navrecv
|
||||
testrunner
|
||||
tlecatch
|
||||
ubxtool
|
||||
# - created by update-tles
|
||||
active.txt
|
||||
beidou.txt
|
||||
galileo.txt
|
||||
glo-ops.txt
|
||||
gps-ops.txt
|
||||
# - git version internals
|
||||
githash.h
|
||||
#
|
||||
*.csv
|
||||
# Prerequisites
|
||||
*.d
|
||||
|
|
|
@ -4,4 +4,3 @@
|
|||
[submodule "ext/sgp4"]
|
||||
path = ext/sgp4
|
||||
url = https://github.com/dnwrnr/sgp4.git
|
||||
ignore = untracked
|
||||
|
|
19
Building.md
19
Building.md
|
@ -1,19 +0,0 @@
|
|||
Raspberry Pi 3B+
|
||||
---------------
|
||||
|
||||
Building more than ubxtool, navdump, and navparse will require quite a bit of available RAM.
|
||||
In the Raspbian image, the default swap file size for _/var/swap_ is only 100MB.
|
||||
More swap space will be needed if you want the compiling to finish without it seeming
|
||||
to take a geologic age.
|
||||
|
||||
The swap file is configured in _/etc/dphys-swapfile_ and managed using the __/sbin/dphys-swapfile__ utility.
|
||||
|
||||
Assuming the output of _df -h_ and _free_ indicate you have sufficient free file space and
|
||||
the swap file is indeed 100MB (respectively), and you do not have Chromium running (it eats
|
||||
RAM), you edit the config file (suggest changing 100 to 960) and then (as root):
|
||||
```
|
||||
/sbin/dphys-swapfile swapoff
|
||||
echo Be Patient. This could take a while
|
||||
/sbin/dphys-swapfile setup
|
||||
/sbin/dphys-swapfile swapon
|
||||
```
|
38
Dockerfile
38
Dockerfile
|
@ -1,38 +0,0 @@
|
|||
#
|
||||
# First stage - builder
|
||||
#
|
||||
FROM debian:10-slim AS builder
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
ENV LC_ALL C.UTF-8
|
||||
|
||||
# This allows you to use a local Debian mirror
|
||||
ARG APT_URL=http://deb.debian.org/debian/
|
||||
ARG MAKE_FLAGS=-j2
|
||||
|
||||
RUN sed -i "s%http://deb.debian.org/debian/%${APT_URL}%" /etc/apt/sources.list \
|
||||
&& 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 \
|
||||
make gcc g++ git build-essential curl autoconf automake help2man
|
||||
|
||||
# Build
|
||||
ADD . /galmon-src/
|
||||
RUN cd /galmon-src/ \
|
||||
&& make $MAKE_FLAGS \
|
||||
&& prefix=/galmon make install
|
||||
|
||||
#
|
||||
# Second stage - contains just the binaries
|
||||
#
|
||||
FROM debian:10-slim
|
||||
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
|
132
Makefile
132
Makefile
|
@ -1,31 +1,12 @@
|
|||
CFLAGS = -O3 -Wall -ggdb
|
||||
|
||||
CXXFLAGS:= -std=gnu++17 -Wall -O3 -ggdb -MMD -MP -fno-omit-frame-pointer -Iext/CLI11 \
|
||||
-Iext/fmt-6.1.2/include/ -Iext/powerblog/ext/simplesocket -Iext/powerblog/ext/ \
|
||||
CXXFLAGS:= -std=gnu++17 -Wall -O3 -MMD -MP -ggdb -fno-omit-frame-pointer -Iext/CLI11 \
|
||||
-Iext/fmt-5.2.1/include/ -Iext/powerblog/ext/simplesocket -Iext/powerblog/ext/ \
|
||||
-I/usr/local/opt/openssl/include/ \
|
||||
-Iext/sgp4/libsgp4/ \
|
||||
-I/usr/local/include
|
||||
|
||||
# CXXFLAGS += -Wno-delete-non-virtual-dtor
|
||||
|
||||
# If unset, create a variable for the path or binary to use as "install" for debuild.
|
||||
INSTALL ?= install
|
||||
# If unset, create a variable with the path used by "make install"
|
||||
prefix ?= /usr/local/ubxtool
|
||||
# If unset, create a variable for a path underneath $prefix that stores html files
|
||||
htdocs ?= /share/package
|
||||
|
||||
ifneq (,$(wildcard ubxsec.c))
|
||||
EXTRADEP = ubxsec.o
|
||||
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
|
||||
PROGRAMS = navparse ubxtool navnexus navrecv navdump testrunner navdisplay tlecatch
|
||||
|
||||
all: navmon.pb.cc $(PROGRAMS)
|
||||
|
||||
|
@ -33,114 +14,39 @@ all: navmon.pb.cc $(PROGRAMS)
|
|||
|
||||
-include *.d
|
||||
|
||||
navmon.pb.h: navmon.proto
|
||||
protoc --cpp_out=./ navmon.proto
|
||||
|
||||
navmon.pb.cc: navmon.proto
|
||||
protoc --cpp_out=./ navmon.proto
|
||||
|
||||
clean:
|
||||
rm -f *~ *.o *.d ext/*/*.o $(PROGRAMS) navmon.pb.h navmon.pb.cc
|
||||
|
||||
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)) $(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
|
||||
HELP2MAN_DESCRIPTION="Open-source GNSS Monitoring Project"
|
||||
$(foreach binaryfile,$(PROGRAMS),help2man -N -n "$(HELP2MAN_DESCRIPTION)" ./$(binaryfile) | gzip > $(DESTDIR)$(prefix)/share/man/man1/$(binaryfile).1.gz;)
|
||||
@echo until these binaries support --help and --version remove the broken output
|
||||
rm -f $(DESTDIR)$(prefix)/share/man/man1/rinreport.1.gz
|
||||
rm -f $(DESTDIR)$(prefix)/share/man/man1/rtcmtool.1.gz
|
||||
rm -f $(DESTDIR)$(prefix)/share/man/man1/testrunner.1.gz
|
||||
|
||||
install: $(PROGRAMS) help2man
|
||||
$(INSTALL) -m 755 -d $(DESTDIR)$(prefix)/bin
|
||||
$(foreach binaryfile,$(PROGRAMS),$(INSTALL) -s -m 755 -D ./$(binaryfile) $(DESTDIR)$(prefix)/bin/$(binaryfile);)
|
||||
@echo "using cp instead of install because recursive directories of ascii"
|
||||
mkdir -p $(DESTDIR)$(prefix)$(htdocs)/galmon
|
||||
cp -a html $(DESTDIR)$(prefix)$(htdocs)/galmon/
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
decrypt: decrypt.o bits.o ext/fmt-6.1.2/src/format.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@
|
||||
|
||||
navparse: navparse.o ext/fmt-6.1.2/src/format.o $(H2OPP) $(SIMPLESOCKETS) minicurl.o ubx.o bits.o navmon.pb.o gps.o ephemeris.o beidou.o glonass.o $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) tle.o navmon.o coverage.o osen.o trkmeas.o influxpush.o ${EXTRADEP} githash.o sbas.o rtcm.o galileo.o
|
||||
navparse: navparse.o ext/fmt-5.2.1/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
|
||||
$(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
|
||||
navdump: navdump.o ext/fmt-5.2.1/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
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf
|
||||
|
||||
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 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 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
|
||||
|
||||
|
||||
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 galileo.o ${EXTRADEP}
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf -lz
|
||||
|
||||
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
|
||||
navdisplay: navdisplay.o ext/fmt-5.2.1/src/format.o bits.o navmon.pb.o gps.o ephemeris.o beidou.o glonass.o ephemeris.o navmon.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf -lncurses
|
||||
|
||||
|
||||
navnexus: navnexus.o ext/fmt-6.1.2/src/format.o $(SIMPLESOCKETS) bits.o navmon.pb.o storage.o githash.o
|
||||
navnexus: navnexus.o ext/fmt-5.2.1/src/format.o $(SIMPLESOCKETS) ubx.o bits.o navmon.pb.o storage.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf
|
||||
|
||||
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 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
|
||||
|
||||
navmerge: navmerge.o ext/fmt-6.1.2/src/format.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
|
||||
|
||||
|
||||
tlecatch: tlecatch.o $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) githash.o
|
||||
navrecv: navrecv.o ext/fmt-5.2.1/src/format.o $(SIMPLESOCKETS) navmon.pb.o storage.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf
|
||||
|
||||
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
|
||||
tlecatch: tlecatch.o $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc))
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf
|
||||
|
||||
rinjoin: rinjoin.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
|
||||
navmon.pb.cc: navmon.proto
|
||||
protoc --cpp_out=./ navmon.proto
|
||||
|
||||
ubxtool: navmon.pb.o ubxtool.o ubx.o bits.o ext/fmt-5.2.1/src/format.o galileo.o gps.o beidou.o navmon.o ephemeris.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -lprotobuf
|
||||
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
|
||||
septool: navmon.pb.o septool.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
|
||||
|
||||
|
||||
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 -pthread
|
||||
|
||||
gndate: gndate.o githash.o ext/fmt-6.1.2/src/format.o navmon.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib
|
||||
testrunner: navmon.pb.o testrunner.o ubx.o bits.o ext/fmt-5.2.1/src/format.o galileo.o gps.o beidou.o ephemeris.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -lprotobuf
|
||||
|
||||
check: testrunner
|
||||
./testrunner
|
||||
|
|
54
Operator.md
54
Operator.md
|
@ -1,54 +0,0 @@
|
|||
Hi!
|
||||
|
||||
Many thanks for joining the galmon.eu project as an observer and station operator!
|
||||
|
||||
Here are some ground rules to follow once you have locally tested your galmon station:
|
||||
|
||||
* Please ask bert@hubertnet.nl or [ahu on
|
||||
IRC](https://webchat.oftc.net/?channels=galileo) or
|
||||
[@PowerDNS_Bert](https://twitter.com/PowerDNS_Bert) for a station ID and receiver
|
||||
hostname.
|
||||
* Please do not randomly pick a station ID!! Project data quality and integrity
|
||||
depends on your cooperation.
|
||||
* The rule is: one station ID per receiver. Do not under any circumstances
|
||||
use the same ID on multiple receivers, even if you think it should be
|
||||
fine. We have 2^64 IDs available, just ask for more IDs.
|
||||
* Even if you think it should be fine to reuse the same ID, for example
|
||||
because your observer page on galmon.eu will look nicer, DO NOT DO IT!
|
||||
It messes up our algorithms which track the clock of your receiver.
|
||||
* Please set the --owner and --remark fields - they help with administration.
|
||||
These fields will show up on https://galmon.eu/observers.html
|
||||
* Ublox8 based receivers support (GPS AND Galileo) + (BeiDou OR GLONASS). They can't
|
||||
process all four systems at the same time. GPS and Galileo share a slot. BeiDou or GLONASS
|
||||
share a second slot. See
|
||||
[here](https://www.geospatialworld.net/blogs/gnss-frequency-bands-for-constellations/)
|
||||
for some more information.
|
||||
* If you want to know if you should add BeiDou or GLONASS, you can either use your own
|
||||
preference or ask us what we are missing in your region.
|
||||
|
||||
Downtime
|
||||
--------
|
||||
The project is grateful for every bit you can provide. Don't feel bad about
|
||||
downtime either because your family needs the power plug for Christmas
|
||||
decorations (this happened) or for whatever reason.
|
||||
|
||||
A good way to increase uptime is to use the Systemd file to ensure automatic
|
||||
startup on reboots. Some stations with bad power, bad hardware or bad cables
|
||||
have had great success enabling the Raspberry Pi watchdog.
|
||||
|
||||
(need instruction here how to do that for various Raspberries, it differs)
|
||||
|
||||
Upgrades
|
||||
--------
|
||||
From time to time we upgrade the ubxtool receiver software. Old data also
|
||||
works but typically if you upgrade, the value to the network increases.
|
||||
|
||||
Improving reception
|
||||
-------------------
|
||||
A good view of the sky can help tremendously, although as noted, we are
|
||||
happy with every bit we receive. It helps to put your receiver on a "ground
|
||||
plate" which is a fancy word for a piece of metal (or mesh even).
|
||||
|
||||
In addition, if you are in a northerly region, your view to the south is
|
||||
most important. Conversely, if you are in the south, more action will be to
|
||||
the north.
|
|
@ -1,112 +0,0 @@
|
|||
# .deb Package Overview
|
||||
|
||||
2020-01-20: Initial Commit
|
||||
|
||||
## Important Information
|
||||
|
||||
This section is a step-by-step tutorial.
|
||||
|
||||
### Basic Installation
|
||||
|
||||
Pick either debian (almost everything) or raspbian (special armv6 build for older models) and create the source file.
|
||||
```sh
|
||||
echo "deb https://ota.bike/raspbian/ buster main" > /etc/apt/sources.list.d/galmon.list
|
||||
echo "deb https://ota.bike/debian/ buster main" > /etc/apt/sources.list.d/galmon.list
|
||||
```
|
||||
|
||||
Install the file used to ensure the software is verified.
|
||||
```sh
|
||||
apt-key adv --fetch-keys https://ota.bike/public-package-signing-keys/86E7F51C04FBAAB0.asc
|
||||
```
|
||||
|
||||
Update your package list and install galmon. Then create a configuration file and start the daemon.
|
||||
If you have a typical device using the onboard USB at /dev/ttyACM0, drop the directory element
|
||||
and refer to ttyACM0 in both the default variable file and the unit name.
|
||||
```sh
|
||||
apt-get update && apt-get install -y galmon
|
||||
cp /etc/default/galmon /etc/default/ubxtool-ttyACM0
|
||||
systemctl enable --now ubxtool@ttyACM0
|
||||
```
|
||||
|
||||
Alternate or multiple devices just repeats that, updating the device name:
|
||||
```sh
|
||||
cp /etc/default/galmon /etc/default/ubxtool-ttyACM3
|
||||
systemctl enable --now ubxtool@ttyACM3
|
||||
```
|
||||
Both the ubxtool-ttyXYZn file and /dev/ttyXYZn device must exist for the unit file conditions to pass.
|
||||
|
||||
### Automatic Updates
|
||||
|
||||
Armbian and Raspbian have apt-daily timers enabled by default.
|
||||
However, most configurations for unattended installs require customization.
|
||||
|
||||
A simple timer is included that will apply any galmon upgrades every three days:
|
||||
```sh
|
||||
systemctl enable --now galmon-upgrade.timer
|
||||
```
|
||||
|
||||
You can perform an immediate update by hand:
|
||||
```sh
|
||||
apt-get update && apt-get -y install galmon && systemctl restart ubxtool@*
|
||||
```
|
||||
|
||||
## Reference Information
|
||||
|
||||
You can stop reading here if your interest was limited to installing a compiled package.
|
||||
|
||||
### One time steps for bootstrapping package build on a fresh git repo
|
||||
|
||||
Run debmake in the source directory. It tries to autocreate 90% of everything you need in the debian folder.
|
||||
Key files: copyright, changelog, control, and anything else that looks interesting to cat. Once they exist, we're done.
|
||||
Refer to the manual's [tutorial](https://www.debian.org/doc/manuals/debmake-doc/ch04.en.html).
|
||||
|
||||
### One time steps for creating package-specific files and scripts
|
||||
|
||||
Inside the debian directory are files that begin with galmon, the name of the package as defined in the control file.
|
||||
- galmon.postinst: this script is run after installation to verify a system account exists.
|
||||
- galmon.default: this file is installed as /etc/default/galmon
|
||||
- galmon.ubxtool@.service: this unit file uses %i as a reference to the device for computers with multiple inputs.
|
||||
|
||||
### How to build the package locally
|
||||
|
||||
In short you need to set some variables, refer to profile-debuild.sh in the debian/ directory.
|
||||
After that, use debuild to install the package. Signing of the end result may fail and creating
|
||||
GPG key pairs is beyond the scope of this document but just make sure you match the email in the changelog.
|
||||
```sh
|
||||
apt-get install -y build-essential devscripts lintian diffutils patch patchutils
|
||||
git clone $flags galmon.git ; cd galmon
|
||||
./make-githash.h
|
||||
# create and source variables in /etc/profile.d/debuild.sh
|
||||
debuild
|
||||
dpkg -i ../*.deb
|
||||
```
|
||||
|
||||
### Future maintenance considerations
|
||||
|
||||
The githash.h files cannot change after the debuild process has started.
|
||||
For now, the Makefile used by debuild does not run that script and
|
||||
the files must be created before starting the debuild process.
|
||||
|
||||
### Real World Build Results in January 2020
|
||||
|
||||
Avoid compiling on arm6 computers, it is slow. The arm6 used in cheap Raspberry Pi models is an expensive model to support
|
||||
relative to the much faster arm7 and arm8 computers available. Compiling approaches 90 minutes at O3.
|
||||
|
||||
The arm7 and arm8 both compile in 20 to 30 minutes at -j1 -O3 but the 64 bit arm8 has approximately
|
||||
double the RAM requirements during compilation. To avoid swapping, increasing compile time 150%,
|
||||
use hardware with at least 1GB of RAM. The NanoPi Neo2 and NanoPi ZeroPi models with 512MB of RAM
|
||||
are perfect clients, but the OrangePi PCs with 1GB of RAM and the Allwinner H3 or H5 are
|
||||
better suited for smooth building. For comparison, a VM on a low-end AMD Ryzen 3 2200G builds the package at -j1 in about two minutes.
|
||||
|
||||
These are fast multi-core computers but we turn off parallel compiles because of limited RAM.
|
||||
Limiting optimizations to -O0 cuts the compile time in half approximately.
|
||||
|
||||
### Why do this?
|
||||
|
||||
Convenience, uniformity, and scalability:
|
||||
Hand-compiling software is fun, but vendor package management solutions
|
||||
exist to give us reliable unattended installations for free.
|
||||
|
||||
### Signing key
|
||||
|
||||
GPG Public Key [86E7F51C04FBAAB0](debian/86E7F51C04FBAAB0.asc)
|
391
README.md
391
README.md
|
@ -1,62 +1,22 @@
|
|||
# galmon
|
||||
galileo/GPS/GLONASS/BeiDou open source monitoring. GPL3 licensed.
|
||||
(C) AHU Holding BV - bert@hubertnet.nl - https://berthub.eu/
|
||||
(C) AHU Holding BV - bert@hubertnet.nl - https://ds9a.nl/
|
||||
|
||||
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).
|
||||
Theoretically multi-vendor, although currently only the U-blox 8 chipset is
|
||||
supported. Navilock NL-8012U receiver works really well, as does the U-blox evaluation kit for the 8MT.
|
||||
|
||||
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.
|
||||
|
||||
An annotated presentation about our project aimed at GNSS professionals can
|
||||
be found [here](https://berthub.eu/galileo/The%20galmon.eu%20project.pdf).
|
||||
|
||||
> NOTE: One of our programs is called 'ubxtool'. Sadly, we did not do our
|
||||
> research, and there is another '[ubxtool](https://gpsd.io/ubxtool.html)' already, part of
|
||||
> [gpsd](https://gpsd.io). You might have ended up on our page by mistake.
|
||||
> Sorry!
|
||||
|
||||
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/ahupowerdns/galmon/blob/master/Operator.md).
|
||||
|
||||
Highlights
|
||||
----------
|
||||
|
||||
* Support for Septentrio and U-blox.
|
||||
Highlights:
|
||||
* 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.
|
||||
* Calculate ephemeris positions
|
||||
* Comparison of ephemerides to independent SP3 data to determine SISE
|
||||
* Globally, locally, worst user location
|
||||
* Record discontinuities between subsequent ephemerides (in time and space)
|
||||
* Record discontinuities between subsequent ephemerides
|
||||
* Compare doppler shift as reported by receiver with that expected from ephemeris
|
||||
* Track atomic clock & report jumps
|
||||
* Coverage maps (number of satellites >5, >10, >20 elevation)
|
||||
* HDOP/VDOP/PDOP maps
|
||||
* Compare orbit to TLE, match up to best matching satellite
|
||||
* Tear out every bit that tells us how well an SV is doing
|
||||
* Full almanac processing to see what _should_ be transmitting
|
||||
* Distributed receivers, combined into a single source of all messages
|
||||
* Ready to detect/report spoofing/jamming
|
||||
|
||||
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/ahupowerdns/galmon/blob/master/influxdb.md).
|
||||
|
||||
There is also tooling to extract raw frames/strings/words from specific
|
||||
timeframes.
|
||||
|
||||
Goals:
|
||||
|
||||
|
@ -70,20 +30,17 @@ Goals:
|
|||
5) Populate an InfluxDB timeseries database with raw measurements and higher
|
||||
order calculations
|
||||
|
||||
Works on Linux (including Raspbian Buster on Pi Zero W), OSX and OpenBSD.
|
||||
Works on Linux (including Raspbian on Pi Zero W), OSX and OpenBSD.
|
||||
|
||||
Build locally (Linux, Debian, Ubuntu)
|
||||
-------------------------------------
|
||||
To get started, make sure you have a C++17 compiler, git, protobuf-compiler.
|
||||
Then run 'make ubxtool navdump' to build the receiver-only tools.
|
||||
|
||||
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
|
||||
receiver-only tools.
|
||||
To also run the webserver locally, intall libh2o-dev and run 'make'.
|
||||
|
||||
To build everything, including the webserver, try:
|
||||
To build everything, try:
|
||||
|
||||
```
|
||||
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 g++
|
||||
apt-get install protobuf-compiler libh2o-dev libcurl4-openssl-dev libssl-dev libprotobuf-dev libh2o-evloop-dev libwslay-dev
|
||||
git clone https://github.com/ahupowerdns/galmon.git --recursive
|
||||
cd galmon
|
||||
make
|
||||
|
@ -96,56 +53,7 @@ library installed. If you get an error about 'wslay', do the following, and run
|
|||
echo WSLAY=-lwslay > Makefile.local
|
||||
```
|
||||
|
||||
Building on OSX
|
||||
---------------
|
||||
With thanks to a contributor from Prague. First make sure you've installed
|
||||
brew, which you can get [here](https://brew.sh/). Then do:
|
||||
|
||||
```
|
||||
brew install protobuf lzlib zstd h2o eigen
|
||||
```
|
||||
|
||||
And then:
|
||||
```
|
||||
git clone https://github.com/ahupowerdns/galmon.git --recursive
|
||||
cd galmon
|
||||
make
|
||||
```
|
||||
|
||||
Running in Docker
|
||||
-----------------
|
||||
|
||||
We publish official Docker images for galmon on
|
||||
[docker hub](https://hub.docker.com/r/berthubert/galmon) for multiple architectures.
|
||||
|
||||
To run a container with a shell in there (this will also expose a port so you
|
||||
can view the UI too and assumes a ublox GPS device too -
|
||||
you may need to tweak as necessary):
|
||||
|
||||
```
|
||||
docker run -it --rm --device=/dev/ttyACM0 -p 10000:10000 berthubert/galmon
|
||||
```
|
||||
|
||||
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.
|
||||
Once compiled, run for example `./ubxtool --wait /dev/ttyACM0 1 | ./ubxparse 10000 html null`
|
||||
|
||||
Next up, browse to http://[::1]:10000 (or try http://localhost:10000/ and
|
||||
you should be in business. ubxtool changes (non-permanently) the
|
||||
|
@ -153,41 +61,20 @@ configuration of your u-blox receiver so it emits the required frames for
|
|||
GPS and Galileo. If you have a u-blox timing receiver it will also enable
|
||||
the doppler frames.
|
||||
|
||||
By default the ublox receiver module will be configured to use the USB port,
|
||||
if you want to use a different interface port on the ublox module then add
|
||||
the `--ubxport <id>` option using one of the following numeric IDs:
|
||||
|
||||
0 : DDC (aka. I2C)
|
||||
1 : UART[1]
|
||||
2 : UART2
|
||||
3 : USB (default)
|
||||
4 : SPI
|
||||
|
||||
To see what is going on, try:
|
||||
|
||||
```
|
||||
./ubxtool --wait --port /dev/ttyACM0 --station 1 --stdout --galileo | ./navdump
|
||||
./ubxtool --wait /dev/ttyACM0 1 | ./navdump
|
||||
```
|
||||
|
||||
To distribute data to a remote `navrecv`, use:
|
||||
|
||||
```
|
||||
./ubxtool --wait --port /dev/ttyACM0 --galileo --station 255 --destination 127.0.0.1
|
||||
```
|
||||
|
||||
This will send protobuf to 127.0.0.1:29603. You can add as many destinations
|
||||
as you want, they will buffer and automatically reconnect. To also send data
|
||||
to stdout, add `--stdout`.
|
||||
Setting up a distributed setup is slightly more complicated & may still
|
||||
change.
|
||||
|
||||
Tooling:
|
||||
|
||||
* 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
|
||||
|
@ -200,104 +87,17 @@ Tooling:
|
|||
computations on ephemerides.
|
||||
* grafana dashboard: makes pretty graphs
|
||||
|
||||
Linux Systemd
|
||||
-------------
|
||||
First make sure 'ubxtool' has been compiled (run: make ubxtool). Then, as
|
||||
root:
|
||||
```
|
||||
mkdir /usr/local/ubxtool
|
||||
cp ubxtool ubxtool.sh /usr/local/ubxtool/
|
||||
cp ubxtool.service /etc/systemd/system/
|
||||
```
|
||||
|
||||
Then please reach out as indicated in [Operator.md](Operator.md) to obtain your
|
||||
station ID and the receiver hostname and run:
|
||||
|
||||
```
|
||||
echo RECEIVER-NAME > /usr/local/ubxtool/destination
|
||||
echo STATION-NUMBER > /usr/local/ubxtool/station
|
||||
```
|
||||
|
||||
Then start up the service (as root):
|
||||
```
|
||||
systemctl enable ubxtool
|
||||
systemctl start ubxtool
|
||||
```
|
||||
|
||||
To check if it is all working, do 'service ubxtool status'.
|
||||
|
||||
> NOTE! If you don't use one of the AliExpress or Navilock devices, it may
|
||||
> be that your U-blox is not connected to the USB-port of the U-blox chip
|
||||
> but to the UART1 or UART2 port. If so, you'll need to edit the script so
|
||||
> it finds your USB-to-serial adapter. At the very least you'll have to
|
||||
> update the DEVICE line. You'll likely also have to add --ubxport 1 at the
|
||||
> end, and likely also the baudrate (-b) and/or --rtscts=0.
|
||||
|
||||
To change the default constellations, create a file called
|
||||
/usr/local/ubxtool/constellations and set your favorites. To set all four
|
||||
constellations (which only F9-receivers support), do as root:
|
||||
|
||||
```
|
||||
echo --gps --glonass --beidou --galileo > /usr/local/ubxtool/constellations
|
||||
```
|
||||
|
||||
And then 'service ubxtool restart'.
|
||||
|
||||
Distributed setup
|
||||
-----------------
|
||||
Run `navrecv -b :: --storage ./storage` to receive frames on port 29603 of
|
||||
::, aka all your IPv6 addresses (and IPv4 too on Linux). This allows anyone
|
||||
to send you frames, so be aware.
|
||||
Run `navrecv :: ./storage` to receive frames on port 29603 of ::, aka all your IPv6 addresses (and IPv4 too on Linux).
|
||||
This allows anyone to send you frames, so be aware.
|
||||
|
||||
Next up, run `navnexus --storage ./storage -b ::`, which will serve your
|
||||
recorded data from port 29601. It will merge messages coming in from all
|
||||
sources and serve them in time order.
|
||||
Next up, run `navnexus ./storage ::`, which will serve your recorded data from port 29601. It will merge messages
|
||||
coming in from all sources and serve them in time order.
|
||||
|
||||
Finally, you can do `nc 127.0.0.1 29601 | ./navdump`, which will give you all messages over the past 24 hours, and stream you more.
|
||||
This also works for `navparse` for the pretty website and influx storage, `nc 127.0.0.1 29601 | ./navparse --influxdb=galileo`,
|
||||
Finally, you can do `nv 127.0.0.1 29601 | ./navdump`, which will give you all messages over the past 24 hours, and stream you more.
|
||||
This also works for `navparse` for the pretty website and influx storage, `nc 127.0.0.1 29601 | ./navparse 127.0.0.0:10000 html galileo`,
|
||||
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
|
||||
---------
|
||||
|
@ -324,144 +124,25 @@ Documents
|
|||
not actually relevant for the CDMA aspects, but has appendices on more
|
||||
precise orbit determinations.
|
||||
* [GPS](https://www.gps.gov/technical/icwg/IS-GPS-200K.pdf)
|
||||
* [U-blox 8 interface specification](https://www.u-blox.com/sites/default/files/products/documents/u-blox8-M8_ReceiverDescrProtSpec_%28UBX-13003221%29_Public.pdf)
|
||||
* [U-blox 8 interface specification](https://www.u-blox.com/sites/default/files/products/documents/u-blox8-M8_ReceiverDescrProtSpec_%28UBX-13003221%29_Public.pdfhttps://www.u-blox.com/sites/default/files/products/documents/u-blox8-M8_ReceiverDescrProtSpec_%28UBX-13003221%29_Public.pdf)
|
||||
* [U-blox 9 interface specification](https://www.u-blox.com/sites/default/files/u-blox_ZED-F9P_InterfaceDescription_%28UBX-18010854%29.pdf)
|
||||
* [U-blox 9 integration manual](https://www.u-blox.com/sites/default/files/ZED-F9P_IntegrationManual_%28UBX-18010802%29.pdf)
|
||||
|
||||
Data sources
|
||||
------------
|
||||
The software can interpret SP3 files, good sources:
|
||||
|
||||
* ESA/ESOC: http://navigation-office.esa.int/products/gnss-products/ - pick the
|
||||
relevant GPS week number, and then a series (.sp3 extension):
|
||||
* ESU = ultra rapid, 2-8h delay, only GPS and GLONASS
|
||||
* ESR = rapid, 2-26h delay, only GPS and GLONASS
|
||||
* ESM = finals, 6-13d delay, GPS, GLONASS, Galileo, BeiDou, QZSS
|
||||
* File format is esXWWWWD.sp3 - where X is U, R or M, WWWW is the
|
||||
(non-wrapping) GPS week number and D is day of week, Sunday is 0.
|
||||
* Further description: http://navigation-office.esa.int/GNSS_based_products.html
|
||||
* GFZ Potsdam: ftp://ftp.gfz-potsdam.de/GNSS/products/mgnss
|
||||
* The GBM series covers GPS, GLONASS, Galileo, BeiDou, QZSS and appears
|
||||
to have less of a delay than the ESA ESM series.
|
||||
* GBU = ultra rapid, still a few days delay, but much more recent.
|
||||
Big TODO
|
||||
--------
|
||||
|
||||
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
|
||||
----
|
||||
RTCM is the Radio Technical Commission for Maritime Services, and
|
||||
confusingly, also the name of a protocol.
|
||||
|
||||
This protocol is proprietary, but search for a file called `RTCM3.2.pdf` or
|
||||
`104-2013-SC104-STD - Vers. 3.2.docx` and you might find a copy.
|
||||
|
||||
This project can parse RTCM 10403.1 messages, and currently processes State
|
||||
Space Representation (SSR) messages, specifically types 1057/1240
|
||||
(GPS/Galileo Orbit corrections to broadcast ephemeris) and 1058/1241
|
||||
(GPS/Galileo Clock corrections to broadcast ephemeris).
|
||||
|
||||
RTCM messages need to be converted to protobuf format, and the `rtcmtool` is
|
||||
provided for this purpose.
|
||||
|
||||
RTCM is frequently transmitted over the internet using 'ntrip', a typical
|
||||
commandline to process RTCM in our project is:
|
||||
```
|
||||
$ ntripclient ntrip:CLKA0_DEU1/user:password@navcast.spaceopal.com:2101 | ./rtcmtool --station x --destination y
|
||||
```
|
||||
|
||||
User and password can be obtained from https://spaceopal.com/navcast/ - the
|
||||
Galileo operating company.
|
||||
|
||||
The IGS also offers excellent streams, but without Galileo. Information is
|
||||
[here](http://www.igs.org/rts/products). A typical commandline is:
|
||||
|
||||
```
|
||||
$ ntripclient ntrip:IGS01/user:password@products.igs-ip.net:2101 | ./rtcmtool --station x --destination y
|
||||
```
|
||||
|
||||
User and password can be requested through http://www.igs.org/rts/access
|
||||
|
||||
An interesting list is here: http://products.igs-ip.net/
|
||||
|
||||
There are many other sources of RTCM but currently not many offer the SSR
|
||||
messages we can use.
|
||||
|
||||
Tooling
|
||||
-------
|
||||
|
||||
* ubxtool: Configure and use a Ublox chip to gather messages, and send as
|
||||
protobuf to standard output or a remote server (with buffering).
|
||||
* navdump: convert protobuf format data into a textual display of messages
|
||||
* navparse: consume protobuf and turn into a webserver with data, plus
|
||||
optionally fill an influxdb time-series database for graphing and analysis
|
||||
purposes.
|
||||
* navrecv: receive protobuf messages over the network and store them on
|
||||
disk
|
||||
* navnexus: serve protobuf messages from disk over the network
|
||||
* navcat: serve protobuf messages from disk directly to stdout
|
||||
* reporter: make "the galmon.eu weekly galileo report"
|
||||
* rinreport: rinex analysis tooling (not generically useful yet)
|
||||
* galmonmon: monitor a navparse instance for changes, tweet them out
|
||||
* navdisplay: some eye-candy that converts protobuf into a live display
|
||||
(not very good)
|
||||
* rtcmtool: accepts RTCM messages on standard input (for example coming
|
||||
from ntripclient) and transmits them as protobuf messages, either to
|
||||
stdout or to a navrecv server. This is the equivalent of 'ubxtool'
|
||||
except for submitting RTCM messages.
|
||||
|
||||
Sample command lines
|
||||
--------------------
|
||||
Look at old data:
|
||||
|
||||
```
|
||||
$ ./navcat storage "2020-01-01 00:00" "2020-01-02 00:00" | ./navdump
|
||||
```
|
||||
|
||||
Global coverage (via volunteers)
|
||||
--------------------------------
|
||||
|
||||
In alphabetical order:
|
||||
|
||||
* Austria (Vienna area)
|
||||
* Brazil
|
||||
* Holland (Nootdorp, Hilversum, etc)
|
||||
* India (New Delhi area)
|
||||
* Israel (Jerusalem)
|
||||
* Italy (Rome)
|
||||
* New Zealand (Auckland area)
|
||||
* Rusia (Moscow area)
|
||||
* Singapore
|
||||
* South Africa (Cape Town area)
|
||||
* Spain
|
||||
* Tonga
|
||||
* USA
|
||||
* Alaska (Anchorage)
|
||||
* California (Santa Cruz, Los Angeles area, etc)
|
||||
* Massachusetts (Boston area)
|
||||
* Uruguay
|
||||
|
||||
Additional sites are welcome (and encouraged) as the more data receiving sites that exist, then more accurate data and absolute coverage of each constellation can be had.
|
||||
|
||||
The galmon project is very grateful to all its volunteering receiving stations.
|
||||
* Dual goals: completeness, liveness, not the same
|
||||
For forensics, great if the packet is there
|
||||
For display, not that bad if we missed a message
|
||||
* In general, consider refeed strategy
|
||||
Raw serial
|
||||
Protobuf
|
||||
Influxdb
|
||||
".csv files"
|
||||
* Delivery needs to be bit more stateful (queue)
|
||||
|
||||
* Semantics definition for output of Navnexus
|
||||
"we'll never surprise you with old data"
|
||||
|
||||
ubxtool
|
||||
-------
|
||||
|
|
|
@ -1,84 +0,0 @@
|
|||
Architecture
|
||||
------------
|
||||
|
||||
The galmon project consists of several components. Core of what we do are
|
||||
protobuf messages that are (mostly) device independent and contain satellite
|
||||
data and metadata.
|
||||
|
||||
All protobuf messages are 'NavMonMessages', of which there are 15 types.
|
||||
These messages are:
|
||||
|
||||
* Navigation messages from satellites, various types
|
||||
* Device / software metadata
|
||||
* Reception strength details
|
||||
* Doppler, carrier phase data
|
||||
* RTCM messages
|
||||
|
||||
For every supported receiver type, there is a tool to convert the device
|
||||
specific protocol to these protobuf messages. Currently we only have
|
||||
'ubxtool' that does this for many u-blox devices. In addition, 'rtcmtool'
|
||||
converts a RTCM to protobuf.
|
||||
|
||||
Components
|
||||
----------
|
||||
|
||||
Core components:
|
||||
* ubxtool/rtcmtool: generate protobuf messages, transmit them over network
|
||||
* navrecv: receive protobuf messages over the network and store them on
|
||||
disk
|
||||
* navnexus: serve protobuf messages from disk over the network
|
||||
* navparse: consume protobuf and turn into a webserver with data, plus
|
||||
optionally fill an influxdb time-series database for graphing and analysis
|
||||
purposes.
|
||||
|
||||
This offers a complete suite for:
|
||||
|
||||
* generating protobuf messages and transmitting them
|
||||
* reception & long-term storage
|
||||
* serving protobuf messages
|
||||
* analysing them for display & storing statistics for analysis
|
||||
|
||||
|
||||
Non-core tools:
|
||||
* navdump: convert protobuf format data into a textual display of messages
|
||||
* navcat: serve protobuf messages from disk directly to stdout
|
||||
* reporter: make "the galmon.eu weekly galileo report", based on the
|
||||
time-series database filled by navparse
|
||||
* rinreport: rinex analysis tooling (not generically useful yet)
|
||||
* galmonmon: monitor a navparse instance for changes, tweet them out
|
||||
* navdisplay: some eye-candy that converts protobuf into a live display
|
||||
(not very good)
|
||||
|
||||
Transmission details
|
||||
--------------------
|
||||
Messages are sent as protobuf messages with an intervening magic value and a
|
||||
length field.
|
||||
|
||||
Both rtcmtool and ubxtool can send data over TCP/IP, but also to standard
|
||||
output. This standard output mode makes it possible to pipe the output of
|
||||
these tools straight into navdump or navparse.
|
||||
|
||||
There are two transport protocols, one uncompressed, which consists of the 4
|
||||
byte magic value, a 2 byte length field, and then the message. This protocol
|
||||
has been deprecated for TCP, but it is still there for --stdout output.
|
||||
|
||||
The second protocol is zstd compressed, and features acknowledgements. The
|
||||
protocol is an initial 4 byte magic value "RNIE", followed by 8 reserved
|
||||
bytes which are currently ignored. Then zstd compression starts, each
|
||||
message consists of a 4 byte message number, followed by a two byte length
|
||||
field, followed by the message. There are no further magic values beyond the
|
||||
first one.
|
||||
|
||||
The receiver is expected to return the 4 byte message number for each
|
||||
message received. Messages which have not been acknowledged like this will
|
||||
be retransmitted on reconnect.
|
||||
|
||||
Storage details
|
||||
---------------
|
||||
Storage is very simplistic but robust. For every receiver, for every hour,
|
||||
there is a file with protobuf messages. That's it. The goal is for the
|
||||
receiver to never ever go down.
|
||||
|
||||
navnexus and navcat can consume data from this simplistic storage and serve
|
||||
it over TCP/IP or to standard output.
|
||||
|
16
beidou.hh
16
beidou.hh
|
@ -17,7 +17,7 @@ int beidouBitconv(int their);
|
|||
C05 (58.75E)
|
||||
*/
|
||||
|
||||
struct BeidouMessage : GPSLikeEphemeris
|
||||
struct BeidouMessage
|
||||
{
|
||||
uint8_t strtype;
|
||||
|
||||
|
@ -72,7 +72,7 @@ struct BeidouMessage : GPSLikeEphemeris
|
|||
int wn{-1}, a0, a1, a2;
|
||||
// 2^17 2^30
|
||||
|
||||
uint32_t getT0c() const
|
||||
uint32_t getT0c()
|
||||
{
|
||||
return 8 * t0c;
|
||||
}
|
||||
|
@ -118,11 +118,7 @@ struct BeidouMessage : GPSLikeEphemeris
|
|||
double getCrc() const { return ldexp(crc, -6); } // meters
|
||||
double getCrs() const { return ldexp(crs, -6); } // meters
|
||||
double getM0() const { return ldexp(m0 * M_PI, -31); } // radians
|
||||
|
||||
int getIOD() const
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
void parse2(std::basic_string_view<uint8_t> cond)
|
||||
{
|
||||
|
@ -228,8 +224,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
|
||||
|
@ -269,9 +264,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);
|
||||
|
|
143
coverage.cc
143
coverage.cc
|
@ -1,143 +0,0 @@
|
|||
#include <map>
|
||||
#include "galileo.hh"
|
||||
#include "minivec.hh"
|
||||
#include "navmon.hh"
|
||||
#include "navparse.hh"
|
||||
#include "ephemeris.hh"
|
||||
#include "fmt/format.h"
|
||||
#include "fmt/printf.h"
|
||||
#include <fstream>
|
||||
|
||||
#include <eigen3/Eigen/Dense>
|
||||
using Eigen::MatrixXd;
|
||||
|
||||
|
||||
using namespace std;
|
||||
|
||||
xDOP getDOP(Point& us, vector<Point> sats)
|
||||
{
|
||||
xDOP ret;
|
||||
if(sats.size() < 4) {
|
||||
return ret;
|
||||
}
|
||||
MatrixXd G(sats.size(), 4); // 4 columns
|
||||
|
||||
// (x1 - x)/R1 (y1 -y)/R1 (z1 - z)/R1 -1
|
||||
|
||||
for(size_t n = 0 ; n < sats.size(); ++n) {
|
||||
const auto& s = sats[n];
|
||||
auto R = Vector(us, s).length();
|
||||
|
||||
G(n, 0) = (s.x - us.x)/R;
|
||||
G(n, 1) = (s.y - us.y)/R;
|
||||
G(n, 2) = (s.z - us.z)/R;
|
||||
G(n, 3) = -1;
|
||||
}
|
||||
|
||||
// cout<<"Matrix: "<<endl;
|
||||
// cout<<G<<endl;
|
||||
MatrixXd Q = (G.transpose() * G).inverse();
|
||||
|
||||
auto [lambda, phi] = getLongLat(us.x, us.y, us.z);
|
||||
|
||||
// https://gssc.esa.int/navipedia/index.php/Positioning_Error
|
||||
Eigen::Matrix3d Renu;
|
||||
Renu <<
|
||||
(-sin(lambda)) , (-sin(phi)*cos(lambda)) , (cos(phi)*cos(lambda)),
|
||||
(cos(lambda)) , (-sin(phi)*sin(lambda)) , (cos(phi)*sin(lambda)),
|
||||
(0.0) , (cos(phi)) , (sin(phi));
|
||||
|
||||
Eigen::Matrix3d Qxyz;
|
||||
for(int x=0; x<3; ++x) // feels like there should be a better way for this, but not sure
|
||||
for(int y=0; y<3; ++y)
|
||||
Qxyz(x,y) = Q(x,y);
|
||||
|
||||
Eigen::Matrix3d Qenu = Renu.transpose()*Qxyz*Renu;
|
||||
// if(Qenu(0,0) < 0 || Qenu(1,1) < 0 || Qenu(2,2) < 0)
|
||||
// cout << "Original: \n"<<Qxyz<<"\nRotated: \n"<<Qenu<<endl;
|
||||
|
||||
ret.pdop = sqrt(Q(0,0) + Q(1,1) + Q(2,2)); // already squared
|
||||
// ret.pdop = sqrt(Qenu(0,0) + Qenu(1,1) + Qenu(2,2)); // already squared
|
||||
|
||||
ret.tdop = sqrt(Q(3,3));
|
||||
ret.gdop = sqrt(ret.pdop*ret.pdop + ret.tdop*ret.tdop);
|
||||
if(Qenu(0,0) >=0 && Qenu(1,1) >=0)
|
||||
ret.hdop = sqrt(Qenu(0,0) + Qenu(1,1));
|
||||
if(Qenu(2,2)>=0)
|
||||
ret.vdop = sqrt(Qenu(2,2));
|
||||
return ret;
|
||||
};
|
||||
|
||||
// in covmap:
|
||||
// 0
|
||||
// lon,
|
||||
// 1 2 3
|
||||
// numsats5, numsats10, numsats20
|
||||
// 4 5 6
|
||||
// pdop5, pdop10, pdop20
|
||||
// hdop5, hdop10, hdop10
|
||||
// vdop5, vdop10, vdop20
|
||||
covmap_t emitCoverage(const vector<Point>& sats)
|
||||
{
|
||||
covmap_t ret;
|
||||
// ofstream cmap("covmap.csv");
|
||||
// cmap<<"latitude longitude count5 count10 count20"<<endl;
|
||||
double R = 6371000;
|
||||
for(double latitude = 90 ; latitude > -90; latitude-=2) { // north-south
|
||||
double phi = M_PI* latitude / 180;
|
||||
double longsteps = 1 + 360.0 * cos(phi);
|
||||
double step = 4*180.0 / longsteps;
|
||||
// this does sorta equi-distanced measurements
|
||||
vector<tuple<double, int, int, int, double, double, double, double, double, double,double, double, double>> latvect;
|
||||
for(double longitude = -180; longitude < 180; longitude += step) { // east - west
|
||||
Point p;
|
||||
// phi = latitude, lambda = longitude
|
||||
|
||||
double lambda = M_PI* longitude / 180;
|
||||
p.x = R * cos(phi) * cos(lambda);
|
||||
p.y = R * cos(phi) * sin(lambda);
|
||||
p.z = R * sin(phi);
|
||||
|
||||
if(longitude == -180) {
|
||||
// auto longlat = getLongLat(p.x, p.y, p.z);
|
||||
// cout<<fmt::sprintf("%3.0f ", 180.0*longlat.second/M_PI);
|
||||
}
|
||||
|
||||
|
||||
int numsats5=0, numsats10=0, numsats20=0;
|
||||
vector<Point> satposs5, satposs10, satposs20;
|
||||
for(const auto& s : sats) {
|
||||
// double getElevationDeg(const Point& sat, const Point& our);
|
||||
double elev = getElevationDeg(s, p);
|
||||
if(elev > 5.0) {
|
||||
satposs5.push_back(s);
|
||||
numsats5++;
|
||||
}
|
||||
if(elev > 10.0) {
|
||||
satposs10.push_back(s);
|
||||
numsats10++;
|
||||
}
|
||||
if(elev > 20.0) {
|
||||
satposs20.push_back(s);
|
||||
numsats20++;
|
||||
}
|
||||
}
|
||||
latvect.push_back(make_tuple(longitude,
|
||||
numsats5, numsats10, numsats20,
|
||||
getDOP(p, satposs5).pdop,
|
||||
getDOP(p, satposs10).pdop,
|
||||
getDOP(p, satposs20).pdop,
|
||||
getDOP(p, satposs5).hdop,
|
||||
getDOP(p, satposs10).hdop,
|
||||
getDOP(p, satposs20).hdop,
|
||||
getDOP(p, satposs5).vdop,
|
||||
getDOP(p, satposs10).vdop,
|
||||
getDOP(p, satposs20).vdop
|
||||
|
||||
));
|
||||
// cmap << longitude <<" " <<latitude <<" " << numsats5 << " " <<numsats10<<" "<<numsats20<<endl;
|
||||
}
|
||||
ret.push_back(make_pair(latitude, latvect));
|
||||
}
|
||||
return ret;
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mQINBF4kzeMBEAClLiZy741lfdoZHvlCbU1k4o9CglhVAPnSqTo7tH0AkdXmp/sG
|
||||
Sxk20miLlNNaxTYhHLyWIMhLoR3ogAJSoUsbMo4cpC5owdai06Q2nINTGiD1Vwak
|
||||
MmSStQl/aG01G1u9Ei/FVSr/K6Nd8u+dvUoU6tFSoXtXXaRB5pkyLOaZXq6UTwhw
|
||||
tI5f+uH2d8kgkoGNWl/jevn7K5AqCWmklfzYFCeogXemcNMdisrm2RzYzay70Qo+
|
||||
3qKHzQUN5S6Ne2yYKYN1UacGg+38+zhoSNGy8fB2XfCf8mbW2NnnZBdPEmpu1yos
|
||||
uvjj/oauZlLgkx/ZzK4PzmWqpe+aEiV7Z8lPO3Y2U/cwkU56aMIFQq2DU4FgQUdn
|
||||
6J+tLeMPh0sbIfYeOfF5IdFi85PdD4w7C8hmxu7aHl/B/GCnOFYAsSChpf4bpgWB
|
||||
G1Pne56evHeSNYXQCdE4rjyYGqB/F+EWn/NYcOdKSkOxxE5bL/aU/uqPss5E54nt
|
||||
z+qsN9ynGNWsnkPQECulM3svS3/nffnmgFSuDZyON4Y8/LFIkPizBiqOtrVEAKdK
|
||||
CqKz+A2fgSamM5IbJbCSkXWMcU6W0Sz0DzSpMbVf5kKER7WeDoeWr6yUHqAkPyHz
|
||||
bx/YLWwrAjuuN00/MuIl8XXHWBHsfRqIrqLSBbie5mbOv2MloBEdrvZqvwARAQAB
|
||||
tDRHYWxtb24gRGViaWFuIFByaW1hcnkgU2lnbmluZyBLZXkgPGRlYmlhbkBnYWxt
|
||||
b24uZXU+iQJUBBMBCAA+FiEEbe3qslh61wzGNEF6huf1HAT7qrAFAl4kzeMCGwMF
|
||||
CRaOagAFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQhuf1HAT7qrCwxxAAj2fs
|
||||
9qcWKJSCPPpDNU6gzjuqFj2fUmvihf0Ki9LbBLMt5pA5q4RJLCMj/iSz31s8Cm8P
|
||||
+oWX3ZFxWzG5ZiWHWhpmgSYIPbfEwniwskLJXfZSEr+kcLoVnZsbWOxwLCmdanZl
|
||||
wWRNyFW4waRJaCf+jsPPoR5LEh5d0ustpP/2EtbP52OLY865bfr18SUhPllrX0ia
|
||||
L3zSCMC/S7H45xzOdSneYrYNA1W199hY/4lBafh7t8a07C2CFMqN/jaUJOFHQIzS
|
||||
VIsVHvpVQlpslxcPGOOZsh5tJwEIKuepHm6nqsZbKMuO6Qzi5hXfwsI3cjD+NZY8
|
||||
Hg2H0IDXfXKWD/mcxeAjgc3lzzchGZ56EOMN75Fzl/HLCBu6pYuTbOmwG9whnaYv
|
||||
VbNhDSzC+x39o4O2nMsfixOe4tx2eK04nzWKIEfYeIJZc+9xfJW16hbmErFx1VWR
|
||||
x2Pi55hbOCEeOVxnhc50rqFN+tXiLvVzZaLN7H/S7Bqk2qCA/b6HjxY/cZ+KyQSo
|
||||
gMc2Mz5/dwROBEuFryjpFzTOFRfnu8krZAaHim1TlDCNFI4+NO5xu1AkFpepYphQ
|
||||
+7qWjLPqXqHH54C79Qs5ey+q+iEh3ulK3u5yeyvL9+rjaPJOQxd3yTfcstHPpeoG
|
||||
ePCEhrpLvkfukrMhfgg8R4gtkST2LWQNrvSCnFa5Ag0EXiTN4wEQAMGv94CKgsE/
|
||||
H6V0YPxNFTfUFgZ+Rbse+DpGBUHSc2Bnp79ks9zq5zN6rRD8oztA2nyZAB39JJGq
|
||||
Y55yRRaQx/JqB1+WzSQywIdaAEt/D+PVfukfzjkNQ9/nELfBtx4W2xG5xiowzUqf
|
||||
XbWJvEmfOhKFYZrVayhVbc0DwZc1lBrO+1FX/z7NqyjgTkUusKIpjTjxl6afa79x
|
||||
8B8O8vcpwZO5I6LIVf75W5YcY0vJ0tXqYqF5/JOkWtRTGCcZQvovXsPg/LQtRz39
|
||||
61DuA0J02bCv/dUOPfwtk/PY4UzvIWmYH+xvfuJ4txX71VZVDVeSIMp7Iv9LVQL/
|
||||
pefdyIf4RTya30qHTLLJmXhQNTS7fE2EFoOfi9xlP8CE+vEnf5gxUsLQ98aoIaWu
|
||||
+DpZelPTUpEJ45RNFxizmhv+X/d1WHyFS5bU53guxDSrFOKNKilCEqO5ukxYhMSY
|
||||
va0REZQh4iwuPFO3R4D1NM3HSJa2YmgVp8tHNJBTQImYTCd+NfY/wLlGFfgrjOLh
|
||||
XkIO2mHzANiCmoPcc4yoXGpsAr8N8yVe5sOKsbUvfMpisJv500BJA33ONQU60wWk
|
||||
S1g/RVn/dzJffSC+WOgxLkLAGaA0clrmJVmD/mlYhv8P/Ptwh0zAnkZsWk+NI15T
|
||||
EW2efOEj5QBg5PdQFyxJuOQXs7B+vRF9ABEBAAGJAjwEGAEIACYWIQRt7eqyWHrX
|
||||
DMY0QXqG5/UcBPuqsAUCXiTN4wIbDAUJFo5qAAAKCRCG5/UcBPuqsAvVD/9v/91W
|
||||
QyRLH+0EU/1vJM9pw/oNAPmLWB1sVy8n9Fk0z6sPtZbDTpP9+t0K+ZHiGOoa0B2I
|
||||
cGhd/VWTpZo6IfaqEjPkXyC9iVRyazlJIdkzwl9QOEqqPhO75IxwXN7LuBV6Vx12
|
||||
3iDPh651EpN2NdbxmtMVymsWBjgfjKmTK8ye3r513F4I7Rp1EZOSNvmPutr3QaqT
|
||||
3lAqX9S6bMsRhQA0kDL7wtk8V++tIjko70/y/I/nrY3IQD61CTLkjOSw1Uj5FUGR
|
||||
Y61Yl+ANai4R9R/AkTuRinzpolFyPpOM9bGJ5xzG+O29eWH7fq68fk/c4t/uQu2g
|
||||
rULrNGBVTCkka0p6uNjjFUn+sJzkibNucHYtS1DEiV4SXZ+mANohLjaocVr7MZ6p
|
||||
nhAAKIYD8EWi8IwXXQXgUBNqr6qQB5yFnPcAhR5aCLFgh+RCMqVVXzZ7FO4oc0Fh
|
||||
IaVijpzACjLCZWTo++W9T0RPP/2KQpij2ZIOtB80hF8dfeWZeP0/OEaPkjUZEHla
|
||||
y5kwSAI6zkqnRtZRJpXPyvum8H6ACniMUHjRmtoFtNrlLZMM2i1Q4Qf8B4eqga6r
|
||||
+ehY3JUxfk3BtV0Y8DgKCBXSd3N2YvVn623Z546o/Bxio8Db+gkprgAW+5F3RRin
|
||||
iK7xxd0ZLzHgO0ASOgAKLbglZ2T+80NSMbWUbrkCDQReJM5qARAAuenyMXbUjin5
|
||||
2bntoz2I6b5dLMQlqRiggksMX1x/FFAO0cOaXxNtNpTzblN5Y2uZC6T+i5fnfXQ9
|
||||
yWkXIBLCXaaqziLx5wCg7rYsnS2/HYTVtd0ptYzqjKO++YAjHGcNnhUWdTJFRRMe
|
||||
CrYpRWtCH2YhdNPdfvsabLmF3LqI2GZp6x30q2oLxqLZFVA10d+AFCXEI5OcR9OW
|
||||
1N4okodA5HE9o3e+wYnRbJJUW4Lmxc8BFli7eqLNIhoI//tx276EKBhatBM6nAlZ
|
||||
AQJuEoQk08RnXPaMewC9HHjZQBjdM+ej3gl9PeYj6ptqcTqreN4AuRtcvvOgS80b
|
||||
TwixWYpDe58XUnT9JUF/C+n4AP+JdvSZHtK0FG9eNbLjcCSarsvOV98CkHthidJz
|
||||
mfUjP2txPZVxoB2CxXfUjYZ4Ib981CmdPON+HN/wtDpsWj5mOKeER9dWdXwr1d/a
|
||||
eRYZviTXEHMnEB/LnWzNx01RmyIUhw13fGr+6KBU3E4AogKwp/xFgoccvNR0LfSA
|
||||
HhNCRUPCh85bg9yTVroPmYeW6u0UWHflY6mVid08QDYFkHWGkGrEwsslpu9FpSBu
|
||||
J9lAuzVGlfWK2mFA8Lq8NmpuzJLUURYnXDfaQB2SxOjf9hjV7Z7GbNa95HBHZzQa
|
||||
vOExLxEUzspwR9d0isytL4Ytz9v3QpUAEQEAAYkEcgQYAQgAJhYhBG3t6rJYetcM
|
||||
xjRBeobn9RwE+6qwBQJeJM5qAhsCBQkFxJAAAkAJEIbn9RwE+6qwwXQgBBkBCAAd
|
||||
FiEEbws8jxbNPEKXXCt7wPFsNLeCUjAFAl4kzmoACgkQwPFsNLeCUjCbyRAAttzx
|
||||
zxca+YOlki8VQSwr+vktRBtwKRkzr3LCSJ7gY1SQeeJ9wQDXlBeNLxN1ev8+Xhce
|
||||
JNMv39gG3mfUUwnq+nlOA92mY1ZNWgdtN3ZUcJeQSTL+Xs2NC6tC/lMTBTm4kPlz
|
||||
c5f7GIPRURfoNFnytHuPvIEL+i1iAQfsLxCWHUWlJcsvbiFhv3mTKHhGVgpQlLDo
|
||||
kww1tJzBg+GfvBVNgUGoTBHLx6s1lbcODF6jrumYBQHYL93FwXBTKXeTcPdwypzt
|
||||
SHRioPh8aJ+EVfmt6n0LWMmNOAZVD0Ps5jKaq8ymrYABdS8wgrVZ6TRTDuAfmZwr
|
||||
4SY8gFqDLYEqXbl9iZZ3FFc8mWuBs7eqR5dxkVULgyjuYSdeppIhL2uuZmtsjAuJ
|
||||
8ewzuTGWTmrmZCDhD9AwGyqsrOOtqejgsE85yKsPiADUFHq7JNSIRfsb+HBU9j/a
|
||||
yi0fIwW4bzhL4xXrlsx1HtH2obCGyfeO4WXdZOCqYBfC21jzyJPY+KO7I79BBANd
|
||||
HamkAy1971SiPKvjFPtY93Fp8dKWywc6tq6oe8SMGvKk0SZ+OXSMeBomZWjbdjR7
|
||||
CUiyf/va5+v5QKLzApRGrhvBxgwAxgvK4zfLoKOC4WZQl8lYhgFYGZcFYNclcGvE
|
||||
wy9SsCpIIOT1z8NUl2K68qC/7A5eWU4MHfGkDaX8/g/+MdflD32Ldmwiv9lptDMm
|
||||
CtzE/uD/J902v7BuQeXX+pdHkgWeK8zTAJkIPEMB1kNFIoKVcGoic0msB+cN4RYy
|
||||
VZN56+kkhMRP+0m+xjPKyZ687VF88j8mEo/ybFxhwh58LydKNBqkdNeY9UbTT5bv
|
||||
WUlHWO5qClsCbRIPwjvsTgZk9qjAWrWbIFivXpPcC8z7NDCFVD5JTBxnkBbcXyvP
|
||||
W68+BvknNJ2Ilnww2aNNp5zmzGeDLSV28MTtUJXb3w6cXfeOazqcydgHqPO1gyTq
|
||||
oarCkgPEQlyLey/3qDmsmUskHQ8c81bQKPyGKH4dLiz0qdayRB/GICEIN7O+uk7/
|
||||
GdSem/sTXa/k9IpOm2eimdKG0y9C0k2kQb/IAr4cYMpbtN/gsyOr/UhqkzjawRCx
|
||||
wgHjKnXTNYtKcxuCpBpaPLQPFOE6IjG6Acpkh+LEdIYrY4hZmlaQqE3rXcye94rr
|
||||
F8ga+2XPNAwVH9zngrk1gZrTZmPkNkTG94kTfFwuAEmFWd6Dyf4IBoMacdpUyLTV
|
||||
99zduIWc4SOdzEawU0JdD50dkSlHDJsoNamhHe60MELbLOQjN43n3KqKHjIpgZcd
|
||||
2k2OFa/4uOXYoj3KmWxTKCV+BIcMuQfaaLJLHd1Uar59DExIatvw0aVWuj2M70tu
|
||||
J7VNNdmxD1RF3zsMFsPTQWY=
|
||||
=fqVo
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
|
@ -1,9 +0,0 @@
|
|||
galmon for Debian
|
||||
|
||||
This directory contains files created by debmake and
|
||||
required by debuild to create Debian packages.
|
||||
|
||||
For further instruction, review the Guide for Debian Maintainers
|
||||
by Osamu Aoki: https:/www.debian.org/doc/manuals/debmake-doc/
|
||||
|
||||
-- Patrick Tudor <debian@ptudor.net> Sun, 19 Jan 2020 22:52:16 +0000
|
|
@ -1,5 +0,0 @@
|
|||
galmon (0.20191231-1) stable; urgency=low
|
||||
|
||||
* Initial release.
|
||||
|
||||
-- Patrick Tudor <debian@ptudor.net> Tue, 31 Dec 2019 00:00:00 +0000
|
|
@ -1 +0,0 @@
|
|||
11
|
|
@ -1,24 +0,0 @@
|
|||
Source: galmon
|
||||
Section: net
|
||||
Priority: optional
|
||||
Maintainer: Patrick Tudor <debian@ptudor.net>
|
||||
Build-Depends: debhelper (>=11~), help2man, libzstd-dev, adduser
|
||||
Standards-Version: 4.3.0
|
||||
Homepage: https://github.com/ahupowerdns/galmon/
|
||||
|
||||
Package: galmon
|
||||
Architecture: any
|
||||
Multi-Arch: foreign
|
||||
Conflicts: gpsd-clients
|
||||
Depends: ${misc:Depends}, ${shlibs:Depends}
|
||||
Description: galmon GNSS Monitoring Project software
|
||||
88
|
||||
88
|
||||
88
|
||||
,adPPYb,d8 ,adPPYYba, 88 88,dPYba,,adPYba, ,adPPYba, 8b,dPPYba,
|
||||
a8" `Y88 "" `Y8 88 88P' "88" "8a a8" "8a 88P' `"8a
|
||||
8b 88 ,adPPPPP88 88 88 88 88 8b d8 88 88
|
||||
"8a, ,d88 88, ,88 88 88 88 88 "8a, ,a8" 88 88
|
||||
`"YbbdP"Y8 `"8bbdP"Y8 88 88 88 88 `"YbbdP"' 88 88
|
||||
aa, ,88
|
||||
"Y8bbdP"
|
|
@ -1,45 +0,0 @@
|
|||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: galmon
|
||||
Source: https://github.com/ahupowerdns/galmon
|
||||
|
||||
Files: *
|
||||
Copyright: AHU Holding BV - bert@hubertnet.nl - https://berthub.eu/
|
||||
License: GPL-3
|
||||
This package is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
.
|
||||
This package is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this package; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
.
|
||||
On Debian systems, the complete text of the GNU General
|
||||
Public License can be found in `/usr/share/common-licenses/GPL-3'.
|
||||
|
||||
Files: minicurl.cc
|
||||
minicurl.hh
|
||||
Copyright: 2018-2019 powerdns.com bv
|
||||
License: Expat
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
.
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -1,32 +0,0 @@
|
|||
# Copyright 2020 AHU Holding BV - bert@hubertnet.nl - https://berthub.eu/
|
||||
# https://galmon.eu - https://github.com/ahupowerdns/galmon
|
||||
# This package is free software: /usr/share/common-licenses/GPL-3
|
||||
#
|
||||
# INSTRUCTIONS:
|
||||
# (Pre-condition: "dmesg | tail" reports your GPS device appears on /dev/ttyACM0.)
|
||||
# Copy this file from /etc/default/galmon to /etc/default/ubxtool-ttyACM0
|
||||
# Please choose your constellations, update the owner and remark, and set your station and destination (ipv6 supported)
|
||||
# After customization, start the daemon:
|
||||
# systemctl enable --now ubxtool@ttyACM0 && journalctl -fu ubxtool
|
||||
# If you want, enable automatic upgrades at boot and every three days following:
|
||||
# systemctl enable --now galmon-upgrade.timer
|
||||
# If you have several devices, enable multiple services with their unique device names.
|
||||
#
|
||||
# FOR HELP:
|
||||
# Review the Operator.md file: https://github.com/ahupowerdns/galmon/blob/master/Operator.md
|
||||
# Download an IRC client like Textual (Mac) or HexChat (Windows). Join #galileo on the OFTC servers.
|
||||
# Thank you for contributing, it is nice to see data from your location on the map.
|
||||
#
|
||||
#
|
||||
# DO NOT SET THE "PORT" OPTION HERE.
|
||||
# The device is set by an argument to service, see above.
|
||||
#
|
||||
# uBlox M8-series and Aliexpress Specials support ( (gps AND galileo) AND ( beidou OR glonass) ) with optional SBAS.
|
||||
#
|
||||
DAEMON_OPTS="--owner 'OWNER' --remark 'REMARK' --gps --galileo --sbas --station 65000 --destination ::1"
|
||||
#DAEMON_OPTS="--owner 'OWNER' --remark 'REMARK' --gps --galileo --beidou --sbas --station 65000 --destination ::1"
|
||||
#DAEMON_OPTS="--owner 'OWNER' --remark 'REMARK' --gps --galileo --glonass --sbas --station 65000 --destination ::1"
|
||||
#
|
||||
# uBlox ZED F9T and F9P modules support all four major constellations simultaneously but not SBAS.
|
||||
#
|
||||
#DAEMON_OPTS="--owner 'OWNER' --remark 'REMARK' --gps --galileo --beidou --glonass --station 65000 --destination ::1"
|
|
@ -1,6 +0,0 @@
|
|||
Building.md
|
||||
influxdb.md
|
||||
Operator.md
|
||||
PACKAGE-DEBIAN.md
|
||||
README.md
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
[Unit]
|
||||
Description=Upgrade Galmon Software
|
||||
After=network.target nss-lookup.target
|
||||
StartLimitIntervalSec=0
|
||||
# require that the configuration exists
|
||||
ConditionPathExists=/etc/default/galmon
|
||||
|
||||
[Service]
|
||||
Environment=DEBIAN_FRONTEND=noninteractive
|
||||
Type=simple
|
||||
Restart=no
|
||||
ExecStartPre=+apt-get update
|
||||
ExecStart=+apt-get install -y galmon
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
|
@ -1,10 +0,0 @@
|
|||
[Unit]
|
||||
Description=Update galmon
|
||||
Documentation=https://github.com/ahupowerdns/galmon
|
||||
|
||||
[Timer]
|
||||
OnBootSec=3min
|
||||
OnUnitActiveSec=3d
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
|
@ -1,20 +0,0 @@
|
|||
[Unit]
|
||||
Description=galmon navnexus (serve recorded data on port 29601)
|
||||
After=network.target nss-lookup.target
|
||||
StartLimitIntervalSec=0
|
||||
# require that the pre-installed configuration file exists
|
||||
ConditionPathExists=/etc/default/navstar
|
||||
|
||||
[Service]
|
||||
# Customize $DAEMON_OPTS_NAVNEXUS in /etc/default/navstar
|
||||
EnvironmentFile=-/etc/default/navstar
|
||||
Type=simple
|
||||
Restart=always
|
||||
RestartSec=4
|
||||
User=ubxtool
|
||||
Group=ubxtool
|
||||
RuntimeDirectory=navnexus
|
||||
ExecStart=/usr/bin/navnexus $DAEMON_OPTS_NAVNEXUS
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
|
@ -1,20 +0,0 @@
|
|||
[Unit]
|
||||
Description=galmon navrecv (receive data on port 29603)
|
||||
After=network.target nss-lookup.target
|
||||
StartLimitIntervalSec=0
|
||||
# require that the pre-installed configuration file exists
|
||||
ConditionPathExists=/etc/default/navstar
|
||||
|
||||
[Service]
|
||||
# Customize $DAEMON_OPTS_NAVRECV in /etc/default/navstar
|
||||
EnvironmentFile=-/etc/default/navstar
|
||||
Type=simple
|
||||
Restart=always
|
||||
RestartSec=1
|
||||
User=ubxtool
|
||||
Group=ubxtool
|
||||
RuntimeDirectory=navrecv
|
||||
ExecStart=/usr/bin/navrecv $DAEMON_OPTS_NAVRECV
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
|
@ -1,9 +0,0 @@
|
|||
# Copyright 2020 AHU Holding BV - bert@hubertnet.nl - https://berthub.eu/
|
||||
# https://galmon.eu - https://github.com/ahupowerdns/galmon
|
||||
# This package is free software: /usr/share/common-licenses/GPL-3
|
||||
#
|
||||
# Here are daemon options used by the navrecv and navnexus services
|
||||
DAEMON_OPTS_NAVNEXUS="--storage /var/lib/galmon/storage --bind ::1"
|
||||
DAEMON_OPTS_NAVRECV="--bind ::1 --storage /var/lib/galmon/storage"
|
||||
#
|
||||
DAEMON_OPTS_NAVPARSE="--bind [::1]:10000 --html /usr/share/package/galmon/html --influxdb galileo"
|
|
@ -1,54 +0,0 @@
|
|||
#!/bin/sh
|
||||
# ptudor 20200120
|
||||
set -e
|
||||
|
||||
#. /usr/share/debconf/confmodule
|
||||
|
||||
setup_user() {
|
||||
|
||||
if getent group ubxtool > /dev/null ; then
|
||||
echo "galmon: ubxtool group exists, skipping"
|
||||
else
|
||||
echo "galmon: creating ubxtool system group"
|
||||
addgroup --system ubxtool
|
||||
fi
|
||||
|
||||
if getent passwd ubxtool > /dev/null ; then
|
||||
echo "galmon: ubxtool user exists, skipping"
|
||||
else
|
||||
echo "galmon: creating ubxtool system user"
|
||||
adduser --system ubxtool --no-create-home --home /run/ubxtool
|
||||
echo "galmon: adding ubxtool user to ubxtool group"
|
||||
adduser ubxtool ubxtool
|
||||
echo "galmon: adding ubxtool user to dialout group"
|
||||
adduser ubxtool dialout
|
||||
fi
|
||||
}
|
||||
|
||||
restart_ubxtool_daemon() {
|
||||
# I feel like this belongs in rules with dh_installsystemd but do not understand how to add the wildcard.
|
||||
if systemctl is-active 'ubxtool@*' > /dev/null ; then
|
||||
echo "galmon: restarting ubxtool."
|
||||
systemctl daemon-reload && systemctl restart 'ubxtool@*'
|
||||
else
|
||||
echo "galmon: ubxtool services are not currently enabled, not restarting."
|
||||
fi
|
||||
}
|
||||
|
||||
print_help_text() {
|
||||
echo "Galmon installation finished. If this is your first time, please:"
|
||||
echo " 1) Create a ubxtool configuration 2) Enable the service"
|
||||
echo " 3) Enable the timer for automatic upgrades if you want"
|
||||
echo "Replace ttyACM0 below with your device listed in /dev"
|
||||
echo "Example: cp /etc/default/galmon /etc/default/ubxtool-ttyACM0"
|
||||
echo "Example: vi /etc/default/ubxtool-ttyACM0"
|
||||
echo "Example: systemctl enable --now ubxtool@ttyACM0"
|
||||
echo "Example: (beta testing) systemctl enable --now galmon-upgrade.timer"
|
||||
}
|
||||
|
||||
setup_user
|
||||
print_help_text
|
||||
restart_ubxtool_daemon
|
||||
|
||||
#DEBHELPER#
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
shlibs:Depends=libc6 (>= 2.28), libcurl4 (>= 7.16.2), libgcc1 (>= 1:3.5), libh2o-evloop0.13, libncurses6 (>= 6), libprotobuf17, libssl1.1 (>= 1.1.0), libstdc++6 (>= 6), libtinfo6 (>= 6), zlib1g (>= 1:1.1.4)
|
||||
misc:Depends=
|
||||
misc:Pre-Depends=
|
|
@ -1,20 +0,0 @@
|
|||
[Unit]
|
||||
Description=galmon ubxtool on /dev/%I
|
||||
After=network.target nss-lookup.target
|
||||
StartLimitIntervalSec=0
|
||||
# require that both the device and configuration exist
|
||||
ConditionPathExists=/dev/%i
|
||||
ConditionPathExists=/etc/default/ubxtool-%i
|
||||
|
||||
[Service]
|
||||
EnvironmentFile=-/etc/default/ubxtool-%i
|
||||
Type=simple
|
||||
Restart=always
|
||||
RestartSec=4
|
||||
User=ubxtool
|
||||
Group=ubxtool
|
||||
RuntimeDirectory=ubxtool
|
||||
ExecStart=/usr/bin/ubxtool --port /dev/%i $DAEMON_OPTS
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
|
@ -1 +0,0 @@
|
|||
# You must remove unused comment lines for the released package.
|
|
@ -1,17 +0,0 @@
|
|||
# This file of environment variables is sourced via /etc/profile.d/debuild.sh
|
||||
#
|
||||
# It sets the email and name of the maintainer first.
|
||||
# Next we disable parallel builds for ram conservation, normally enabled.
|
||||
# Finally we add hardening to the build.
|
||||
|
||||
DEBEMAIL="debian@ptudor.net"
|
||||
DEBFULLNAME="Patrick Tudor"
|
||||
|
||||
# "nocheck" here because testrunner runs out of ram on most current arm hardware -pht
|
||||
# manual: dh_auto_test: If the DEB_BUILD_OPTIONS environment variable contains nocheck, no tests will be performed.
|
||||
DEB_BUILD_OPTIONS='parallel=1 nocheck'
|
||||
|
||||
# https://wiki.debian.org/Hardening
|
||||
DEB_BUILD_MAINT_OPTIONS='hardening=+all'
|
||||
|
||||
export DEBEMAIL DEBFULLNAME DEB_BUILD_OPTIONS DEB_BUILD_MAINT_OPTIONS
|
|
@ -1,22 +0,0 @@
|
|||
#!/usr/bin/make -f
|
||||
export DEB_BUILD_MAINT_OPTIONS = hardening=+all
|
||||
export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic
|
||||
export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed
|
||||
|
||||
%:
|
||||
dh $@
|
||||
|
||||
override_dh_auto_install:
|
||||
dh_auto_install -- prefix=/usr
|
||||
|
||||
# override to install /etc/default/navstar alongside /etc/default/galmon
|
||||
override_dh_installinit:
|
||||
dh_installinit --name=navstar
|
||||
dh_installinit
|
||||
|
||||
override_dh_installsystemd:
|
||||
dh_installsystemd --no-enable --no-start --name=galmon-upgrade galmon-upgrade.service
|
||||
dh_installsystemd --no-enable --no-start --name=galmon-upgrade galmon-upgrade.timer
|
||||
dh_installsystemd --no-enable --no-start --name=navnexus navnexus.service
|
||||
dh_installsystemd --no-enable --no-start --name=navrecv navrecv.service
|
||||
dh_installsystemd --no-enable --no-start --name=ubxtool@ ubxtool@.service
|
|
@ -1 +0,0 @@
|
|||
3.0 (quilt)
|
|
@ -1,2 +0,0 @@
|
|||
#abort-on-upstream-changes
|
||||
#unapply-patches
|
|
@ -1 +0,0 @@
|
|||
version=3
|
27
ephemeris.cc
27
ephemeris.cc
|
@ -1,6 +1,5 @@
|
|||
#include "ephemeris.hh"
|
||||
#include "minivec.hh"
|
||||
#include <tuple>
|
||||
/* | t0e tow | - > tow - t0e, <3.5 days, so ok
|
||||
|
||||
| t0e tow | -> tow - t0e > 3.5 days, so
|
||||
|
@ -13,9 +12,9 @@
|
|||
*/
|
||||
|
||||
// positive age = t0e in the past
|
||||
double ephAge(double tow, int t0e)
|
||||
int ephAge(int tow, int t0e)
|
||||
{
|
||||
double diff = tow - t0e;
|
||||
int diff = tow - t0e;
|
||||
if(diff > 3.5*86400)
|
||||
diff -= 604800;
|
||||
if(diff < -3.5*86400)
|
||||
|
@ -23,8 +22,6 @@ double ephAge(double tow, int t0e)
|
|||
return diff;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// all axes start at earth center of gravity
|
||||
// x-axis is on equator, 0 longitude
|
||||
// y-axis is on equator, 90 longitude
|
||||
|
@ -32,8 +29,24 @@ double ephAge(double tow, int t0e)
|
|||
// https://en.wikipedia.org/wiki/ECEF#/media/File:Ecef.png
|
||||
std::pair<double,double> getLongLat(double x, double y, double z)
|
||||
{
|
||||
auto ret = ecefToWGS84(x, y, z);
|
||||
return {std::get<1>(ret), std::get<0>(ret)};
|
||||
Point core{0,0,0};
|
||||
Point LatLon0{1,0,0};
|
||||
|
||||
Point pos{x, y, z};
|
||||
|
||||
Point proj{x, y, 0}; // in equatorial plane now
|
||||
Vector flat(core, proj);
|
||||
Vector toLatLon0{core, LatLon0};
|
||||
double longitude = acos( toLatLon0.inner(flat) / (toLatLon0.length() * flat.length()));
|
||||
if(y < 0)
|
||||
longitude *= -1;
|
||||
|
||||
Vector toUs{core, pos};
|
||||
double latitude = acos( flat.inner(toUs) / (toUs.length() * flat.length()));
|
||||
if(z < 0)
|
||||
latitude *= -1;
|
||||
|
||||
return std::make_pair(longitude, latitude);
|
||||
}
|
||||
|
||||
|
||||
|
|
83
ephemeris.hh
83
ephemeris.hh
|
@ -1,56 +1,11 @@
|
|||
#pragma once
|
||||
#include "minivec.hh"
|
||||
#include <iostream>
|
||||
#include <tuple>
|
||||
#include <stdint.h>
|
||||
struct GPSLikeEphemeris
|
||||
{
|
||||
virtual double getMu() const = 0;
|
||||
virtual double getOmegaE() const = 0;
|
||||
virtual double getE() const = 0;
|
||||
virtual uint32_t getT0e() const = 0;
|
||||
|
||||
virtual double getI0() const = 0;
|
||||
|
||||
virtual double getOmegadot() const = 0;
|
||||
|
||||
virtual double getSqrtA() const = 0;
|
||||
virtual double getOmega0() const = 0;
|
||||
virtual double getOmega() const = 0;
|
||||
|
||||
virtual double getM0() const = 0;
|
||||
virtual double getIdot() const = 0;
|
||||
virtual double getCic() const = 0;
|
||||
virtual double getCis() const = 0;
|
||||
virtual double getCuc() const = 0;
|
||||
virtual double getCus() const = 0;
|
||||
virtual double getCrc() const = 0;
|
||||
virtual double getCrs() const = 0;
|
||||
virtual double getDeltan()const = 0;
|
||||
|
||||
virtual int getIOD() const = 0;
|
||||
// maybe af0, af1, af2?
|
||||
// maybe getUTCOffset? getAtomicOffset etc
|
||||
|
||||
};
|
||||
|
||||
// lat, lon, height (rad, rad, meters)
|
||||
std::tuple<double, double, double> ecefToWGS84(double x, double y, double z);
|
||||
|
||||
// lat, lon, height (deg, deg, meters)
|
||||
inline std::tuple<double, double, double> ecefToWGS84Deg(double x, double y, double z)
|
||||
{
|
||||
auto ret = ecefToWGS84(x, y, z);
|
||||
std::get<0>(ret) /= (M_PI / 180);
|
||||
std::get<1>(ret) /= (M_PI / 180);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
double ephAge(double tow, int t0e);
|
||||
int ephAge(int tow, int t0e);
|
||||
|
||||
template<typename T>
|
||||
double getCoordinates(double tow, const T& iod, Point* p, bool quiet=true)
|
||||
void getCoordinates(double tow, const T& iod, Point* p, bool quiet=true)
|
||||
{
|
||||
using namespace std;
|
||||
// here goes
|
||||
|
@ -114,7 +69,7 @@ double getCoordinates(double tow, const T& iod, Point* p, bool quiet=true)
|
|||
double A3 = pow(sqrtA, 6.0);
|
||||
|
||||
double n0 = sqrt(mu/A3);
|
||||
double tk = ephAge(tow, t0e);
|
||||
double tk = ephAge(tow, t0e); // in seconds, should do ephAge
|
||||
|
||||
double n = n0 + deltan;
|
||||
if(!quiet)
|
||||
|
@ -126,11 +81,10 @@ double getCoordinates(double tow, const T& iod, Point* p, bool quiet=true)
|
|||
double E = M;
|
||||
double newE;
|
||||
for(int k =0 ; k < 10; ++k) {
|
||||
newE = M + e * sin(E);
|
||||
if(!quiet)
|
||||
cerr<<"k "<<k<<" M = "<<M<<", E = "<< E << ", delta: "<< (E-newE) << endl;
|
||||
|
||||
if(fabs(E-newE) < 0.0000001) {
|
||||
cerr<<"k "<<k<<" M = "<<M<<", E = "<< E << endl;
|
||||
newE = M + e * sin(E);
|
||||
if(fabs(E-newE) < 0.00001) {
|
||||
E = newE;
|
||||
break;
|
||||
}
|
||||
|
@ -150,28 +104,19 @@ double getCoordinates(double tow, const T& iod, Point* p, bool quiet=true)
|
|||
((cos(E) - e)/ (1-e*cos(E)))
|
||||
);
|
||||
|
||||
double nu2A = atan( (sqrt(1-e*e) * sin(E)) /
|
||||
double nu2 = atan( (sqrt(1-e*e) * sin(E)) /
|
||||
(cos(E) - e)
|
||||
);
|
||||
|
||||
|
||||
double nu2B = atan2( (sqrt(1-e*e) * sin(E)) ,
|
||||
(cos(E) - e)
|
||||
);
|
||||
|
||||
|
||||
double nu3 = 2* atan( sqrt((1+e)/(1-e)) * tan(E/2));
|
||||
cerr << "e: "<<e<<", M: "<< M<<", E: "<< E<<endl;
|
||||
cerr << "e: "<<e<<", M: "<< M<<endl;
|
||||
cerr <<" nu sis: "<<nu1<< " / +pi = " << nu1 +M_PI << endl;
|
||||
cerr <<" nu ?: "<<nu2A<< " / +pi = " << nu2A +M_PI << endl;
|
||||
cerr <<" nu ?: "<<nu2B<< " / +pi = " << nu2B +M_PI << endl;
|
||||
cerr <<"* nu fourier/esa: "<<nu2<< " + " << corr <<" = " << nu2 + corr<<" | "<< std::fixed<<nu2+corr-nu1<<endl;
|
||||
cerr <<" nu ?: "<<nu2<< " / +pi = " << nu2 +M_PI << endl;
|
||||
cerr <<" nu fourier/esa: "<<nu2<< " + " << corr <<" = " << nu2 + corr<<endl;
|
||||
cerr <<" nu wikipedia: "<<nu3<< " / +pi = " <<nu3 +M_PI << endl;
|
||||
}
|
||||
double nu = atan2( (sqrt(1-e*e) * sin(E)) ,
|
||||
(cos(E) - e)
|
||||
);
|
||||
|
||||
|
||||
double nu = nu2 + corr;
|
||||
|
||||
// https://en.wikipedia.org/wiki/True_anomaly is good
|
||||
|
||||
|
@ -187,7 +132,7 @@ double getCoordinates(double tow, const T& iod, Point* p, bool quiet=true)
|
|||
|
||||
double u = psi + deltau;
|
||||
|
||||
double r = A * (1 - e * cos(E)) + deltar;
|
||||
double r = A * (1- e * cos(E)) + deltar;
|
||||
|
||||
double xprime = r*cos(u), yprime = r*sin(u);
|
||||
if(!quiet) {
|
||||
|
@ -206,7 +151,7 @@ double getCoordinates(double tow, const T& iod, Point* p, bool quiet=true)
|
|||
Vector radius(core, *p);
|
||||
cerr << radius.length() << " calculated r "<<endl;
|
||||
}
|
||||
return E;
|
||||
|
||||
}
|
||||
|
||||
struct DopplerData
|
||||
|
|
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
|
|
@ -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
|
||||
|
|
|
@ -1,50 +1,26 @@
|
|||
// 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 <algorithm>
|
||||
#include <iomanip>
|
||||
#include <locale>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace CLI {
|
||||
|
||||
/// Include the items in this namespace to get free conversion of enums to/from streams.
|
||||
/// (This is available inside CLI as well, so CLI11 will use this without a using statement).
|
||||
namespace enums {
|
||||
|
||||
/// output streaming for enumerations
|
||||
template <typename T, typename = typename std::enable_if<std::is_enum<T>::value>::type>
|
||||
std::ostream &operator<<(std::ostream &in, const T &item) {
|
||||
// make sure this is out of the detail namespace otherwise it won't be found when needed
|
||||
return in << static_cast<typename std::underlying_type<T>::type>(item);
|
||||
}
|
||||
|
||||
} // namespace enums
|
||||
|
||||
/// Export to CLI namespace
|
||||
using enums::operator<<;
|
||||
|
||||
namespace detail {
|
||||
/// a constant defining an expected max vector size defined to be a big number that could be multiplied by 4 and not
|
||||
/// produce overflow for some expected uses
|
||||
constexpr int expected_max_vector_size{1 << 29};
|
||||
|
||||
// Based on http://stackoverflow.com/questions/236129/split-a-string-in-c
|
||||
/// Split a string by a delim
|
||||
inline std::vector<std::string> split(const std::string &s, char delim) {
|
||||
std::vector<std::string> elems;
|
||||
// Check to see if empty string, give consistent result
|
||||
if(s.empty()) {
|
||||
elems.emplace_back();
|
||||
} else {
|
||||
// Check to see if emtpy string, give consistent result
|
||||
if(s == "")
|
||||
elems.emplace_back("");
|
||||
else {
|
||||
std::stringstream ss;
|
||||
ss.str(s);
|
||||
std::string item;
|
||||
|
@ -58,28 +34,11 @@ inline std::vector<std::string> split(const std::string &s, char delim) {
|
|||
/// Simple function to join a string
|
||||
template <typename T> std::string join(const T &v, std::string delim = ",") {
|
||||
std::ostringstream s;
|
||||
auto beg = std::begin(v);
|
||||
auto end = std::end(v);
|
||||
if(beg != end)
|
||||
s << *beg++;
|
||||
while(beg != end) {
|
||||
s << delim << *beg++;
|
||||
}
|
||||
return s.str();
|
||||
}
|
||||
|
||||
/// Simple function to join a string from processed elements
|
||||
template <typename T,
|
||||
typename Callable,
|
||||
typename = typename std::enable_if<!std::is_constructible<std::string, Callable>::value>::type>
|
||||
std::string join(const T &v, Callable func, std::string delim = ",") {
|
||||
std::ostringstream s;
|
||||
auto beg = std::begin(v);
|
||||
auto end = std::end(v);
|
||||
if(beg != end)
|
||||
s << func(*beg++);
|
||||
while(beg != end) {
|
||||
s << delim << func(*beg++);
|
||||
size_t start = 0;
|
||||
for(const auto &i : v) {
|
||||
if(start++ > 0)
|
||||
s << delim;
|
||||
s << i;
|
||||
}
|
||||
return s.str();
|
||||
}
|
||||
|
@ -87,7 +46,7 @@ std::string join(const T &v, Callable func, std::string delim = ",") {
|
|||
/// Join a string in reverse order
|
||||
template <typename T> std::string rjoin(const T &v, std::string delim = ",") {
|
||||
std::ostringstream s;
|
||||
for(std::size_t start = 0; start < v.size(); start++) {
|
||||
for(size_t start = 0; start < v.size(); start++) {
|
||||
if(start > 0)
|
||||
s << delim;
|
||||
s << v[v.size() - start - 1];
|
||||
|
@ -138,47 +97,30 @@ inline std::string trim_copy(const std::string &str) {
|
|||
return trim(s);
|
||||
}
|
||||
|
||||
/// remove quotes at the front and back of a string either '"' or '\''
|
||||
inline std::string &remove_quotes(std::string &str) {
|
||||
if(str.length() > 1 && (str.front() == '"' || str.front() == '\'')) {
|
||||
if(str.front() == str.back()) {
|
||||
str.pop_back();
|
||||
str.erase(str.begin(), str.begin() + 1);
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
/// Make a copy of the string and then trim it, any filter string can be used (any char in string is filtered)
|
||||
inline std::string trim_copy(const std::string &str, const std::string &filter) {
|
||||
std::string s = str;
|
||||
return trim(s, filter);
|
||||
}
|
||||
/// Print a two part "help" string
|
||||
inline std::ostream &format_help(std::ostream &out, std::string name, std::string description, std::size_t wid) {
|
||||
inline void format_help(std::stringstream &out, std::string name, std::string description, size_t wid) {
|
||||
name = " " + name;
|
||||
out << std::setw(static_cast<int>(wid)) << std::left << name;
|
||||
if(!description.empty()) {
|
||||
if(description != "") {
|
||||
if(name.length() >= wid)
|
||||
out << "\n" << std::setw(static_cast<int>(wid)) << "";
|
||||
for(const char c : description) {
|
||||
out.put(c);
|
||||
if(c == '\n') {
|
||||
out << std::setw(static_cast<int>(wid)) << "";
|
||||
}
|
||||
}
|
||||
out << std::endl << std::setw(static_cast<int>(wid)) << "";
|
||||
out << description;
|
||||
}
|
||||
out << "\n";
|
||||
return out;
|
||||
out << std::endl;
|
||||
}
|
||||
|
||||
/// Verify the first character of an option
|
||||
template <typename T> bool valid_first_char(T c) {
|
||||
return std::isalnum(c, std::locale()) || c == '_' || c == '?' || c == '@';
|
||||
}
|
||||
template <typename T> bool valid_first_char(T c) { return std::isalpha(c, std::locale()) || c == '_'; }
|
||||
|
||||
/// Verify following characters of an option
|
||||
template <typename T> bool valid_later_char(T c) { return valid_first_char(c) || c == '.' || c == '-'; }
|
||||
template <typename T> bool valid_later_char(T c) {
|
||||
return std::isalnum(c, std::locale()) || c == '_' || c == '.' || c == '-';
|
||||
}
|
||||
|
||||
/// Verify an option name
|
||||
inline bool valid_name_string(const std::string &str) {
|
||||
|
@ -190,11 +132,6 @@ inline bool valid_name_string(const std::string &str) {
|
|||
return true;
|
||||
}
|
||||
|
||||
/// Verify that str consists of letters only
|
||||
inline bool isalpha(const std::string &str) {
|
||||
return std::all_of(str.begin(), str.end(), [](char c) { return std::isalpha(c, std::locale()); });
|
||||
}
|
||||
|
||||
/// Return a lower case version of a string
|
||||
inline std::string to_lower(std::string str) {
|
||||
std::transform(std::begin(str), std::end(str), std::begin(str), [](const std::string::value_type &x) {
|
||||
|
@ -203,105 +140,18 @@ inline std::string to_lower(std::string str) {
|
|||
return str;
|
||||
}
|
||||
|
||||
/// remove underscores from a string
|
||||
inline std::string remove_underscore(std::string str) {
|
||||
str.erase(std::remove(std::begin(str), std::end(str), '_'), std::end(str));
|
||||
return str;
|
||||
}
|
||||
|
||||
/// Find and replace a substring with another substring
|
||||
inline std::string find_and_replace(std::string str, std::string from, std::string to) {
|
||||
|
||||
std::size_t start_pos = 0;
|
||||
|
||||
while((start_pos = str.find(from, start_pos)) != std::string::npos) {
|
||||
str.replace(start_pos, from.length(), to);
|
||||
start_pos += to.length();
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
/// check if the flag definitions has possible false flags
|
||||
inline bool has_default_flag_values(const std::string &flags) {
|
||||
return (flags.find_first_of("{!") != std::string::npos);
|
||||
}
|
||||
|
||||
inline void remove_default_flag_values(std::string &flags) {
|
||||
auto loc = flags.find_first_of('{');
|
||||
while(loc != std::string::npos) {
|
||||
auto finish = flags.find_first_of("},", loc + 1);
|
||||
if((finish != std::string::npos) && (flags[finish] == '}')) {
|
||||
flags.erase(flags.begin() + static_cast<std::ptrdiff_t>(loc),
|
||||
flags.begin() + static_cast<std::ptrdiff_t>(finish) + 1);
|
||||
}
|
||||
loc = flags.find_first_of('{', loc + 1);
|
||||
}
|
||||
flags.erase(std::remove(flags.begin(), flags.end(), '!'), flags.end());
|
||||
}
|
||||
|
||||
/// Check if a string is a member of a list of strings and optionally ignore case or ignore underscores
|
||||
inline std::ptrdiff_t find_member(std::string name,
|
||||
const std::vector<std::string> names,
|
||||
bool ignore_case = false,
|
||||
bool ignore_underscore = false) {
|
||||
auto it = std::end(names);
|
||||
if(ignore_case) {
|
||||
if(ignore_underscore) {
|
||||
name = detail::to_lower(detail::remove_underscore(name));
|
||||
it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
|
||||
return detail::to_lower(detail::remove_underscore(local_name)) == name;
|
||||
});
|
||||
} else {
|
||||
name = detail::to_lower(name);
|
||||
it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
|
||||
return detail::to_lower(local_name) == name;
|
||||
});
|
||||
}
|
||||
|
||||
} else if(ignore_underscore) {
|
||||
name = detail::remove_underscore(name);
|
||||
it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
|
||||
return detail::remove_underscore(local_name) == name;
|
||||
});
|
||||
} else {
|
||||
it = std::find(std::begin(names), std::end(names), name);
|
||||
}
|
||||
|
||||
return (it != std::end(names)) ? (it - std::begin(names)) : (-1);
|
||||
}
|
||||
|
||||
/// Find a trigger string and call a modify callable function that takes the current string and starting position of the
|
||||
/// trigger and returns the position in the string to search for the next trigger string
|
||||
template <typename Callable> inline std::string find_and_modify(std::string str, std::string trigger, Callable modify) {
|
||||
std::size_t start_pos = 0;
|
||||
while((start_pos = str.find(trigger, start_pos)) != std::string::npos) {
|
||||
start_pos = modify(str, start_pos);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
/// Split a string '"one two" "three"' into 'one two', 'three'
|
||||
/// Quote characters can be ` ' or "
|
||||
inline std::vector<std::string> split_up(std::string str, char delimiter = '\0') {
|
||||
inline std::vector<std::string> split_up(std::string str) {
|
||||
|
||||
const std::string delims("\'\"`");
|
||||
auto find_ws = [delimiter](char ch) {
|
||||
return (delimiter == '\0') ? (std::isspace<char>(ch, std::locale()) != 0) : (ch == delimiter);
|
||||
};
|
||||
std::vector<char> delims = {'\'', '\"'};
|
||||
auto find_ws = [](char ch) { return std::isspace<char>(ch, std::locale()); };
|
||||
trim(str);
|
||||
|
||||
std::vector<std::string> output;
|
||||
bool embeddedQuote = false;
|
||||
char keyChar = ' ';
|
||||
|
||||
while(!str.empty()) {
|
||||
if(delims.find_first_of(str[0]) != std::string::npos) {
|
||||
keyChar = str[0];
|
||||
auto end = str.find_first_of(keyChar, 1);
|
||||
while((end != std::string::npos) && (str[end - 1] == '\\')) { // deal with escaped quotes
|
||||
end = str.find_first_of(keyChar, end + 1);
|
||||
embeddedQuote = true;
|
||||
}
|
||||
if(str[0] == '\'') {
|
||||
auto end = str.find('\'', 1);
|
||||
if(end != std::string::npos) {
|
||||
output.push_back(str.substr(1, end - 1));
|
||||
str = str.substr(end + 1);
|
||||
|
@ -309,71 +159,32 @@ inline std::vector<std::string> split_up(std::string str, char delimiter = '\0')
|
|||
output.push_back(str.substr(1));
|
||||
str = "";
|
||||
}
|
||||
} else if(str[0] == '\"') {
|
||||
auto end = str.find('\"', 1);
|
||||
if(end != std::string::npos) {
|
||||
output.push_back(str.substr(1, end - 1));
|
||||
str = str.substr(end + 1);
|
||||
} else {
|
||||
output.push_back(str.substr(1));
|
||||
str = "";
|
||||
}
|
||||
|
||||
} else {
|
||||
auto it = std::find_if(std::begin(str), std::end(str), find_ws);
|
||||
if(it != std::end(str)) {
|
||||
std::string value = std::string(str.begin(), it);
|
||||
output.push_back(value);
|
||||
str = std::string(it + 1, str.end());
|
||||
str = std::string(it, str.end());
|
||||
} else {
|
||||
output.push_back(str);
|
||||
str = "";
|
||||
}
|
||||
}
|
||||
// transform any embedded quotes into the regular character
|
||||
if(embeddedQuote) {
|
||||
output.back() = find_and_replace(output.back(), std::string("\\") + keyChar, std::string(1, keyChar));
|
||||
embeddedQuote = false;
|
||||
}
|
||||
trim(str);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// Add a leader to the beginning of all new lines (nothing is added
|
||||
/// at the start of the first line). `"; "` would be for ini files
|
||||
///
|
||||
/// Can't use Regex, or this would be a subs.
|
||||
inline std::string fix_newlines(const std::string &leader, std::string input) {
|
||||
std::string::size_type n = 0;
|
||||
while(n != std::string::npos && n < input.size()) {
|
||||
n = input.find('\n', n);
|
||||
if(n != std::string::npos) {
|
||||
input = input.substr(0, n + 1) + leader + input.substr(n + 1);
|
||||
n += leader.size();
|
||||
}
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
/// This function detects an equal or colon followed by an escaped quote after an argument
|
||||
/// then modifies the string to replace the equality with a space. This is needed
|
||||
/// to allow the split up function to work properly and is intended to be used with the find_and_modify function
|
||||
/// the return value is the offset+1 which is required by the find_and_modify function.
|
||||
inline std::size_t escape_detect(std::string &str, std::size_t offset) {
|
||||
auto next = str[offset + 1];
|
||||
if((next == '\"') || (next == '\'') || (next == '`')) {
|
||||
auto astart = str.find_last_of("-/ \"\'`", offset - 1);
|
||||
if(astart != std::string::npos) {
|
||||
if(str[astart] == ((str[offset] == '=') ? '-' : '/'))
|
||||
str[offset] = ' '; // interpret this as a space so the split_up works properly
|
||||
}
|
||||
}
|
||||
return offset + 1;
|
||||
}
|
||||
|
||||
/// Add quotes if the string contains spaces
|
||||
inline std::string &add_quotes_if_needed(std::string &str) {
|
||||
if((str.front() != '"' && str.front() != '\'') || str.front() != str.back()) {
|
||||
char quote = str.find('"') < str.find('\'') ? '\'' : '"';
|
||||
if(str.find(' ') != std::string::npos) {
|
||||
str.insert(0, 1, quote);
|
||||
str.append(1, quote);
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
} // namespace CLI
|
||||
} // namespace detail
|
||||
} // namespace CLI
|
||||
|
|
|
@ -1,19 +1,9 @@
|
|||
// 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
|
||||
|
||||
// On GCC < 4.8, the following define is often missing. Due to the
|
||||
// fact that this library only uses sleep_for, this should be safe
|
||||
#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 5 && __GNUC_MINOR__ < 8
|
||||
#define _GLIBCXX_USE_NANOSLEEP
|
||||
#endif
|
||||
// Distributed under the 3-Clause BSD License. See accompanying
|
||||
// file LICENSE or https://github.com/CLIUtils/CLI11 for details.
|
||||
|
||||
#include <array>
|
||||
#include <chrono> // NOLINT(build/c++11)
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
@ -21,7 +11,6 @@
|
|||
|
||||
namespace CLI {
|
||||
|
||||
/// This is a simple timer with pretty printing. Creating the timer starts counting.
|
||||
class Timer {
|
||||
protected:
|
||||
/// This is a typedef to make clocks easier to use
|
||||
|
@ -43,7 +32,7 @@ class Timer {
|
|||
time_point start_;
|
||||
|
||||
/// This is the number of times cycles (print divides by this number)
|
||||
std::size_t cycles{1};
|
||||
size_t cycles{1};
|
||||
|
||||
public:
|
||||
/// Standard print function, this one is set by default
|
||||
|
@ -57,7 +46,7 @@ class Timer {
|
|||
|
||||
public:
|
||||
/// Standard constructor, can set title and print function
|
||||
explicit Timer(std::string title = "Timer", time_print_t time_print = Simple)
|
||||
Timer(std::string title = "Timer", time_print_t time_print = Simple)
|
||||
: title_(std::move(title)), time_print_(std::move(time_print)), start_(clock::now()) {}
|
||||
|
||||
/// Time a function by running it multiple times. Target time is the len to target.
|
||||
|
@ -66,14 +55,14 @@ class Timer {
|
|||
double total_time;
|
||||
|
||||
start_ = clock::now();
|
||||
std::size_t n = 0;
|
||||
size_t n = 0;
|
||||
do {
|
||||
f();
|
||||
std::chrono::duration<double> elapsed = clock::now() - start_;
|
||||
total_time = elapsed.count();
|
||||
} while(n++ < 100u && total_time < target_time);
|
||||
} while(n++ < 100 && total_time < target_time);
|
||||
|
||||
std::string out = make_time_str(total_time / static_cast<double>(n)) + " for " + std::to_string(n) + " tries";
|
||||
std::string out = make_time_str(total_time / n) + " for " + std::to_string(n) + " tries";
|
||||
start_ = start;
|
||||
return out;
|
||||
}
|
||||
|
@ -82,18 +71,16 @@ class Timer {
|
|||
std::string make_time_str() const {
|
||||
time_point stop = clock::now();
|
||||
std::chrono::duration<double> elapsed = stop - start_;
|
||||
double time = elapsed.count() / static_cast<double>(cycles);
|
||||
double time = elapsed.count() / cycles;
|
||||
return make_time_str(time);
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START
|
||||
/// This prints out a time string from a time
|
||||
std::string make_time_str(double time) const {
|
||||
auto print_it = [](double x, std::string unit) {
|
||||
const unsigned int buffer_length = 50;
|
||||
std::array<char, buffer_length> buffer;
|
||||
std::snprintf(buffer.data(), buffer_length, "%.5g", x);
|
||||
return buffer.data() + std::string(" ") + unit;
|
||||
char buffer[50];
|
||||
std::snprintf(buffer, 50, "%.5g", x);
|
||||
return buffer + std::string(" ") + unit;
|
||||
};
|
||||
|
||||
if(time < .000001)
|
||||
|
@ -105,13 +92,13 @@ class Timer {
|
|||
else
|
||||
return print_it(time, "s");
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
// LCOV_EXCL_END
|
||||
|
||||
/// This is the main function, it creates a string
|
||||
std::string to_string() const { return time_print_(title_, make_time_str()); }
|
||||
|
||||
/// Division sets the number of cycles to divide by (no graphical change)
|
||||
Timer &operator/(std::size_t val) {
|
||||
Timer &operator/(size_t val) {
|
||||
cycles = val;
|
||||
return *this;
|
||||
}
|
||||
|
@ -121,14 +108,14 @@ class Timer {
|
|||
class AutoTimer : public Timer {
|
||||
public:
|
||||
/// Reimplementing the constructor is required in GCC 4.7
|
||||
explicit AutoTimer(std::string title = "Timer", time_print_t time_print = Simple) : Timer(title, time_print) {}
|
||||
AutoTimer(std::string title = "Timer", time_print_t time_print = Simple) : Timer(title, time_print) {}
|
||||
// GCC 4.7 does not support using inheriting constructors.
|
||||
|
||||
/// This destructor prints the string
|
||||
/// This desctructor prints the string
|
||||
~AutoTimer() { std::cout << to_string() << std::endl; }
|
||||
};
|
||||
|
||||
} // namespace CLI
|
||||
} // namespace CLI
|
||||
|
||||
/// This prints out the time if shifted into a std::cout like stream.
|
||||
inline std::ostream &operator<<(std::ostream &in, const CLI::Timer &timer) { return in << timer.to_string(); }
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,16 +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]
|
||||
|
||||
#define CLI11_VERSION_MAJOR 1
|
||||
#define CLI11_VERSION_MINOR 9
|
||||
#define CLI11_VERSION_PATCH 1
|
||||
#define CLI11_VERSION "1.9.1"
|
||||
|
||||
// [CLI11:verbatim]
|
2931
ext/doctest.h
2931
ext/doctest.h
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1 @@
|
|||
This is fmt from https://github.com/fmtlib/fmt
|
|
@ -0,0 +1,278 @@
|
|||
// Formatting library for C++ - color support
|
||||
//
|
||||
// Copyright (c) 2018 - present, Victor Zverovich and fmt contributors
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_COLOR_H_
|
||||
#define FMT_COLOR_H_
|
||||
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
#ifdef FMT_DEPRECATED_COLORS
|
||||
|
||||
// color and (v)print_colored are deprecated.
|
||||
enum color { black, red, green, yellow, blue, magenta, cyan, white };
|
||||
FMT_API void vprint_colored(color c, string_view format, format_args args);
|
||||
FMT_API void vprint_colored(color c, wstring_view format, wformat_args args);
|
||||
template <typename... Args>
|
||||
inline void print_colored(color c, string_view format_str,
|
||||
const Args & ... args) {
|
||||
vprint_colored(c, format_str, make_format_args(args...));
|
||||
}
|
||||
template <typename... Args>
|
||||
inline void print_colored(color c, wstring_view format_str,
|
||||
const Args & ... args) {
|
||||
vprint_colored(c, format_str, make_format_args<wformat_context>(args...));
|
||||
}
|
||||
|
||||
inline void vprint_colored(color c, string_view format, format_args args) {
|
||||
char escape[] = "\x1b[30m";
|
||||
escape[3] = static_cast<char>('0' + c);
|
||||
std::fputs(escape, stdout);
|
||||
vprint(format, args);
|
||||
std::fputs(internal::data::RESET_COLOR, stdout);
|
||||
}
|
||||
|
||||
inline void vprint_colored(color c, wstring_view format, wformat_args args) {
|
||||
wchar_t escape[] = L"\x1b[30m";
|
||||
escape[3] = static_cast<wchar_t>('0' + c);
|
||||
std::fputws(escape, stdout);
|
||||
vprint(format, args);
|
||||
std::fputws(internal::data::WRESET_COLOR, stdout);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
// Experimental color support.
|
||||
enum class color : uint32_t {
|
||||
alice_blue = 0xF0F8FF, // rgb(240,248,255)
|
||||
antique_white = 0xFAEBD7, // rgb(250,235,215)
|
||||
aqua = 0x00FFFF, // rgb(0,255,255)
|
||||
aquamarine = 0x7FFFD4, // rgb(127,255,212)
|
||||
azure = 0xF0FFFF, // rgb(240,255,255)
|
||||
beige = 0xF5F5DC, // rgb(245,245,220)
|
||||
bisque = 0xFFE4C4, // rgb(255,228,196)
|
||||
black = 0x000000, // rgb(0,0,0)
|
||||
blanched_almond = 0xFFEBCD, // rgb(255,235,205)
|
||||
blue = 0x0000FF, // rgb(0,0,255)
|
||||
blue_violet = 0x8A2BE2, // rgb(138,43,226)
|
||||
brown = 0xA52A2A, // rgb(165,42,42)
|
||||
burly_wood = 0xDEB887, // rgb(222,184,135)
|
||||
cadet_blue = 0x5F9EA0, // rgb(95,158,160)
|
||||
chartreuse = 0x7FFF00, // rgb(127,255,0)
|
||||
chocolate = 0xD2691E, // rgb(210,105,30)
|
||||
coral = 0xFF7F50, // rgb(255,127,80)
|
||||
cornflower_blue = 0x6495ED, // rgb(100,149,237)
|
||||
cornsilk = 0xFFF8DC, // rgb(255,248,220)
|
||||
crimson = 0xDC143C, // rgb(220,20,60)
|
||||
cyan = 0x00FFFF, // rgb(0,255,255)
|
||||
dark_blue = 0x00008B, // rgb(0,0,139)
|
||||
dark_cyan = 0x008B8B, // rgb(0,139,139)
|
||||
dark_golden_rod = 0xB8860B, // rgb(184,134,11)
|
||||
dark_gray = 0xA9A9A9, // rgb(169,169,169)
|
||||
dark_green = 0x006400, // rgb(0,100,0)
|
||||
dark_khaki = 0xBDB76B, // rgb(189,183,107)
|
||||
dark_magenta = 0x8B008B, // rgb(139,0,139)
|
||||
dark_olive_green = 0x556B2F, // rgb(85,107,47)
|
||||
dark_orange = 0xFF8C00, // rgb(255,140,0)
|
||||
dark_orchid = 0x9932CC, // rgb(153,50,204)
|
||||
dark_red = 0x8B0000, // rgb(139,0,0)
|
||||
dark_salmon = 0xE9967A, // rgb(233,150,122)
|
||||
dark_sea_green = 0x8FBC8F, // rgb(143,188,143)
|
||||
dark_slate_blue = 0x483D8B, // rgb(72,61,139)
|
||||
dark_slate_gray = 0x2F4F4F, // rgb(47,79,79)
|
||||
dark_turquoise = 0x00CED1, // rgb(0,206,209)
|
||||
dark_violet = 0x9400D3, // rgb(148,0,211)
|
||||
deep_pink = 0xFF1493, // rgb(255,20,147)
|
||||
deep_sky_blue = 0x00BFFF, // rgb(0,191,255)
|
||||
dim_gray = 0x696969, // rgb(105,105,105)
|
||||
dodger_blue = 0x1E90FF, // rgb(30,144,255)
|
||||
fire_brick = 0xB22222, // rgb(178,34,34)
|
||||
floral_white = 0xFFFAF0, // rgb(255,250,240)
|
||||
forest_green = 0x228B22, // rgb(34,139,34)
|
||||
fuchsia = 0xFF00FF, // rgb(255,0,255)
|
||||
gainsboro = 0xDCDCDC, // rgb(220,220,220)
|
||||
ghost_white = 0xF8F8FF, // rgb(248,248,255)
|
||||
gold = 0xFFD700, // rgb(255,215,0)
|
||||
golden_rod = 0xDAA520, // rgb(218,165,32)
|
||||
gray = 0x808080, // rgb(128,128,128)
|
||||
green = 0x008000, // rgb(0,128,0)
|
||||
green_yellow = 0xADFF2F, // rgb(173,255,47)
|
||||
honey_dew = 0xF0FFF0, // rgb(240,255,240)
|
||||
hot_pink = 0xFF69B4, // rgb(255,105,180)
|
||||
indian_red = 0xCD5C5C, // rgb(205,92,92)
|
||||
indigo = 0x4B0082, // rgb(75,0,130)
|
||||
ivory = 0xFFFFF0, // rgb(255,255,240)
|
||||
khaki = 0xF0E68C, // rgb(240,230,140)
|
||||
lavender = 0xE6E6FA, // rgb(230,230,250)
|
||||
lavender_blush = 0xFFF0F5, // rgb(255,240,245)
|
||||
lawn_green = 0x7CFC00, // rgb(124,252,0)
|
||||
lemon_chiffon = 0xFFFACD, // rgb(255,250,205)
|
||||
light_blue = 0xADD8E6, // rgb(173,216,230)
|
||||
light_coral = 0xF08080, // rgb(240,128,128)
|
||||
light_cyan = 0xE0FFFF, // rgb(224,255,255)
|
||||
light_golden_rod_yellow = 0xFAFAD2, // rgb(250,250,210)
|
||||
light_gray = 0xD3D3D3, // rgb(211,211,211)
|
||||
light_green = 0x90EE90, // rgb(144,238,144)
|
||||
light_pink = 0xFFB6C1, // rgb(255,182,193)
|
||||
light_salmon = 0xFFA07A, // rgb(255,160,122)
|
||||
light_sea_green = 0x20B2AA, // rgb(32,178,170)
|
||||
light_sky_blue = 0x87CEFA, // rgb(135,206,250)
|
||||
light_slate_gray = 0x778899, // rgb(119,136,153)
|
||||
light_steel_blue = 0xB0C4DE, // rgb(176,196,222)
|
||||
light_yellow = 0xFFFFE0, // rgb(255,255,224)
|
||||
lime = 0x00FF00, // rgb(0,255,0)
|
||||
lime_green = 0x32CD32, // rgb(50,205,50)
|
||||
linen = 0xFAF0E6, // rgb(250,240,230)
|
||||
magenta = 0xFF00FF, // rgb(255,0,255)
|
||||
maroon = 0x800000, // rgb(128,0,0)
|
||||
medium_aquamarine = 0x66CDAA, // rgb(102,205,170)
|
||||
medium_blue = 0x0000CD, // rgb(0,0,205)
|
||||
medium_orchid = 0xBA55D3, // rgb(186,85,211)
|
||||
medium_purple = 0x9370DB, // rgb(147,112,219)
|
||||
medium_sea_green = 0x3CB371, // rgb(60,179,113)
|
||||
medium_slate_blue = 0x7B68EE, // rgb(123,104,238)
|
||||
medium_spring_green = 0x00FA9A, // rgb(0,250,154)
|
||||
medium_turquoise = 0x48D1CC, // rgb(72,209,204)
|
||||
medium_violet_red = 0xC71585, // rgb(199,21,133)
|
||||
midnight_blue = 0x191970, // rgb(25,25,112)
|
||||
mint_cream = 0xF5FFFA, // rgb(245,255,250)
|
||||
misty_rose = 0xFFE4E1, // rgb(255,228,225)
|
||||
moccasin = 0xFFE4B5, // rgb(255,228,181)
|
||||
navajo_white = 0xFFDEAD, // rgb(255,222,173)
|
||||
navy = 0x000080, // rgb(0,0,128)
|
||||
old_lace = 0xFDF5E6, // rgb(253,245,230)
|
||||
olive = 0x808000, // rgb(128,128,0)
|
||||
olive_drab = 0x6B8E23, // rgb(107,142,35)
|
||||
orange = 0xFFA500, // rgb(255,165,0)
|
||||
orange_red = 0xFF4500, // rgb(255,69,0)
|
||||
orchid = 0xDA70D6, // rgb(218,112,214)
|
||||
pale_golden_rod = 0xEEE8AA, // rgb(238,232,170)
|
||||
pale_green = 0x98FB98, // rgb(152,251,152)
|
||||
pale_turquoise = 0xAFEEEE, // rgb(175,238,238)
|
||||
pale_violet_red = 0xDB7093, // rgb(219,112,147)
|
||||
papaya_whip = 0xFFEFD5, // rgb(255,239,213)
|
||||
peach_puff = 0xFFDAB9, // rgb(255,218,185)
|
||||
peru = 0xCD853F, // rgb(205,133,63)
|
||||
pink = 0xFFC0CB, // rgb(255,192,203)
|
||||
plum = 0xDDA0DD, // rgb(221,160,221)
|
||||
powder_blue = 0xB0E0E6, // rgb(176,224,230)
|
||||
purple = 0x800080, // rgb(128,0,128)
|
||||
rebecca_purple = 0x663399, // rgb(102,51,153)
|
||||
red = 0xFF0000, // rgb(255,0,0)
|
||||
rosy_brown = 0xBC8F8F, // rgb(188,143,143)
|
||||
royal_blue = 0x4169E1, // rgb(65,105,225)
|
||||
saddle_brown = 0x8B4513, // rgb(139,69,19)
|
||||
salmon = 0xFA8072, // rgb(250,128,114)
|
||||
sandy_brown = 0xF4A460, // rgb(244,164,96)
|
||||
sea_green = 0x2E8B57, // rgb(46,139,87)
|
||||
sea_shell = 0xFFF5EE, // rgb(255,245,238)
|
||||
sienna = 0xA0522D, // rgb(160,82,45)
|
||||
silver = 0xC0C0C0, // rgb(192,192,192)
|
||||
sky_blue = 0x87CEEB, // rgb(135,206,235)
|
||||
slate_blue = 0x6A5ACD, // rgb(106,90,205)
|
||||
slate_gray = 0x708090, // rgb(112,128,144)
|
||||
snow = 0xFFFAFA, // rgb(255,250,250)
|
||||
spring_green = 0x00FF7F, // rgb(0,255,127)
|
||||
steel_blue = 0x4682B4, // rgb(70,130,180)
|
||||
tan = 0xD2B48C, // rgb(210,180,140)
|
||||
teal = 0x008080, // rgb(0,128,128)
|
||||
thistle = 0xD8BFD8, // rgb(216,191,216)
|
||||
tomato = 0xFF6347, // rgb(255,99,71)
|
||||
turquoise = 0x40E0D0, // rgb(64,224,208)
|
||||
violet = 0xEE82EE, // rgb(238,130,238)
|
||||
wheat = 0xF5DEB3, // rgb(245,222,179)
|
||||
white = 0xFFFFFF, // rgb(255,255,255)
|
||||
white_smoke = 0xF5F5F5, // rgb(245,245,245)
|
||||
yellow = 0xFFFF00, // rgb(255,255,0)
|
||||
yellow_green = 0x9ACD32, // rgb(154,205,50)
|
||||
}; // enum class color
|
||||
|
||||
// rgb is a struct for red, green and blue colors.
|
||||
// We use rgb as name because some editors will show it as color direct in the
|
||||
// editor.
|
||||
struct rgb {
|
||||
FMT_CONSTEXPR_DECL rgb() : r(0), g(0), b(0) {}
|
||||
FMT_CONSTEXPR_DECL rgb(uint8_t r_, uint8_t g_, uint8_t b_)
|
||||
: r(r_), g(g_), b(b_) {}
|
||||
FMT_CONSTEXPR_DECL rgb(uint32_t hex)
|
||||
: r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b((hex) & 0xFF) {}
|
||||
FMT_CONSTEXPR_DECL rgb(color hex)
|
||||
: r((uint32_t(hex) >> 16) & 0xFF), g((uint32_t(hex) >> 8) & 0xFF),
|
||||
b(uint32_t(hex) & 0xFF) {}
|
||||
uint8_t r;
|
||||
uint8_t g;
|
||||
uint8_t b;
|
||||
};
|
||||
|
||||
void vprint_rgb(rgb fd, string_view format, format_args args);
|
||||
void vprint_rgb(rgb fd, rgb bg, string_view format, format_args args);
|
||||
|
||||
/**
|
||||
Formats a string and prints it to stdout using ANSI escape sequences to
|
||||
specify foreground color 'fd'.
|
||||
Example:
|
||||
fmt::print(fmt::color::red, "Elapsed time: {0:.2f} seconds", 1.23);
|
||||
*/
|
||||
template <typename... Args>
|
||||
inline void print(rgb fd, string_view format_str, const Args & ... args) {
|
||||
vprint_rgb(fd, format_str, make_format_args(args...));
|
||||
}
|
||||
|
||||
/**
|
||||
Formats a string and prints it to stdout using ANSI escape sequences to
|
||||
specify foreground color 'fd' and background color 'bg'.
|
||||
Example:
|
||||
fmt::print(fmt::color::red, fmt::color::black,
|
||||
"Elapsed time: {0:.2f} seconds", 1.23);
|
||||
*/
|
||||
template <typename... Args>
|
||||
inline void print(rgb fd, rgb bg, string_view format_str,
|
||||
const Args & ... args) {
|
||||
vprint_rgb(fd, bg, format_str, make_format_args(args...));
|
||||
}
|
||||
namespace internal {
|
||||
FMT_CONSTEXPR void to_esc(uint8_t c, char out[], int offset) {
|
||||
out[offset + 0] = static_cast<char>('0' + c / 100);
|
||||
out[offset + 1] = static_cast<char>('0' + c / 10 % 10);
|
||||
out[offset + 2] = static_cast<char>('0' + c % 10);
|
||||
}
|
||||
} // namespace internal
|
||||
|
||||
inline void vprint_rgb(rgb fd, string_view format, format_args args) {
|
||||
char escape_fd[] = "\x1b[38;2;000;000;000m";
|
||||
internal::to_esc(fd.r, escape_fd, 7);
|
||||
internal::to_esc(fd.g, escape_fd, 11);
|
||||
internal::to_esc(fd.b, escape_fd, 15);
|
||||
|
||||
std::fputs(escape_fd, stdout);
|
||||
vprint(format, args);
|
||||
std::fputs(internal::data::RESET_COLOR, stdout);
|
||||
}
|
||||
|
||||
inline void vprint_rgb(rgb fd, rgb bg, string_view format, format_args args) {
|
||||
char escape_fd[] = "\x1b[38;2;000;000;000m"; // foreground color
|
||||
char escape_bg[] = "\x1b[48;2;000;000;000m"; // background color
|
||||
internal::to_esc(fd.r, escape_fd, 7);
|
||||
internal::to_esc(fd.g, escape_fd, 11);
|
||||
internal::to_esc(fd.b, escape_fd, 15);
|
||||
|
||||
internal::to_esc(bg.r, escape_bg, 7);
|
||||
internal::to_esc(bg.g, escape_bg, 11);
|
||||
internal::to_esc(bg.b, escape_bg, 15);
|
||||
|
||||
std::fputs(escape_fd, stdout);
|
||||
std::fputs(escape_bg, stdout);
|
||||
vprint(format, args);
|
||||
std::fputs(internal::data::RESET_COLOR, stdout);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_COLOR_H_
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,866 @@
|
|||
// Formatting library for C++
|
||||
//
|
||||
// Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_FORMAT_INL_H_
|
||||
#define FMT_FORMAT_INL_H_
|
||||
|
||||
#include "format.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <cctype>
|
||||
#include <cerrno>
|
||||
#include <climits>
|
||||
#include <cmath>
|
||||
#include <cstdarg>
|
||||
#include <cstddef> // for std::ptrdiff_t
|
||||
#include <cstring> // for std::memmove
|
||||
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
|
||||
# include <locale>
|
||||
#endif
|
||||
|
||||
#if FMT_USE_WINDOWS_H
|
||||
# if !defined(FMT_HEADER_ONLY) && !defined(WIN32_LEAN_AND_MEAN)
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# endif
|
||||
# if defined(NOMINMAX) || defined(FMT_WIN_MINMAX)
|
||||
# include <windows.h>
|
||||
# else
|
||||
# define NOMINMAX
|
||||
# include <windows.h>
|
||||
# undef NOMINMAX
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if FMT_EXCEPTIONS
|
||||
# define FMT_TRY try
|
||||
# define FMT_CATCH(x) catch (x)
|
||||
#else
|
||||
# define FMT_TRY if (true)
|
||||
# define FMT_CATCH(x) if (false)
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
# pragma warning(push)
|
||||
# pragma warning(disable: 4127) // conditional expression is constant
|
||||
# pragma warning(disable: 4702) // unreachable code
|
||||
// Disable deprecation warning for strerror. The latter is not called but
|
||||
// MSVC fails to detect it.
|
||||
# pragma warning(disable: 4996)
|
||||
#endif
|
||||
|
||||
// Dummy implementations of strerror_r and strerror_s called if corresponding
|
||||
// system functions are not available.
|
||||
inline fmt::internal::null<> strerror_r(int, char *, ...) {
|
||||
return fmt::internal::null<>();
|
||||
}
|
||||
inline fmt::internal::null<> strerror_s(char *, std::size_t, ...) {
|
||||
return fmt::internal::null<>();
|
||||
}
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
namespace {
|
||||
|
||||
#ifndef _MSC_VER
|
||||
# define FMT_SNPRINTF snprintf
|
||||
#else // _MSC_VER
|
||||
inline int fmt_snprintf(char *buffer, size_t size, const char *format, ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
int result = vsnprintf_s(buffer, size, _TRUNCATE, format, args);
|
||||
va_end(args);
|
||||
return result;
|
||||
}
|
||||
# define FMT_SNPRINTF fmt_snprintf
|
||||
#endif // _MSC_VER
|
||||
|
||||
#if defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT)
|
||||
# define FMT_SWPRINTF snwprintf
|
||||
#else
|
||||
# define FMT_SWPRINTF swprintf
|
||||
#endif // defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT)
|
||||
|
||||
typedef void (*FormatFunc)(internal::buffer &, int, string_view);
|
||||
|
||||
// Portable thread-safe version of strerror.
|
||||
// Sets buffer to point to a string describing the error code.
|
||||
// This can be either a pointer to a string stored in buffer,
|
||||
// or a pointer to some static immutable string.
|
||||
// Returns one of the following values:
|
||||
// 0 - success
|
||||
// ERANGE - buffer is not large enough to store the error message
|
||||
// other - failure
|
||||
// Buffer should be at least of size 1.
|
||||
int safe_strerror(
|
||||
int error_code, char *&buffer, std::size_t buffer_size) FMT_NOEXCEPT {
|
||||
FMT_ASSERT(buffer != FMT_NULL && buffer_size != 0, "invalid buffer");
|
||||
|
||||
class dispatcher {
|
||||
private:
|
||||
int error_code_;
|
||||
char *&buffer_;
|
||||
std::size_t buffer_size_;
|
||||
|
||||
// A noop assignment operator to avoid bogus warnings.
|
||||
void operator=(const dispatcher &) {}
|
||||
|
||||
// Handle the result of XSI-compliant version of strerror_r.
|
||||
int handle(int result) {
|
||||
// glibc versions before 2.13 return result in errno.
|
||||
return result == -1 ? errno : result;
|
||||
}
|
||||
|
||||
// Handle the result of GNU-specific version of strerror_r.
|
||||
int handle(char *message) {
|
||||
// If the buffer is full then the message is probably truncated.
|
||||
if (message == buffer_ && strlen(buffer_) == buffer_size_ - 1)
|
||||
return ERANGE;
|
||||
buffer_ = message;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Handle the case when strerror_r is not available.
|
||||
int handle(internal::null<>) {
|
||||
return fallback(strerror_s(buffer_, buffer_size_, error_code_));
|
||||
}
|
||||
|
||||
// Fallback to strerror_s when strerror_r is not available.
|
||||
int fallback(int result) {
|
||||
// If the buffer is full then the message is probably truncated.
|
||||
return result == 0 && strlen(buffer_) == buffer_size_ - 1 ?
|
||||
ERANGE : result;
|
||||
}
|
||||
|
||||
// Fallback to strerror if strerror_r and strerror_s are not available.
|
||||
int fallback(internal::null<>) {
|
||||
errno = 0;
|
||||
buffer_ = strerror(error_code_);
|
||||
return errno;
|
||||
}
|
||||
|
||||
public:
|
||||
dispatcher(int err_code, char *&buf, std::size_t buf_size)
|
||||
: error_code_(err_code), buffer_(buf), buffer_size_(buf_size) {}
|
||||
|
||||
int run() {
|
||||
return handle(strerror_r(error_code_, buffer_, buffer_size_));
|
||||
}
|
||||
};
|
||||
return dispatcher(error_code, buffer, buffer_size).run();
|
||||
}
|
||||
|
||||
void format_error_code(internal::buffer &out, int error_code,
|
||||
string_view message) FMT_NOEXCEPT {
|
||||
// Report error code making sure that the output fits into
|
||||
// inline_buffer_size to avoid dynamic memory allocation and potential
|
||||
// bad_alloc.
|
||||
out.resize(0);
|
||||
static const char SEP[] = ": ";
|
||||
static const char ERROR_STR[] = "error ";
|
||||
// Subtract 2 to account for terminating null characters in SEP and ERROR_STR.
|
||||
std::size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2;
|
||||
typedef internal::int_traits<int>::main_type main_type;
|
||||
main_type abs_value = static_cast<main_type>(error_code);
|
||||
if (internal::is_negative(error_code)) {
|
||||
abs_value = 0 - abs_value;
|
||||
++error_code_size;
|
||||
}
|
||||
error_code_size += internal::count_digits(abs_value);
|
||||
writer w(out);
|
||||
if (message.size() <= inline_buffer_size - error_code_size) {
|
||||
w.write(message);
|
||||
w.write(SEP);
|
||||
}
|
||||
w.write(ERROR_STR);
|
||||
w.write(error_code);
|
||||
assert(out.size() <= inline_buffer_size);
|
||||
}
|
||||
|
||||
void report_error(FormatFunc func, int error_code,
|
||||
string_view message) FMT_NOEXCEPT {
|
||||
memory_buffer full_message;
|
||||
func(full_message, error_code, message);
|
||||
// Use Writer::data instead of Writer::c_str to avoid potential memory
|
||||
// allocation.
|
||||
std::fwrite(full_message.data(), full_message.size(), 1, stderr);
|
||||
std::fputc('\n', stderr);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
|
||||
class locale {
|
||||
private:
|
||||
std::locale locale_;
|
||||
|
||||
public:
|
||||
explicit locale(std::locale loc = std::locale()) : locale_(loc) {}
|
||||
std::locale get() { return locale_; }
|
||||
};
|
||||
|
||||
FMT_FUNC size_t internal::count_code_points(u8string_view s) {
|
||||
const char8_t *data = s.data();
|
||||
int num_code_points = 0;
|
||||
for (size_t i = 0, size = s.size(); i != size; ++i) {
|
||||
if ((data[i].value & 0xc0) != 0x80)
|
||||
++num_code_points;
|
||||
}
|
||||
return num_code_points;
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
FMT_FUNC Char internal::thousands_sep(locale_provider *lp) {
|
||||
std::locale loc = lp ? lp->locale().get() : std::locale();
|
||||
return std::use_facet<std::numpunct<Char>>(loc).thousands_sep();
|
||||
}
|
||||
#else
|
||||
template <typename Char>
|
||||
FMT_FUNC Char internal::thousands_sep(locale_provider *lp) {
|
||||
return FMT_STATIC_THOUSANDS_SEPARATOR;
|
||||
}
|
||||
#endif
|
||||
|
||||
FMT_FUNC void system_error::init(
|
||||
int err_code, string_view format_str, format_args args) {
|
||||
error_code_ = err_code;
|
||||
memory_buffer buffer;
|
||||
format_system_error(buffer, err_code, vformat(format_str, args));
|
||||
std::runtime_error &base = *this;
|
||||
base = std::runtime_error(to_string(buffer));
|
||||
}
|
||||
|
||||
namespace internal {
|
||||
template <typename T>
|
||||
int char_traits<char>::format_float(
|
||||
char *buffer, std::size_t size, const char *format, int precision, T value) {
|
||||
return precision < 0 ?
|
||||
FMT_SNPRINTF(buffer, size, format, value) :
|
||||
FMT_SNPRINTF(buffer, size, format, precision, value);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
int char_traits<wchar_t>::format_float(
|
||||
wchar_t *buffer, std::size_t size, const wchar_t *format, int precision,
|
||||
T value) {
|
||||
return precision < 0 ?
|
||||
FMT_SWPRINTF(buffer, size, format, value) :
|
||||
FMT_SWPRINTF(buffer, size, format, precision, value);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const char basic_data<T>::DIGITS[] =
|
||||
"0001020304050607080910111213141516171819"
|
||||
"2021222324252627282930313233343536373839"
|
||||
"4041424344454647484950515253545556575859"
|
||||
"6061626364656667686970717273747576777879"
|
||||
"8081828384858687888990919293949596979899";
|
||||
|
||||
#define FMT_POWERS_OF_10(factor) \
|
||||
factor * 10, \
|
||||
factor * 100, \
|
||||
factor * 1000, \
|
||||
factor * 10000, \
|
||||
factor * 100000, \
|
||||
factor * 1000000, \
|
||||
factor * 10000000, \
|
||||
factor * 100000000, \
|
||||
factor * 1000000000
|
||||
|
||||
template <typename T>
|
||||
const uint32_t basic_data<T>::POWERS_OF_10_32[] = {
|
||||
1, FMT_POWERS_OF_10(1)
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
const uint32_t basic_data<T>::ZERO_OR_POWERS_OF_10_32[] = {
|
||||
0, FMT_POWERS_OF_10(1)
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
const uint64_t basic_data<T>::ZERO_OR_POWERS_OF_10_64[] = {
|
||||
0,
|
||||
FMT_POWERS_OF_10(1),
|
||||
FMT_POWERS_OF_10(1000000000ull),
|
||||
10000000000000000000ull
|
||||
};
|
||||
|
||||
// Normalized 64-bit significands of pow(10, k), for k = -348, -340, ..., 340.
|
||||
// These are generated by support/compute-powers.py.
|
||||
template <typename T>
|
||||
const uint64_t basic_data<T>::POW10_SIGNIFICANDS[] = {
|
||||
0xfa8fd5a0081c0288, 0xbaaee17fa23ebf76, 0x8b16fb203055ac76,
|
||||
0xcf42894a5dce35ea, 0x9a6bb0aa55653b2d, 0xe61acf033d1a45df,
|
||||
0xab70fe17c79ac6ca, 0xff77b1fcbebcdc4f, 0xbe5691ef416bd60c,
|
||||
0x8dd01fad907ffc3c, 0xd3515c2831559a83, 0x9d71ac8fada6c9b5,
|
||||
0xea9c227723ee8bcb, 0xaecc49914078536d, 0x823c12795db6ce57,
|
||||
0xc21094364dfb5637, 0x9096ea6f3848984f, 0xd77485cb25823ac7,
|
||||
0xa086cfcd97bf97f4, 0xef340a98172aace5, 0xb23867fb2a35b28e,
|
||||
0x84c8d4dfd2c63f3b, 0xc5dd44271ad3cdba, 0x936b9fcebb25c996,
|
||||
0xdbac6c247d62a584, 0xa3ab66580d5fdaf6, 0xf3e2f893dec3f126,
|
||||
0xb5b5ada8aaff80b8, 0x87625f056c7c4a8b, 0xc9bcff6034c13053,
|
||||
0x964e858c91ba2655, 0xdff9772470297ebd, 0xa6dfbd9fb8e5b88f,
|
||||
0xf8a95fcf88747d94, 0xb94470938fa89bcf, 0x8a08f0f8bf0f156b,
|
||||
0xcdb02555653131b6, 0x993fe2c6d07b7fac, 0xe45c10c42a2b3b06,
|
||||
0xaa242499697392d3, 0xfd87b5f28300ca0e, 0xbce5086492111aeb,
|
||||
0x8cbccc096f5088cc, 0xd1b71758e219652c, 0x9c40000000000000,
|
||||
0xe8d4a51000000000, 0xad78ebc5ac620000, 0x813f3978f8940984,
|
||||
0xc097ce7bc90715b3, 0x8f7e32ce7bea5c70, 0xd5d238a4abe98068,
|
||||
0x9f4f2726179a2245, 0xed63a231d4c4fb27, 0xb0de65388cc8ada8,
|
||||
0x83c7088e1aab65db, 0xc45d1df942711d9a, 0x924d692ca61be758,
|
||||
0xda01ee641a708dea, 0xa26da3999aef774a, 0xf209787bb47d6b85,
|
||||
0xb454e4a179dd1877, 0x865b86925b9bc5c2, 0xc83553c5c8965d3d,
|
||||
0x952ab45cfa97a0b3, 0xde469fbd99a05fe3, 0xa59bc234db398c25,
|
||||
0xf6c69a72a3989f5c, 0xb7dcbf5354e9bece, 0x88fcf317f22241e2,
|
||||
0xcc20ce9bd35c78a5, 0x98165af37b2153df, 0xe2a0b5dc971f303a,
|
||||
0xa8d9d1535ce3b396, 0xfb9b7cd9a4a7443c, 0xbb764c4ca7a44410,
|
||||
0x8bab8eefb6409c1a, 0xd01fef10a657842c, 0x9b10a4e5e9913129,
|
||||
0xe7109bfba19c0c9d, 0xac2820d9623bf429, 0x80444b5e7aa7cf85,
|
||||
0xbf21e44003acdd2d, 0x8e679c2f5e44ff8f, 0xd433179d9c8cb841,
|
||||
0x9e19db92b4e31ba9, 0xeb96bf6ebadf77d9, 0xaf87023b9bf0ee6b,
|
||||
};
|
||||
|
||||
// Binary exponents of pow(10, k), for k = -348, -340, ..., 340, corresponding
|
||||
// to significands above.
|
||||
template <typename T>
|
||||
const int16_t basic_data<T>::POW10_EXPONENTS[] = {
|
||||
-1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980, -954,
|
||||
-927, -901, -874, -847, -821, -794, -768, -741, -715, -688, -661,
|
||||
-635, -608, -582, -555, -529, -502, -475, -449, -422, -396, -369,
|
||||
-343, -316, -289, -263, -236, -210, -183, -157, -130, -103, -77,
|
||||
-50, -24, 3, 30, 56, 83, 109, 136, 162, 189, 216,
|
||||
242, 269, 295, 322, 348, 375, 402, 428, 455, 481, 508,
|
||||
534, 561, 588, 614, 641, 667, 694, 720, 747, 774, 800,
|
||||
827, 853, 880, 907, 933, 960, 986, 1013, 1039, 1066
|
||||
};
|
||||
|
||||
template <typename T> const char basic_data<T>::RESET_COLOR[] = "\x1b[0m";
|
||||
template <typename T> const wchar_t basic_data<T>::WRESET_COLOR[] = L"\x1b[0m";
|
||||
|
||||
// A handmade floating-point number f * pow(2, e).
|
||||
class fp {
|
||||
private:
|
||||
typedef uint64_t significand_type;
|
||||
|
||||
// All sizes are in bits.
|
||||
static FMT_CONSTEXPR_DECL const int char_size =
|
||||
std::numeric_limits<unsigned char>::digits;
|
||||
// Subtract 1 to account for an implicit most significant bit in the
|
||||
// normalized form.
|
||||
static FMT_CONSTEXPR_DECL const int double_significand_size =
|
||||
std::numeric_limits<double>::digits - 1;
|
||||
static FMT_CONSTEXPR_DECL const uint64_t implicit_bit =
|
||||
1ull << double_significand_size;
|
||||
|
||||
public:
|
||||
significand_type f;
|
||||
int e;
|
||||
|
||||
static FMT_CONSTEXPR_DECL const int significand_size =
|
||||
sizeof(significand_type) * char_size;
|
||||
|
||||
fp(): f(0), e(0) {}
|
||||
fp(uint64_t f, int e): f(f), e(e) {}
|
||||
|
||||
// Constructs fp from an IEEE754 double. It is a template to prevent compile
|
||||
// errors on platforms where double is not IEEE754.
|
||||
template <typename Double>
|
||||
explicit fp(Double d) {
|
||||
// Assume double is in the format [sign][exponent][significand].
|
||||
typedef std::numeric_limits<Double> limits;
|
||||
const int double_size = static_cast<int>(sizeof(Double) * char_size);
|
||||
const int exponent_size =
|
||||
double_size - double_significand_size - 1; // -1 for sign
|
||||
const uint64_t significand_mask = implicit_bit - 1;
|
||||
const uint64_t exponent_mask = (~0ull >> 1) & ~significand_mask;
|
||||
const int exponent_bias = (1 << exponent_size) - limits::max_exponent - 1;
|
||||
auto u = bit_cast<uint64_t>(d);
|
||||
auto biased_e = (u & exponent_mask) >> double_significand_size;
|
||||
f = u & significand_mask;
|
||||
if (biased_e != 0)
|
||||
f += implicit_bit;
|
||||
else
|
||||
biased_e = 1; // Subnormals use biased exponent 1 (min exponent).
|
||||
e = static_cast<int>(biased_e - exponent_bias - double_significand_size);
|
||||
}
|
||||
|
||||
// Normalizes the value converted from double and multiplied by (1 << SHIFT).
|
||||
template <int SHIFT = 0>
|
||||
void normalize() {
|
||||
// Handle subnormals.
|
||||
auto shifted_implicit_bit = implicit_bit << SHIFT;
|
||||
while ((f & shifted_implicit_bit) == 0) {
|
||||
f <<= 1;
|
||||
--e;
|
||||
}
|
||||
// Subtract 1 to account for hidden bit.
|
||||
auto offset = significand_size - double_significand_size - SHIFT - 1;
|
||||
f <<= offset;
|
||||
e -= offset;
|
||||
}
|
||||
|
||||
// Compute lower and upper boundaries (m^- and m^+ in the Grisu paper), where
|
||||
// a boundary is a value half way between the number and its predecessor
|
||||
// (lower) or successor (upper). The upper boundary is normalized and lower
|
||||
// has the same exponent but may be not normalized.
|
||||
void compute_boundaries(fp &lower, fp &upper) const {
|
||||
lower = f == implicit_bit ?
|
||||
fp((f << 2) - 1, e - 2) : fp((f << 1) - 1, e - 1);
|
||||
upper = fp((f << 1) + 1, e - 1);
|
||||
upper.normalize<1>(); // 1 is to account for the exponent shift above.
|
||||
lower.f <<= lower.e - upper.e;
|
||||
lower.e = upper.e;
|
||||
}
|
||||
};
|
||||
|
||||
// Returns an fp number representing x - y. Result may not be normalized.
|
||||
inline fp operator-(fp x, fp y) {
|
||||
FMT_ASSERT(x.f >= y.f && x.e == y.e, "invalid operands");
|
||||
return fp(x.f - y.f, x.e);
|
||||
}
|
||||
|
||||
// Computes an fp number r with r.f = x.f * y.f / pow(2, 64) rounded to nearest
|
||||
// with half-up tie breaking, r.e = x.e + y.e + 64. Result may not be normalized.
|
||||
FMT_API fp operator*(fp x, fp y);
|
||||
|
||||
// Returns cached power (of 10) c_k = c_k.f * pow(2, c_k.e) such that its
|
||||
// (binary) exponent satisfies min_exponent <= c_k.e <= min_exponent + 3.
|
||||
FMT_API fp get_cached_power(int min_exponent, int &pow10_exponent);
|
||||
|
||||
FMT_FUNC fp operator*(fp x, fp y) {
|
||||
// Multiply 32-bit parts of significands.
|
||||
uint64_t mask = (1ULL << 32) - 1;
|
||||
uint64_t a = x.f >> 32, b = x.f & mask;
|
||||
uint64_t c = y.f >> 32, d = y.f & mask;
|
||||
uint64_t ac = a * c, bc = b * c, ad = a * d, bd = b * d;
|
||||
// Compute mid 64-bit of result and round.
|
||||
uint64_t mid = (bd >> 32) + (ad & mask) + (bc & mask) + (1U << 31);
|
||||
return fp(ac + (ad >> 32) + (bc >> 32) + (mid >> 32), x.e + y.e + 64);
|
||||
}
|
||||
|
||||
FMT_FUNC fp get_cached_power(int min_exponent, int &pow10_exponent) {
|
||||
const double one_over_log2_10 = 0.30102999566398114; // 1 / log2(10)
|
||||
int index = static_cast<int>(std::ceil(
|
||||
(min_exponent + fp::significand_size - 1) * one_over_log2_10));
|
||||
// Decimal exponent of the first (smallest) cached power of 10.
|
||||
const int first_dec_exp = -348;
|
||||
// Difference between 2 consecutive decimal exponents in cached powers of 10.
|
||||
const int dec_exp_step = 8;
|
||||
index = (index - first_dec_exp - 1) / dec_exp_step + 1;
|
||||
pow10_exponent = first_dec_exp + index * dec_exp_step;
|
||||
return fp(data::POW10_SIGNIFICANDS[index], data::POW10_EXPONENTS[index]);
|
||||
}
|
||||
|
||||
// Generates output using Grisu2 digit-gen algorithm.
|
||||
FMT_FUNC void grisu2_gen_digits(
|
||||
const fp &scaled_value, const fp &scaled_upper, uint64_t delta,
|
||||
char *buffer, size_t &size, int &dec_exp) {
|
||||
internal::fp one(1ull << -scaled_upper.e, scaled_upper.e);
|
||||
// hi (p1 in Grisu) contains the most significant digits of scaled_upper.
|
||||
// hi = floor(scaled_upper / one).
|
||||
uint32_t hi = static_cast<uint32_t>(scaled_upper.f >> -one.e);
|
||||
// lo (p2 in Grisu) contains the least significants digits of scaled_upper.
|
||||
// lo = scaled_upper mod 1.
|
||||
uint64_t lo = scaled_upper.f & (one.f - 1);
|
||||
size = 0;
|
||||
auto exp = count_digits(hi); // kappa in Grisu.
|
||||
while (exp > 0) {
|
||||
uint32_t digit = 0;
|
||||
// This optimization by miloyip reduces the number of integer divisions by
|
||||
// one per iteration.
|
||||
switch (exp) {
|
||||
case 10: digit = hi / 1000000000; hi %= 1000000000; break;
|
||||
case 9: digit = hi / 100000000; hi %= 100000000; break;
|
||||
case 8: digit = hi / 10000000; hi %= 10000000; break;
|
||||
case 7: digit = hi / 1000000; hi %= 1000000; break;
|
||||
case 6: digit = hi / 100000; hi %= 100000; break;
|
||||
case 5: digit = hi / 10000; hi %= 10000; break;
|
||||
case 4: digit = hi / 1000; hi %= 1000; break;
|
||||
case 3: digit = hi / 100; hi %= 100; break;
|
||||
case 2: digit = hi / 10; hi %= 10; break;
|
||||
case 1: digit = hi; hi = 0; break;
|
||||
default:
|
||||
FMT_ASSERT(false, "invalid number of digits");
|
||||
}
|
||||
if (digit != 0 || size != 0)
|
||||
buffer[size++] = static_cast<char>('0' + digit);
|
||||
--exp;
|
||||
uint64_t remainder = (static_cast<uint64_t>(hi) << -one.e) + lo;
|
||||
if (remainder <= delta) {
|
||||
dec_exp += exp;
|
||||
// TODO: use scaled_value
|
||||
(void)scaled_value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (;;) {
|
||||
lo *= 10;
|
||||
delta *= 10;
|
||||
char digit = static_cast<char>(lo >> -one.e);
|
||||
if (digit != 0 || size != 0)
|
||||
buffer[size++] = static_cast<char>('0' + digit);
|
||||
lo &= one.f - 1;
|
||||
--exp;
|
||||
if (lo < delta) {
|
||||
dec_exp += exp;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FMT_FUNC void grisu2_format_positive(double value, char *buffer, size_t &size,
|
||||
int &dec_exp) {
|
||||
FMT_ASSERT(value > 0, "value is nonpositive");
|
||||
fp fp_value(value);
|
||||
fp lower, upper; // w^- and w^+ in the Grisu paper.
|
||||
fp_value.compute_boundaries(lower, upper);
|
||||
// Find a cached power of 10 close to 1 / upper.
|
||||
const int min_exp = -60; // alpha in Grisu.
|
||||
auto dec_pow = get_cached_power( // \tilde{c}_{-k} in Grisu.
|
||||
min_exp - (upper.e + fp::significand_size), dec_exp);
|
||||
dec_exp = -dec_exp;
|
||||
fp_value.normalize();
|
||||
fp scaled_value = fp_value * dec_pow;
|
||||
fp scaled_lower = lower * dec_pow; // \tilde{M}^- in Grisu.
|
||||
fp scaled_upper = upper * dec_pow; // \tilde{M}^+ in Grisu.
|
||||
++scaled_lower.f; // \tilde{M}^- + 1 ulp -> M^-_{\uparrow}.
|
||||
--scaled_upper.f; // \tilde{M}^+ - 1 ulp -> M^+_{\downarrow}.
|
||||
uint64_t delta = scaled_upper.f - scaled_lower.f;
|
||||
grisu2_gen_digits(scaled_value, scaled_upper, delta, buffer, size, dec_exp);
|
||||
}
|
||||
|
||||
FMT_FUNC void round(char *buffer, size_t &size, int &exp,
|
||||
int digits_to_remove) {
|
||||
size -= to_unsigned(digits_to_remove);
|
||||
exp += digits_to_remove;
|
||||
int digit = buffer[size] - '0';
|
||||
// TODO: proper rounding and carry
|
||||
if (digit > 5 || (digit == 5 && (digits_to_remove > 1 ||
|
||||
(buffer[size - 1] - '0') % 2) != 0)) {
|
||||
++buffer[size - 1];
|
||||
}
|
||||
}
|
||||
|
||||
// Writes the exponent exp in the form "[+-]d{1,3}" to buffer.
|
||||
FMT_FUNC char *write_exponent(char *buffer, int exp) {
|
||||
FMT_ASSERT(-1000 < exp && exp < 1000, "exponent out of range");
|
||||
if (exp < 0) {
|
||||
*buffer++ = '-';
|
||||
exp = -exp;
|
||||
} else {
|
||||
*buffer++ = '+';
|
||||
}
|
||||
if (exp >= 100) {
|
||||
*buffer++ = static_cast<char>('0' + exp / 100);
|
||||
exp %= 100;
|
||||
const char *d = data::DIGITS + exp * 2;
|
||||
*buffer++ = d[0];
|
||||
*buffer++ = d[1];
|
||||
} else {
|
||||
const char *d = data::DIGITS + exp * 2;
|
||||
*buffer++ = d[0];
|
||||
*buffer++ = d[1];
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
FMT_FUNC void format_exp_notation(
|
||||
char *buffer, size_t &size, int exp, int precision, bool upper) {
|
||||
// Insert a decimal point after the first digit and add an exponent.
|
||||
std::memmove(buffer + 2, buffer + 1, size - 1);
|
||||
buffer[1] = '.';
|
||||
exp += static_cast<int>(size) - 1;
|
||||
int num_digits = precision - static_cast<int>(size) + 1;
|
||||
if (num_digits > 0) {
|
||||
std::uninitialized_fill_n(buffer + size + 1, num_digits, '0');
|
||||
size += to_unsigned(num_digits);
|
||||
} else if (num_digits < 0) {
|
||||
round(buffer, size, exp, -num_digits);
|
||||
}
|
||||
char *p = buffer + size + 1;
|
||||
*p++ = upper ? 'E' : 'e';
|
||||
size = to_unsigned(write_exponent(p, exp) - buffer);
|
||||
}
|
||||
|
||||
// Prettifies the output of the Grisu2 algorithm.
|
||||
// The number is given as v = buffer * 10^exp.
|
||||
FMT_FUNC void grisu2_prettify(char *buffer, size_t &size, int exp,
|
||||
int precision, bool upper) {
|
||||
// pow(10, full_exp - 1) <= v <= pow(10, full_exp).
|
||||
int int_size = static_cast<int>(size);
|
||||
int full_exp = int_size + exp;
|
||||
const int exp_threshold = 21;
|
||||
if (int_size <= full_exp && full_exp <= exp_threshold) {
|
||||
// 1234e7 -> 12340000000[.0+]
|
||||
std::uninitialized_fill_n(buffer + int_size, full_exp - int_size, '0');
|
||||
char *p = buffer + full_exp;
|
||||
if (precision > 0) {
|
||||
*p++ = '.';
|
||||
std::uninitialized_fill_n(p, precision, '0');
|
||||
p += precision;
|
||||
}
|
||||
size = to_unsigned(p - buffer);
|
||||
} else if (0 < full_exp && full_exp <= exp_threshold) {
|
||||
// 1234e-2 -> 12.34[0+]
|
||||
int fractional_size = -exp;
|
||||
std::memmove(buffer + full_exp + 1, buffer + full_exp,
|
||||
to_unsigned(fractional_size));
|
||||
buffer[full_exp] = '.';
|
||||
int num_zeros = precision - fractional_size;
|
||||
if (num_zeros > 0) {
|
||||
std::uninitialized_fill_n(buffer + size + 1, num_zeros, '0');
|
||||
size += to_unsigned(num_zeros);
|
||||
}
|
||||
++size;
|
||||
} else if (-6 < full_exp && full_exp <= 0) {
|
||||
// 1234e-6 -> 0.001234
|
||||
int offset = 2 - full_exp;
|
||||
std::memmove(buffer + offset, buffer, size);
|
||||
buffer[0] = '0';
|
||||
buffer[1] = '.';
|
||||
std::uninitialized_fill_n(buffer + 2, -full_exp, '0');
|
||||
size = to_unsigned(int_size + offset);
|
||||
} else {
|
||||
format_exp_notation(buffer, size, exp, precision, upper);
|
||||
}
|
||||
}
|
||||
|
||||
#if FMT_CLANG_VERSION
|
||||
# define FMT_FALLTHROUGH [[clang::fallthrough]];
|
||||
#elif FMT_GCC_VERSION >= 700
|
||||
# define FMT_FALLTHROUGH [[gnu::fallthrough]];
|
||||
#else
|
||||
# define FMT_FALLTHROUGH
|
||||
#endif
|
||||
|
||||
// Formats a nonnegative value using Grisu2 algorithm. Grisu2 doesn't give any
|
||||
// guarantees on the shortness of the result.
|
||||
FMT_FUNC void grisu2_format(double value, char *buffer, size_t &size, char type,
|
||||
int precision, bool write_decimal_point) {
|
||||
FMT_ASSERT(value >= 0, "value is negative");
|
||||
int dec_exp = 0; // K in Grisu.
|
||||
if (value > 0) {
|
||||
grisu2_format_positive(value, buffer, size, dec_exp);
|
||||
} else {
|
||||
*buffer = '0';
|
||||
size = 1;
|
||||
}
|
||||
const int default_precision = 6;
|
||||
if (precision < 0)
|
||||
precision = default_precision;
|
||||
bool upper = false;
|
||||
switch (type) {
|
||||
case 'G':
|
||||
upper = true;
|
||||
FMT_FALLTHROUGH
|
||||
case '\0': case 'g': {
|
||||
int digits_to_remove = static_cast<int>(size) - precision;
|
||||
if (digits_to_remove > 0) {
|
||||
round(buffer, size, dec_exp, digits_to_remove);
|
||||
// Remove trailing zeros.
|
||||
while (size > 0 && buffer[size - 1] == '0') {
|
||||
--size;
|
||||
++dec_exp;
|
||||
}
|
||||
}
|
||||
precision = 0;
|
||||
break;
|
||||
}
|
||||
case 'F':
|
||||
upper = true;
|
||||
FMT_FALLTHROUGH
|
||||
case 'f': {
|
||||
int digits_to_remove = -dec_exp - precision;
|
||||
if (digits_to_remove > 0) {
|
||||
if (digits_to_remove >= static_cast<int>(size))
|
||||
digits_to_remove = static_cast<int>(size) - 1;
|
||||
round(buffer, size, dec_exp, digits_to_remove);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'e': case 'E':
|
||||
format_exp_notation(buffer, size, dec_exp, precision, type == 'E');
|
||||
return;
|
||||
}
|
||||
if (write_decimal_point && precision < 1)
|
||||
precision = 1;
|
||||
grisu2_prettify(buffer, size, dec_exp, precision, upper);
|
||||
}
|
||||
} // namespace internal
|
||||
|
||||
#if FMT_USE_WINDOWS_H
|
||||
|
||||
FMT_FUNC internal::utf8_to_utf16::utf8_to_utf16(string_view s) {
|
||||
static const char ERROR_MSG[] = "cannot convert string from UTF-8 to UTF-16";
|
||||
if (s.size() > INT_MAX)
|
||||
FMT_THROW(windows_error(ERROR_INVALID_PARAMETER, ERROR_MSG));
|
||||
int s_size = static_cast<int>(s.size());
|
||||
if (s_size == 0) {
|
||||
// MultiByteToWideChar does not support zero length, handle separately.
|
||||
buffer_.resize(1);
|
||||
buffer_[0] = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
int length = MultiByteToWideChar(
|
||||
CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, FMT_NULL, 0);
|
||||
if (length == 0)
|
||||
FMT_THROW(windows_error(GetLastError(), ERROR_MSG));
|
||||
buffer_.resize(length + 1);
|
||||
length = MultiByteToWideChar(
|
||||
CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, &buffer_[0], length);
|
||||
if (length == 0)
|
||||
FMT_THROW(windows_error(GetLastError(), ERROR_MSG));
|
||||
buffer_[length] = 0;
|
||||
}
|
||||
|
||||
FMT_FUNC internal::utf16_to_utf8::utf16_to_utf8(wstring_view s) {
|
||||
if (int error_code = convert(s)) {
|
||||
FMT_THROW(windows_error(error_code,
|
||||
"cannot convert string from UTF-16 to UTF-8"));
|
||||
}
|
||||
}
|
||||
|
||||
FMT_FUNC int internal::utf16_to_utf8::convert(wstring_view s) {
|
||||
if (s.size() > INT_MAX)
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
int s_size = static_cast<int>(s.size());
|
||||
if (s_size == 0) {
|
||||
// WideCharToMultiByte does not support zero length, handle separately.
|
||||
buffer_.resize(1);
|
||||
buffer_[0] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int length = WideCharToMultiByte(
|
||||
CP_UTF8, 0, s.data(), s_size, FMT_NULL, 0, FMT_NULL, FMT_NULL);
|
||||
if (length == 0)
|
||||
return GetLastError();
|
||||
buffer_.resize(length + 1);
|
||||
length = WideCharToMultiByte(
|
||||
CP_UTF8, 0, s.data(), s_size, &buffer_[0], length, FMT_NULL, FMT_NULL);
|
||||
if (length == 0)
|
||||
return GetLastError();
|
||||
buffer_[length] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
FMT_FUNC void windows_error::init(
|
||||
int err_code, string_view format_str, format_args args) {
|
||||
error_code_ = err_code;
|
||||
memory_buffer buffer;
|
||||
internal::format_windows_error(buffer, err_code, vformat(format_str, args));
|
||||
std::runtime_error &base = *this;
|
||||
base = std::runtime_error(to_string(buffer));
|
||||
}
|
||||
|
||||
FMT_FUNC void internal::format_windows_error(
|
||||
internal::buffer &out, int error_code, string_view message) FMT_NOEXCEPT {
|
||||
FMT_TRY {
|
||||
wmemory_buffer buf;
|
||||
buf.resize(inline_buffer_size);
|
||||
for (;;) {
|
||||
wchar_t *system_message = &buf[0];
|
||||
int result = FormatMessageW(
|
||||
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
FMT_NULL, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
system_message, static_cast<uint32_t>(buf.size()), FMT_NULL);
|
||||
if (result != 0) {
|
||||
utf16_to_utf8 utf8_message;
|
||||
if (utf8_message.convert(system_message) == ERROR_SUCCESS) {
|
||||
writer w(out);
|
||||
w.write(message);
|
||||
w.write(": ");
|
||||
w.write(utf8_message);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
|
||||
break; // Can't get error message, report error code instead.
|
||||
buf.resize(buf.size() * 2);
|
||||
}
|
||||
} FMT_CATCH(...) {}
|
||||
format_error_code(out, error_code, message);
|
||||
}
|
||||
|
||||
#endif // FMT_USE_WINDOWS_H
|
||||
|
||||
FMT_FUNC void format_system_error(
|
||||
internal::buffer &out, int error_code, string_view message) FMT_NOEXCEPT {
|
||||
FMT_TRY {
|
||||
memory_buffer buf;
|
||||
buf.resize(inline_buffer_size);
|
||||
for (;;) {
|
||||
char *system_message = &buf[0];
|
||||
int result = safe_strerror(error_code, system_message, buf.size());
|
||||
if (result == 0) {
|
||||
writer w(out);
|
||||
w.write(message);
|
||||
w.write(": ");
|
||||
w.write(system_message);
|
||||
return;
|
||||
}
|
||||
if (result != ERANGE)
|
||||
break; // Can't get error message, report error code instead.
|
||||
buf.resize(buf.size() * 2);
|
||||
}
|
||||
} FMT_CATCH(...) {}
|
||||
format_error_code(out, error_code, message);
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
void basic_fixed_buffer<Char>::grow(std::size_t) {
|
||||
FMT_THROW(std::runtime_error("buffer overflow"));
|
||||
}
|
||||
|
||||
FMT_FUNC void internal::error_handler::on_error(const char *message) {
|
||||
FMT_THROW(format_error(message));
|
||||
}
|
||||
|
||||
FMT_FUNC void report_system_error(
|
||||
int error_code, fmt::string_view message) FMT_NOEXCEPT {
|
||||
report_error(format_system_error, error_code, message);
|
||||
}
|
||||
|
||||
#if FMT_USE_WINDOWS_H
|
||||
FMT_FUNC void report_windows_error(
|
||||
int error_code, fmt::string_view message) FMT_NOEXCEPT {
|
||||
report_error(internal::format_windows_error, error_code, message);
|
||||
}
|
||||
#endif
|
||||
|
||||
FMT_FUNC void vprint(std::FILE *f, string_view format_str, format_args args) {
|
||||
memory_buffer buffer;
|
||||
vformat_to(buffer, format_str, args);
|
||||
std::fwrite(buffer.data(), 1, buffer.size(), f);
|
||||
}
|
||||
|
||||
FMT_FUNC void vprint(std::FILE *f, wstring_view format_str, wformat_args args) {
|
||||
wmemory_buffer buffer;
|
||||
vformat_to(buffer, format_str, args);
|
||||
std::fwrite(buffer.data(), sizeof(wchar_t), buffer.size(), f);
|
||||
}
|
||||
|
||||
FMT_FUNC void vprint(string_view format_str, format_args args) {
|
||||
vprint(stdout, format_str, args);
|
||||
}
|
||||
|
||||
FMT_FUNC void vprint(wstring_view format_str, wformat_args args) {
|
||||
vprint(stdout, format_str, args);
|
||||
}
|
||||
|
||||
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
|
||||
FMT_FUNC locale locale_provider::locale() { return fmt::locale(); }
|
||||
#endif
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#ifdef _MSC_VER
|
||||
# pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#endif // FMT_FORMAT_INL_H_
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,157 @@
|
|||
// Formatting library for C++ - std::ostream support
|
||||
//
|
||||
// Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_OSTREAM_H_
|
||||
#define FMT_OSTREAM_H_
|
||||
|
||||
#include "format.h"
|
||||
#include <ostream>
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace internal {
|
||||
|
||||
template <class Char>
|
||||
class formatbuf : public std::basic_streambuf<Char> {
|
||||
private:
|
||||
typedef typename std::basic_streambuf<Char>::int_type int_type;
|
||||
typedef typename std::basic_streambuf<Char>::traits_type traits_type;
|
||||
|
||||
basic_buffer<Char> &buffer_;
|
||||
|
||||
public:
|
||||
formatbuf(basic_buffer<Char> &buffer) : buffer_(buffer) {}
|
||||
|
||||
protected:
|
||||
// The put-area is actually always empty. This makes the implementation
|
||||
// simpler and has the advantage that the streambuf and the buffer are always
|
||||
// in sync and sputc never writes into uninitialized memory. The obvious
|
||||
// disadvantage is that each call to sputc always results in a (virtual) call
|
||||
// to overflow. There is no disadvantage here for sputn since this always
|
||||
// results in a call to xsputn.
|
||||
|
||||
int_type overflow(int_type ch = traits_type::eof()) FMT_OVERRIDE {
|
||||
if (!traits_type::eq_int_type(ch, traits_type::eof()))
|
||||
buffer_.push_back(static_cast<Char>(ch));
|
||||
return ch;
|
||||
}
|
||||
|
||||
std::streamsize xsputn(const Char *s, std::streamsize count) FMT_OVERRIDE {
|
||||
buffer_.append(s, s + count);
|
||||
return count;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
struct test_stream : std::basic_ostream<Char> {
|
||||
private:
|
||||
struct null;
|
||||
// Hide all operator<< from std::basic_ostream<Char>.
|
||||
void operator<<(null);
|
||||
};
|
||||
|
||||
// Checks if T has a user-defined operator<< (e.g. not a member of std::ostream).
|
||||
template <typename T, typename Char>
|
||||
class is_streamable {
|
||||
private:
|
||||
template <typename U>
|
||||
static decltype(
|
||||
internal::declval<test_stream<Char>&>()
|
||||
<< internal::declval<U>(), std::true_type()) test(int);
|
||||
|
||||
template <typename>
|
||||
static std::false_type test(...);
|
||||
|
||||
typedef decltype(test<T>(0)) result;
|
||||
|
||||
public:
|
||||
static const bool value = result::value;
|
||||
};
|
||||
|
||||
// Write the content of buf to os.
|
||||
template <typename Char>
|
||||
void write(std::basic_ostream<Char> &os, basic_buffer<Char> &buf) {
|
||||
const Char *data = buf.data();
|
||||
typedef std::make_unsigned<std::streamsize>::type UnsignedStreamSize;
|
||||
UnsignedStreamSize size = buf.size();
|
||||
UnsignedStreamSize max_size =
|
||||
internal::to_unsigned((std::numeric_limits<std::streamsize>::max)());
|
||||
do {
|
||||
UnsignedStreamSize n = size <= max_size ? size : max_size;
|
||||
os.write(data, static_cast<std::streamsize>(n));
|
||||
data += n;
|
||||
size -= n;
|
||||
} while (size != 0);
|
||||
}
|
||||
|
||||
template <typename Char, typename T>
|
||||
void format_value(basic_buffer<Char> &buffer, const T &value) {
|
||||
internal::formatbuf<Char> format_buf(buffer);
|
||||
std::basic_ostream<Char> output(&format_buf);
|
||||
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
|
||||
output << value;
|
||||
buffer.resize(buffer.size());
|
||||
}
|
||||
} // namespace internal
|
||||
|
||||
// Disable conversion to int if T has an overloaded operator<< which is a free
|
||||
// function (not a member of std::ostream).
|
||||
template <typename T, typename Char>
|
||||
struct convert_to_int<T, Char, void> {
|
||||
static const bool value =
|
||||
convert_to_int<T, Char, int>::value &&
|
||||
!internal::is_streamable<T, Char>::value;
|
||||
};
|
||||
|
||||
// Formats an object of type T that has an overloaded ostream operator<<.
|
||||
template <typename T, typename Char>
|
||||
struct formatter<T, Char,
|
||||
typename std::enable_if<
|
||||
internal::is_streamable<T, Char>::value &&
|
||||
!internal::format_type<
|
||||
typename buffer_context<Char>::type, T>::value>::type>
|
||||
: formatter<basic_string_view<Char>, Char> {
|
||||
|
||||
template <typename Context>
|
||||
auto format(const T &value, Context &ctx) -> decltype(ctx.out()) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
internal::format_value(buffer, value);
|
||||
basic_string_view<Char> str(buffer.data(), buffer.size());
|
||||
return formatter<basic_string_view<Char>, Char>::format(str, ctx);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
inline void vprint(std::basic_ostream<Char> &os,
|
||||
basic_string_view<Char> format_str,
|
||||
basic_format_args<typename buffer_context<Char>::type> args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
vformat_to(buffer, format_str, args);
|
||||
internal::write(os, buffer);
|
||||
}
|
||||
/**
|
||||
\rst
|
||||
Prints formatted data to the stream *os*.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::print(cerr, "Don't {}!", "panic");
|
||||
\endrst
|
||||
*/
|
||||
template <typename... Args>
|
||||
inline void print(std::ostream &os, string_view format_str,
|
||||
const Args & ... args) {
|
||||
vprint<char>(os, format_str, make_format_args<format_context>(args...));
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
inline void print(std::wostream &os, wstring_view format_str,
|
||||
const Args & ... args) {
|
||||
vprint<wchar_t>(os, format_str, make_format_args<wformat_context>(args...));
|
||||
}
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_OSTREAM_H_
|
|
@ -10,65 +10,54 @@
|
|||
|
||||
#if defined(__MINGW32__) || defined(__CYGWIN__)
|
||||
// Workaround MinGW bug https://sourceforge.net/p/mingw/bugs/2024/.
|
||||
# undef __STRICT_ANSI__
|
||||
# undef __STRICT_ANSI__
|
||||
#endif
|
||||
|
||||
#include <cerrno>
|
||||
#include <clocale> // for locale_t
|
||||
#include <cstdio>
|
||||
#include <cstdlib> // for strtod_l
|
||||
#include <errno.h>
|
||||
#include <fcntl.h> // for O_RDONLY
|
||||
#include <locale.h> // for locale_t
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h> // for strtod_l
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#if defined __APPLE__ || defined(__FreeBSD__)
|
||||
# include <xlocale.h> // for LC_NUMERIC_MASK on OS X
|
||||
# include <xlocale.h> // for LC_NUMERIC_MASK on OS X
|
||||
#endif
|
||||
|
||||
#include "format.h"
|
||||
|
||||
// UWP doesn't provide _pipe.
|
||||
#if FMT_HAS_INCLUDE("winapifamily.h")
|
||||
# include <winapifamily.h>
|
||||
#endif
|
||||
#if FMT_HAS_INCLUDE("fcntl.h") && \
|
||||
(!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
|
||||
# include <fcntl.h> // for O_RDONLY
|
||||
# define FMT_USE_FCNTL 1
|
||||
#else
|
||||
# define FMT_USE_FCNTL 0
|
||||
#endif
|
||||
|
||||
#ifndef FMT_POSIX
|
||||
# if defined(_WIN32) && !defined(__MINGW32__)
|
||||
# if defined(_WIN32) && !defined(__MINGW32__)
|
||||
// Fix warnings about deprecated symbols.
|
||||
# define FMT_POSIX(call) _##call
|
||||
# else
|
||||
# define FMT_POSIX(call) call
|
||||
# endif
|
||||
# define FMT_POSIX(call) _##call
|
||||
# else
|
||||
# define FMT_POSIX(call) call
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// Calls to system functions are wrapped in FMT_SYSTEM for testability.
|
||||
#ifdef FMT_SYSTEM
|
||||
# define FMT_POSIX_CALL(call) FMT_SYSTEM(call)
|
||||
# define FMT_POSIX_CALL(call) FMT_SYSTEM(call)
|
||||
#else
|
||||
# define FMT_SYSTEM(call) call
|
||||
# ifdef _WIN32
|
||||
# define FMT_SYSTEM(call) call
|
||||
# ifdef _WIN32
|
||||
// Fix warnings about deprecated symbols.
|
||||
# define FMT_POSIX_CALL(call) ::_##call
|
||||
# else
|
||||
# define FMT_POSIX_CALL(call) ::call
|
||||
# endif
|
||||
# define FMT_POSIX_CALL(call) ::_##call
|
||||
# else
|
||||
# define FMT_POSIX_CALL(call) ::call
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// Retries the expression while it evaluates to error_result and errno
|
||||
// equals to EINTR.
|
||||
#ifndef _WIN32
|
||||
# define FMT_RETRY_VAL(result, expression, error_result) \
|
||||
do { \
|
||||
(result) = (expression); \
|
||||
} while ((result) == (error_result) && errno == EINTR)
|
||||
# define FMT_RETRY_VAL(result, expression, error_result) \
|
||||
do { \
|
||||
result = (expression); \
|
||||
} while (result == error_result && errno == EINTR)
|
||||
#else
|
||||
# define FMT_RETRY_VAL(result, expression, error_result) result = (expression)
|
||||
# define FMT_RETRY_VAL(result, expression, error_result) result = (expression)
|
||||
#endif
|
||||
|
||||
#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1)
|
||||
|
@ -80,7 +69,7 @@ FMT_BEGIN_NAMESPACE
|
|||
A reference to a null-terminated string. It can be constructed from a C
|
||||
string or ``std::string``.
|
||||
|
||||
You can use one of the following type aliases for common character types:
|
||||
You can use one of the following typedefs for common character types:
|
||||
|
||||
+---------------+-----------------------------+
|
||||
| Type | Definition |
|
||||
|
@ -100,27 +89,28 @@ FMT_BEGIN_NAMESPACE
|
|||
format(std::string("{}"), 42);
|
||||
\endrst
|
||||
*/
|
||||
template <typename Char> class basic_cstring_view {
|
||||
template <typename Char>
|
||||
class basic_cstring_view {
|
||||
private:
|
||||
const Char* data_;
|
||||
const Char *data_;
|
||||
|
||||
public:
|
||||
/** Constructs a string reference object from a C string. */
|
||||
basic_cstring_view(const Char* s) : data_(s) {}
|
||||
basic_cstring_view(const Char *s) : data_(s) {}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Constructs a string reference from an ``std::string`` object.
|
||||
\endrst
|
||||
*/
|
||||
basic_cstring_view(const std::basic_string<Char>& s) : data_(s.c_str()) {}
|
||||
basic_cstring_view(const std::basic_string<Char> &s) : data_(s.c_str()) {}
|
||||
|
||||
/** Returns the pointer to a C string. */
|
||||
const Char* c_str() const { return data_; }
|
||||
const Char *c_str() const { return data_; }
|
||||
};
|
||||
|
||||
using cstring_view = basic_cstring_view<char>;
|
||||
using wcstring_view = basic_cstring_view<wchar_t>;
|
||||
typedef basic_cstring_view<char> cstring_view;
|
||||
typedef basic_cstring_view<wchar_t> wcstring_view;
|
||||
|
||||
// An error code.
|
||||
class error_code {
|
||||
|
@ -136,31 +126,33 @@ class error_code {
|
|||
// A buffered file.
|
||||
class buffered_file {
|
||||
private:
|
||||
FILE* file_;
|
||||
FILE *file_;
|
||||
|
||||
friend class file;
|
||||
|
||||
explicit buffered_file(FILE* f) : file_(f) {}
|
||||
explicit buffered_file(FILE *f) : file_(f) {}
|
||||
|
||||
public:
|
||||
buffered_file(const buffered_file&) = delete;
|
||||
void operator=(const buffered_file&) = delete;
|
||||
|
||||
// Constructs a buffered_file object which doesn't represent any file.
|
||||
buffered_file() FMT_NOEXCEPT : file_(nullptr) {}
|
||||
buffered_file() FMT_NOEXCEPT : file_(FMT_NULL) {}
|
||||
|
||||
// Destroys the object closing the file it represents if any.
|
||||
FMT_API ~buffered_file() FMT_NOEXCEPT;
|
||||
FMT_API ~buffered_file() FMT_DTOR_NOEXCEPT;
|
||||
|
||||
private:
|
||||
buffered_file(const buffered_file &) = delete;
|
||||
void operator=(const buffered_file &) = delete;
|
||||
|
||||
|
||||
public:
|
||||
buffered_file(buffered_file&& other) FMT_NOEXCEPT : file_(other.file_) {
|
||||
other.file_ = nullptr;
|
||||
buffered_file(buffered_file &&other) FMT_NOEXCEPT : file_(other.file_) {
|
||||
other.file_ = FMT_NULL;
|
||||
}
|
||||
|
||||
buffered_file& operator=(buffered_file&& other) {
|
||||
buffered_file& operator=(buffered_file &&other) {
|
||||
close();
|
||||
file_ = other.file_;
|
||||
other.file_ = nullptr;
|
||||
other.file_ = FMT_NULL;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
@ -171,23 +163,22 @@ class buffered_file {
|
|||
FMT_API void close();
|
||||
|
||||
// Returns the pointer to a FILE object representing this file.
|
||||
FILE* get() const FMT_NOEXCEPT { return file_; }
|
||||
FILE *get() const FMT_NOEXCEPT { return file_; }
|
||||
|
||||
// We place parentheses around fileno to workaround a bug in some versions
|
||||
// of MinGW that define fileno as a macro.
|
||||
FMT_API int(fileno)() const;
|
||||
FMT_API int (fileno)() const;
|
||||
|
||||
void vprint(string_view format_str, format_args args) {
|
||||
fmt::vprint(file_, format_str, args);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
inline void print(string_view format_str, const Args&... args) {
|
||||
inline void print(string_view format_str, const Args & ... args) {
|
||||
vprint(format_str, make_format_args(args...));
|
||||
}
|
||||
};
|
||||
|
||||
#if FMT_USE_FCNTL
|
||||
// A file. Closed file is represented by a file object with descriptor -1.
|
||||
// Methods that are not declared with FMT_NOEXCEPT may throw
|
||||
// fmt::system_error in case of failure. Note that some errors such as
|
||||
|
@ -204,9 +195,9 @@ class file {
|
|||
public:
|
||||
// Possible values for the oflag argument to the constructor.
|
||||
enum {
|
||||
RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only.
|
||||
WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only.
|
||||
RDWR = FMT_POSIX(O_RDWR) // Open for reading and writing.
|
||||
RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only.
|
||||
WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only.
|
||||
RDWR = FMT_POSIX(O_RDWR) // Open for reading and writing.
|
||||
};
|
||||
|
||||
// Constructs a file object which doesn't represent any file.
|
||||
|
@ -215,13 +206,16 @@ class file {
|
|||
// Opens a file and constructs a file object representing this file.
|
||||
FMT_API file(cstring_view path, int oflag);
|
||||
|
||||
private:
|
||||
file(const file &) = delete;
|
||||
void operator=(const file &) = delete;
|
||||
|
||||
public:
|
||||
file(const file&) = delete;
|
||||
void operator=(const file&) = delete;
|
||||
file(file &&other) FMT_NOEXCEPT : fd_(other.fd_) {
|
||||
other.fd_ = -1;
|
||||
}
|
||||
|
||||
file(file&& other) FMT_NOEXCEPT : fd_(other.fd_) { other.fd_ = -1; }
|
||||
|
||||
file& operator=(file&& other) FMT_NOEXCEPT {
|
||||
file& operator=(file &&other) {
|
||||
close();
|
||||
fd_ = other.fd_;
|
||||
other.fd_ = -1;
|
||||
|
@ -229,7 +223,7 @@ class file {
|
|||
}
|
||||
|
||||
// Destroys the object closing the file it represents if any.
|
||||
FMT_API ~file() FMT_NOEXCEPT;
|
||||
FMT_API ~file() FMT_DTOR_NOEXCEPT;
|
||||
|
||||
// Returns the file descriptor.
|
||||
int descriptor() const FMT_NOEXCEPT { return fd_; }
|
||||
|
@ -242,10 +236,10 @@ class file {
|
|||
FMT_API long long size() const;
|
||||
|
||||
// Attempts to read count bytes from the file into the specified buffer.
|
||||
FMT_API std::size_t read(void* buffer, std::size_t count);
|
||||
FMT_API std::size_t read(void *buffer, std::size_t count);
|
||||
|
||||
// Attempts to write count bytes from the specified buffer to the file.
|
||||
FMT_API std::size_t write(const void* buffer, std::size_t count);
|
||||
FMT_API std::size_t write(const void *buffer, std::size_t count);
|
||||
|
||||
// Duplicates a file descriptor with the dup function and returns
|
||||
// the duplicate as a file object.
|
||||
|
@ -257,59 +251,68 @@ class file {
|
|||
|
||||
// Makes fd be the copy of this file descriptor, closing fd first if
|
||||
// necessary.
|
||||
FMT_API void dup2(int fd, error_code& ec) FMT_NOEXCEPT;
|
||||
FMT_API void dup2(int fd, error_code &ec) FMT_NOEXCEPT;
|
||||
|
||||
// Creates a pipe setting up read_end and write_end file objects for reading
|
||||
// and writing respectively.
|
||||
FMT_API static void pipe(file& read_end, file& write_end);
|
||||
FMT_API static void pipe(file &read_end, file &write_end);
|
||||
|
||||
// Creates a buffered_file object associated with this file and detaches
|
||||
// this file object from the file.
|
||||
FMT_API buffered_file fdopen(const char* mode);
|
||||
FMT_API buffered_file fdopen(const char *mode);
|
||||
};
|
||||
|
||||
// Returns the memory page size.
|
||||
long getpagesize();
|
||||
#endif // FMT_USE_FCNTL
|
||||
|
||||
#if (defined(LC_NUMERIC_MASK) || defined(_MSC_VER)) && \
|
||||
!defined(__ANDROID__) && !defined(__CYGWIN__) && !defined(__OpenBSD__) && \
|
||||
!defined(__NEWLIB_H__)
|
||||
# define FMT_LOCALE
|
||||
#endif
|
||||
|
||||
#ifdef FMT_LOCALE
|
||||
// A "C" numeric locale.
|
||||
class Locale {
|
||||
private:
|
||||
# ifdef _WIN32
|
||||
using locale_t = _locale_t;
|
||||
# ifdef _MSC_VER
|
||||
typedef _locale_t locale_t;
|
||||
|
||||
enum { LC_NUMERIC_MASK = LC_NUMERIC };
|
||||
|
||||
static locale_t newlocale(int category_mask, const char* locale, locale_t) {
|
||||
static locale_t newlocale(int category_mask, const char *locale, locale_t) {
|
||||
return _create_locale(category_mask, locale);
|
||||
}
|
||||
|
||||
static void freelocale(locale_t locale) { _free_locale(locale); }
|
||||
static void freelocale(locale_t locale) {
|
||||
_free_locale(locale);
|
||||
}
|
||||
|
||||
static double strtod_l(const char* nptr, char** endptr, _locale_t locale) {
|
||||
static double strtod_l(const char *nptr, char **endptr, _locale_t locale) {
|
||||
return _strtod_l(nptr, endptr, locale);
|
||||
}
|
||||
# endif
|
||||
# endif
|
||||
|
||||
locale_t locale_;
|
||||
|
||||
public:
|
||||
using type = locale_t;
|
||||
Locale(const Locale&) = delete;
|
||||
void operator=(const Locale&) = delete;
|
||||
Locale(const Locale &) = delete;
|
||||
void operator=(const Locale &) = delete;
|
||||
|
||||
Locale() : locale_(newlocale(LC_NUMERIC_MASK, "C", nullptr)) {
|
||||
if (!locale_) FMT_THROW(system_error(errno, "cannot create locale"));
|
||||
public:
|
||||
typedef locale_t Type;
|
||||
|
||||
Locale() : locale_(newlocale(LC_NUMERIC_MASK, "C", FMT_NULL)) {
|
||||
if (!locale_)
|
||||
FMT_THROW(system_error(errno, "cannot create locale"));
|
||||
}
|
||||
~Locale() { freelocale(locale_); }
|
||||
|
||||
type get() const { return locale_; }
|
||||
Type get() const { return locale_; }
|
||||
|
||||
// Converts string to floating-point number and advances str past the end
|
||||
// of the parsed input.
|
||||
double strtod(const char*& str) const {
|
||||
char* end = nullptr;
|
||||
double strtod(const char *&str) const {
|
||||
char *end = FMT_NULL;
|
||||
double result = strtod_l(str, &end, locale_);
|
||||
str = end;
|
||||
return result;
|
|
@ -0,0 +1,726 @@
|
|||
// Formatting library for C++
|
||||
//
|
||||
// Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_PRINTF_H_
|
||||
#define FMT_PRINTF_H_
|
||||
|
||||
#include <algorithm> // std::fill_n
|
||||
#include <limits> // std::numeric_limits
|
||||
|
||||
#include "ostream.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace internal {
|
||||
|
||||
// Checks if a value fits in int - used to avoid warnings about comparing
|
||||
// signed and unsigned integers.
|
||||
template <bool IsSigned>
|
||||
struct int_checker {
|
||||
template <typename T>
|
||||
static bool fits_in_int(T value) {
|
||||
unsigned max = std::numeric_limits<int>::max();
|
||||
return value <= max;
|
||||
}
|
||||
static bool fits_in_int(bool) { return true; }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct int_checker<true> {
|
||||
template <typename T>
|
||||
static bool fits_in_int(T value) {
|
||||
return value >= std::numeric_limits<int>::min() &&
|
||||
value <= std::numeric_limits<int>::max();
|
||||
}
|
||||
static bool fits_in_int(int) { return true; }
|
||||
};
|
||||
|
||||
class printf_precision_handler: public function<int> {
|
||||
public:
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_integral<T>::value, int>::type
|
||||
operator()(T value) {
|
||||
if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value))
|
||||
FMT_THROW(format_error("number is too big"));
|
||||
return static_cast<int>(value);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<!std::is_integral<T>::value, int>::type operator()(T) {
|
||||
FMT_THROW(format_error("precision is not integer"));
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
// An argument visitor that returns true iff arg is a zero integer.
|
||||
class is_zero_int: public function<bool> {
|
||||
public:
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_integral<T>::value, bool>::type
|
||||
operator()(T value) { return value == 0; }
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<!std::is_integral<T>::value, bool>::type
|
||||
operator()(T) { return false; }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct make_unsigned_or_bool : std::make_unsigned<T> {};
|
||||
|
||||
template <>
|
||||
struct make_unsigned_or_bool<bool> {
|
||||
typedef bool type;
|
||||
};
|
||||
|
||||
template <typename T, typename Context>
|
||||
class arg_converter: public function<void> {
|
||||
private:
|
||||
typedef typename Context::char_type Char;
|
||||
|
||||
basic_format_arg<Context> &arg_;
|
||||
typename Context::char_type type_;
|
||||
|
||||
public:
|
||||
arg_converter(basic_format_arg<Context> &arg, Char type)
|
||||
: arg_(arg), type_(type) {}
|
||||
|
||||
void operator()(bool value) {
|
||||
if (type_ != 's')
|
||||
operator()<bool>(value);
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
typename std::enable_if<std::is_integral<U>::value>::type
|
||||
operator()(U value) {
|
||||
bool is_signed = type_ == 'd' || type_ == 'i';
|
||||
typedef typename std::conditional<
|
||||
std::is_same<T, void>::value, U, T>::type TargetType;
|
||||
if (const_check(sizeof(TargetType) <= sizeof(int))) {
|
||||
// Extra casts are used to silence warnings.
|
||||
if (is_signed) {
|
||||
arg_ = internal::make_arg<Context>(
|
||||
static_cast<int>(static_cast<TargetType>(value)));
|
||||
} else {
|
||||
typedef typename make_unsigned_or_bool<TargetType>::type Unsigned;
|
||||
arg_ = internal::make_arg<Context>(
|
||||
static_cast<unsigned>(static_cast<Unsigned>(value)));
|
||||
}
|
||||
} else {
|
||||
if (is_signed) {
|
||||
// glibc's printf doesn't sign extend arguments of smaller types:
|
||||
// std::printf("%lld", -42); // prints "4294967254"
|
||||
// but we don't have to do the same because it's a UB.
|
||||
arg_ = internal::make_arg<Context>(static_cast<long long>(value));
|
||||
} else {
|
||||
arg_ = internal::make_arg<Context>(
|
||||
static_cast<typename make_unsigned_or_bool<U>::type>(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
typename std::enable_if<!std::is_integral<U>::value>::type operator()(U) {
|
||||
// No coversion needed for non-integral types.
|
||||
}
|
||||
};
|
||||
|
||||
// Converts an integer argument to T for printf, if T is an integral type.
|
||||
// If T is void, the argument is converted to corresponding signed or unsigned
|
||||
// type depending on the type specifier: 'd' and 'i' - signed, other -
|
||||
// unsigned).
|
||||
template <typename T, typename Context, typename Char>
|
||||
void convert_arg(basic_format_arg<Context> &arg, Char type) {
|
||||
fmt::visit(arg_converter<T, Context>(arg, type), arg);
|
||||
}
|
||||
|
||||
// Converts an integer argument to char for printf.
|
||||
template <typename Context>
|
||||
class char_converter: public function<void> {
|
||||
private:
|
||||
basic_format_arg<Context> &arg_;
|
||||
|
||||
public:
|
||||
explicit char_converter(basic_format_arg<Context> &arg) : arg_(arg) {}
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_integral<T>::value>::type
|
||||
operator()(T value) {
|
||||
typedef typename Context::char_type Char;
|
||||
arg_ = internal::make_arg<Context>(static_cast<Char>(value));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<!std::is_integral<T>::value>::type operator()(T) {
|
||||
// No coversion needed for non-integral types.
|
||||
}
|
||||
};
|
||||
|
||||
// Checks if an argument is a valid printf width specifier and sets
|
||||
// left alignment if it is negative.
|
||||
template <typename Char>
|
||||
class printf_width_handler: public function<unsigned> {
|
||||
private:
|
||||
typedef basic_format_specs<Char> format_specs;
|
||||
|
||||
format_specs &spec_;
|
||||
|
||||
public:
|
||||
explicit printf_width_handler(format_specs &spec) : spec_(spec) {}
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_integral<T>::value, unsigned>::type
|
||||
operator()(T value) {
|
||||
typedef typename internal::int_traits<T>::main_type UnsignedType;
|
||||
UnsignedType width = static_cast<UnsignedType>(value);
|
||||
if (internal::is_negative(value)) {
|
||||
spec_.align_ = ALIGN_LEFT;
|
||||
width = 0 - width;
|
||||
}
|
||||
unsigned int_max = std::numeric_limits<int>::max();
|
||||
if (width > int_max)
|
||||
FMT_THROW(format_error("number is too big"));
|
||||
return static_cast<unsigned>(width);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<!std::is_integral<T>::value, unsigned>::type
|
||||
operator()(T) {
|
||||
FMT_THROW(format_error("width is not integer"));
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
} // namespace internal
|
||||
|
||||
template <typename Range>
|
||||
class printf_arg_formatter;
|
||||
|
||||
template <
|
||||
typename OutputIt, typename Char,
|
||||
typename ArgFormatter =
|
||||
printf_arg_formatter<back_insert_range<internal::basic_buffer<Char>>>>
|
||||
class basic_printf_context;
|
||||
|
||||
/**
|
||||
\rst
|
||||
The ``printf`` argument formatter.
|
||||
\endrst
|
||||
*/
|
||||
template <typename Range>
|
||||
class printf_arg_formatter:
|
||||
public internal::function<
|
||||
typename internal::arg_formatter_base<Range>::iterator>,
|
||||
public internal::arg_formatter_base<Range> {
|
||||
private:
|
||||
typedef typename Range::value_type char_type;
|
||||
typedef decltype(internal::declval<Range>().begin()) iterator;
|
||||
typedef internal::arg_formatter_base<Range> base;
|
||||
typedef basic_printf_context<iterator, char_type> context_type;
|
||||
|
||||
context_type &context_;
|
||||
|
||||
void write_null_pointer(char) {
|
||||
this->spec()->type_ = 0;
|
||||
this->write("(nil)");
|
||||
}
|
||||
|
||||
void write_null_pointer(wchar_t) {
|
||||
this->spec()->type_ = 0;
|
||||
this->write(L"(nil)");
|
||||
}
|
||||
|
||||
public:
|
||||
typedef typename base::format_specs format_specs;
|
||||
|
||||
/**
|
||||
\rst
|
||||
Constructs an argument formatter object.
|
||||
*buffer* is a reference to the output buffer and *spec* contains format
|
||||
specifier information for standard argument types.
|
||||
\endrst
|
||||
*/
|
||||
printf_arg_formatter(internal::basic_buffer<char_type> &buffer,
|
||||
format_specs &spec, context_type &ctx)
|
||||
: base(back_insert_range<internal::basic_buffer<char_type>>(buffer), &spec),
|
||||
context_(ctx) {}
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_integral<T>::value, iterator>::type
|
||||
operator()(T value) {
|
||||
// MSVC2013 fails to compile separate overloads for bool and char_type so
|
||||
// use std::is_same instead.
|
||||
if (std::is_same<T, bool>::value) {
|
||||
format_specs &fmt_spec = *this->spec();
|
||||
if (fmt_spec.type_ != 's')
|
||||
return base::operator()(value ? 1 : 0);
|
||||
fmt_spec.type_ = 0;
|
||||
this->write(value != 0);
|
||||
} else if (std::is_same<T, char_type>::value) {
|
||||
format_specs &fmt_spec = *this->spec();
|
||||
if (fmt_spec.type_ && fmt_spec.type_ != 'c')
|
||||
return (*this)(static_cast<int>(value));
|
||||
fmt_spec.flags_ = 0;
|
||||
fmt_spec.align_ = ALIGN_RIGHT;
|
||||
return base::operator()(value);
|
||||
} else {
|
||||
return base::operator()(value);
|
||||
}
|
||||
return this->out();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_floating_point<T>::value, iterator>::type
|
||||
operator()(T value) {
|
||||
return base::operator()(value);
|
||||
}
|
||||
|
||||
/** Formats a null-terminated C string. */
|
||||
iterator operator()(const char *value) {
|
||||
if (value)
|
||||
base::operator()(value);
|
||||
else if (this->spec()->type_ == 'p')
|
||||
write_null_pointer(char_type());
|
||||
else
|
||||
this->write("(null)");
|
||||
return this->out();
|
||||
}
|
||||
|
||||
/** Formats a null-terminated wide C string. */
|
||||
iterator operator()(const wchar_t *value) {
|
||||
if (value)
|
||||
base::operator()(value);
|
||||
else if (this->spec()->type_ == 'p')
|
||||
write_null_pointer(char_type());
|
||||
else
|
||||
this->write(L"(null)");
|
||||
return this->out();
|
||||
}
|
||||
|
||||
iterator operator()(basic_string_view<char_type> value) {
|
||||
return base::operator()(value);
|
||||
}
|
||||
|
||||
iterator operator()(monostate value) {
|
||||
return base::operator()(value);
|
||||
}
|
||||
|
||||
/** Formats a pointer. */
|
||||
iterator operator()(const void *value) {
|
||||
if (value)
|
||||
return base::operator()(value);
|
||||
this->spec()->type_ = 0;
|
||||
write_null_pointer(char_type());
|
||||
return this->out();
|
||||
}
|
||||
|
||||
/** Formats an argument of a custom (user-defined) type. */
|
||||
iterator operator()(typename basic_format_arg<context_type>::handle handle) {
|
||||
handle.format(context_);
|
||||
return this->out();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct printf_formatter {
|
||||
template <typename ParseContext>
|
||||
auto parse(ParseContext &ctx) -> decltype(ctx.begin()) { return ctx.begin(); }
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const T &value, FormatContext &ctx) -> decltype(ctx.out()) {
|
||||
internal::format_value(internal::get_container(ctx.out()), value);
|
||||
return ctx.out();
|
||||
}
|
||||
};
|
||||
|
||||
/** This template formats data and writes the output to a writer. */
|
||||
template <typename OutputIt, typename Char, typename ArgFormatter>
|
||||
class basic_printf_context :
|
||||
// Inherit publicly as a workaround for the icc bug
|
||||
// https://software.intel.com/en-us/forums/intel-c-compiler/topic/783476.
|
||||
public internal::context_base<
|
||||
OutputIt, basic_printf_context<OutputIt, Char, ArgFormatter>, Char> {
|
||||
public:
|
||||
/** The character type for the output. */
|
||||
typedef Char char_type;
|
||||
|
||||
template <typename T>
|
||||
struct formatter_type { typedef printf_formatter<T> type; };
|
||||
|
||||
private:
|
||||
typedef internal::context_base<OutputIt, basic_printf_context, Char> base;
|
||||
typedef typename base::format_arg format_arg;
|
||||
typedef basic_format_specs<char_type> format_specs;
|
||||
typedef internal::null_terminating_iterator<char_type> iterator;
|
||||
|
||||
void parse_flags(format_specs &spec, iterator &it);
|
||||
|
||||
// Returns the argument with specified index or, if arg_index is equal
|
||||
// to the maximum unsigned value, the next argument.
|
||||
format_arg get_arg(
|
||||
iterator it,
|
||||
unsigned arg_index = (std::numeric_limits<unsigned>::max)());
|
||||
|
||||
// Parses argument index, flags and width and returns the argument index.
|
||||
unsigned parse_header(iterator &it, format_specs &spec);
|
||||
|
||||
public:
|
||||
/**
|
||||
\rst
|
||||
Constructs a ``printf_context`` object. References to the arguments and
|
||||
the writer are stored in the context object so make sure they have
|
||||
appropriate lifetimes.
|
||||
\endrst
|
||||
*/
|
||||
basic_printf_context(OutputIt out, basic_string_view<char_type> format_str,
|
||||
basic_format_args<basic_printf_context> args)
|
||||
: base(out, format_str, args) {}
|
||||
|
||||
using base::parse_context;
|
||||
using base::out;
|
||||
using base::advance_to;
|
||||
|
||||
/** Formats stored arguments and writes the output to the range. */
|
||||
void format();
|
||||
};
|
||||
|
||||
template <typename OutputIt, typename Char, typename AF>
|
||||
void basic_printf_context<OutputIt, Char, AF>::parse_flags(
|
||||
format_specs &spec, iterator &it) {
|
||||
for (;;) {
|
||||
switch (*it++) {
|
||||
case '-':
|
||||
spec.align_ = ALIGN_LEFT;
|
||||
break;
|
||||
case '+':
|
||||
spec.flags_ |= SIGN_FLAG | PLUS_FLAG;
|
||||
break;
|
||||
case '0':
|
||||
spec.fill_ = '0';
|
||||
break;
|
||||
case ' ':
|
||||
spec.flags_ |= SIGN_FLAG;
|
||||
break;
|
||||
case '#':
|
||||
spec.flags_ |= HASH_FLAG;
|
||||
break;
|
||||
default:
|
||||
--it;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename Char, typename AF>
|
||||
typename basic_printf_context<OutputIt, Char, AF>::format_arg
|
||||
basic_printf_context<OutputIt, Char, AF>::get_arg(
|
||||
iterator it, unsigned arg_index) {
|
||||
(void)it;
|
||||
if (arg_index == std::numeric_limits<unsigned>::max())
|
||||
return this->do_get_arg(this->parse_context().next_arg_id());
|
||||
return base::get_arg(arg_index - 1);
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename Char, typename AF>
|
||||
unsigned basic_printf_context<OutputIt, Char, AF>::parse_header(
|
||||
iterator &it, format_specs &spec) {
|
||||
unsigned arg_index = std::numeric_limits<unsigned>::max();
|
||||
char_type c = *it;
|
||||
if (c >= '0' && c <= '9') {
|
||||
// Parse an argument index (if followed by '$') or a width possibly
|
||||
// preceded with '0' flag(s).
|
||||
internal::error_handler eh;
|
||||
unsigned value = parse_nonnegative_int(it, eh);
|
||||
if (*it == '$') { // value is an argument index
|
||||
++it;
|
||||
arg_index = value;
|
||||
} else {
|
||||
if (c == '0')
|
||||
spec.fill_ = '0';
|
||||
if (value != 0) {
|
||||
// Nonzero value means that we parsed width and don't need to
|
||||
// parse it or flags again, so return now.
|
||||
spec.width_ = value;
|
||||
return arg_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
parse_flags(spec, it);
|
||||
// Parse width.
|
||||
if (*it >= '0' && *it <= '9') {
|
||||
internal::error_handler eh;
|
||||
spec.width_ = parse_nonnegative_int(it, eh);
|
||||
} else if (*it == '*') {
|
||||
++it;
|
||||
spec.width_ =
|
||||
fmt::visit(internal::printf_width_handler<char_type>(spec), get_arg(it));
|
||||
}
|
||||
return arg_index;
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename Char, typename AF>
|
||||
void basic_printf_context<OutputIt, Char, AF>::format() {
|
||||
auto &buffer = internal::get_container(this->out());
|
||||
auto start = iterator(this->parse_context());
|
||||
auto it = start;
|
||||
using internal::pointer_from;
|
||||
while (*it) {
|
||||
char_type c = *it++;
|
||||
if (c != '%') continue;
|
||||
if (*it == c) {
|
||||
buffer.append(pointer_from(start), pointer_from(it));
|
||||
start = ++it;
|
||||
continue;
|
||||
}
|
||||
buffer.append(pointer_from(start), pointer_from(it) - 1);
|
||||
|
||||
format_specs spec;
|
||||
spec.align_ = ALIGN_RIGHT;
|
||||
|
||||
// Parse argument index, flags and width.
|
||||
unsigned arg_index = parse_header(it, spec);
|
||||
|
||||
// Parse precision.
|
||||
if (*it == '.') {
|
||||
++it;
|
||||
if ('0' <= *it && *it <= '9') {
|
||||
internal::error_handler eh;
|
||||
spec.precision_ = static_cast<int>(parse_nonnegative_int(it, eh));
|
||||
} else if (*it == '*') {
|
||||
++it;
|
||||
spec.precision_ =
|
||||
fmt::visit(internal::printf_precision_handler(), get_arg(it));
|
||||
} else {
|
||||
spec.precision_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
format_arg arg = get_arg(it, arg_index);
|
||||
if (spec.flag(HASH_FLAG) && fmt::visit(internal::is_zero_int(), arg))
|
||||
spec.flags_ &= ~internal::to_unsigned<int>(HASH_FLAG);
|
||||
if (spec.fill_ == '0') {
|
||||
if (arg.is_arithmetic())
|
||||
spec.align_ = ALIGN_NUMERIC;
|
||||
else
|
||||
spec.fill_ = ' '; // Ignore '0' flag for non-numeric types.
|
||||
}
|
||||
|
||||
// Parse length and convert the argument to the required type.
|
||||
using internal::convert_arg;
|
||||
switch (*it++) {
|
||||
case 'h':
|
||||
if (*it == 'h')
|
||||
convert_arg<signed char>(arg, *++it);
|
||||
else
|
||||
convert_arg<short>(arg, *it);
|
||||
break;
|
||||
case 'l':
|
||||
if (*it == 'l')
|
||||
convert_arg<long long>(arg, *++it);
|
||||
else
|
||||
convert_arg<long>(arg, *it);
|
||||
break;
|
||||
case 'j':
|
||||
convert_arg<intmax_t>(arg, *it);
|
||||
break;
|
||||
case 'z':
|
||||
convert_arg<std::size_t>(arg, *it);
|
||||
break;
|
||||
case 't':
|
||||
convert_arg<std::ptrdiff_t>(arg, *it);
|
||||
break;
|
||||
case 'L':
|
||||
// printf produces garbage when 'L' is omitted for long double, no
|
||||
// need to do the same.
|
||||
break;
|
||||
default:
|
||||
--it;
|
||||
convert_arg<void>(arg, *it);
|
||||
}
|
||||
|
||||
// Parse type.
|
||||
if (!*it)
|
||||
FMT_THROW(format_error("invalid format string"));
|
||||
spec.type_ = static_cast<char>(*it++);
|
||||
if (arg.is_integral()) {
|
||||
// Normalize type.
|
||||
switch (spec.type_) {
|
||||
case 'i': case 'u':
|
||||
spec.type_ = 'd';
|
||||
break;
|
||||
case 'c':
|
||||
// TODO: handle wchar_t better?
|
||||
fmt::visit(internal::char_converter<basic_printf_context>(arg), arg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
start = it;
|
||||
|
||||
// Format argument.
|
||||
fmt::visit(AF(buffer, spec, *this), arg);
|
||||
}
|
||||
buffer.append(pointer_from(start), pointer_from(it));
|
||||
}
|
||||
|
||||
template <typename Char, typename Context>
|
||||
void printf(internal::basic_buffer<Char> &buf, basic_string_view<Char> format,
|
||||
basic_format_args<Context> args) {
|
||||
Context(std::back_inserter(buf), format, args).format();
|
||||
}
|
||||
|
||||
template <typename Buffer>
|
||||
struct printf_context {
|
||||
typedef basic_printf_context<
|
||||
std::back_insert_iterator<Buffer>, typename Buffer::value_type> type;
|
||||
};
|
||||
|
||||
template <typename ...Args>
|
||||
inline format_arg_store<printf_context<internal::buffer>::type, Args...>
|
||||
make_printf_args(const Args & ... args) {
|
||||
return format_arg_store<printf_context<internal::buffer>::type, Args...>(
|
||||
args...);
|
||||
}
|
||||
typedef basic_format_args<printf_context<internal::buffer>::type> printf_args;
|
||||
typedef basic_format_args<printf_context<internal::wbuffer>::type> wprintf_args;
|
||||
|
||||
inline std::string vsprintf(string_view format, printf_args args) {
|
||||
memory_buffer buffer;
|
||||
printf(buffer, format, args);
|
||||
return to_string(buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Formats arguments and returns the result as a string.
|
||||
|
||||
**Example**::
|
||||
|
||||
std::string message = fmt::sprintf("The answer is %d", 42);
|
||||
\endrst
|
||||
*/
|
||||
template <typename... Args>
|
||||
inline std::string sprintf(string_view format_str, const Args & ... args) {
|
||||
return vsprintf(format_str,
|
||||
make_format_args<typename printf_context<internal::buffer>::type>(args...));
|
||||
}
|
||||
|
||||
inline std::wstring vsprintf(wstring_view format, wprintf_args args) {
|
||||
wmemory_buffer buffer;
|
||||
printf(buffer, format, args);
|
||||
return to_string(buffer);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
inline std::wstring sprintf(wstring_view format_str, const Args & ... args) {
|
||||
return vsprintf(format_str,
|
||||
make_format_args<typename printf_context<internal::wbuffer>::type>(args...));
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
inline int vfprintf(std::FILE *f, basic_string_view<Char> format,
|
||||
basic_format_args<typename printf_context<
|
||||
internal::basic_buffer<Char>>::type> args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
printf(buffer, format, args);
|
||||
std::size_t size = buffer.size();
|
||||
return std::fwrite(
|
||||
buffer.data(), sizeof(Char), size, f) < size ? -1 : static_cast<int>(size);
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Prints formatted data to the file *f*.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::fprintf(stderr, "Don't %s!", "panic");
|
||||
\endrst
|
||||
*/
|
||||
template <typename... Args>
|
||||
inline int fprintf(std::FILE *f, string_view format_str, const Args & ... args) {
|
||||
auto vargs = make_format_args<
|
||||
typename printf_context<internal::buffer>::type>(args...);
|
||||
return vfprintf<char>(f, format_str, vargs);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
inline int fprintf(std::FILE *f, wstring_view format_str,
|
||||
const Args & ... args) {
|
||||
return vfprintf(f, format_str,
|
||||
make_format_args<typename printf_context<internal::wbuffer>::type>(args...));
|
||||
}
|
||||
|
||||
inline int vprintf(string_view format, printf_args args) {
|
||||
return vfprintf(stdout, format, args);
|
||||
}
|
||||
|
||||
inline int vprintf(wstring_view format, wprintf_args args) {
|
||||
return vfprintf(stdout, format, args);
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Prints formatted data to ``stdout``.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::printf("Elapsed time: %.2f seconds", 1.23);
|
||||
\endrst
|
||||
*/
|
||||
template <typename... Args>
|
||||
inline int printf(string_view format_str, const Args & ... args) {
|
||||
return vprintf(format_str,
|
||||
make_format_args<typename printf_context<internal::buffer>::type>(args...));
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
inline int printf(wstring_view format_str, const Args & ... args) {
|
||||
return vprintf(format_str,
|
||||
make_format_args<typename printf_context<internal::wbuffer>::type>(args...));
|
||||
}
|
||||
|
||||
inline int vfprintf(std::ostream &os, string_view format_str,
|
||||
printf_args args) {
|
||||
memory_buffer buffer;
|
||||
printf(buffer, format_str, args);
|
||||
internal::write(os, buffer);
|
||||
return static_cast<int>(buffer.size());
|
||||
}
|
||||
|
||||
inline int vfprintf(std::wostream &os, wstring_view format_str,
|
||||
wprintf_args args) {
|
||||
wmemory_buffer buffer;
|
||||
printf(buffer, format_str, args);
|
||||
internal::write(os, buffer);
|
||||
return static_cast<int>(buffer.size());
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Prints formatted data to the stream *os*.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::fprintf(cerr, "Don't %s!", "panic");
|
||||
\endrst
|
||||
*/
|
||||
template <typename... Args>
|
||||
inline int fprintf(std::ostream &os, string_view format_str,
|
||||
const Args & ... args) {
|
||||
auto vargs = make_format_args<
|
||||
typename printf_context<internal::buffer>::type>(args...);
|
||||
return vfprintf(os, format_str, vargs);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
inline int fprintf(std::wostream &os, wstring_view format_str,
|
||||
const Args & ... args) {
|
||||
auto vargs = make_format_args<
|
||||
typename printf_context<internal::buffer>::type>(args...);
|
||||
return vfprintf(os, format_str, vargs);
|
||||
}
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_PRINTF_H_
|
|
@ -0,0 +1,308 @@
|
|||
// Formatting library for C++ - the core API
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
//
|
||||
// Copyright (c) 2018 - present, Remotion (Igor Schulz)
|
||||
// All Rights Reserved
|
||||
// {fmt} support for ranges, containers and types tuple interface.
|
||||
|
||||
#ifndef FMT_RANGES_H_
|
||||
#define FMT_RANGES_H_
|
||||
|
||||
#include "format.h"
|
||||
#include <type_traits>
|
||||
|
||||
// output only up to N items from the range.
|
||||
#ifndef FMT_RANGE_OUTPUT_LENGTH_LIMIT
|
||||
# define FMT_RANGE_OUTPUT_LENGTH_LIMIT 256
|
||||
#endif
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
template <typename Char>
|
||||
struct formatting_base {
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext &ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char, typename Enable = void>
|
||||
struct formatting_range : formatting_base<Char> {
|
||||
static FMT_CONSTEXPR_DECL const std::size_t range_length_limit =
|
||||
FMT_RANGE_OUTPUT_LENGTH_LIMIT; // output only up to N items from the range.
|
||||
Char prefix;
|
||||
Char delimiter;
|
||||
Char postfix;
|
||||
formatting_range() : prefix('{'), delimiter(','), postfix('}') {}
|
||||
static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true;
|
||||
static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false;
|
||||
};
|
||||
|
||||
template <typename Char, typename Enable = void>
|
||||
struct formatting_tuple : formatting_base<Char> {
|
||||
Char prefix;
|
||||
Char delimiter;
|
||||
Char postfix;
|
||||
formatting_tuple() : prefix('('), delimiter(','), postfix(')') {}
|
||||
static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true;
|
||||
static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false;
|
||||
};
|
||||
|
||||
namespace internal {
|
||||
|
||||
template <typename RangeT, typename OutputIterator>
|
||||
void copy(const RangeT &range, OutputIterator out) {
|
||||
for (auto it = range.begin(), end = range.end(); it != end; ++it)
|
||||
*out++ = *it;
|
||||
}
|
||||
|
||||
template <typename OutputIterator>
|
||||
void copy(const char *str, OutputIterator out) {
|
||||
const char *p_curr = str;
|
||||
while (*p_curr) {
|
||||
*out++ = *p_curr++;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename OutputIterator>
|
||||
void copy(char ch, OutputIterator out) {
|
||||
*out++ = ch;
|
||||
}
|
||||
|
||||
/// Return true value if T has std::string interface, like std::string_view.
|
||||
template <typename T>
|
||||
class is_like_std_string {
|
||||
template <typename U>
|
||||
static auto check(U *p) ->
|
||||
decltype(p->find('a'), p->length(), p->data(), int());
|
||||
template <typename>
|
||||
static void check(...);
|
||||
|
||||
public:
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
!std::is_void<decltype(check<T>(FMT_NULL))>::value;
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
struct is_like_std_string<fmt::basic_string_view<Char>> : std::true_type {};
|
||||
|
||||
template <typename... Ts>
|
||||
struct conditional_helper {};
|
||||
|
||||
template <typename T, typename _ = void>
|
||||
struct is_range_ : std::false_type {};
|
||||
|
||||
#if !FMT_MSC_VER || FMT_MSC_VER > 1800
|
||||
template <typename T>
|
||||
struct is_range_<T, typename std::conditional<
|
||||
false,
|
||||
conditional_helper<decltype(internal::declval<T>().begin()),
|
||||
decltype(internal::declval<T>().end())>,
|
||||
void>::type> : std::true_type {};
|
||||
#endif
|
||||
|
||||
/// tuple_size and tuple_element check.
|
||||
template <typename T>
|
||||
class is_tuple_like_ {
|
||||
template <typename U>
|
||||
static auto check(U *p) ->
|
||||
decltype(std::tuple_size<U>::value,
|
||||
internal::declval<typename std::tuple_element<0, U>::type>(), int());
|
||||
template <typename>
|
||||
static void check(...);
|
||||
|
||||
public:
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
!std::is_void<decltype(check<T>(FMT_NULL))>::value;
|
||||
};
|
||||
|
||||
// Check for integer_sequence
|
||||
#if defined(__cpp_lib_integer_sequence) || FMT_MSC_VER >= 1900
|
||||
template <typename T, T... N>
|
||||
using integer_sequence = std::integer_sequence<T, N...>;
|
||||
template <std::size_t... N>
|
||||
using index_sequence = std::index_sequence<N...>;
|
||||
template <std::size_t N>
|
||||
using make_index_sequence = std::make_index_sequence<N>;
|
||||
#else
|
||||
template <typename T, T... N>
|
||||
struct integer_sequence {
|
||||
typedef T value_type;
|
||||
|
||||
static FMT_CONSTEXPR std::size_t size() {
|
||||
return sizeof...(N);
|
||||
}
|
||||
};
|
||||
|
||||
template <std::size_t... N>
|
||||
using index_sequence = integer_sequence<std::size_t, N...>;
|
||||
|
||||
template <typename T, std::size_t N, T... Ns>
|
||||
struct make_integer_sequence : make_integer_sequence<T, N - 1, N - 1, Ns...> {};
|
||||
template <typename T, T... Ns>
|
||||
struct make_integer_sequence<T, 0, Ns...> : integer_sequence<T, Ns...> {};
|
||||
|
||||
template <std::size_t N>
|
||||
using make_index_sequence = make_integer_sequence<std::size_t, N>;
|
||||
#endif
|
||||
|
||||
template <class Tuple, class F, size_t... Is>
|
||||
void for_each(index_sequence<Is...>, Tuple &&tup, F &&f) FMT_NOEXCEPT {
|
||||
using std::get;
|
||||
// using free function get<I>(T) now.
|
||||
const int _[] = {0, ((void)f(get<Is>(tup)), 0)...};
|
||||
(void)_; // blocks warnings
|
||||
}
|
||||
|
||||
template <class T>
|
||||
FMT_CONSTEXPR make_index_sequence<std::tuple_size<T>::value>
|
||||
get_indexes(T const &) { return {}; }
|
||||
|
||||
template <class Tuple, class F>
|
||||
void for_each(Tuple &&tup, F &&f) {
|
||||
const auto indexes = get_indexes(tup);
|
||||
for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f));
|
||||
}
|
||||
|
||||
template<typename Arg>
|
||||
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&,
|
||||
typename std::enable_if<
|
||||
!is_like_std_string<typename std::decay<Arg>::type>::value>::type* = nullptr) {
|
||||
return add_space ? " {}" : "{}";
|
||||
}
|
||||
|
||||
template<typename Arg>
|
||||
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&,
|
||||
typename std::enable_if<
|
||||
is_like_std_string<typename std::decay<Arg>::type>::value>::type* = nullptr) {
|
||||
return add_space ? " \"{}\"" : "\"{}\"";
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const char*) {
|
||||
return add_space ? " \"{}\"" : "\"{}\"";
|
||||
}
|
||||
FMT_CONSTEXPR const wchar_t* format_str_quoted(bool add_space, const wchar_t*) {
|
||||
return add_space ? L" \"{}\"" : L"\"{}\"";
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const char) {
|
||||
return add_space ? " '{}'" : "'{}'";
|
||||
}
|
||||
FMT_CONSTEXPR const wchar_t* format_str_quoted(bool add_space, const wchar_t) {
|
||||
return add_space ? L" '{}'" : L"'{}'";
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
||||
template <typename T>
|
||||
struct is_tuple_like {
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
internal::is_tuple_like_<T>::value && !internal::is_range_<T>::value;
|
||||
};
|
||||
|
||||
template <typename TupleT, typename Char>
|
||||
struct formatter<TupleT, Char,
|
||||
typename std::enable_if<fmt::is_tuple_like<TupleT>::value>::type> {
|
||||
private:
|
||||
// C++11 generic lambda for format()
|
||||
template <typename FormatContext>
|
||||
struct format_each {
|
||||
template <typename T>
|
||||
void operator()(const T& v) {
|
||||
if (i > 0) {
|
||||
if (formatting.add_prepostfix_space) {
|
||||
*out++ = ' ';
|
||||
}
|
||||
internal::copy(formatting.delimiter, out);
|
||||
}
|
||||
format_to(out,
|
||||
internal::format_str_quoted(
|
||||
(formatting.add_delimiter_spaces && i > 0), v),
|
||||
v);
|
||||
++i;
|
||||
}
|
||||
|
||||
formatting_tuple<Char>& formatting;
|
||||
std::size_t& i;
|
||||
typename std::add_lvalue_reference<decltype(std::declval<FormatContext>().out())>::type out;
|
||||
};
|
||||
|
||||
public:
|
||||
formatting_tuple<Char> formatting;
|
||||
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext &ctx) -> decltype(ctx.begin()) {
|
||||
return formatting.parse(ctx);
|
||||
}
|
||||
|
||||
template <typename FormatContext = format_context>
|
||||
auto format(const TupleT &values, FormatContext &ctx) -> decltype(ctx.out()) {
|
||||
auto out = ctx.out();
|
||||
std::size_t i = 0;
|
||||
internal::copy(formatting.prefix, out);
|
||||
|
||||
internal::for_each(values, format_each<FormatContext>{formatting, i, out});
|
||||
if (formatting.add_prepostfix_space) {
|
||||
*out++ = ' ';
|
||||
}
|
||||
internal::copy(formatting.postfix, out);
|
||||
|
||||
return ctx.out();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct is_range {
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
internal::is_range_<T>::value && !internal::is_like_std_string<T>::value;
|
||||
};
|
||||
|
||||
template <typename RangeT, typename Char>
|
||||
struct formatter<RangeT, Char,
|
||||
typename std::enable_if<fmt::is_range<RangeT>::value>::type> {
|
||||
|
||||
formatting_range<Char> formatting;
|
||||
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext &ctx) -> decltype(ctx.begin()) {
|
||||
return formatting.parse(ctx);
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
typename FormatContext::iterator format(
|
||||
const RangeT &values, FormatContext &ctx) {
|
||||
auto out = ctx.out();
|
||||
internal::copy(formatting.prefix, out);
|
||||
std::size_t i = 0;
|
||||
for (auto it = values.begin(), end = values.end(); it != end; ++it) {
|
||||
if (i > 0) {
|
||||
if (formatting.add_prepostfix_space) {
|
||||
*out++ = ' ';
|
||||
}
|
||||
internal::copy(formatting.delimiter, out);
|
||||
}
|
||||
format_to(out,
|
||||
internal::format_str_quoted(
|
||||
(formatting.add_delimiter_spaces && i > 0), *it),
|
||||
*it);
|
||||
if (++i > formatting.range_length_limit) {
|
||||
format_to(out, " ... <other elements>");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (formatting.add_prepostfix_space) {
|
||||
*out++ = ' ';
|
||||
}
|
||||
internal::copy(formatting.postfix, out);
|
||||
return ctx.out();
|
||||
}
|
||||
};
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_RANGES_H_
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
// Formatting library for C++ - time formatting
|
||||
//
|
||||
// Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_TIME_H_
|
||||
#define FMT_TIME_H_
|
||||
|
||||
#include "format.h"
|
||||
#include <ctime>
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
// Prevents expansion of a preceding token as a function-style macro.
|
||||
// Usage: f FMT_NOMACRO()
|
||||
#define FMT_NOMACRO
|
||||
|
||||
namespace internal{
|
||||
inline null<> localtime_r FMT_NOMACRO(...) { return null<>(); }
|
||||
inline null<> localtime_s(...) { return null<>(); }
|
||||
inline null<> gmtime_r(...) { return null<>(); }
|
||||
inline null<> gmtime_s(...) { return null<>(); }
|
||||
}
|
||||
|
||||
// Thread-safe replacement for std::localtime
|
||||
inline std::tm localtime(std::time_t time) {
|
||||
struct dispatcher {
|
||||
std::time_t time_;
|
||||
std::tm tm_;
|
||||
|
||||
dispatcher(std::time_t t): time_(t) {}
|
||||
|
||||
bool run() {
|
||||
using namespace fmt::internal;
|
||||
return handle(localtime_r(&time_, &tm_));
|
||||
}
|
||||
|
||||
bool handle(std::tm *tm) { return tm != FMT_NULL; }
|
||||
|
||||
bool handle(internal::null<>) {
|
||||
using namespace fmt::internal;
|
||||
return fallback(localtime_s(&tm_, &time_));
|
||||
}
|
||||
|
||||
bool fallback(int res) { return res == 0; }
|
||||
|
||||
bool fallback(internal::null<>) {
|
||||
using namespace fmt::internal;
|
||||
std::tm *tm = std::localtime(&time_);
|
||||
if (tm) tm_ = *tm;
|
||||
return tm != FMT_NULL;
|
||||
}
|
||||
};
|
||||
dispatcher lt(time);
|
||||
if (lt.run())
|
||||
return lt.tm_;
|
||||
// Too big time values may be unsupported.
|
||||
FMT_THROW(format_error("time_t value out of range"));
|
||||
}
|
||||
|
||||
// Thread-safe replacement for std::gmtime
|
||||
inline std::tm gmtime(std::time_t time) {
|
||||
struct dispatcher {
|
||||
std::time_t time_;
|
||||
std::tm tm_;
|
||||
|
||||
dispatcher(std::time_t t): time_(t) {}
|
||||
|
||||
bool run() {
|
||||
using namespace fmt::internal;
|
||||
return handle(gmtime_r(&time_, &tm_));
|
||||
}
|
||||
|
||||
bool handle(std::tm *tm) { return tm != FMT_NULL; }
|
||||
|
||||
bool handle(internal::null<>) {
|
||||
using namespace fmt::internal;
|
||||
return fallback(gmtime_s(&tm_, &time_));
|
||||
}
|
||||
|
||||
bool fallback(int res) { return res == 0; }
|
||||
|
||||
bool fallback(internal::null<>) {
|
||||
std::tm *tm = std::gmtime(&time_);
|
||||
if (tm) tm_ = *tm;
|
||||
return tm != FMT_NULL;
|
||||
}
|
||||
};
|
||||
dispatcher gt(time);
|
||||
if (gt.run())
|
||||
return gt.tm_;
|
||||
// Too big time values may be unsupported.
|
||||
FMT_THROW(format_error("time_t value out of range"));
|
||||
}
|
||||
|
||||
namespace internal {
|
||||
inline std::size_t strftime(char *str, std::size_t count, const char *format,
|
||||
const std::tm *time) {
|
||||
return std::strftime(str, count, format, time);
|
||||
}
|
||||
|
||||
inline std::size_t strftime(wchar_t *str, std::size_t count,
|
||||
const wchar_t *format, const std::tm *time) {
|
||||
return std::wcsftime(str, count, format, time);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
struct formatter<std::tm, Char> {
|
||||
template <typename ParseContext>
|
||||
auto parse(ParseContext &ctx) -> decltype(ctx.begin()) {
|
||||
auto it = internal::null_terminating_iterator<Char>(ctx);
|
||||
if (*it == ':')
|
||||
++it;
|
||||
auto end = it;
|
||||
while (*end && *end != '}')
|
||||
++end;
|
||||
tm_format.reserve(end - it + 1);
|
||||
using internal::pointer_from;
|
||||
tm_format.append(pointer_from(it), pointer_from(end));
|
||||
tm_format.push_back('\0');
|
||||
return pointer_from(end);
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const std::tm &tm, FormatContext &ctx) -> decltype(ctx.out()) {
|
||||
internal::basic_buffer<Char> &buf = internal::get_container(ctx.out());
|
||||
std::size_t start = buf.size();
|
||||
for (;;) {
|
||||
std::size_t size = buf.capacity() - start;
|
||||
std::size_t count =
|
||||
internal::strftime(&buf[start], size, &tm_format[0], &tm);
|
||||
if (count != 0) {
|
||||
buf.resize(start + count);
|
||||
break;
|
||||
}
|
||||
if (size >= tm_format.size() * 256) {
|
||||
// If the buffer is 256 times larger than the format string, assume
|
||||
// that `strftime` gives an empty result. There doesn't seem to be a
|
||||
// better way to distinguish the two cases:
|
||||
// https://github.com/fmtlib/fmt/issues/367
|
||||
break;
|
||||
}
|
||||
const std::size_t MIN_GROWTH = 10;
|
||||
buf.reserve(buf.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH));
|
||||
}
|
||||
return ctx.out();
|
||||
}
|
||||
|
||||
basic_memory_buffer<Char> tm_format;
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_TIME_H_
|
|
@ -0,0 +1,53 @@
|
|||
// Formatting library for C++
|
||||
//
|
||||
// Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#include "fmt/format-inl.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
template struct internal::basic_data<void>;
|
||||
|
||||
// Explicit instantiations for char.
|
||||
|
||||
template FMT_API char internal::thousands_sep(locale_provider *lp);
|
||||
|
||||
template void internal::basic_buffer<char>::append(const char *, const char *);
|
||||
|
||||
template void basic_fixed_buffer<char>::grow(std::size_t);
|
||||
|
||||
template void internal::arg_map<format_context>::init(
|
||||
const basic_format_args<format_context> &args);
|
||||
|
||||
template FMT_API int internal::char_traits<char>::format_float(
|
||||
char *, std::size_t, const char *, int, double);
|
||||
|
||||
template FMT_API int internal::char_traits<char>::format_float(
|
||||
char *, std::size_t, const char *, int, long double);
|
||||
|
||||
template FMT_API std::string internal::vformat<char>(
|
||||
string_view, basic_format_args<format_context>);
|
||||
|
||||
// Explicit instantiations for wchar_t.
|
||||
|
||||
template FMT_API wchar_t internal::thousands_sep(locale_provider *);
|
||||
|
||||
template void internal::basic_buffer<wchar_t>::append(
|
||||
const wchar_t *, const wchar_t *);
|
||||
|
||||
template void basic_fixed_buffer<wchar_t>::grow(std::size_t);
|
||||
|
||||
template void internal::arg_map<wformat_context>::init(
|
||||
const basic_format_args<wformat_context> &);
|
||||
|
||||
template FMT_API int internal::char_traits<wchar_t>::format_float(
|
||||
wchar_t *, std::size_t, const wchar_t *, int, double);
|
||||
|
||||
template FMT_API int internal::char_traits<wchar_t>::format_float(
|
||||
wchar_t *, std::size_t, const wchar_t *, int, long double);
|
||||
|
||||
template FMT_API std::wstring internal::vformat<wchar_t>(
|
||||
wstring_view, basic_format_args<wformat_context>);
|
||||
FMT_END_NAMESPACE
|
|
@ -7,51 +7,49 @@
|
|||
|
||||
// Disable bogus MSVC warnings.
|
||||
#if !defined(_CRT_SECURE_NO_WARNINGS) && defined(_MSC_VER)
|
||||
# define _CRT_SECURE_NO_WARNINGS
|
||||
# define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "fmt/posix.h"
|
||||
|
||||
#include <climits>
|
||||
|
||||
#if FMT_USE_FCNTL
|
||||
#include <sys/stat.h>
|
||||
#include <limits.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#ifndef _WIN32
|
||||
# include <unistd.h>
|
||||
# include <unistd.h>
|
||||
#else
|
||||
# ifndef WIN32_LEAN_AND_MEAN
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# endif
|
||||
# include <io.h>
|
||||
# include <windows.h>
|
||||
# ifndef WIN32_LEAN_AND_MEAN
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# endif
|
||||
# include <windows.h>
|
||||
# include <io.h>
|
||||
|
||||
# define O_CREAT _O_CREAT
|
||||
# define O_TRUNC _O_TRUNC
|
||||
# define O_CREAT _O_CREAT
|
||||
# define O_TRUNC _O_TRUNC
|
||||
|
||||
# ifndef S_IRUSR
|
||||
# define S_IRUSR _S_IREAD
|
||||
# endif
|
||||
# ifndef S_IRUSR
|
||||
# define S_IRUSR _S_IREAD
|
||||
# endif
|
||||
|
||||
# ifndef S_IWUSR
|
||||
# define S_IWUSR _S_IWRITE
|
||||
# endif
|
||||
# ifndef S_IWUSR
|
||||
# define S_IWUSR _S_IWRITE
|
||||
# endif
|
||||
|
||||
# ifdef __MINGW32__
|
||||
# define _SH_DENYNO 0x40
|
||||
# endif
|
||||
|
||||
# ifdef __MINGW32__
|
||||
# define _SH_DENYNO 0x40
|
||||
# endif
|
||||
#endif // _WIN32
|
||||
#endif // FMT_USE_FCNTL
|
||||
|
||||
#ifdef fileno
|
||||
# undef fileno
|
||||
# undef fileno
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
#ifdef _WIN32
|
||||
// Return type of read and write functions.
|
||||
using RWResult = int;
|
||||
typedef int RWResult;
|
||||
|
||||
// On Windows the count argument to read and write is unsigned, so convert
|
||||
// it from size_t preventing integer overflow.
|
||||
|
@ -60,11 +58,11 @@ inline unsigned convert_rwcount(std::size_t count) {
|
|||
}
|
||||
#else
|
||||
// Return type of read and write functions.
|
||||
using RWResult = ssize_t;
|
||||
typedef ssize_t RWResult;
|
||||
|
||||
inline std::size_t convert_rwcount(std::size_t count) { return count; }
|
||||
#endif
|
||||
} // namespace
|
||||
}
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
|
@ -74,17 +72,19 @@ buffered_file::~buffered_file() FMT_NOEXCEPT {
|
|||
}
|
||||
|
||||
buffered_file::buffered_file(cstring_view filename, cstring_view mode) {
|
||||
FMT_RETRY_VAL(file_, FMT_SYSTEM(fopen(filename.c_str(), mode.c_str())),
|
||||
nullptr);
|
||||
FMT_RETRY_VAL(file_,
|
||||
FMT_SYSTEM(fopen(filename.c_str(), mode.c_str())), FMT_NULL);
|
||||
if (!file_)
|
||||
FMT_THROW(system_error(errno, "cannot open file {}", filename.c_str()));
|
||||
}
|
||||
|
||||
void buffered_file::close() {
|
||||
if (!file_) return;
|
||||
if (!file_)
|
||||
return;
|
||||
int result = FMT_SYSTEM(fclose(file_));
|
||||
file_ = nullptr;
|
||||
if (result != 0) FMT_THROW(system_error(errno, "cannot close file"));
|
||||
file_ = FMT_NULL;
|
||||
if (result != 0)
|
||||
FMT_THROW(system_error(errno, "cannot close file"));
|
||||
}
|
||||
|
||||
// A macro used to prevent expansion of fileno on broken versions of MinGW.
|
||||
|
@ -92,11 +92,11 @@ void buffered_file::close() {
|
|||
|
||||
int buffered_file::fileno() const {
|
||||
int fd = FMT_POSIX_CALL(fileno FMT_ARGS(file_));
|
||||
if (fd == -1) FMT_THROW(system_error(errno, "cannot get file descriptor"));
|
||||
if (fd == -1)
|
||||
FMT_THROW(system_error(errno, "cannot get file descriptor"));
|
||||
return fd;
|
||||
}
|
||||
|
||||
#if FMT_USE_FCNTL
|
||||
file::file(cstring_view path, int oflag) {
|
||||
int mode = S_IRUSR | S_IWUSR;
|
||||
#if defined(_WIN32) && !defined(__MINGW32__)
|
||||
|
@ -117,12 +117,14 @@ file::~file() FMT_NOEXCEPT {
|
|||
}
|
||||
|
||||
void file::close() {
|
||||
if (fd_ == -1) return;
|
||||
if (fd_ == -1)
|
||||
return;
|
||||
// Don't retry close in case of EINTR!
|
||||
// See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html
|
||||
int result = FMT_POSIX_CALL(close(fd_));
|
||||
fd_ = -1;
|
||||
if (result != 0) FMT_THROW(system_error(errno, "cannot close file"));
|
||||
if (result != 0)
|
||||
FMT_THROW(system_error(errno, "cannot close file"));
|
||||
}
|
||||
|
||||
long long file::size() const {
|
||||
|
@ -141,27 +143,29 @@ long long file::size() const {
|
|||
unsigned long long long_size = size_upper;
|
||||
return (long_size << sizeof(DWORD) * CHAR_BIT) | size_lower;
|
||||
#else
|
||||
using Stat = struct stat;
|
||||
typedef struct stat Stat;
|
||||
Stat file_stat = Stat();
|
||||
if (FMT_POSIX_CALL(fstat(fd_, &file_stat)) == -1)
|
||||
FMT_THROW(system_error(errno, "cannot get file attributes"));
|
||||
static_assert(sizeof(long long) >= sizeof(file_stat.st_size),
|
||||
"return type of file::size is not large enough");
|
||||
"return type of file::size is not large enough");
|
||||
return file_stat.st_size;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::size_t file::read(void* buffer, std::size_t count) {
|
||||
std::size_t file::read(void *buffer, std::size_t count) {
|
||||
RWResult result = 0;
|
||||
FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count))));
|
||||
if (result < 0) FMT_THROW(system_error(errno, "cannot read from file"));
|
||||
if (result < 0)
|
||||
FMT_THROW(system_error(errno, "cannot read from file"));
|
||||
return internal::to_unsigned(result);
|
||||
}
|
||||
|
||||
std::size_t file::write(const void* buffer, std::size_t count) {
|
||||
std::size_t file::write(const void *buffer, std::size_t count) {
|
||||
RWResult result = 0;
|
||||
FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count))));
|
||||
if (result < 0) FMT_THROW(system_error(errno, "cannot write to file"));
|
||||
if (result < 0)
|
||||
FMT_THROW(system_error(errno, "cannot write to file"));
|
||||
return internal::to_unsigned(result);
|
||||
}
|
||||
|
||||
|
@ -178,18 +182,19 @@ void file::dup2(int fd) {
|
|||
int result = 0;
|
||||
FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd)));
|
||||
if (result == -1) {
|
||||
FMT_THROW(system_error(errno, "cannot duplicate file descriptor {} to {}",
|
||||
fd_, fd));
|
||||
FMT_THROW(system_error(errno,
|
||||
"cannot duplicate file descriptor {} to {}", fd_, fd));
|
||||
}
|
||||
}
|
||||
|
||||
void file::dup2(int fd, error_code& ec) FMT_NOEXCEPT {
|
||||
void file::dup2(int fd, error_code &ec) FMT_NOEXCEPT {
|
||||
int result = 0;
|
||||
FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd)));
|
||||
if (result == -1) ec = error_code(errno);
|
||||
if (result == -1)
|
||||
ec = error_code(errno);
|
||||
}
|
||||
|
||||
void file::pipe(file& read_end, file& write_end) {
|
||||
void file::pipe(file &read_end, file &write_end) {
|
||||
// Close the descriptors first to make sure that assignments don't throw
|
||||
// and there are no leaks.
|
||||
read_end.close();
|
||||
|
@ -204,19 +209,20 @@ void file::pipe(file& read_end, file& write_end) {
|
|||
// http://pubs.opengroup.org/onlinepubs/009696799/functions/pipe.html
|
||||
int result = FMT_POSIX_CALL(pipe(fds));
|
||||
#endif
|
||||
if (result != 0) FMT_THROW(system_error(errno, "cannot create pipe"));
|
||||
if (result != 0)
|
||||
FMT_THROW(system_error(errno, "cannot create pipe"));
|
||||
// The following assignments don't throw because read_fd and write_fd
|
||||
// are closed.
|
||||
read_end = file(fds[0]);
|
||||
write_end = file(fds[1]);
|
||||
}
|
||||
|
||||
buffered_file file::fdopen(const char* mode) {
|
||||
buffered_file file::fdopen(const char *mode) {
|
||||
// Don't retry as fdopen doesn't return EINTR.
|
||||
FILE* f = FMT_POSIX_CALL(fdopen(fd_, mode));
|
||||
FILE *f = FMT_POSIX_CALL(fdopen(fd_, mode));
|
||||
if (!f)
|
||||
FMT_THROW(
|
||||
system_error(errno, "cannot associate stream with file descriptor"));
|
||||
FMT_THROW(system_error(errno,
|
||||
"cannot associate stream with file descriptor"));
|
||||
buffered_file bf(f);
|
||||
fd_ = -1;
|
||||
return bf;
|
||||
|
@ -229,9 +235,10 @@ long getpagesize() {
|
|||
return si.dwPageSize;
|
||||
#else
|
||||
long size = FMT_POSIX_CALL(sysconf(_SC_PAGESIZE));
|
||||
if (size < 0) FMT_THROW(system_error(errno, "cannot get memory page size"));
|
||||
if (size < 0)
|
||||
FMT_THROW(system_error(errno, "cannot get memory page size"));
|
||||
return size;
|
||||
#endif
|
||||
}
|
||||
#endif // FMT_USE_FCNTL
|
||||
FMT_END_NAMESPACE
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
Copyright (c) 2012 - present, Victor Zverovich
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
--- Optional exception to the license ---
|
||||
|
||||
As an exception, if, as a result of your compiling your source code, portions
|
||||
of this Software are embedded into a machine-executable object form of such
|
||||
source code, you may redistribute such embedded portions in such object form
|
||||
without including the above copyright and permission notices.
|
|
@ -1,501 +0,0 @@
|
|||
{fmt}
|
||||
=====
|
||||
|
||||
.. image:: https://travis-ci.org/fmtlib/fmt.png?branch=master
|
||||
:target: https://travis-ci.org/fmtlib/fmt
|
||||
|
||||
.. image:: https://ci.appveyor.com/api/projects/status/ehjkiefde6gucy1v
|
||||
:target: https://ci.appveyor.com/project/vitaut/fmt
|
||||
|
||||
.. image:: https://oss-fuzz-build-logs.storage.googleapis.com/badges/libfmt.svg
|
||||
:alt: fmt is continuously fuzzed att oss-fuzz
|
||||
:target: https://bugs.chromium.org/p/oss-fuzz/issues/list?colspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20Summary&q=proj%3Dlibfmt&can=1
|
||||
|
||||
.. image:: https://img.shields.io/badge/stackoverflow-fmt-blue.svg
|
||||
:alt: Ask questions at StackOverflow with the tag fmt
|
||||
:target: http://stackoverflow.com/questions/tagged/fmt
|
||||
|
||||
**{fmt}** is an open-source formatting library for C++.
|
||||
It can be used as a safe and fast alternative to (s)printf and iostreams.
|
||||
|
||||
`Documentation <https://fmt.dev/latest/>`__
|
||||
|
||||
Q&A: ask questions on `StackOverflow with the tag fmt <http://stackoverflow.com/questions/tagged/fmt>`_.
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
* Replacement-based `format API <https://fmt.dev/dev/api.html>`_ with
|
||||
positional arguments for localization.
|
||||
* `Format string syntax <https://fmt.dev/dev/syntax.html>`_ similar to the one
|
||||
of `str.format <https://docs.python.org/3/library/stdtypes.html#str.format>`_
|
||||
in Python.
|
||||
* Safe `printf implementation
|
||||
<https://fmt.dev/latest/api.html#printf-formatting>`_ including
|
||||
the POSIX extension for positional arguments.
|
||||
* Implementation of `C++20 std::format <https://fmt.dev/Text%20Formatting.html>`__.
|
||||
* Support for user-defined types.
|
||||
* High performance: faster than common standard library implementations of
|
||||
`printf <http://en.cppreference.com/w/cpp/io/c/fprintf>`_ and
|
||||
iostreams. See `Speed tests`_ and `Fast integer to string conversion in C++
|
||||
<http://zverovich.net/2013/09/07/integer-to-string-conversion-in-cplusplus.html>`_.
|
||||
* Small code size both in terms of source code (the minimum configuration
|
||||
consists of just three header files, ``core.h``, ``format.h`` and
|
||||
``format-inl.h``) and compiled code. See `Compile time and code bloat`_.
|
||||
* Reliability: the library has an extensive set of `unit tests
|
||||
<https://github.com/fmtlib/fmt/tree/master/test>`_ and is continuously fuzzed.
|
||||
* Safety: the library is fully type safe, errors in format strings can be
|
||||
reported at compile time, automatic memory management prevents buffer overflow
|
||||
errors.
|
||||
* Ease of use: small self-contained code base, no external dependencies,
|
||||
permissive MIT `license
|
||||
<https://github.com/fmtlib/fmt/blob/master/LICENSE.rst>`_
|
||||
* `Portability <https://fmt.dev/latest/index.html#portability>`_ with
|
||||
consistent output across platforms and support for older compilers.
|
||||
* Clean warning-free codebase even on high warning levels
|
||||
(``-Wall -Wextra -pedantic``).
|
||||
* Support for wide strings.
|
||||
* Optional header-only configuration enabled with the ``FMT_HEADER_ONLY`` macro.
|
||||
|
||||
See the `documentation <https://fmt.dev/latest/>`_ for more details.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
Print ``Hello, world!`` to ``stdout``:
|
||||
|
||||
.. code:: c++
|
||||
|
||||
fmt::print("Hello, {}!", "world"); // Python-like format string syntax
|
||||
fmt::printf("Hello, %s!", "world"); // printf format string syntax
|
||||
|
||||
Format a string and use positional arguments:
|
||||
|
||||
.. code:: c++
|
||||
|
||||
std::string s = fmt::format("I'd rather be {1} than {0}.", "right", "happy");
|
||||
// s == "I'd rather be happy than right."
|
||||
|
||||
Check a format string at compile time:
|
||||
|
||||
.. code:: c++
|
||||
|
||||
// test.cc
|
||||
#include <fmt/format.h>
|
||||
std::string s = format(FMT_STRING("{2}"), 42);
|
||||
|
||||
.. code::
|
||||
|
||||
$ c++ -Iinclude -std=c++14 test.cc
|
||||
...
|
||||
test.cc:4:17: note: in instantiation of function template specialization 'fmt::v5::format<S, int>' requested here
|
||||
std::string s = format(FMT_STRING("{2}"), 42);
|
||||
^
|
||||
include/fmt/core.h:778:19: note: non-constexpr function 'on_error' cannot be used in a constant expression
|
||||
ErrorHandler::on_error(message);
|
||||
^
|
||||
include/fmt/format.h:2226:16: note: in call to '&checker.context_->on_error(&"argument index out of range"[0])'
|
||||
context_.on_error("argument index out of range");
|
||||
^
|
||||
|
||||
Use {fmt} as a safe portable replacement for ``itoa``
|
||||
(`godbolt <https://godbolt.org/g/NXmpU4>`_):
|
||||
|
||||
.. code:: c++
|
||||
|
||||
fmt::memory_buffer buf;
|
||||
format_to(buf, "{}", 42); // replaces itoa(42, buffer, 10)
|
||||
format_to(buf, "{:x}", 42); // replaces itoa(42, buffer, 16)
|
||||
// access the string with to_string(buf) or buf.data()
|
||||
|
||||
Format objects of user-defined types via a simple `extension API
|
||||
<https://fmt.dev/latest/api.html#formatting-user-defined-types>`_:
|
||||
|
||||
.. code:: c++
|
||||
|
||||
#include "fmt/format.h"
|
||||
|
||||
struct date {
|
||||
int year, month, day;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<date> {
|
||||
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const date& d, FormatContext& ctx) {
|
||||
return format_to(ctx.out(), "{}-{}-{}", d.year, d.month, d.day);
|
||||
}
|
||||
};
|
||||
|
||||
std::string s = fmt::format("The date is {}", date{2012, 12, 9});
|
||||
// s == "The date is 2012-12-9"
|
||||
|
||||
Create your own functions similar to `format
|
||||
<https://fmt.dev/latest/api.html#format>`_ and
|
||||
`print <https://fmt.dev/latest/api.html#print>`_
|
||||
which take arbitrary arguments (`godbolt <https://godbolt.org/g/MHjHVf>`_):
|
||||
|
||||
.. code:: c++
|
||||
|
||||
// Prints formatted error message.
|
||||
void vreport_error(const char* format, fmt::format_args args) {
|
||||
fmt::print("Error: ");
|
||||
fmt::vprint(format, args);
|
||||
}
|
||||
template <typename... Args>
|
||||
void report_error(const char* format, const Args & ... args) {
|
||||
vreport_error(format, fmt::make_format_args(args...));
|
||||
}
|
||||
|
||||
report_error("file not found: {}", path);
|
||||
|
||||
Note that ``vreport_error`` is not parameterized on argument types which can
|
||||
improve compile times and reduce code size compared to a fully parameterized
|
||||
version.
|
||||
|
||||
Benchmarks
|
||||
----------
|
||||
|
||||
Speed tests
|
||||
~~~~~~~~~~~
|
||||
|
||||
================= ============= ===========
|
||||
Library Method Run Time, s
|
||||
================= ============= ===========
|
||||
libc printf 1.04
|
||||
libc++ std::ostream 3.05
|
||||
{fmt} 6.1.1 fmt::print 0.75
|
||||
Boost Format 1.67 boost::format 7.24
|
||||
Folly Format folly::format 2.23
|
||||
================= ============= ===========
|
||||
|
||||
{fmt} is the fastest of the benchmarked methods, ~35% faster than ``printf``.
|
||||
|
||||
The above results were generated by building ``tinyformat_test.cpp`` on macOS
|
||||
10.14.6 with ``clang++ -O3 -DSPEED_TEST -DHAVE_FORMAT``, and taking the best of
|
||||
three runs. In the test, the format string ``"%0.10f:%04d:%+g:%s:%p:%c:%%\n"``
|
||||
or equivalent is filled 2,000,000 times with output sent to ``/dev/null``; for
|
||||
further details refer to the `source
|
||||
<https://github.com/fmtlib/format-benchmark/blob/master/tinyformat_test.cpp>`_.
|
||||
|
||||
{fmt} is 10x faster than ``std::ostringstream`` and ``sprintf`` on floating-point
|
||||
formatting (`dtoa-benchmark <https://github.com/fmtlib/dtoa-benchmark>`_)
|
||||
and as fast as `double-conversion <https://github.com/google/double-conversion>`_:
|
||||
|
||||
.. image:: https://user-images.githubusercontent.com/576385/69767160-cdaca400-112f-11ea-9fc5-347c9f83caad.png
|
||||
:target: https://fmt.dev/unknown_mac64_clang10.0.html
|
||||
|
||||
Compile time and code bloat
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The script `bloat-test.py
|
||||
<https://github.com/fmtlib/format-benchmark/blob/master/bloat-test.py>`_
|
||||
from `format-benchmark <https://github.com/fmtlib/format-benchmark>`_
|
||||
tests compile time and code bloat for nontrivial projects.
|
||||
It generates 100 translation units and uses ``printf()`` or its alternative
|
||||
five times in each to simulate a medium sized project. The resulting
|
||||
executable size and compile time (Apple LLVM version 8.1.0 (clang-802.0.42),
|
||||
macOS Sierra, best of three) is shown in the following tables.
|
||||
|
||||
**Optimized build (-O3)**
|
||||
|
||||
============= =============== ==================== ==================
|
||||
Method Compile Time, s Executable size, KiB Stripped size, KiB
|
||||
============= =============== ==================== ==================
|
||||
printf 2.6 29 26
|
||||
printf+string 16.4 29 26
|
||||
iostreams 31.1 59 55
|
||||
{fmt} 19.0 37 34
|
||||
Boost Format 91.9 226 203
|
||||
Folly Format 115.7 101 88
|
||||
============= =============== ==================== ==================
|
||||
|
||||
As you can see, {fmt} has 60% less overhead in terms of resulting binary code
|
||||
size compared to iostreams and comes pretty close to ``printf``. Boost Format
|
||||
and Folly Format have the largest overheads.
|
||||
|
||||
``printf+string`` is the same as ``printf`` but with extra ``<string>``
|
||||
include to measure the overhead of the latter.
|
||||
|
||||
**Non-optimized build**
|
||||
|
||||
============= =============== ==================== ==================
|
||||
Method Compile Time, s Executable size, KiB Stripped size, KiB
|
||||
============= =============== ==================== ==================
|
||||
printf 2.2 33 30
|
||||
printf+string 16.0 33 30
|
||||
iostreams 28.3 56 52
|
||||
{fmt} 18.2 59 50
|
||||
Boost Format 54.1 365 303
|
||||
Folly Format 79.9 445 430
|
||||
============= =============== ==================== ==================
|
||||
|
||||
``libc``, ``lib(std)c++`` and ``libfmt`` are all linked as shared libraries to
|
||||
compare formatting function overhead only. Boost Format is a
|
||||
header-only library so it doesn't provide any linkage options.
|
||||
|
||||
Running the tests
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Please refer to `Building the library`__ for the instructions on how to build
|
||||
the library and run the unit tests.
|
||||
|
||||
__ https://fmt.dev/latest/usage.html#building-the-library
|
||||
|
||||
Benchmarks reside in a separate repository,
|
||||
`format-benchmarks <https://github.com/fmtlib/format-benchmark>`_,
|
||||
so to run the benchmarks you first need to clone this repository and
|
||||
generate Makefiles with CMake::
|
||||
|
||||
$ git clone --recursive https://github.com/fmtlib/format-benchmark.git
|
||||
$ cd format-benchmark
|
||||
$ cmake .
|
||||
|
||||
Then you can run the speed test::
|
||||
|
||||
$ make speed-test
|
||||
|
||||
or the bloat test::
|
||||
|
||||
$ make bloat-test
|
||||
|
||||
Projects using this library
|
||||
---------------------------
|
||||
|
||||
* `0 A.D. <http://play0ad.com/>`_: A free, open-source, cross-platform real-time
|
||||
strategy game
|
||||
|
||||
* `AMPL/MP <https://github.com/ampl/mp>`_:
|
||||
An open-source library for mathematical programming
|
||||
|
||||
* `AvioBook <https://www.aviobook.aero/en>`_: A comprehensive aircraft
|
||||
operations suite
|
||||
|
||||
* `Celestia <https://celestia.space/>`_: Real-time 3D visualization of space
|
||||
|
||||
* `Ceph <https://ceph.com/>`_: A scalable distributed storage system
|
||||
|
||||
* `ccache <https://ccache.dev/>`_: A compiler cache
|
||||
|
||||
* `CUAUV <http://cuauv.org/>`_: Cornell University's autonomous underwater
|
||||
vehicle
|
||||
|
||||
* `HarpyWar/pvpgn <https://github.com/pvpgn/pvpgn-server>`_:
|
||||
Player vs Player Gaming Network with tweaks
|
||||
|
||||
* `KBEngine <http://kbengine.org/>`_: An open-source MMOG server engine
|
||||
|
||||
* `Keypirinha <http://keypirinha.com/>`_: A semantic launcher for Windows
|
||||
|
||||
* `Kodi <https://kodi.tv/>`_ (formerly xbmc): Home theater software
|
||||
|
||||
* `Lifeline <https://github.com/peter-clark/lifeline>`_: A 2D game
|
||||
|
||||
* `Drake <http://drake.mit.edu/>`_: A planning, control, and analysis toolbox
|
||||
for nonlinear dynamical systems (MIT)
|
||||
|
||||
* `Envoy <https://lyft.github.io/envoy/>`_: C++ L7 proxy and communication bus
|
||||
(Lyft)
|
||||
|
||||
* `FiveM <https://fivem.net/>`_: a modification framework for GTA V
|
||||
|
||||
* `MongoDB <https://mongodb.com/>`_: Distributed document database
|
||||
|
||||
* `MongoDB Smasher <https://github.com/duckie/mongo_smasher>`_: A small tool to
|
||||
generate randomized datasets
|
||||
|
||||
* `OpenSpace <http://openspaceproject.com/>`_: An open-source astrovisualization
|
||||
framework
|
||||
|
||||
* `PenUltima Online (POL) <http://www.polserver.com/>`_:
|
||||
An MMO server, compatible with most Ultima Online clients
|
||||
|
||||
* `quasardb <https://www.quasardb.net/>`_: A distributed, high-performance,
|
||||
associative database
|
||||
|
||||
* `readpe <https://bitbucket.org/sys_dev/readpe>`_: Read Portable Executable
|
||||
|
||||
* `redis-cerberus <https://github.com/HunanTV/redis-cerberus>`_: A Redis cluster
|
||||
proxy
|
||||
|
||||
* `rpclib <http://rpclib.net/>`_: A modern C++ msgpack-RPC server and client
|
||||
library
|
||||
|
||||
* `Saddy <https://github.com/mamontov-cpp/saddy-graphics-engine-2d>`_:
|
||||
Small crossplatform 2D graphic engine
|
||||
|
||||
* `Salesforce Analytics Cloud <http://www.salesforce.com/analytics-cloud/overview/>`_:
|
||||
Business intelligence software
|
||||
|
||||
* `Scylla <http://www.scylladb.com/>`_: A Cassandra-compatible NoSQL data store
|
||||
that can handle 1 million transactions per second on a single server
|
||||
|
||||
* `Seastar <http://www.seastar-project.org/>`_: An advanced, open-source C++
|
||||
framework for high-performance server applications on modern hardware
|
||||
|
||||
* `spdlog <https://github.com/gabime/spdlog>`_: Super fast C++ logging library
|
||||
|
||||
* `Stellar <https://www.stellar.org/>`_: Financial platform
|
||||
|
||||
* `Touch Surgery <https://www.touchsurgery.com/>`_: Surgery simulator
|
||||
|
||||
* `TrinityCore <https://github.com/TrinityCore/TrinityCore>`_: Open-source
|
||||
MMORPG framework
|
||||
|
||||
`More... <https://github.com/search?q=fmtlib&type=Code>`_
|
||||
|
||||
If you are aware of other projects using this library, please let me know
|
||||
by `email <mailto:victor.zverovich@gmail.com>`_ or by submitting an
|
||||
`issue <https://github.com/fmtlib/fmt/issues>`_.
|
||||
|
||||
Motivation
|
||||
----------
|
||||
|
||||
So why yet another formatting library?
|
||||
|
||||
There are plenty of methods for doing this task, from standard ones like
|
||||
the printf family of function and iostreams to Boost Format and FastFormat
|
||||
libraries. The reason for creating a new library is that every existing
|
||||
solution that I found either had serious issues or didn't provide
|
||||
all the features I needed.
|
||||
|
||||
printf
|
||||
~~~~~~
|
||||
|
||||
The good thing about ``printf`` is that it is pretty fast and readily available
|
||||
being a part of the C standard library. The main drawback is that it
|
||||
doesn't support user-defined types. ``printf`` also has safety issues although
|
||||
they are somewhat mitigated with `__attribute__ ((format (printf, ...))
|
||||
<http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html>`_ in GCC.
|
||||
There is a POSIX extension that adds positional arguments required for
|
||||
`i18n <https://en.wikipedia.org/wiki/Internationalization_and_localization>`_
|
||||
to ``printf`` but it is not a part of C99 and may not be available on some
|
||||
platforms.
|
||||
|
||||
iostreams
|
||||
~~~~~~~~~
|
||||
|
||||
The main issue with iostreams is best illustrated with an example:
|
||||
|
||||
.. code:: c++
|
||||
|
||||
std::cout << std::setprecision(2) << std::fixed << 1.23456 << "\n";
|
||||
|
||||
which is a lot of typing compared to printf:
|
||||
|
||||
.. code:: c++
|
||||
|
||||
printf("%.2f\n", 1.23456);
|
||||
|
||||
Matthew Wilson, the author of FastFormat, called this "chevron hell". iostreams
|
||||
don't support positional arguments by design.
|
||||
|
||||
The good part is that iostreams support user-defined types and are safe although
|
||||
error handling is awkward.
|
||||
|
||||
Boost Format
|
||||
~~~~~~~~~~~~
|
||||
|
||||
This is a very powerful library which supports both ``printf``-like format
|
||||
strings and positional arguments. Its main drawback is performance. According to
|
||||
various benchmarks it is much slower than other methods considered here. Boost
|
||||
Format also has excessive build times and severe code bloat issues (see
|
||||
`Benchmarks`_).
|
||||
|
||||
FastFormat
|
||||
~~~~~~~~~~
|
||||
|
||||
This is an interesting library which is fast, safe and has positional
|
||||
arguments. However it has significant limitations, citing its author:
|
||||
|
||||
Three features that have no hope of being accommodated within the
|
||||
current design are:
|
||||
|
||||
* Leading zeros (or any other non-space padding)
|
||||
* Octal/hexadecimal encoding
|
||||
* Runtime width/alignment specification
|
||||
|
||||
It is also quite big and has a heavy dependency, STLSoft, which might be
|
||||
too restrictive for using it in some projects.
|
||||
|
||||
Boost Spirit.Karma
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This is not really a formatting library but I decided to include it here for
|
||||
completeness. As iostreams, it suffers from the problem of mixing verbatim text
|
||||
with arguments. The library is pretty fast, but slower on integer formatting
|
||||
than ``fmt::format_int`` on Karma's own benchmark,
|
||||
see `Fast integer to string conversion in C++
|
||||
<http://zverovich.net/2013/09/07/integer-to-string-conversion-in-cplusplus.html>`_.
|
||||
|
||||
FAQ
|
||||
---
|
||||
|
||||
Q: how can I capture formatting arguments and format them later?
|
||||
|
||||
A: use ``std::tuple``:
|
||||
|
||||
.. code:: c++
|
||||
|
||||
template <typename... Args>
|
||||
auto capture(const Args&... args) {
|
||||
return std::make_tuple(args...);
|
||||
}
|
||||
|
||||
auto print_message = [](const auto&... args) {
|
||||
fmt::print(args...);
|
||||
};
|
||||
|
||||
// Capture and store arguments:
|
||||
auto args = capture("{} {}", 42, "foo");
|
||||
// Do formatting:
|
||||
std::apply(print_message, args);
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
{fmt} is distributed under the MIT `license
|
||||
<https://github.com/fmtlib/fmt/blob/master/LICENSE.rst>`_.
|
||||
|
||||
The `Format String Syntax
|
||||
<https://fmt.dev/latest/syntax.html>`_
|
||||
section in the documentation is based on the one from Python `string module
|
||||
documentation <https://docs.python.org/3/library/string.html#module-string>`_
|
||||
adapted for the current library. For this reason the documentation is
|
||||
distributed under the Python Software Foundation license available in
|
||||
`doc/python-license.txt
|
||||
<https://raw.github.com/fmtlib/fmt/master/doc/python-license.txt>`_.
|
||||
It only applies if you distribute the documentation of fmt.
|
||||
|
||||
Acknowledgments
|
||||
---------------
|
||||
|
||||
The {fmt} library is maintained by Victor Zverovich (`vitaut
|
||||
<https://github.com/vitaut>`_) and Jonathan Müller (`foonathan
|
||||
<https://github.com/foonathan>`_) with contributions from many other people.
|
||||
See `Contributors <https://github.com/fmtlib/fmt/graphs/contributors>`_ and
|
||||
`Releases <https://github.com/fmtlib/fmt/releases>`_ for some of the names.
|
||||
Let us know if your contribution is not listed or mentioned incorrectly and
|
||||
we'll make it right.
|
||||
|
||||
The benchmark section of this readme file and the performance tests are taken
|
||||
from the excellent `tinyformat <https://github.com/c42f/tinyformat>`_ library
|
||||
written by Chris Foster. Boost Format library is acknowledged transitively
|
||||
since it had some influence on tinyformat.
|
||||
Some ideas used in the implementation are borrowed from `Loki
|
||||
<http://loki-lib.sourceforge.net/>`_ SafeFormat and `Diagnostic API
|
||||
<http://clang.llvm.org/doxygen/classclang_1_1Diagnostic.html>`_ in
|
||||
`Clang <http://clang.llvm.org/>`_.
|
||||
Format string syntax and the documentation are based on Python's `str.format
|
||||
<https://docs.python.org/3/library/stdtypes.html#str.format>`_.
|
||||
Thanks `Doug Turnbull <https://github.com/softwaredoug>`_ for his valuable
|
||||
comments and contribution to the design of the type-safe API and
|
||||
`Gregory Czajkowski <https://github.com/gcflymoto>`_ for implementing binary
|
||||
formatting. Thanks `Ruslan Baratov <https://github.com/ruslo>`_ for comprehensive
|
||||
`comparison of integer formatting algorithms <https://github.com/ruslo/int-dec-format-tests>`_
|
||||
and useful comments regarding performance, `Boris Kaul <https://github.com/localvoid>`_ for
|
||||
`C++ counting digits benchmark <https://github.com/localvoid/cxx-benchmark-count-digits>`_.
|
||||
Thanks to `CarterLi <https://github.com/CarterLi>`_ for contributing various
|
||||
improvements to the code.
|
File diff suppressed because it is too large
Load Diff
|
@ -1,570 +0,0 @@
|
|||
// Formatting library for C++ - color support
|
||||
//
|
||||
// Copyright (c) 2018 - present, Victor Zverovich and fmt contributors
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_COLOR_H_
|
||||
#define FMT_COLOR_H_
|
||||
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
enum class color : uint32_t {
|
||||
alice_blue = 0xF0F8FF, // rgb(240,248,255)
|
||||
antique_white = 0xFAEBD7, // rgb(250,235,215)
|
||||
aqua = 0x00FFFF, // rgb(0,255,255)
|
||||
aquamarine = 0x7FFFD4, // rgb(127,255,212)
|
||||
azure = 0xF0FFFF, // rgb(240,255,255)
|
||||
beige = 0xF5F5DC, // rgb(245,245,220)
|
||||
bisque = 0xFFE4C4, // rgb(255,228,196)
|
||||
black = 0x000000, // rgb(0,0,0)
|
||||
blanched_almond = 0xFFEBCD, // rgb(255,235,205)
|
||||
blue = 0x0000FF, // rgb(0,0,255)
|
||||
blue_violet = 0x8A2BE2, // rgb(138,43,226)
|
||||
brown = 0xA52A2A, // rgb(165,42,42)
|
||||
burly_wood = 0xDEB887, // rgb(222,184,135)
|
||||
cadet_blue = 0x5F9EA0, // rgb(95,158,160)
|
||||
chartreuse = 0x7FFF00, // rgb(127,255,0)
|
||||
chocolate = 0xD2691E, // rgb(210,105,30)
|
||||
coral = 0xFF7F50, // rgb(255,127,80)
|
||||
cornflower_blue = 0x6495ED, // rgb(100,149,237)
|
||||
cornsilk = 0xFFF8DC, // rgb(255,248,220)
|
||||
crimson = 0xDC143C, // rgb(220,20,60)
|
||||
cyan = 0x00FFFF, // rgb(0,255,255)
|
||||
dark_blue = 0x00008B, // rgb(0,0,139)
|
||||
dark_cyan = 0x008B8B, // rgb(0,139,139)
|
||||
dark_golden_rod = 0xB8860B, // rgb(184,134,11)
|
||||
dark_gray = 0xA9A9A9, // rgb(169,169,169)
|
||||
dark_green = 0x006400, // rgb(0,100,0)
|
||||
dark_khaki = 0xBDB76B, // rgb(189,183,107)
|
||||
dark_magenta = 0x8B008B, // rgb(139,0,139)
|
||||
dark_olive_green = 0x556B2F, // rgb(85,107,47)
|
||||
dark_orange = 0xFF8C00, // rgb(255,140,0)
|
||||
dark_orchid = 0x9932CC, // rgb(153,50,204)
|
||||
dark_red = 0x8B0000, // rgb(139,0,0)
|
||||
dark_salmon = 0xE9967A, // rgb(233,150,122)
|
||||
dark_sea_green = 0x8FBC8F, // rgb(143,188,143)
|
||||
dark_slate_blue = 0x483D8B, // rgb(72,61,139)
|
||||
dark_slate_gray = 0x2F4F4F, // rgb(47,79,79)
|
||||
dark_turquoise = 0x00CED1, // rgb(0,206,209)
|
||||
dark_violet = 0x9400D3, // rgb(148,0,211)
|
||||
deep_pink = 0xFF1493, // rgb(255,20,147)
|
||||
deep_sky_blue = 0x00BFFF, // rgb(0,191,255)
|
||||
dim_gray = 0x696969, // rgb(105,105,105)
|
||||
dodger_blue = 0x1E90FF, // rgb(30,144,255)
|
||||
fire_brick = 0xB22222, // rgb(178,34,34)
|
||||
floral_white = 0xFFFAF0, // rgb(255,250,240)
|
||||
forest_green = 0x228B22, // rgb(34,139,34)
|
||||
fuchsia = 0xFF00FF, // rgb(255,0,255)
|
||||
gainsboro = 0xDCDCDC, // rgb(220,220,220)
|
||||
ghost_white = 0xF8F8FF, // rgb(248,248,255)
|
||||
gold = 0xFFD700, // rgb(255,215,0)
|
||||
golden_rod = 0xDAA520, // rgb(218,165,32)
|
||||
gray = 0x808080, // rgb(128,128,128)
|
||||
green = 0x008000, // rgb(0,128,0)
|
||||
green_yellow = 0xADFF2F, // rgb(173,255,47)
|
||||
honey_dew = 0xF0FFF0, // rgb(240,255,240)
|
||||
hot_pink = 0xFF69B4, // rgb(255,105,180)
|
||||
indian_red = 0xCD5C5C, // rgb(205,92,92)
|
||||
indigo = 0x4B0082, // rgb(75,0,130)
|
||||
ivory = 0xFFFFF0, // rgb(255,255,240)
|
||||
khaki = 0xF0E68C, // rgb(240,230,140)
|
||||
lavender = 0xE6E6FA, // rgb(230,230,250)
|
||||
lavender_blush = 0xFFF0F5, // rgb(255,240,245)
|
||||
lawn_green = 0x7CFC00, // rgb(124,252,0)
|
||||
lemon_chiffon = 0xFFFACD, // rgb(255,250,205)
|
||||
light_blue = 0xADD8E6, // rgb(173,216,230)
|
||||
light_coral = 0xF08080, // rgb(240,128,128)
|
||||
light_cyan = 0xE0FFFF, // rgb(224,255,255)
|
||||
light_golden_rod_yellow = 0xFAFAD2, // rgb(250,250,210)
|
||||
light_gray = 0xD3D3D3, // rgb(211,211,211)
|
||||
light_green = 0x90EE90, // rgb(144,238,144)
|
||||
light_pink = 0xFFB6C1, // rgb(255,182,193)
|
||||
light_salmon = 0xFFA07A, // rgb(255,160,122)
|
||||
light_sea_green = 0x20B2AA, // rgb(32,178,170)
|
||||
light_sky_blue = 0x87CEFA, // rgb(135,206,250)
|
||||
light_slate_gray = 0x778899, // rgb(119,136,153)
|
||||
light_steel_blue = 0xB0C4DE, // rgb(176,196,222)
|
||||
light_yellow = 0xFFFFE0, // rgb(255,255,224)
|
||||
lime = 0x00FF00, // rgb(0,255,0)
|
||||
lime_green = 0x32CD32, // rgb(50,205,50)
|
||||
linen = 0xFAF0E6, // rgb(250,240,230)
|
||||
magenta = 0xFF00FF, // rgb(255,0,255)
|
||||
maroon = 0x800000, // rgb(128,0,0)
|
||||
medium_aquamarine = 0x66CDAA, // rgb(102,205,170)
|
||||
medium_blue = 0x0000CD, // rgb(0,0,205)
|
||||
medium_orchid = 0xBA55D3, // rgb(186,85,211)
|
||||
medium_purple = 0x9370DB, // rgb(147,112,219)
|
||||
medium_sea_green = 0x3CB371, // rgb(60,179,113)
|
||||
medium_slate_blue = 0x7B68EE, // rgb(123,104,238)
|
||||
medium_spring_green = 0x00FA9A, // rgb(0,250,154)
|
||||
medium_turquoise = 0x48D1CC, // rgb(72,209,204)
|
||||
medium_violet_red = 0xC71585, // rgb(199,21,133)
|
||||
midnight_blue = 0x191970, // rgb(25,25,112)
|
||||
mint_cream = 0xF5FFFA, // rgb(245,255,250)
|
||||
misty_rose = 0xFFE4E1, // rgb(255,228,225)
|
||||
moccasin = 0xFFE4B5, // rgb(255,228,181)
|
||||
navajo_white = 0xFFDEAD, // rgb(255,222,173)
|
||||
navy = 0x000080, // rgb(0,0,128)
|
||||
old_lace = 0xFDF5E6, // rgb(253,245,230)
|
||||
olive = 0x808000, // rgb(128,128,0)
|
||||
olive_drab = 0x6B8E23, // rgb(107,142,35)
|
||||
orange = 0xFFA500, // rgb(255,165,0)
|
||||
orange_red = 0xFF4500, // rgb(255,69,0)
|
||||
orchid = 0xDA70D6, // rgb(218,112,214)
|
||||
pale_golden_rod = 0xEEE8AA, // rgb(238,232,170)
|
||||
pale_green = 0x98FB98, // rgb(152,251,152)
|
||||
pale_turquoise = 0xAFEEEE, // rgb(175,238,238)
|
||||
pale_violet_red = 0xDB7093, // rgb(219,112,147)
|
||||
papaya_whip = 0xFFEFD5, // rgb(255,239,213)
|
||||
peach_puff = 0xFFDAB9, // rgb(255,218,185)
|
||||
peru = 0xCD853F, // rgb(205,133,63)
|
||||
pink = 0xFFC0CB, // rgb(255,192,203)
|
||||
plum = 0xDDA0DD, // rgb(221,160,221)
|
||||
powder_blue = 0xB0E0E6, // rgb(176,224,230)
|
||||
purple = 0x800080, // rgb(128,0,128)
|
||||
rebecca_purple = 0x663399, // rgb(102,51,153)
|
||||
red = 0xFF0000, // rgb(255,0,0)
|
||||
rosy_brown = 0xBC8F8F, // rgb(188,143,143)
|
||||
royal_blue = 0x4169E1, // rgb(65,105,225)
|
||||
saddle_brown = 0x8B4513, // rgb(139,69,19)
|
||||
salmon = 0xFA8072, // rgb(250,128,114)
|
||||
sandy_brown = 0xF4A460, // rgb(244,164,96)
|
||||
sea_green = 0x2E8B57, // rgb(46,139,87)
|
||||
sea_shell = 0xFFF5EE, // rgb(255,245,238)
|
||||
sienna = 0xA0522D, // rgb(160,82,45)
|
||||
silver = 0xC0C0C0, // rgb(192,192,192)
|
||||
sky_blue = 0x87CEEB, // rgb(135,206,235)
|
||||
slate_blue = 0x6A5ACD, // rgb(106,90,205)
|
||||
slate_gray = 0x708090, // rgb(112,128,144)
|
||||
snow = 0xFFFAFA, // rgb(255,250,250)
|
||||
spring_green = 0x00FF7F, // rgb(0,255,127)
|
||||
steel_blue = 0x4682B4, // rgb(70,130,180)
|
||||
tan = 0xD2B48C, // rgb(210,180,140)
|
||||
teal = 0x008080, // rgb(0,128,128)
|
||||
thistle = 0xD8BFD8, // rgb(216,191,216)
|
||||
tomato = 0xFF6347, // rgb(255,99,71)
|
||||
turquoise = 0x40E0D0, // rgb(64,224,208)
|
||||
violet = 0xEE82EE, // rgb(238,130,238)
|
||||
wheat = 0xF5DEB3, // rgb(245,222,179)
|
||||
white = 0xFFFFFF, // rgb(255,255,255)
|
||||
white_smoke = 0xF5F5F5, // rgb(245,245,245)
|
||||
yellow = 0xFFFF00, // rgb(255,255,0)
|
||||
yellow_green = 0x9ACD32 // rgb(154,205,50)
|
||||
}; // enum class color
|
||||
|
||||
enum class terminal_color : uint8_t {
|
||||
black = 30,
|
||||
red,
|
||||
green,
|
||||
yellow,
|
||||
blue,
|
||||
magenta,
|
||||
cyan,
|
||||
white,
|
||||
bright_black = 90,
|
||||
bright_red,
|
||||
bright_green,
|
||||
bright_yellow,
|
||||
bright_blue,
|
||||
bright_magenta,
|
||||
bright_cyan,
|
||||
bright_white
|
||||
};
|
||||
|
||||
enum class emphasis : uint8_t {
|
||||
bold = 1,
|
||||
italic = 1 << 1,
|
||||
underline = 1 << 2,
|
||||
strikethrough = 1 << 3
|
||||
};
|
||||
|
||||
// rgb is a struct for red, green and blue colors.
|
||||
// Using the name "rgb" makes some editors show the color in a tooltip.
|
||||
struct rgb {
|
||||
FMT_CONSTEXPR rgb() : r(0), g(0), b(0) {}
|
||||
FMT_CONSTEXPR rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {}
|
||||
FMT_CONSTEXPR rgb(uint32_t hex)
|
||||
: r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {}
|
||||
FMT_CONSTEXPR rgb(color hex)
|
||||
: r((uint32_t(hex) >> 16) & 0xFF),
|
||||
g((uint32_t(hex) >> 8) & 0xFF),
|
||||
b(uint32_t(hex) & 0xFF) {}
|
||||
uint8_t r;
|
||||
uint8_t g;
|
||||
uint8_t b;
|
||||
};
|
||||
|
||||
namespace internal {
|
||||
|
||||
// color is a struct of either a rgb color or a terminal color.
|
||||
struct color_type {
|
||||
FMT_CONSTEXPR color_type() FMT_NOEXCEPT : is_rgb(), value{} {}
|
||||
FMT_CONSTEXPR color_type(color rgb_color) FMT_NOEXCEPT : is_rgb(true),
|
||||
value{} {
|
||||
value.rgb_color = static_cast<uint32_t>(rgb_color);
|
||||
}
|
||||
FMT_CONSTEXPR color_type(rgb rgb_color) FMT_NOEXCEPT : is_rgb(true), value{} {
|
||||
value.rgb_color = (static_cast<uint32_t>(rgb_color.r) << 16) |
|
||||
(static_cast<uint32_t>(rgb_color.g) << 8) | rgb_color.b;
|
||||
}
|
||||
FMT_CONSTEXPR color_type(terminal_color term_color) FMT_NOEXCEPT : is_rgb(),
|
||||
value{} {
|
||||
value.term_color = static_cast<uint8_t>(term_color);
|
||||
}
|
||||
bool is_rgb;
|
||||
union color_union {
|
||||
uint8_t term_color;
|
||||
uint32_t rgb_color;
|
||||
} value;
|
||||
};
|
||||
} // namespace internal
|
||||
|
||||
// Experimental text formatting support.
|
||||
class text_style {
|
||||
public:
|
||||
FMT_CONSTEXPR text_style(emphasis em = emphasis()) FMT_NOEXCEPT
|
||||
: set_foreground_color(),
|
||||
set_background_color(),
|
||||
ems(em) {}
|
||||
|
||||
FMT_CONSTEXPR text_style& operator|=(const text_style& rhs) {
|
||||
if (!set_foreground_color) {
|
||||
set_foreground_color = rhs.set_foreground_color;
|
||||
foreground_color = rhs.foreground_color;
|
||||
} else if (rhs.set_foreground_color) {
|
||||
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
|
||||
FMT_THROW(format_error("can't OR a terminal color"));
|
||||
foreground_color.value.rgb_color |= rhs.foreground_color.value.rgb_color;
|
||||
}
|
||||
|
||||
if (!set_background_color) {
|
||||
set_background_color = rhs.set_background_color;
|
||||
background_color = rhs.background_color;
|
||||
} else if (rhs.set_background_color) {
|
||||
if (!background_color.is_rgb || !rhs.background_color.is_rgb)
|
||||
FMT_THROW(format_error("can't OR a terminal color"));
|
||||
background_color.value.rgb_color |= rhs.background_color.value.rgb_color;
|
||||
}
|
||||
|
||||
ems = static_cast<emphasis>(static_cast<uint8_t>(ems) |
|
||||
static_cast<uint8_t>(rhs.ems));
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend FMT_CONSTEXPR text_style operator|(text_style lhs,
|
||||
const text_style& rhs) {
|
||||
return lhs |= rhs;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR text_style& operator&=(const text_style& rhs) {
|
||||
if (!set_foreground_color) {
|
||||
set_foreground_color = rhs.set_foreground_color;
|
||||
foreground_color = rhs.foreground_color;
|
||||
} else if (rhs.set_foreground_color) {
|
||||
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
|
||||
FMT_THROW(format_error("can't AND a terminal color"));
|
||||
foreground_color.value.rgb_color &= rhs.foreground_color.value.rgb_color;
|
||||
}
|
||||
|
||||
if (!set_background_color) {
|
||||
set_background_color = rhs.set_background_color;
|
||||
background_color = rhs.background_color;
|
||||
} else if (rhs.set_background_color) {
|
||||
if (!background_color.is_rgb || !rhs.background_color.is_rgb)
|
||||
FMT_THROW(format_error("can't AND a terminal color"));
|
||||
background_color.value.rgb_color &= rhs.background_color.value.rgb_color;
|
||||
}
|
||||
|
||||
ems = static_cast<emphasis>(static_cast<uint8_t>(ems) &
|
||||
static_cast<uint8_t>(rhs.ems));
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend FMT_CONSTEXPR text_style operator&(text_style lhs,
|
||||
const text_style& rhs) {
|
||||
return lhs &= rhs;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR bool has_foreground() const FMT_NOEXCEPT {
|
||||
return set_foreground_color;
|
||||
}
|
||||
FMT_CONSTEXPR bool has_background() const FMT_NOEXCEPT {
|
||||
return set_background_color;
|
||||
}
|
||||
FMT_CONSTEXPR bool has_emphasis() const FMT_NOEXCEPT {
|
||||
return static_cast<uint8_t>(ems) != 0;
|
||||
}
|
||||
FMT_CONSTEXPR internal::color_type get_foreground() const FMT_NOEXCEPT {
|
||||
FMT_ASSERT(has_foreground(), "no foreground specified for this style");
|
||||
return foreground_color;
|
||||
}
|
||||
FMT_CONSTEXPR internal::color_type get_background() const FMT_NOEXCEPT {
|
||||
FMT_ASSERT(has_background(), "no background specified for this style");
|
||||
return background_color;
|
||||
}
|
||||
FMT_CONSTEXPR emphasis get_emphasis() const FMT_NOEXCEPT {
|
||||
FMT_ASSERT(has_emphasis(), "no emphasis specified for this style");
|
||||
return ems;
|
||||
}
|
||||
|
||||
private:
|
||||
FMT_CONSTEXPR text_style(bool is_foreground,
|
||||
internal::color_type text_color) FMT_NOEXCEPT
|
||||
: set_foreground_color(),
|
||||
set_background_color(),
|
||||
ems() {
|
||||
if (is_foreground) {
|
||||
foreground_color = text_color;
|
||||
set_foreground_color = true;
|
||||
} else {
|
||||
background_color = text_color;
|
||||
set_background_color = true;
|
||||
}
|
||||
}
|
||||
|
||||
friend FMT_CONSTEXPR_DECL text_style fg(internal::color_type foreground)
|
||||
FMT_NOEXCEPT;
|
||||
friend FMT_CONSTEXPR_DECL text_style bg(internal::color_type background)
|
||||
FMT_NOEXCEPT;
|
||||
|
||||
internal::color_type foreground_color;
|
||||
internal::color_type background_color;
|
||||
bool set_foreground_color;
|
||||
bool set_background_color;
|
||||
emphasis ems;
|
||||
};
|
||||
|
||||
FMT_CONSTEXPR text_style fg(internal::color_type foreground) FMT_NOEXCEPT {
|
||||
return text_style(/*is_foreground=*/true, foreground);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR text_style bg(internal::color_type background) FMT_NOEXCEPT {
|
||||
return text_style(/*is_foreground=*/false, background);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR text_style operator|(emphasis lhs, emphasis rhs) FMT_NOEXCEPT {
|
||||
return text_style(lhs) | rhs;
|
||||
}
|
||||
|
||||
namespace internal {
|
||||
|
||||
template <typename Char> struct ansi_color_escape {
|
||||
FMT_CONSTEXPR ansi_color_escape(internal::color_type text_color,
|
||||
const char* esc) FMT_NOEXCEPT {
|
||||
// If we have a terminal color, we need to output another escape code
|
||||
// sequence.
|
||||
if (!text_color.is_rgb) {
|
||||
bool is_background = esc == internal::data::background_color;
|
||||
uint32_t value = text_color.value.term_color;
|
||||
// Background ASCII codes are the same as the foreground ones but with
|
||||
// 10 more.
|
||||
if (is_background) value += 10u;
|
||||
|
||||
std::size_t index = 0;
|
||||
buffer[index++] = static_cast<Char>('\x1b');
|
||||
buffer[index++] = static_cast<Char>('[');
|
||||
|
||||
if (value >= 100u) {
|
||||
buffer[index++] = static_cast<Char>('1');
|
||||
value %= 100u;
|
||||
}
|
||||
buffer[index++] = static_cast<Char>('0' + value / 10u);
|
||||
buffer[index++] = static_cast<Char>('0' + value % 10u);
|
||||
|
||||
buffer[index++] = static_cast<Char>('m');
|
||||
buffer[index++] = static_cast<Char>('\0');
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 7; i++) {
|
||||
buffer[i] = static_cast<Char>(esc[i]);
|
||||
}
|
||||
rgb color(text_color.value.rgb_color);
|
||||
to_esc(color.r, buffer + 7, ';');
|
||||
to_esc(color.g, buffer + 11, ';');
|
||||
to_esc(color.b, buffer + 15, 'm');
|
||||
buffer[19] = static_cast<Char>(0);
|
||||
}
|
||||
FMT_CONSTEXPR ansi_color_escape(emphasis em) FMT_NOEXCEPT {
|
||||
uint8_t em_codes[4] = {};
|
||||
uint8_t em_bits = static_cast<uint8_t>(em);
|
||||
if (em_bits & static_cast<uint8_t>(emphasis::bold)) em_codes[0] = 1;
|
||||
if (em_bits & static_cast<uint8_t>(emphasis::italic)) em_codes[1] = 3;
|
||||
if (em_bits & static_cast<uint8_t>(emphasis::underline)) em_codes[2] = 4;
|
||||
if (em_bits & static_cast<uint8_t>(emphasis::strikethrough))
|
||||
em_codes[3] = 9;
|
||||
|
||||
std::size_t index = 0;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
if (!em_codes[i]) continue;
|
||||
buffer[index++] = static_cast<Char>('\x1b');
|
||||
buffer[index++] = static_cast<Char>('[');
|
||||
buffer[index++] = static_cast<Char>('0' + em_codes[i]);
|
||||
buffer[index++] = static_cast<Char>('m');
|
||||
}
|
||||
buffer[index++] = static_cast<Char>(0);
|
||||
}
|
||||
FMT_CONSTEXPR operator const Char*() const FMT_NOEXCEPT { return buffer; }
|
||||
|
||||
FMT_CONSTEXPR const Char* begin() const FMT_NOEXCEPT { return buffer; }
|
||||
FMT_CONSTEXPR const Char* end() const FMT_NOEXCEPT {
|
||||
return buffer + std::strlen(buffer);
|
||||
}
|
||||
|
||||
private:
|
||||
Char buffer[7u + 3u * 4u + 1u];
|
||||
|
||||
static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out,
|
||||
char delimiter) FMT_NOEXCEPT {
|
||||
out[0] = static_cast<Char>('0' + c / 100);
|
||||
out[1] = static_cast<Char>('0' + c / 10 % 10);
|
||||
out[2] = static_cast<Char>('0' + c % 10);
|
||||
out[3] = static_cast<Char>(delimiter);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
FMT_CONSTEXPR ansi_color_escape<Char> make_foreground_color(
|
||||
internal::color_type foreground) FMT_NOEXCEPT {
|
||||
return ansi_color_escape<Char>(foreground, internal::data::foreground_color);
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
FMT_CONSTEXPR ansi_color_escape<Char> make_background_color(
|
||||
internal::color_type background) FMT_NOEXCEPT {
|
||||
return ansi_color_escape<Char>(background, internal::data::background_color);
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
FMT_CONSTEXPR ansi_color_escape<Char> make_emphasis(emphasis em) FMT_NOEXCEPT {
|
||||
return ansi_color_escape<Char>(em);
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
inline void fputs(const Char* chars, FILE* stream) FMT_NOEXCEPT {
|
||||
std::fputs(chars, stream);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void fputs<wchar_t>(const wchar_t* chars, FILE* stream) FMT_NOEXCEPT {
|
||||
std::fputws(chars, stream);
|
||||
}
|
||||
|
||||
template <typename Char> inline void reset_color(FILE* stream) FMT_NOEXCEPT {
|
||||
fputs(internal::data::reset_color, stream);
|
||||
}
|
||||
|
||||
template <> inline void reset_color<wchar_t>(FILE* stream) FMT_NOEXCEPT {
|
||||
fputs(internal::data::wreset_color, stream);
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
inline void reset_color(basic_memory_buffer<Char>& buffer) FMT_NOEXCEPT {
|
||||
const char* begin = data::reset_color;
|
||||
const char* end = begin + sizeof(data::reset_color) - 1;
|
||||
buffer.append(begin, end);
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
void vformat_to(basic_memory_buffer<Char>& buf, const text_style& ts,
|
||||
basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<Char>> args) {
|
||||
bool has_style = false;
|
||||
if (ts.has_emphasis()) {
|
||||
has_style = true;
|
||||
auto emphasis = internal::make_emphasis<Char>(ts.get_emphasis());
|
||||
buf.append(emphasis.begin(), emphasis.end());
|
||||
}
|
||||
if (ts.has_foreground()) {
|
||||
has_style = true;
|
||||
auto foreground =
|
||||
internal::make_foreground_color<Char>(ts.get_foreground());
|
||||
buf.append(foreground.begin(), foreground.end());
|
||||
}
|
||||
if (ts.has_background()) {
|
||||
has_style = true;
|
||||
auto background =
|
||||
internal::make_background_color<Char>(ts.get_background());
|
||||
buf.append(background.begin(), background.end());
|
||||
}
|
||||
vformat_to(buf, format_str, args);
|
||||
if (has_style) {
|
||||
internal::reset_color<Char>(buf);
|
||||
}
|
||||
}
|
||||
} // namespace internal
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
void vprint(std::FILE* f, const text_style& ts, const S& format,
|
||||
basic_format_args<buffer_context<Char>> args) {
|
||||
basic_memory_buffer<Char> buf;
|
||||
internal::vformat_to(buf, ts, to_string_view(format), args);
|
||||
buf.push_back(Char(0));
|
||||
internal::fputs(buf.data(), f);
|
||||
}
|
||||
|
||||
/**
|
||||
Formats a string and prints it to the specified file stream using ANSI
|
||||
escape sequences to specify text formatting.
|
||||
Example:
|
||||
fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
|
||||
"Elapsed time: {0:.2f} seconds", 1.23);
|
||||
*/
|
||||
template <typename S, typename... Args,
|
||||
FMT_ENABLE_IF(internal::is_string<S>::value)>
|
||||
void print(std::FILE* f, const text_style& ts, const S& format_str,
|
||||
const Args&... args) {
|
||||
internal::check_format_string<Args...>(format_str);
|
||||
using context = buffer_context<char_t<S>>;
|
||||
format_arg_store<context, Args...> as{args...};
|
||||
vprint(f, ts, format_str, basic_format_args<context>(as));
|
||||
}
|
||||
|
||||
/**
|
||||
Formats a string and prints it to stdout using ANSI escape sequences to
|
||||
specify text formatting.
|
||||
Example:
|
||||
fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
|
||||
"Elapsed time: {0:.2f} seconds", 1.23);
|
||||
*/
|
||||
template <typename S, typename... Args,
|
||||
FMT_ENABLE_IF(internal::is_string<S>::value)>
|
||||
void print(const text_style& ts, const S& format_str, const Args&... args) {
|
||||
return print(stdout, ts, format_str, args...);
|
||||
}
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
inline std::basic_string<Char> vformat(
|
||||
const text_style& ts, const S& format_str,
|
||||
basic_format_args<buffer_context<Char>> args) {
|
||||
basic_memory_buffer<Char> buf;
|
||||
internal::vformat_to(buf, ts, to_string_view(format_str), args);
|
||||
return fmt::to_string(buf);
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Formats arguments and returns the result as a string using ANSI
|
||||
escape sequences to specify text formatting.
|
||||
|
||||
**Example**::
|
||||
|
||||
#include <fmt/color.h>
|
||||
std::string message = fmt::format(fmt::emphasis::bold | fg(fmt::color::red),
|
||||
"The answer is {}", 42);
|
||||
\endrst
|
||||
*/
|
||||
template <typename S, typename... Args, typename Char = char_t<S>>
|
||||
inline std::basic_string<Char> format(const text_style& ts, const S& format_str,
|
||||
const Args&... args) {
|
||||
return vformat(ts, to_string_view(format_str),
|
||||
{internal::make_args_checked<Args...>(format_str, args...)});
|
||||
}
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_COLOR_H_
|
|
@ -1,585 +0,0 @@
|
|||
// Formatting library for C++ - experimental format string compilation
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich and fmt contributors
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_COMPILE_H_
|
||||
#define FMT_COMPILE_H_
|
||||
|
||||
#include <vector>
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace internal {
|
||||
|
||||
// Part of a compiled format string. It can be either literal text or a
|
||||
// replacement field.
|
||||
template <typename Char> struct format_part {
|
||||
enum class kind { arg_index, arg_name, text, replacement };
|
||||
|
||||
struct replacement {
|
||||
arg_ref<Char> arg_id;
|
||||
dynamic_format_specs<Char> specs;
|
||||
};
|
||||
|
||||
kind part_kind;
|
||||
union value {
|
||||
int arg_index;
|
||||
basic_string_view<Char> str;
|
||||
replacement repl;
|
||||
|
||||
FMT_CONSTEXPR value(int index = 0) : arg_index(index) {}
|
||||
FMT_CONSTEXPR value(basic_string_view<Char> s) : str(s) {}
|
||||
FMT_CONSTEXPR value(replacement r) : repl(r) {}
|
||||
} val;
|
||||
// Position past the end of the argument id.
|
||||
const Char* arg_id_end = nullptr;
|
||||
|
||||
FMT_CONSTEXPR format_part(kind k = kind::arg_index, value v = {})
|
||||
: part_kind(k), val(v) {}
|
||||
|
||||
static FMT_CONSTEXPR format_part make_arg_index(int index) {
|
||||
return format_part(kind::arg_index, index);
|
||||
}
|
||||
static FMT_CONSTEXPR format_part make_arg_name(basic_string_view<Char> name) {
|
||||
return format_part(kind::arg_name, name);
|
||||
}
|
||||
static FMT_CONSTEXPR format_part make_text(basic_string_view<Char> text) {
|
||||
return format_part(kind::text, text);
|
||||
}
|
||||
static FMT_CONSTEXPR format_part make_replacement(replacement repl) {
|
||||
return format_part(kind::replacement, repl);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char> struct part_counter {
|
||||
unsigned num_parts = 0;
|
||||
|
||||
FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) {
|
||||
if (begin != end) ++num_parts;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void on_arg_id() { ++num_parts; }
|
||||
FMT_CONSTEXPR void on_arg_id(int) { ++num_parts; }
|
||||
FMT_CONSTEXPR void on_arg_id(basic_string_view<Char>) { ++num_parts; }
|
||||
|
||||
FMT_CONSTEXPR void on_replacement_field(const Char*) {}
|
||||
|
||||
FMT_CONSTEXPR const Char* on_format_specs(const Char* begin,
|
||||
const Char* end) {
|
||||
// Find the matching brace.
|
||||
unsigned brace_counter = 0;
|
||||
for (; begin != end; ++begin) {
|
||||
if (*begin == '{') {
|
||||
++brace_counter;
|
||||
} else if (*begin == '}') {
|
||||
if (brace_counter == 0u) break;
|
||||
--brace_counter;
|
||||
}
|
||||
}
|
||||
return begin;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void on_error(const char*) {}
|
||||
};
|
||||
|
||||
// Counts the number of parts in a format string.
|
||||
template <typename Char>
|
||||
FMT_CONSTEXPR unsigned count_parts(basic_string_view<Char> format_str) {
|
||||
part_counter<Char> counter;
|
||||
parse_format_string<true>(format_str, counter);
|
||||
return counter.num_parts;
|
||||
}
|
||||
|
||||
template <typename Char, typename PartHandler>
|
||||
class format_string_compiler : public error_handler {
|
||||
private:
|
||||
using part = format_part<Char>;
|
||||
|
||||
PartHandler handler_;
|
||||
part part_;
|
||||
basic_string_view<Char> format_str_;
|
||||
basic_format_parse_context<Char> parse_context_;
|
||||
|
||||
public:
|
||||
FMT_CONSTEXPR format_string_compiler(basic_string_view<Char> format_str,
|
||||
PartHandler handler)
|
||||
: handler_(handler),
|
||||
format_str_(format_str),
|
||||
parse_context_(format_str) {}
|
||||
|
||||
FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) {
|
||||
if (begin != end)
|
||||
handler_(part::make_text({begin, to_unsigned(end - begin)}));
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void on_arg_id() {
|
||||
part_ = part::make_arg_index(parse_context_.next_arg_id());
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void on_arg_id(int id) {
|
||||
parse_context_.check_arg_id(id);
|
||||
part_ = part::make_arg_index(id);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void on_arg_id(basic_string_view<Char> id) {
|
||||
part_ = part::make_arg_name(id);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void on_replacement_field(const Char* ptr) {
|
||||
part_.arg_id_end = ptr;
|
||||
handler_(part_);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR const Char* on_format_specs(const Char* begin,
|
||||
const Char* end) {
|
||||
auto repl = typename part::replacement();
|
||||
dynamic_specs_handler<basic_format_parse_context<Char>> handler(
|
||||
repl.specs, parse_context_);
|
||||
auto it = parse_format_specs(begin, end, handler);
|
||||
if (*it != '}') on_error("missing '}' in format string");
|
||||
repl.arg_id = part_.part_kind == part::kind::arg_index
|
||||
? arg_ref<Char>(part_.val.arg_index)
|
||||
: arg_ref<Char>(part_.val.str);
|
||||
auto part = part::make_replacement(repl);
|
||||
part.arg_id_end = begin;
|
||||
handler_(part);
|
||||
return it;
|
||||
}
|
||||
};
|
||||
|
||||
// Compiles a format string and invokes handler(part) for each parsed part.
|
||||
template <bool IS_CONSTEXPR, typename Char, typename PartHandler>
|
||||
FMT_CONSTEXPR void compile_format_string(basic_string_view<Char> format_str,
|
||||
PartHandler handler) {
|
||||
parse_format_string<IS_CONSTEXPR>(
|
||||
format_str,
|
||||
format_string_compiler<Char, PartHandler>(format_str, handler));
|
||||
}
|
||||
|
||||
template <typename Range, typename Context, typename Id>
|
||||
void format_arg(
|
||||
basic_format_parse_context<typename Range::value_type>& parse_ctx,
|
||||
Context& ctx, Id arg_id) {
|
||||
ctx.advance_to(
|
||||
visit_format_arg(arg_formatter<Range>(ctx, &parse_ctx), ctx.arg(arg_id)));
|
||||
}
|
||||
|
||||
// vformat_to is defined in a subnamespace to prevent ADL.
|
||||
namespace cf {
|
||||
template <typename Context, typename Range, typename CompiledFormat>
|
||||
auto vformat_to(Range out, CompiledFormat& cf, basic_format_args<Context> args)
|
||||
-> typename Context::iterator {
|
||||
using char_type = typename Context::char_type;
|
||||
basic_format_parse_context<char_type> parse_ctx(
|
||||
to_string_view(cf.format_str_));
|
||||
Context ctx(out.begin(), args);
|
||||
|
||||
const auto& parts = cf.parts();
|
||||
for (auto part_it = std::begin(parts); part_it != std::end(parts);
|
||||
++part_it) {
|
||||
const auto& part = *part_it;
|
||||
const auto& value = part.val;
|
||||
|
||||
using format_part_t = format_part<char_type>;
|
||||
switch (part.part_kind) {
|
||||
case format_part_t::kind::text: {
|
||||
const auto text = value.str;
|
||||
auto output = ctx.out();
|
||||
auto&& it = reserve(output, text.size());
|
||||
it = std::copy_n(text.begin(), text.size(), it);
|
||||
ctx.advance_to(output);
|
||||
break;
|
||||
}
|
||||
|
||||
case format_part_t::kind::arg_index:
|
||||
advance_to(parse_ctx, part.arg_id_end);
|
||||
internal::format_arg<Range>(parse_ctx, ctx, value.arg_index);
|
||||
break;
|
||||
|
||||
case format_part_t::kind::arg_name:
|
||||
advance_to(parse_ctx, part.arg_id_end);
|
||||
internal::format_arg<Range>(parse_ctx, ctx, value.str);
|
||||
break;
|
||||
|
||||
case format_part_t::kind::replacement: {
|
||||
const auto& arg_id_value = value.repl.arg_id.val;
|
||||
const auto arg = value.repl.arg_id.kind == arg_id_kind::index
|
||||
? ctx.arg(arg_id_value.index)
|
||||
: ctx.arg(arg_id_value.name);
|
||||
|
||||
auto specs = value.repl.specs;
|
||||
|
||||
handle_dynamic_spec<width_checker>(specs.width, specs.width_ref, ctx);
|
||||
handle_dynamic_spec<precision_checker>(specs.precision,
|
||||
specs.precision_ref, ctx);
|
||||
|
||||
error_handler h;
|
||||
numeric_specs_checker<error_handler> checker(h, arg.type());
|
||||
if (specs.align == align::numeric) checker.require_numeric_argument();
|
||||
if (specs.sign != sign::none) checker.check_sign();
|
||||
if (specs.alt) checker.require_numeric_argument();
|
||||
if (specs.precision >= 0) checker.check_precision();
|
||||
|
||||
advance_to(parse_ctx, part.arg_id_end);
|
||||
ctx.advance_to(
|
||||
visit_format_arg(arg_formatter<Range>(ctx, nullptr, &specs), arg));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ctx.out();
|
||||
}
|
||||
} // namespace cf
|
||||
|
||||
struct basic_compiled_format {};
|
||||
|
||||
template <typename S, typename = void>
|
||||
struct compiled_format_base : basic_compiled_format {
|
||||
using char_type = char_t<S>;
|
||||
using parts_container = std::vector<internal::format_part<char_type>>;
|
||||
|
||||
parts_container compiled_parts;
|
||||
|
||||
explicit compiled_format_base(basic_string_view<char_type> format_str) {
|
||||
compile_format_string<false>(format_str,
|
||||
[this](const format_part<char_type>& part) {
|
||||
compiled_parts.push_back(part);
|
||||
});
|
||||
}
|
||||
|
||||
const parts_container& parts() const { return compiled_parts; }
|
||||
};
|
||||
|
||||
template <typename Char, unsigned N> struct format_part_array {
|
||||
format_part<Char> data[N] = {};
|
||||
FMT_CONSTEXPR format_part_array() = default;
|
||||
};
|
||||
|
||||
template <typename Char, unsigned N>
|
||||
FMT_CONSTEXPR format_part_array<Char, N> compile_to_parts(
|
||||
basic_string_view<Char> format_str) {
|
||||
format_part_array<Char, N> parts;
|
||||
unsigned counter = 0;
|
||||
// This is not a lambda for compatibility with older compilers.
|
||||
struct {
|
||||
format_part<Char>* parts;
|
||||
unsigned* counter;
|
||||
FMT_CONSTEXPR void operator()(const format_part<Char>& part) {
|
||||
parts[(*counter)++] = part;
|
||||
}
|
||||
} collector{parts.data, &counter};
|
||||
compile_format_string<true>(format_str, collector);
|
||||
if (counter < N) {
|
||||
parts.data[counter] =
|
||||
format_part<Char>::make_text(basic_string_view<Char>());
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
template <typename T> constexpr const T& constexpr_max(const T& a, const T& b) {
|
||||
return (a < b) ? b : a;
|
||||
}
|
||||
|
||||
template <typename S>
|
||||
struct compiled_format_base<S, enable_if_t<is_compile_string<S>::value>>
|
||||
: basic_compiled_format {
|
||||
using char_type = char_t<S>;
|
||||
|
||||
FMT_CONSTEXPR explicit compiled_format_base(basic_string_view<char_type>) {}
|
||||
|
||||
// Workaround for old compilers. Format string compilation will not be
|
||||
// performed there anyway.
|
||||
#if FMT_USE_CONSTEXPR
|
||||
static FMT_CONSTEXPR_DECL const unsigned num_format_parts =
|
||||
constexpr_max(count_parts(to_string_view(S())), 1u);
|
||||
#else
|
||||
static const unsigned num_format_parts = 1;
|
||||
#endif
|
||||
|
||||
using parts_container = format_part<char_type>[num_format_parts];
|
||||
|
||||
const parts_container& parts() const {
|
||||
static FMT_CONSTEXPR_DECL const auto compiled_parts =
|
||||
compile_to_parts<char_type, num_format_parts>(
|
||||
internal::to_string_view(S()));
|
||||
return compiled_parts.data;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename S, typename... Args>
|
||||
class compiled_format : private compiled_format_base<S> {
|
||||
public:
|
||||
using typename compiled_format_base<S>::char_type;
|
||||
|
||||
private:
|
||||
basic_string_view<char_type> format_str_;
|
||||
|
||||
template <typename Context, typename Range, typename CompiledFormat>
|
||||
friend auto cf::vformat_to(Range out, CompiledFormat& cf,
|
||||
basic_format_args<Context> args) ->
|
||||
typename Context::iterator;
|
||||
|
||||
public:
|
||||
compiled_format() = delete;
|
||||
explicit constexpr compiled_format(basic_string_view<char_type> format_str)
|
||||
: compiled_format_base<S>(format_str), format_str_(format_str) {}
|
||||
};
|
||||
|
||||
#ifdef __cpp_if_constexpr
|
||||
template <typename... Args> struct type_list {};
|
||||
|
||||
// Returns a reference to the argument at index N from [first, rest...].
|
||||
template <int N, typename T, typename... Args>
|
||||
constexpr const auto& get(const T& first, const Args&... rest) {
|
||||
static_assert(N < 1 + sizeof...(Args), "index is out of bounds");
|
||||
if constexpr (N == 0)
|
||||
return first;
|
||||
else
|
||||
return get<N - 1>(rest...);
|
||||
}
|
||||
|
||||
template <int N, typename> struct get_type_impl;
|
||||
|
||||
template <int N, typename... Args> struct get_type_impl<N, type_list<Args...>> {
|
||||
using type = remove_cvref_t<decltype(get<N>(std::declval<Args>()...))>;
|
||||
};
|
||||
|
||||
template <int N, typename T>
|
||||
using get_type = typename get_type_impl<N, T>::type;
|
||||
|
||||
template <typename Char> struct text {
|
||||
basic_string_view<Char> data;
|
||||
using char_type = Char;
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
OutputIt format(OutputIt out, const Args&...) const {
|
||||
// TODO: reserve
|
||||
return copy_str<Char>(data.begin(), data.end(), out);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
constexpr text<Char> make_text(basic_string_view<Char> s, size_t pos,
|
||||
size_t size) {
|
||||
return {{&s[pos], size}};
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt, typename T,
|
||||
std::enable_if_t<std::is_integral_v<T>, int> = 0>
|
||||
OutputIt format_default(OutputIt out, T value) {
|
||||
// TODO: reserve
|
||||
format_int fi(value);
|
||||
return std::copy(fi.data(), fi.data() + fi.size(), out);
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt>
|
||||
OutputIt format_default(OutputIt out, double value) {
|
||||
writer w(out);
|
||||
w.write(value);
|
||||
return w.out();
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt>
|
||||
OutputIt format_default(OutputIt out, Char value) {
|
||||
*out++ = value;
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt>
|
||||
OutputIt format_default(OutputIt out, const Char* value) {
|
||||
auto length = std::char_traits<Char>::length(value);
|
||||
return copy_str<Char>(value, value + length, out);
|
||||
}
|
||||
|
||||
// A replacement field that refers to argument N.
|
||||
template <typename Char, typename T, int N> struct field {
|
||||
using char_type = Char;
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
OutputIt format(OutputIt out, const Args&... args) const {
|
||||
// This ensures that the argument type is convertile to `const T&`.
|
||||
const T& arg = get<N>(args...);
|
||||
return format_default<Char>(out, arg);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename L, typename R> struct concat {
|
||||
L lhs;
|
||||
R rhs;
|
||||
using char_type = typename L::char_type;
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
OutputIt format(OutputIt out, const Args&... args) const {
|
||||
out = lhs.format(out, args...);
|
||||
return rhs.format(out, args...);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename L, typename R>
|
||||
constexpr concat<L, R> make_concat(L lhs, R rhs) {
|
||||
return {lhs, rhs};
|
||||
}
|
||||
|
||||
struct unknown_format {};
|
||||
|
||||
template <typename Char>
|
||||
constexpr size_t parse_text(basic_string_view<Char> str, size_t pos) {
|
||||
for (size_t size = str.size(); pos != size; ++pos) {
|
||||
if (str[pos] == '{' || str[pos] == '}') break;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
template <typename Args, size_t POS, int ID, typename S>
|
||||
constexpr auto compile_format_string(S format_str);
|
||||
|
||||
template <typename Args, size_t POS, int ID, typename T, typename S>
|
||||
constexpr auto parse_tail(T head, S format_str) {
|
||||
if constexpr (POS != to_string_view(format_str).size()) {
|
||||
constexpr auto tail = compile_format_string<Args, POS, ID>(format_str);
|
||||
if constexpr (std::is_same<remove_cvref_t<decltype(tail)>,
|
||||
unknown_format>())
|
||||
return tail;
|
||||
else
|
||||
return make_concat(head, tail);
|
||||
} else {
|
||||
return head;
|
||||
}
|
||||
}
|
||||
|
||||
// Compiles a non-empty format string and returns the compiled representation
|
||||
// or unknown_format() on unrecognized input.
|
||||
template <typename Args, size_t POS, int ID, typename S>
|
||||
constexpr auto compile_format_string(S format_str) {
|
||||
using char_type = typename S::char_type;
|
||||
constexpr basic_string_view<char_type> str = format_str;
|
||||
if constexpr (str[POS] == '{') {
|
||||
if (POS + 1 == str.size())
|
||||
throw format_error("unmatched '{' in format string");
|
||||
if constexpr (str[POS + 1] == '{') {
|
||||
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
|
||||
} else if constexpr (str[POS + 1] == '}') {
|
||||
using type = get_type<ID, Args>;
|
||||
if constexpr (std::is_same<type, int>::value) {
|
||||
return parse_tail<Args, POS + 2, ID + 1>(field<char_type, type, ID>(),
|
||||
format_str);
|
||||
} else {
|
||||
return unknown_format();
|
||||
}
|
||||
} else {
|
||||
return unknown_format();
|
||||
}
|
||||
} else if constexpr (str[POS] == '}') {
|
||||
if (POS + 1 == str.size())
|
||||
throw format_error("unmatched '}' in format string");
|
||||
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
|
||||
} else {
|
||||
constexpr auto end = parse_text(str, POS + 1);
|
||||
return parse_tail<Args, end, ID>(make_text(str, POS, end - POS),
|
||||
format_str);
|
||||
}
|
||||
}
|
||||
#endif // __cpp_if_constexpr
|
||||
} // namespace internal
|
||||
|
||||
#if FMT_USE_CONSTEXPR
|
||||
# ifdef __cpp_if_constexpr
|
||||
template <typename... Args, typename S,
|
||||
FMT_ENABLE_IF(is_compile_string<S>::value)>
|
||||
constexpr auto compile(S format_str) {
|
||||
constexpr basic_string_view<typename S::char_type> str = format_str;
|
||||
if constexpr (str.size() == 0) {
|
||||
return internal::make_text(str, 0, 0);
|
||||
} else {
|
||||
constexpr auto result =
|
||||
internal::compile_format_string<internal::type_list<Args...>, 0, 0>(
|
||||
format_str);
|
||||
if constexpr (std::is_same<remove_cvref_t<decltype(result)>,
|
||||
internal::unknown_format>()) {
|
||||
return internal::compiled_format<S, Args...>(to_string_view(format_str));
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename CompiledFormat, typename... Args,
|
||||
typename Char = typename CompiledFormat::char_type,
|
||||
FMT_ENABLE_IF(!std::is_base_of<internal::basic_compiled_format,
|
||||
CompiledFormat>::value)>
|
||||
std::basic_string<Char> format(const CompiledFormat& cf, const Args&... args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
cf.format(std::back_inserter(buffer), args...);
|
||||
return to_string(buffer);
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename CompiledFormat, typename... Args,
|
||||
FMT_ENABLE_IF(!std::is_base_of<internal::basic_compiled_format,
|
||||
CompiledFormat>::value)>
|
||||
OutputIt format_to(OutputIt out, const CompiledFormat& cf,
|
||||
const Args&... args) {
|
||||
return cf.format(out, args...);
|
||||
}
|
||||
# else
|
||||
template <typename... Args, typename S,
|
||||
FMT_ENABLE_IF(is_compile_string<S>::value)>
|
||||
constexpr auto compile(S format_str) -> internal::compiled_format<S, Args...> {
|
||||
return internal::compiled_format<S, Args...>(to_string_view(format_str));
|
||||
}
|
||||
# endif // __cpp_if_constexpr
|
||||
#endif // FMT_USE_CONSTEXPR
|
||||
|
||||
// Compiles the format string which must be a string literal.
|
||||
template <typename... Args, typename Char, size_t N>
|
||||
auto compile(const Char (&format_str)[N])
|
||||
-> internal::compiled_format<const Char*, Args...> {
|
||||
return internal::compiled_format<const Char*, Args...>(
|
||||
basic_string_view<Char>(format_str, N - 1));
|
||||
}
|
||||
|
||||
template <typename CompiledFormat, typename... Args,
|
||||
typename Char = typename CompiledFormat::char_type,
|
||||
FMT_ENABLE_IF(std::is_base_of<internal::basic_compiled_format,
|
||||
CompiledFormat>::value)>
|
||||
std::basic_string<Char> format(const CompiledFormat& cf, const Args&... args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
using range = buffer_range<Char>;
|
||||
using context = buffer_context<Char>;
|
||||
internal::cf::vformat_to<context>(range(buffer), cf,
|
||||
{make_format_args<context>(args...)});
|
||||
return to_string(buffer);
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename CompiledFormat, typename... Args,
|
||||
FMT_ENABLE_IF(std::is_base_of<internal::basic_compiled_format,
|
||||
CompiledFormat>::value)>
|
||||
OutputIt format_to(OutputIt out, const CompiledFormat& cf,
|
||||
const Args&... args) {
|
||||
using char_type = typename CompiledFormat::char_type;
|
||||
using range = internal::output_range<OutputIt, char_type>;
|
||||
using context = format_context_t<OutputIt, char_type>;
|
||||
return internal::cf::vformat_to<context>(
|
||||
range(out), cf, {make_format_args<context>(args...)});
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename CompiledFormat, typename... Args,
|
||||
FMT_ENABLE_IF(internal::is_output_iterator<OutputIt>::value)>
|
||||
format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n,
|
||||
const CompiledFormat& cf,
|
||||
const Args&... args) {
|
||||
auto it =
|
||||
format_to(internal::truncating_iterator<OutputIt>(out, n), cf, args...);
|
||||
return {it.base(), it.count()};
|
||||
}
|
||||
|
||||
template <typename CompiledFormat, typename... Args>
|
||||
std::size_t formatted_size(const CompiledFormat& cf, const Args&... args) {
|
||||
return format_to(internal::counting_iterator(), cf, args...).count();
|
||||
}
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_COMPILE_H_
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,77 +0,0 @@
|
|||
// Formatting library for C++ - std::locale support
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_LOCALE_H_
|
||||
#define FMT_LOCALE_H_
|
||||
|
||||
#include <locale>
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
namespace internal {
|
||||
template <typename Char>
|
||||
typename buffer_context<Char>::iterator vformat_to(
|
||||
const std::locale& loc, buffer<Char>& buf,
|
||||
basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<Char>> args) {
|
||||
using range = buffer_range<Char>;
|
||||
return vformat_to<arg_formatter<range>>(buf, to_string_view(format_str), args,
|
||||
internal::locale_ref(loc));
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
std::basic_string<Char> vformat(const std::locale& loc,
|
||||
basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<Char>> args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
internal::vformat_to(loc, buffer, format_str, args);
|
||||
return fmt::to_string(buffer);
|
||||
}
|
||||
} // namespace internal
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
inline std::basic_string<Char> vformat(
|
||||
const std::locale& loc, const S& format_str,
|
||||
basic_format_args<buffer_context<Char>> args) {
|
||||
return internal::vformat(loc, to_string_view(format_str), args);
|
||||
}
|
||||
|
||||
template <typename S, typename... Args, typename Char = char_t<S>>
|
||||
inline std::basic_string<Char> format(const std::locale& loc,
|
||||
const S& format_str, Args&&... args) {
|
||||
return internal::vformat(
|
||||
loc, to_string_view(format_str),
|
||||
{internal::make_args_checked<Args...>(format_str, args...)});
|
||||
}
|
||||
|
||||
template <typename S, typename OutputIt, typename... Args,
|
||||
typename Char = enable_if_t<
|
||||
internal::is_output_iterator<OutputIt>::value, char_t<S>>>
|
||||
inline OutputIt vformat_to(OutputIt out, const std::locale& loc,
|
||||
const S& format_str,
|
||||
format_args_t<OutputIt, Char> args) {
|
||||
using range = internal::output_range<OutputIt, Char>;
|
||||
return vformat_to<arg_formatter<range>>(
|
||||
range(out), to_string_view(format_str), args, internal::locale_ref(loc));
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename S, typename... Args,
|
||||
FMT_ENABLE_IF(internal::is_output_iterator<OutputIt>::value&&
|
||||
internal::is_string<S>::value)>
|
||||
inline OutputIt format_to(OutputIt out, const std::locale& loc,
|
||||
const S& format_str, Args&&... args) {
|
||||
internal::check_format_string<Args...>(format_str);
|
||||
using context = format_context_t<OutputIt, char_t<S>>;
|
||||
format_arg_store<context, Args...> as{args...};
|
||||
return vformat_to(out, loc, to_string_view(format_str),
|
||||
basic_format_args<context>(as));
|
||||
}
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_LOCALE_H_
|
|
@ -1,141 +0,0 @@
|
|||
// Formatting library for C++ - std::ostream support
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_OSTREAM_H_
|
||||
#define FMT_OSTREAM_H_
|
||||
|
||||
#include <ostream>
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace internal {
|
||||
|
||||
template <class Char> class formatbuf : public std::basic_streambuf<Char> {
|
||||
private:
|
||||
using int_type = typename std::basic_streambuf<Char>::int_type;
|
||||
using traits_type = typename std::basic_streambuf<Char>::traits_type;
|
||||
|
||||
buffer<Char>& buffer_;
|
||||
|
||||
public:
|
||||
formatbuf(buffer<Char>& buf) : buffer_(buf) {}
|
||||
|
||||
protected:
|
||||
// The put-area is actually always empty. This makes the implementation
|
||||
// simpler and has the advantage that the streambuf and the buffer are always
|
||||
// in sync and sputc never writes into uninitialized memory. The obvious
|
||||
// disadvantage is that each call to sputc always results in a (virtual) call
|
||||
// to overflow. There is no disadvantage here for sputn since this always
|
||||
// results in a call to xsputn.
|
||||
|
||||
int_type overflow(int_type ch = traits_type::eof()) FMT_OVERRIDE {
|
||||
if (!traits_type::eq_int_type(ch, traits_type::eof()))
|
||||
buffer_.push_back(static_cast<Char>(ch));
|
||||
return ch;
|
||||
}
|
||||
|
||||
std::streamsize xsputn(const Char* s, std::streamsize count) FMT_OVERRIDE {
|
||||
buffer_.append(s, s + count);
|
||||
return count;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char> struct test_stream : std::basic_ostream<Char> {
|
||||
private:
|
||||
// Hide all operator<< from std::basic_ostream<Char>.
|
||||
void_t<> operator<<(null<>);
|
||||
void_t<> operator<<(const Char*);
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(std::is_convertible<T, int>::value &&
|
||||
!std::is_enum<T>::value)>
|
||||
void_t<> operator<<(T);
|
||||
};
|
||||
|
||||
// Checks if T has a user-defined operator<< (e.g. not a member of
|
||||
// std::ostream).
|
||||
template <typename T, typename Char> class is_streamable {
|
||||
private:
|
||||
template <typename U>
|
||||
static bool_constant<!std::is_same<decltype(std::declval<test_stream<Char>&>()
|
||||
<< std::declval<U>()),
|
||||
void_t<>>::value>
|
||||
test(int);
|
||||
|
||||
template <typename> static std::false_type test(...);
|
||||
|
||||
using result = decltype(test<T>(0));
|
||||
|
||||
public:
|
||||
static const bool value = result::value;
|
||||
};
|
||||
|
||||
// Write the content of buf to os.
|
||||
template <typename Char>
|
||||
void write(std::basic_ostream<Char>& os, buffer<Char>& buf) {
|
||||
const Char* buf_data = buf.data();
|
||||
using unsigned_streamsize = std::make_unsigned<std::streamsize>::type;
|
||||
unsigned_streamsize size = buf.size();
|
||||
unsigned_streamsize max_size = to_unsigned(max_value<std::streamsize>());
|
||||
do {
|
||||
unsigned_streamsize n = size <= max_size ? size : max_size;
|
||||
os.write(buf_data, static_cast<std::streamsize>(n));
|
||||
buf_data += n;
|
||||
size -= n;
|
||||
} while (size != 0);
|
||||
}
|
||||
|
||||
template <typename Char, typename T>
|
||||
void format_value(buffer<Char>& buf, const T& value,
|
||||
locale_ref loc = locale_ref()) {
|
||||
formatbuf<Char> format_buf(buf);
|
||||
std::basic_ostream<Char> output(&format_buf);
|
||||
if (loc) output.imbue(loc.get<std::locale>());
|
||||
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
|
||||
output << value;
|
||||
buf.resize(buf.size());
|
||||
}
|
||||
|
||||
// Formats an object of type T that has an overloaded ostream operator<<.
|
||||
template <typename T, typename Char>
|
||||
struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>>
|
||||
: formatter<basic_string_view<Char>, Char> {
|
||||
template <typename Context>
|
||||
auto format(const T& value, Context& ctx) -> decltype(ctx.out()) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
format_value(buffer, value, ctx.locale());
|
||||
basic_string_view<Char> str(buffer.data(), buffer.size());
|
||||
return formatter<basic_string_view<Char>, Char>::format(str, ctx);
|
||||
}
|
||||
};
|
||||
} // namespace internal
|
||||
|
||||
template <typename Char>
|
||||
void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<Char>> args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
internal::vformat_to(buffer, format_str, args);
|
||||
internal::write(os, buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Prints formatted data to the stream *os*.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::print(cerr, "Don't {}!", "panic");
|
||||
\endrst
|
||||
*/
|
||||
template <typename S, typename... Args,
|
||||
typename Char = enable_if_t<internal::is_string<S>::value, char_t<S>>>
|
||||
void print(std::basic_ostream<Char>& os, const S& format_str, Args&&... args) {
|
||||
vprint(os, to_string_view(format_str),
|
||||
{internal::make_args_checked<Args...>(format_str, args...)});
|
||||
}
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_OSTREAM_H_
|
|
@ -1,711 +0,0 @@
|
|||
// Formatting library for C++ - legacy printf implementation
|
||||
//
|
||||
// Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_PRINTF_H_
|
||||
#define FMT_PRINTF_H_
|
||||
|
||||
#include <algorithm> // std::max
|
||||
#include <limits> // std::numeric_limits
|
||||
|
||||
#include "ostream.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace internal {
|
||||
|
||||
// Checks if a value fits in int - used to avoid warnings about comparing
|
||||
// signed and unsigned integers.
|
||||
template <bool IsSigned> struct int_checker {
|
||||
template <typename T> static bool fits_in_int(T value) {
|
||||
unsigned max = max_value<int>();
|
||||
return value <= max;
|
||||
}
|
||||
static bool fits_in_int(bool) { return true; }
|
||||
};
|
||||
|
||||
template <> struct int_checker<true> {
|
||||
template <typename T> static bool fits_in_int(T value) {
|
||||
return value >= std::numeric_limits<int>::min() &&
|
||||
value <= max_value<int>();
|
||||
}
|
||||
static bool fits_in_int(int) { return true; }
|
||||
};
|
||||
|
||||
class printf_precision_handler {
|
||||
public:
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
int operator()(T value) {
|
||||
if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value))
|
||||
FMT_THROW(format_error("number is too big"));
|
||||
return (std::max)(static_cast<int>(value), 0);
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||
int operator()(T) {
|
||||
FMT_THROW(format_error("precision is not integer"));
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
// An argument visitor that returns true iff arg is a zero integer.
|
||||
class is_zero_int {
|
||||
public:
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
bool operator()(T value) {
|
||||
return value == 0;
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||
bool operator()(T) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T> struct make_unsigned_or_bool : std::make_unsigned<T> {};
|
||||
|
||||
template <> struct make_unsigned_or_bool<bool> { using type = bool; };
|
||||
|
||||
template <typename T, typename Context> class arg_converter {
|
||||
private:
|
||||
using char_type = typename Context::char_type;
|
||||
|
||||
basic_format_arg<Context>& arg_;
|
||||
char_type type_;
|
||||
|
||||
public:
|
||||
arg_converter(basic_format_arg<Context>& arg, char_type type)
|
||||
: arg_(arg), type_(type) {}
|
||||
|
||||
void operator()(bool value) {
|
||||
if (type_ != 's') operator()<bool>(value);
|
||||
}
|
||||
|
||||
template <typename U, FMT_ENABLE_IF(std::is_integral<U>::value)>
|
||||
void operator()(U value) {
|
||||
bool is_signed = type_ == 'd' || type_ == 'i';
|
||||
using target_type = conditional_t<std::is_same<T, void>::value, U, T>;
|
||||
if (const_check(sizeof(target_type) <= sizeof(int))) {
|
||||
// Extra casts are used to silence warnings.
|
||||
if (is_signed) {
|
||||
arg_ = internal::make_arg<Context>(
|
||||
static_cast<int>(static_cast<target_type>(value)));
|
||||
} else {
|
||||
using unsigned_type = typename make_unsigned_or_bool<target_type>::type;
|
||||
arg_ = internal::make_arg<Context>(
|
||||
static_cast<unsigned>(static_cast<unsigned_type>(value)));
|
||||
}
|
||||
} else {
|
||||
if (is_signed) {
|
||||
// glibc's printf doesn't sign extend arguments of smaller types:
|
||||
// std::printf("%lld", -42); // prints "4294967254"
|
||||
// but we don't have to do the same because it's a UB.
|
||||
arg_ = internal::make_arg<Context>(static_cast<long long>(value));
|
||||
} else {
|
||||
arg_ = internal::make_arg<Context>(
|
||||
static_cast<typename make_unsigned_or_bool<U>::type>(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename U, FMT_ENABLE_IF(!std::is_integral<U>::value)>
|
||||
void operator()(U) {} // No conversion needed for non-integral types.
|
||||
};
|
||||
|
||||
// Converts an integer argument to T for printf, if T is an integral type.
|
||||
// If T is void, the argument is converted to corresponding signed or unsigned
|
||||
// type depending on the type specifier: 'd' and 'i' - signed, other -
|
||||
// unsigned).
|
||||
template <typename T, typename Context, typename Char>
|
||||
void convert_arg(basic_format_arg<Context>& arg, Char type) {
|
||||
visit_format_arg(arg_converter<T, Context>(arg, type), arg);
|
||||
}
|
||||
|
||||
// Converts an integer argument to char for printf.
|
||||
template <typename Context> class char_converter {
|
||||
private:
|
||||
basic_format_arg<Context>& arg_;
|
||||
|
||||
public:
|
||||
explicit char_converter(basic_format_arg<Context>& arg) : arg_(arg) {}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
void operator()(T value) {
|
||||
arg_ = internal::make_arg<Context>(
|
||||
static_cast<typename Context::char_type>(value));
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||
void operator()(T) {} // No conversion needed for non-integral types.
|
||||
};
|
||||
|
||||
// Checks if an argument is a valid printf width specifier and sets
|
||||
// left alignment if it is negative.
|
||||
template <typename Char> class printf_width_handler {
|
||||
private:
|
||||
using format_specs = basic_format_specs<Char>;
|
||||
|
||||
format_specs& specs_;
|
||||
|
||||
public:
|
||||
explicit printf_width_handler(format_specs& specs) : specs_(specs) {}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
unsigned operator()(T value) {
|
||||
auto width = static_cast<uint32_or_64_or_128_t<T>>(value);
|
||||
if (internal::is_negative(value)) {
|
||||
specs_.align = align::left;
|
||||
width = 0 - width;
|
||||
}
|
||||
unsigned int_max = max_value<int>();
|
||||
if (width > int_max) FMT_THROW(format_error("number is too big"));
|
||||
return static_cast<unsigned>(width);
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||
unsigned operator()(T) {
|
||||
FMT_THROW(format_error("width is not integer"));
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char, typename Context>
|
||||
void printf(buffer<Char>& buf, basic_string_view<Char> format,
|
||||
basic_format_args<Context> args) {
|
||||
Context(std::back_inserter(buf), format, args).format();
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename Char, typename Context>
|
||||
internal::truncating_iterator<OutputIt> printf(
|
||||
internal::truncating_iterator<OutputIt> it, basic_string_view<Char> format,
|
||||
basic_format_args<Context> args) {
|
||||
return Context(it, format, args).format();
|
||||
}
|
||||
} // namespace internal
|
||||
|
||||
using internal::printf; // For printing into memory_buffer.
|
||||
|
||||
template <typename Range> class printf_arg_formatter;
|
||||
|
||||
template <typename OutputIt, typename Char> class basic_printf_context;
|
||||
|
||||
/**
|
||||
\rst
|
||||
The ``printf`` argument formatter.
|
||||
\endrst
|
||||
*/
|
||||
template <typename Range>
|
||||
class printf_arg_formatter : public internal::arg_formatter_base<Range> {
|
||||
public:
|
||||
using iterator = typename Range::iterator;
|
||||
|
||||
private:
|
||||
using char_type = typename Range::value_type;
|
||||
using base = internal::arg_formatter_base<Range>;
|
||||
using context_type = basic_printf_context<iterator, char_type>;
|
||||
|
||||
context_type& context_;
|
||||
|
||||
void write_null_pointer(char) {
|
||||
this->specs()->type = 0;
|
||||
this->write("(nil)");
|
||||
}
|
||||
|
||||
void write_null_pointer(wchar_t) {
|
||||
this->specs()->type = 0;
|
||||
this->write(L"(nil)");
|
||||
}
|
||||
|
||||
public:
|
||||
using format_specs = typename base::format_specs;
|
||||
|
||||
/**
|
||||
\rst
|
||||
Constructs an argument formatter object.
|
||||
*buffer* is a reference to the output buffer and *specs* contains format
|
||||
specifier information for standard argument types.
|
||||
\endrst
|
||||
*/
|
||||
printf_arg_formatter(iterator iter, format_specs& specs, context_type& ctx)
|
||||
: base(Range(iter), &specs, internal::locale_ref()), context_(ctx) {}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(fmt::internal::is_integral<T>::value)>
|
||||
iterator operator()(T value) {
|
||||
// MSVC2013 fails to compile separate overloads for bool and char_type so
|
||||
// use std::is_same instead.
|
||||
if (std::is_same<T, bool>::value) {
|
||||
format_specs& fmt_specs = *this->specs();
|
||||
if (fmt_specs.type != 's') return base::operator()(value ? 1 : 0);
|
||||
fmt_specs.type = 0;
|
||||
this->write(value != 0);
|
||||
} else if (std::is_same<T, char_type>::value) {
|
||||
format_specs& fmt_specs = *this->specs();
|
||||
if (fmt_specs.type && fmt_specs.type != 'c')
|
||||
return (*this)(static_cast<int>(value));
|
||||
fmt_specs.sign = sign::none;
|
||||
fmt_specs.alt = false;
|
||||
fmt_specs.align = align::right;
|
||||
return base::operator()(value);
|
||||
} else {
|
||||
return base::operator()(value);
|
||||
}
|
||||
return this->out();
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
|
||||
iterator operator()(T value) {
|
||||
return base::operator()(value);
|
||||
}
|
||||
|
||||
/** Formats a null-terminated C string. */
|
||||
iterator operator()(const char* value) {
|
||||
if (value)
|
||||
base::operator()(value);
|
||||
else if (this->specs()->type == 'p')
|
||||
write_null_pointer(char_type());
|
||||
else
|
||||
this->write("(null)");
|
||||
return this->out();
|
||||
}
|
||||
|
||||
/** Formats a null-terminated wide C string. */
|
||||
iterator operator()(const wchar_t* value) {
|
||||
if (value)
|
||||
base::operator()(value);
|
||||
else if (this->specs()->type == 'p')
|
||||
write_null_pointer(char_type());
|
||||
else
|
||||
this->write(L"(null)");
|
||||
return this->out();
|
||||
}
|
||||
|
||||
iterator operator()(basic_string_view<char_type> value) {
|
||||
return base::operator()(value);
|
||||
}
|
||||
|
||||
iterator operator()(monostate value) { return base::operator()(value); }
|
||||
|
||||
/** Formats a pointer. */
|
||||
iterator operator()(const void* value) {
|
||||
if (value) return base::operator()(value);
|
||||
this->specs()->type = 0;
|
||||
write_null_pointer(char_type());
|
||||
return this->out();
|
||||
}
|
||||
|
||||
/** Formats an argument of a custom (user-defined) type. */
|
||||
iterator operator()(typename basic_format_arg<context_type>::handle handle) {
|
||||
handle.format(context_.parse_context(), context_);
|
||||
return this->out();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T> struct printf_formatter {
|
||||
template <typename ParseContext>
|
||||
auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const T& value, FormatContext& ctx) -> decltype(ctx.out()) {
|
||||
internal::format_value(internal::get_container(ctx.out()), value);
|
||||
return ctx.out();
|
||||
}
|
||||
};
|
||||
|
||||
/** This template formats data and writes the output to a writer. */
|
||||
template <typename OutputIt, typename Char> class basic_printf_context {
|
||||
public:
|
||||
/** The character type for the output. */
|
||||
using char_type = Char;
|
||||
using format_arg = basic_format_arg<basic_printf_context>;
|
||||
template <typename T> using formatter_type = printf_formatter<T>;
|
||||
|
||||
private:
|
||||
using format_specs = basic_format_specs<char_type>;
|
||||
|
||||
OutputIt out_;
|
||||
basic_format_args<basic_printf_context> args_;
|
||||
basic_format_parse_context<Char> parse_ctx_;
|
||||
|
||||
static void parse_flags(format_specs& specs, const Char*& it,
|
||||
const Char* end);
|
||||
|
||||
// Returns the argument with specified index or, if arg_index is -1, the next
|
||||
// argument.
|
||||
format_arg get_arg(int arg_index = -1);
|
||||
|
||||
// Parses argument index, flags and width and returns the argument index.
|
||||
int parse_header(const Char*& it, const Char* end, format_specs& specs);
|
||||
|
||||
public:
|
||||
/**
|
||||
\rst
|
||||
Constructs a ``printf_context`` object. References to the arguments and
|
||||
the writer are stored in the context object so make sure they have
|
||||
appropriate lifetimes.
|
||||
\endrst
|
||||
*/
|
||||
basic_printf_context(OutputIt out, basic_string_view<char_type> format_str,
|
||||
basic_format_args<basic_printf_context> args)
|
||||
: out_(out), args_(args), parse_ctx_(format_str) {}
|
||||
|
||||
OutputIt out() { return out_; }
|
||||
void advance_to(OutputIt it) { out_ = it; }
|
||||
|
||||
format_arg arg(int id) const { return args_.get(id); }
|
||||
|
||||
basic_format_parse_context<Char>& parse_context() { return parse_ctx_; }
|
||||
|
||||
FMT_CONSTEXPR void on_error(const char* message) {
|
||||
parse_ctx_.on_error(message);
|
||||
}
|
||||
|
||||
/** Formats stored arguments and writes the output to the range. */
|
||||
template <typename ArgFormatter = printf_arg_formatter<buffer_range<Char>>>
|
||||
OutputIt format();
|
||||
};
|
||||
|
||||
template <typename OutputIt, typename Char>
|
||||
void basic_printf_context<OutputIt, Char>::parse_flags(format_specs& specs,
|
||||
const Char*& it,
|
||||
const Char* end) {
|
||||
for (; it != end; ++it) {
|
||||
switch (*it) {
|
||||
case '-':
|
||||
specs.align = align::left;
|
||||
break;
|
||||
case '+':
|
||||
specs.sign = sign::plus;
|
||||
break;
|
||||
case '0':
|
||||
specs.fill[0] = '0';
|
||||
break;
|
||||
case ' ':
|
||||
specs.sign = sign::space;
|
||||
break;
|
||||
case '#':
|
||||
specs.alt = true;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename Char>
|
||||
typename basic_printf_context<OutputIt, Char>::format_arg
|
||||
basic_printf_context<OutputIt, Char>::get_arg(int arg_index) {
|
||||
if (arg_index < 0)
|
||||
arg_index = parse_ctx_.next_arg_id();
|
||||
else
|
||||
parse_ctx_.check_arg_id(--arg_index);
|
||||
return internal::get_arg(*this, arg_index);
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename Char>
|
||||
int basic_printf_context<OutputIt, Char>::parse_header(
|
||||
const Char*& it, const Char* end, format_specs& specs) {
|
||||
int arg_index = -1;
|
||||
char_type c = *it;
|
||||
if (c >= '0' && c <= '9') {
|
||||
// Parse an argument index (if followed by '$') or a width possibly
|
||||
// preceded with '0' flag(s).
|
||||
internal::error_handler eh;
|
||||
int value = parse_nonnegative_int(it, end, eh);
|
||||
if (it != end && *it == '$') { // value is an argument index
|
||||
++it;
|
||||
arg_index = value;
|
||||
} else {
|
||||
if (c == '0') specs.fill[0] = '0';
|
||||
if (value != 0) {
|
||||
// Nonzero value means that we parsed width and don't need to
|
||||
// parse it or flags again, so return now.
|
||||
specs.width = value;
|
||||
return arg_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
parse_flags(specs, it, end);
|
||||
// Parse width.
|
||||
if (it != end) {
|
||||
if (*it >= '0' && *it <= '9') {
|
||||
internal::error_handler eh;
|
||||
specs.width = parse_nonnegative_int(it, end, eh);
|
||||
} else if (*it == '*') {
|
||||
++it;
|
||||
specs.width = static_cast<int>(visit_format_arg(
|
||||
internal::printf_width_handler<char_type>(specs), get_arg()));
|
||||
}
|
||||
}
|
||||
return arg_index;
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename Char>
|
||||
template <typename ArgFormatter>
|
||||
OutputIt basic_printf_context<OutputIt, Char>::format() {
|
||||
auto out = this->out();
|
||||
const Char* start = parse_ctx_.begin();
|
||||
const Char* end = parse_ctx_.end();
|
||||
auto it = start;
|
||||
while (it != end) {
|
||||
char_type c = *it++;
|
||||
if (c != '%') continue;
|
||||
if (it != end && *it == c) {
|
||||
out = std::copy(start, it, out);
|
||||
start = ++it;
|
||||
continue;
|
||||
}
|
||||
out = std::copy(start, it - 1, out);
|
||||
|
||||
format_specs specs;
|
||||
specs.align = align::right;
|
||||
|
||||
// Parse argument index, flags and width.
|
||||
int arg_index = parse_header(it, end, specs);
|
||||
if (arg_index == 0) on_error("argument index out of range");
|
||||
|
||||
// Parse precision.
|
||||
if (it != end && *it == '.') {
|
||||
++it;
|
||||
c = it != end ? *it : 0;
|
||||
if ('0' <= c && c <= '9') {
|
||||
internal::error_handler eh;
|
||||
specs.precision = parse_nonnegative_int(it, end, eh);
|
||||
} else if (c == '*') {
|
||||
++it;
|
||||
specs.precision =
|
||||
static_cast<int>(visit_format_arg(internal::printf_precision_handler(), get_arg()));
|
||||
} else {
|
||||
specs.precision = 0;
|
||||
}
|
||||
}
|
||||
|
||||
format_arg arg = get_arg(arg_index);
|
||||
if (specs.alt && visit_format_arg(internal::is_zero_int(), arg))
|
||||
specs.alt = false;
|
||||
if (specs.fill[0] == '0') {
|
||||
if (arg.is_arithmetic())
|
||||
specs.align = align::numeric;
|
||||
else
|
||||
specs.fill[0] = ' '; // Ignore '0' flag for non-numeric types.
|
||||
}
|
||||
|
||||
// Parse length and convert the argument to the required type.
|
||||
c = it != end ? *it++ : 0;
|
||||
char_type t = it != end ? *it : 0;
|
||||
using internal::convert_arg;
|
||||
switch (c) {
|
||||
case 'h':
|
||||
if (t == 'h') {
|
||||
++it;
|
||||
t = it != end ? *it : 0;
|
||||
convert_arg<signed char>(arg, t);
|
||||
} else {
|
||||
convert_arg<short>(arg, t);
|
||||
}
|
||||
break;
|
||||
case 'l':
|
||||
if (t == 'l') {
|
||||
++it;
|
||||
t = it != end ? *it : 0;
|
||||
convert_arg<long long>(arg, t);
|
||||
} else {
|
||||
convert_arg<long>(arg, t);
|
||||
}
|
||||
break;
|
||||
case 'j':
|
||||
convert_arg<intmax_t>(arg, t);
|
||||
break;
|
||||
case 'z':
|
||||
convert_arg<std::size_t>(arg, t);
|
||||
break;
|
||||
case 't':
|
||||
convert_arg<std::ptrdiff_t>(arg, t);
|
||||
break;
|
||||
case 'L':
|
||||
// printf produces garbage when 'L' is omitted for long double, no
|
||||
// need to do the same.
|
||||
break;
|
||||
default:
|
||||
--it;
|
||||
convert_arg<void>(arg, c);
|
||||
}
|
||||
|
||||
// Parse type.
|
||||
if (it == end) FMT_THROW(format_error("invalid format string"));
|
||||
specs.type = static_cast<char>(*it++);
|
||||
if (arg.is_integral()) {
|
||||
// Normalize type.
|
||||
switch (specs.type) {
|
||||
case 'i':
|
||||
case 'u':
|
||||
specs.type = 'd';
|
||||
break;
|
||||
case 'c':
|
||||
visit_format_arg(internal::char_converter<basic_printf_context>(arg),
|
||||
arg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
start = it;
|
||||
|
||||
// Format argument.
|
||||
visit_format_arg(ArgFormatter(out, specs, *this), arg);
|
||||
}
|
||||
return std::copy(start, it, out);
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
using basic_printf_context_t =
|
||||
basic_printf_context<std::back_insert_iterator<internal::buffer<Char>>,
|
||||
Char>;
|
||||
|
||||
using printf_context = basic_printf_context_t<char>;
|
||||
using wprintf_context = basic_printf_context_t<wchar_t>;
|
||||
|
||||
using printf_args = basic_format_args<printf_context>;
|
||||
using wprintf_args = basic_format_args<wprintf_context>;
|
||||
|
||||
/**
|
||||
\rst
|
||||
Constructs an `~fmt::format_arg_store` object that contains references to
|
||||
arguments and can be implicitly converted to `~fmt::printf_args`.
|
||||
\endrst
|
||||
*/
|
||||
template <typename... Args>
|
||||
inline format_arg_store<printf_context, Args...> make_printf_args(
|
||||
const Args&... args) {
|
||||
return {args...};
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Constructs an `~fmt::format_arg_store` object that contains references to
|
||||
arguments and can be implicitly converted to `~fmt::wprintf_args`.
|
||||
\endrst
|
||||
*/
|
||||
template <typename... Args>
|
||||
inline format_arg_store<wprintf_context, Args...> make_wprintf_args(
|
||||
const Args&... args) {
|
||||
return {args...};
|
||||
}
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
inline std::basic_string<Char> vsprintf(
|
||||
const S& format, basic_format_args<basic_printf_context_t<Char>> args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
printf(buffer, to_string_view(format), args);
|
||||
return to_string(buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Formats arguments and returns the result as a string.
|
||||
|
||||
**Example**::
|
||||
|
||||
std::string message = fmt::sprintf("The answer is %d", 42);
|
||||
\endrst
|
||||
*/
|
||||
template <typename S, typename... Args,
|
||||
typename Char = enable_if_t<internal::is_string<S>::value, char_t<S>>>
|
||||
inline std::basic_string<Char> sprintf(const S& format, const Args&... args) {
|
||||
using context = basic_printf_context_t<Char>;
|
||||
return vsprintf(to_string_view(format), {make_format_args<context>(args...)});
|
||||
}
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
inline int vfprintf(std::FILE* f, const S& format,
|
||||
basic_format_args<basic_printf_context_t<Char>> args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
printf(buffer, to_string_view(format), args);
|
||||
std::size_t size = buffer.size();
|
||||
return std::fwrite(buffer.data(), sizeof(Char), size, f) < size
|
||||
? -1
|
||||
: static_cast<int>(size);
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Prints formatted data to the file *f*.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::fprintf(stderr, "Don't %s!", "panic");
|
||||
\endrst
|
||||
*/
|
||||
template <typename S, typename... Args,
|
||||
typename Char = enable_if_t<internal::is_string<S>::value, char_t<S>>>
|
||||
inline int fprintf(std::FILE* f, const S& format, const Args&... args) {
|
||||
using context = basic_printf_context_t<Char>;
|
||||
return vfprintf(f, to_string_view(format),
|
||||
{make_format_args<context>(args...)});
|
||||
}
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
inline int vprintf(const S& format,
|
||||
basic_format_args<basic_printf_context_t<Char>> args) {
|
||||
return vfprintf(stdout, to_string_view(format), args);
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Prints formatted data to ``stdout``.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::printf("Elapsed time: %.2f seconds", 1.23);
|
||||
\endrst
|
||||
*/
|
||||
template <typename S, typename... Args,
|
||||
FMT_ENABLE_IF(internal::is_string<S>::value)>
|
||||
inline int printf(const S& format_str, const Args&... args) {
|
||||
using context = basic_printf_context_t<char_t<S>>;
|
||||
return vprintf(to_string_view(format_str),
|
||||
{make_format_args<context>(args...)});
|
||||
}
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
inline int vfprintf(std::basic_ostream<Char>& os, const S& format,
|
||||
basic_format_args<basic_printf_context_t<Char>> args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
printf(buffer, to_string_view(format), args);
|
||||
internal::write(os, buffer);
|
||||
return static_cast<int>(buffer.size());
|
||||
}
|
||||
|
||||
/** Formats arguments and writes the output to the range. */
|
||||
template <typename ArgFormatter, typename Char,
|
||||
typename Context =
|
||||
basic_printf_context<typename ArgFormatter::iterator, Char>>
|
||||
typename ArgFormatter::iterator vprintf(internal::buffer<Char>& out,
|
||||
basic_string_view<Char> format_str,
|
||||
basic_format_args<Context> args) {
|
||||
typename ArgFormatter::iterator iter(out);
|
||||
Context(iter, format_str, args).template format<ArgFormatter>();
|
||||
return iter;
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Prints formatted data to the stream *os*.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::fprintf(cerr, "Don't %s!", "panic");
|
||||
\endrst
|
||||
*/
|
||||
template <typename S, typename... Args, typename Char = char_t<S>>
|
||||
inline int fprintf(std::basic_ostream<Char>& os, const S& format_str,
|
||||
const Args&... args) {
|
||||
using context = basic_printf_context_t<Char>;
|
||||
return vfprintf(os, to_string_view(format_str),
|
||||
{make_format_args<context>(args...)});
|
||||
}
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_PRINTF_H_
|
|
@ -1,365 +0,0 @@
|
|||
// Formatting library for C++ - experimental range support
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
//
|
||||
// Copyright (c) 2018 - present, Remotion (Igor Schulz)
|
||||
// All Rights Reserved
|
||||
// {fmt} support for ranges, containers and types tuple interface.
|
||||
|
||||
#ifndef FMT_RANGES_H_
|
||||
#define FMT_RANGES_H_
|
||||
|
||||
#include <type_traits>
|
||||
#include "format.h"
|
||||
|
||||
// output only up to N items from the range.
|
||||
#ifndef FMT_RANGE_OUTPUT_LENGTH_LIMIT
|
||||
# define FMT_RANGE_OUTPUT_LENGTH_LIMIT 256
|
||||
#endif
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
template <typename Char> struct formatting_base {
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char, typename Enable = void>
|
||||
struct formatting_range : formatting_base<Char> {
|
||||
static FMT_CONSTEXPR_DECL const std::size_t range_length_limit =
|
||||
FMT_RANGE_OUTPUT_LENGTH_LIMIT; // output only up to N items from the
|
||||
// range.
|
||||
Char prefix;
|
||||
Char delimiter;
|
||||
Char postfix;
|
||||
formatting_range() : prefix('{'), delimiter(','), postfix('}') {}
|
||||
static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true;
|
||||
static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false;
|
||||
};
|
||||
|
||||
template <typename Char, typename Enable = void>
|
||||
struct formatting_tuple : formatting_base<Char> {
|
||||
Char prefix;
|
||||
Char delimiter;
|
||||
Char postfix;
|
||||
formatting_tuple() : prefix('('), delimiter(','), postfix(')') {}
|
||||
static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true;
|
||||
static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false;
|
||||
};
|
||||
|
||||
namespace internal {
|
||||
|
||||
template <typename RangeT, typename OutputIterator>
|
||||
OutputIterator copy(const RangeT& range, OutputIterator out) {
|
||||
for (auto it = range.begin(), end = range.end(); it != end; ++it)
|
||||
*out++ = *it;
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename OutputIterator>
|
||||
OutputIterator copy(const char* str, OutputIterator out) {
|
||||
while (*str) *out++ = *str++;
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename OutputIterator>
|
||||
OutputIterator copy(char ch, OutputIterator out) {
|
||||
*out++ = ch;
|
||||
return out;
|
||||
}
|
||||
|
||||
/// Return true value if T has std::string interface, like std::string_view.
|
||||
template <typename T> class is_like_std_string {
|
||||
template <typename U>
|
||||
static auto check(U* p)
|
||||
-> decltype((void)p->find('a'), p->length(), (void)p->data(), int());
|
||||
template <typename> static void check(...);
|
||||
|
||||
public:
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
is_string<T>::value || !std::is_void<decltype(check<T>(nullptr))>::value;
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
struct is_like_std_string<fmt::basic_string_view<Char>> : std::true_type {};
|
||||
|
||||
template <typename... Ts> struct conditional_helper {};
|
||||
|
||||
template <typename T, typename _ = void> struct is_range_ : std::false_type {};
|
||||
|
||||
#if !FMT_MSC_VER || FMT_MSC_VER > 1800
|
||||
template <typename T>
|
||||
struct is_range_<
|
||||
T, conditional_t<false,
|
||||
conditional_helper<decltype(std::declval<T>().begin()),
|
||||
decltype(std::declval<T>().end())>,
|
||||
void>> : std::true_type {};
|
||||
#endif
|
||||
|
||||
/// tuple_size and tuple_element check.
|
||||
template <typename T> class is_tuple_like_ {
|
||||
template <typename U>
|
||||
static auto check(U* p)
|
||||
-> decltype(std::tuple_size<U>::value,
|
||||
(void)std::declval<typename std::tuple_element<0, U>::type>(),
|
||||
int());
|
||||
template <typename> static void check(...);
|
||||
|
||||
public:
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
!std::is_void<decltype(check<T>(nullptr))>::value;
|
||||
};
|
||||
|
||||
// Check for integer_sequence
|
||||
#if defined(__cpp_lib_integer_sequence) || FMT_MSC_VER >= 1900
|
||||
template <typename T, T... N>
|
||||
using integer_sequence = std::integer_sequence<T, N...>;
|
||||
template <std::size_t... N> using index_sequence = std::index_sequence<N...>;
|
||||
template <std::size_t N>
|
||||
using make_index_sequence = std::make_index_sequence<N>;
|
||||
#else
|
||||
template <typename T, T... N> struct integer_sequence {
|
||||
using value_type = T;
|
||||
|
||||
static FMT_CONSTEXPR std::size_t size() { return sizeof...(N); }
|
||||
};
|
||||
|
||||
template <std::size_t... N>
|
||||
using index_sequence = integer_sequence<std::size_t, N...>;
|
||||
|
||||
template <typename T, std::size_t N, T... Ns>
|
||||
struct make_integer_sequence : make_integer_sequence<T, N - 1, N - 1, Ns...> {};
|
||||
template <typename T, T... Ns>
|
||||
struct make_integer_sequence<T, 0, Ns...> : integer_sequence<T, Ns...> {};
|
||||
|
||||
template <std::size_t N>
|
||||
using make_index_sequence = make_integer_sequence<std::size_t, N>;
|
||||
#endif
|
||||
|
||||
template <class Tuple, class F, size_t... Is>
|
||||
void for_each(index_sequence<Is...>, Tuple&& tup, F&& f) FMT_NOEXCEPT {
|
||||
using std::get;
|
||||
// using free function get<I>(T) now.
|
||||
const int _[] = {0, ((void)f(get<Is>(tup)), 0)...};
|
||||
(void)_; // blocks warnings
|
||||
}
|
||||
|
||||
template <class T>
|
||||
FMT_CONSTEXPR make_index_sequence<std::tuple_size<T>::value> get_indexes(
|
||||
T const&) {
|
||||
return {};
|
||||
}
|
||||
|
||||
template <class Tuple, class F> void for_each(Tuple&& tup, F&& f) {
|
||||
const auto indexes = get_indexes(tup);
|
||||
for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f));
|
||||
}
|
||||
|
||||
template <typename Arg, FMT_ENABLE_IF(!is_like_std_string<
|
||||
typename std::decay<Arg>::type>::value)>
|
||||
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&) {
|
||||
return add_space ? " {}" : "{}";
|
||||
}
|
||||
|
||||
template <typename Arg, FMT_ENABLE_IF(is_like_std_string<
|
||||
typename std::decay<Arg>::type>::value)>
|
||||
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&) {
|
||||
return add_space ? " \"{}\"" : "\"{}\"";
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const char*) {
|
||||
return add_space ? " \"{}\"" : "\"{}\"";
|
||||
}
|
||||
FMT_CONSTEXPR const wchar_t* format_str_quoted(bool add_space, const wchar_t*) {
|
||||
return add_space ? L" \"{}\"" : L"\"{}\"";
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const char) {
|
||||
return add_space ? " '{}'" : "'{}'";
|
||||
}
|
||||
FMT_CONSTEXPR const wchar_t* format_str_quoted(bool add_space, const wchar_t) {
|
||||
return add_space ? L" '{}'" : L"'{}'";
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
||||
template <typename T> struct is_tuple_like {
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
internal::is_tuple_like_<T>::value && !internal::is_range_<T>::value;
|
||||
};
|
||||
|
||||
template <typename TupleT, typename Char>
|
||||
struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> {
|
||||
private:
|
||||
// C++11 generic lambda for format()
|
||||
template <typename FormatContext> struct format_each {
|
||||
template <typename T> void operator()(const T& v) {
|
||||
if (i > 0) {
|
||||
if (formatting.add_prepostfix_space) {
|
||||
*out++ = ' ';
|
||||
}
|
||||
out = internal::copy(formatting.delimiter, out);
|
||||
}
|
||||
out = format_to(out,
|
||||
internal::format_str_quoted(
|
||||
(formatting.add_delimiter_spaces && i > 0), v),
|
||||
v);
|
||||
++i;
|
||||
}
|
||||
|
||||
formatting_tuple<Char>& formatting;
|
||||
std::size_t& i;
|
||||
typename std::add_lvalue_reference<decltype(
|
||||
std::declval<FormatContext>().out())>::type out;
|
||||
};
|
||||
|
||||
public:
|
||||
formatting_tuple<Char> formatting;
|
||||
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return formatting.parse(ctx);
|
||||
}
|
||||
|
||||
template <typename FormatContext = format_context>
|
||||
auto format(const TupleT& values, FormatContext& ctx) -> decltype(ctx.out()) {
|
||||
auto out = ctx.out();
|
||||
std::size_t i = 0;
|
||||
internal::copy(formatting.prefix, out);
|
||||
|
||||
internal::for_each(values, format_each<FormatContext>{formatting, i, out});
|
||||
if (formatting.add_prepostfix_space) {
|
||||
*out++ = ' ';
|
||||
}
|
||||
internal::copy(formatting.postfix, out);
|
||||
|
||||
return ctx.out();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename Char> struct is_range {
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
internal::is_range_<T>::value &&
|
||||
!internal::is_like_std_string<T>::value &&
|
||||
!std::is_convertible<T, std::basic_string<Char>>::value &&
|
||||
!std::is_constructible<internal::std_string_view<Char>, T>::value;
|
||||
};
|
||||
|
||||
template <typename RangeT, typename Char>
|
||||
struct formatter<RangeT, Char,
|
||||
enable_if_t<fmt::is_range<RangeT, Char>::value>> {
|
||||
formatting_range<Char> formatting;
|
||||
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return formatting.parse(ctx);
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
typename FormatContext::iterator format(const RangeT& values,
|
||||
FormatContext& ctx) {
|
||||
auto out = internal::copy(formatting.prefix, ctx.out());
|
||||
std::size_t i = 0;
|
||||
for (auto it = values.begin(), end = values.end(); it != end; ++it) {
|
||||
if (i > 0) {
|
||||
if (formatting.add_prepostfix_space) *out++ = ' ';
|
||||
out = internal::copy(formatting.delimiter, out);
|
||||
}
|
||||
out = format_to(out,
|
||||
internal::format_str_quoted(
|
||||
(formatting.add_delimiter_spaces && i > 0), *it),
|
||||
*it);
|
||||
if (++i > formatting.range_length_limit) {
|
||||
out = format_to(out, " ... <other elements>");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (formatting.add_prepostfix_space) *out++ = ' ';
|
||||
return internal::copy(formatting.postfix, out);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char, typename... T> struct tuple_arg_join : internal::view {
|
||||
const std::tuple<T...>& tuple;
|
||||
basic_string_view<Char> sep;
|
||||
|
||||
tuple_arg_join(const std::tuple<T...>& t, basic_string_view<Char> s)
|
||||
: tuple{t}, sep{s} {}
|
||||
};
|
||||
|
||||
template <typename Char, typename... T>
|
||||
struct formatter<tuple_arg_join<Char, T...>, Char> {
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
typename FormatContext::iterator format(
|
||||
const tuple_arg_join<Char, T...>& value, FormatContext& ctx) {
|
||||
return format(value, ctx, internal::make_index_sequence<sizeof...(T)>{});
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename FormatContext, size_t... N>
|
||||
typename FormatContext::iterator format(
|
||||
const tuple_arg_join<Char, T...>& value, FormatContext& ctx,
|
||||
internal::index_sequence<N...>) {
|
||||
return format_args(value, ctx, std::get<N>(value.tuple)...);
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
typename FormatContext::iterator format_args(
|
||||
const tuple_arg_join<Char, T...>&, FormatContext& ctx) {
|
||||
// NOTE: for compilers that support C++17, this empty function instantiation
|
||||
// can be replaced with a constexpr branch in the variadic overload.
|
||||
return ctx.out();
|
||||
}
|
||||
|
||||
template <typename FormatContext, typename Arg, typename... Args>
|
||||
typename FormatContext::iterator format_args(
|
||||
const tuple_arg_join<Char, T...>& value, FormatContext& ctx,
|
||||
const Arg& arg, const Args&... args) {
|
||||
using base = formatter<typename std::decay<Arg>::type, Char>;
|
||||
auto out = ctx.out();
|
||||
out = base{}.format(arg, ctx);
|
||||
if (sizeof...(Args) > 0) {
|
||||
out = std::copy(value.sep.begin(), value.sep.end(), out);
|
||||
ctx.advance_to(out);
|
||||
return format_args(value, ctx, args...);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
\rst
|
||||
Returns an object that formats `tuple` with elements separated by `sep`.
|
||||
|
||||
**Example**::
|
||||
|
||||
std::tuple<int, char> t = {1, 'a'};
|
||||
fmt::print("{}", fmt::join(t, ", "));
|
||||
// Output: "1, a"
|
||||
\endrst
|
||||
*/
|
||||
template <typename... T>
|
||||
FMT_CONSTEXPR tuple_arg_join<char, T...> join(const std::tuple<T...>& tuple,
|
||||
string_view sep) {
|
||||
return {tuple, sep};
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
FMT_CONSTEXPR tuple_arg_join<wchar_t, T...> join(const std::tuple<T...>& tuple,
|
||||
wstring_view sep) {
|
||||
return {tuple, sep};
|
||||
}
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_RANGES_H_
|
|
@ -1,176 +0,0 @@
|
|||
// Formatting library for C++
|
||||
//
|
||||
// Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#include "fmt/format-inl.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace internal {
|
||||
|
||||
template <typename T>
|
||||
int format_float(char* buf, std::size_t size, const char* format, int precision,
|
||||
T value) {
|
||||
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
|
||||
if (precision > 100000)
|
||||
throw std::runtime_error(
|
||||
"fuzz mode - avoid large allocation inside snprintf");
|
||||
#endif
|
||||
// Suppress the warning about nonliteral format string.
|
||||
auto snprintf_ptr = FMT_SNPRINTF;
|
||||
return precision < 0 ? snprintf_ptr(buf, size, format, value)
|
||||
: snprintf_ptr(buf, size, format, precision, value);
|
||||
}
|
||||
struct sprintf_specs {
|
||||
int precision;
|
||||
char type;
|
||||
bool alt : 1;
|
||||
|
||||
template <typename Char>
|
||||
constexpr sprintf_specs(basic_format_specs<Char> specs)
|
||||
: precision(specs.precision), type(specs.type), alt(specs.alt) {}
|
||||
|
||||
constexpr bool has_precision() const { return precision >= 0; }
|
||||
};
|
||||
|
||||
// This is deprecated and is kept only to preserve ABI compatibility.
|
||||
template <typename Double>
|
||||
char* sprintf_format(Double value, internal::buffer<char>& buf,
|
||||
sprintf_specs specs) {
|
||||
// Buffer capacity must be non-zero, otherwise MSVC's vsnprintf_s will fail.
|
||||
FMT_ASSERT(buf.capacity() != 0, "empty buffer");
|
||||
|
||||
// Build format string.
|
||||
enum { max_format_size = 10 }; // longest format: %#-*.*Lg
|
||||
char format[max_format_size];
|
||||
char* format_ptr = format;
|
||||
*format_ptr++ = '%';
|
||||
if (specs.alt || !specs.type) *format_ptr++ = '#';
|
||||
if (specs.precision >= 0) {
|
||||
*format_ptr++ = '.';
|
||||
*format_ptr++ = '*';
|
||||
}
|
||||
if (std::is_same<Double, long double>::value) *format_ptr++ = 'L';
|
||||
|
||||
char type = specs.type;
|
||||
|
||||
if (type == '%')
|
||||
type = 'f';
|
||||
else if (type == 0 || type == 'n')
|
||||
type = 'g';
|
||||
#if FMT_MSC_VER
|
||||
if (type == 'F') {
|
||||
// MSVC's printf doesn't support 'F'.
|
||||
type = 'f';
|
||||
}
|
||||
#endif
|
||||
*format_ptr++ = type;
|
||||
*format_ptr = '\0';
|
||||
|
||||
// Format using snprintf.
|
||||
char* start = nullptr;
|
||||
char* decimal_point_pos = nullptr;
|
||||
for (;;) {
|
||||
std::size_t buffer_size = buf.capacity();
|
||||
start = &buf[0];
|
||||
int result =
|
||||
format_float(start, buffer_size, format, specs.precision, value);
|
||||
if (result >= 0) {
|
||||
unsigned n = internal::to_unsigned(result);
|
||||
if (n < buf.capacity()) {
|
||||
// Find the decimal point.
|
||||
auto p = buf.data(), end = p + n;
|
||||
if (*p == '+' || *p == '-') ++p;
|
||||
if (specs.type != 'a' && specs.type != 'A') {
|
||||
while (p < end && *p >= '0' && *p <= '9') ++p;
|
||||
if (p < end && *p != 'e' && *p != 'E') {
|
||||
decimal_point_pos = p;
|
||||
if (!specs.type) {
|
||||
// Keep only one trailing zero after the decimal point.
|
||||
++p;
|
||||
if (*p == '0') ++p;
|
||||
while (p != end && *p >= '1' && *p <= '9') ++p;
|
||||
char* where = p;
|
||||
while (p != end && *p == '0') ++p;
|
||||
if (p == end || *p < '0' || *p > '9') {
|
||||
if (p != end) std::memmove(where, p, to_unsigned(end - p));
|
||||
n -= static_cast<unsigned>(p - where);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
buf.resize(n);
|
||||
break; // The buffer is large enough - continue with formatting.
|
||||
}
|
||||
buf.reserve(n + 1);
|
||||
} else {
|
||||
// If result is negative we ask to increase the capacity by at least 1,
|
||||
// but as std::vector, the buffer grows exponentially.
|
||||
buf.reserve(buf.capacity() + 1);
|
||||
}
|
||||
}
|
||||
return decimal_point_pos;
|
||||
}
|
||||
} // namespace internal
|
||||
|
||||
template FMT_API char* internal::sprintf_format(double, internal::buffer<char>&,
|
||||
sprintf_specs);
|
||||
template FMT_API char* internal::sprintf_format(long double,
|
||||
internal::buffer<char>&,
|
||||
sprintf_specs);
|
||||
|
||||
template struct FMT_API internal::basic_data<void>;
|
||||
|
||||
// Workaround a bug in MSVC2013 that prevents instantiation of format_float.
|
||||
int (*instantiate_format_float)(double, int, internal::float_specs,
|
||||
internal::buffer<char>&) =
|
||||
internal::format_float;
|
||||
|
||||
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
||||
template FMT_API internal::locale_ref::locale_ref(const std::locale& loc);
|
||||
template FMT_API std::locale internal::locale_ref::get<std::locale>() const;
|
||||
#endif
|
||||
|
||||
// Explicit instantiations for char.
|
||||
|
||||
template FMT_API std::string internal::grouping_impl<char>(locale_ref);
|
||||
template FMT_API char internal::thousands_sep_impl(locale_ref);
|
||||
template FMT_API char internal::decimal_point_impl(locale_ref);
|
||||
|
||||
template FMT_API void internal::buffer<char>::append(const char*, const char*);
|
||||
|
||||
template FMT_API void internal::arg_map<format_context>::init(
|
||||
const basic_format_args<format_context>& args);
|
||||
|
||||
template FMT_API std::string internal::vformat<char>(
|
||||
string_view, basic_format_args<format_context>);
|
||||
|
||||
template FMT_API format_context::iterator internal::vformat_to(
|
||||
internal::buffer<char>&, string_view, basic_format_args<format_context>);
|
||||
|
||||
template FMT_API int internal::snprintf_float(double, int,
|
||||
internal::float_specs,
|
||||
internal::buffer<char>&);
|
||||
template FMT_API int internal::snprintf_float(long double, int,
|
||||
internal::float_specs,
|
||||
internal::buffer<char>&);
|
||||
template FMT_API int internal::format_float(double, int, internal::float_specs,
|
||||
internal::buffer<char>&);
|
||||
template FMT_API int internal::format_float(long double, int,
|
||||
internal::float_specs,
|
||||
internal::buffer<char>&);
|
||||
|
||||
// Explicit instantiations for wchar_t.
|
||||
|
||||
template FMT_API std::string internal::grouping_impl<wchar_t>(locale_ref);
|
||||
template FMT_API wchar_t internal::thousands_sep_impl(locale_ref);
|
||||
template FMT_API wchar_t internal::decimal_point_impl(locale_ref);
|
||||
|
||||
template FMT_API void internal::buffer<wchar_t>::append(const wchar_t*,
|
||||
const wchar_t*);
|
||||
|
||||
template FMT_API std::wstring internal::vformat<wchar_t>(
|
||||
wstring_view, basic_format_args<wformat_context>);
|
||||
FMT_END_NAMESPACE
|
|
@ -1 +1 @@
|
|||
Subproject commit f944a5992515d8bbee7c2be0642a0fe37a230519
|
||||
Subproject commit 9f53fbba7d910ca06f73149d5ec375f73a85e5ac
|
2
ext/sgp4
2
ext/sgp4
|
@ -1 +1 @@
|
|||
Subproject commit f5cb54b382a5b4787432ab5b9a1e83de1a224610
|
||||
Subproject commit 6b47861cd47a6e31841260c47a52b579f8cf2fa9
|
88
galileo.cc
88
galileo.cc
|
@ -24,91 +24,3 @@ bool getTOWFromInav(std::basic_string_view<uint8_t> inav, uint32_t *satTOW, uint
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
int GalileoMessage::parseFnav(std::basic_string_view<uint8_t> page)
|
||||
{
|
||||
const uint8_t* ptr = &page[0];
|
||||
int offset=0;
|
||||
auto gbum=[&ptr, &offset](int bits) {
|
||||
unsigned int ret = getbitu(ptr, offset, bits);
|
||||
offset += bits;
|
||||
return ret;
|
||||
};
|
||||
|
||||
auto gbsm=[&ptr, &offset](int bits) {
|
||||
int ret = getbits(ptr, offset, bits);
|
||||
offset += bits;
|
||||
return ret;
|
||||
};
|
||||
|
||||
wtype = gbum(6);
|
||||
if(wtype == 1) {
|
||||
/*int sv = */ (void)gbum(6);
|
||||
iodnav = gbum(10);
|
||||
t0c = gbum(14);
|
||||
af0 = gbsm(31);
|
||||
af1 = gbsm(21);
|
||||
af2 = gbsm(6);
|
||||
sisa = gbum(8);
|
||||
ai0 = gbum(11);
|
||||
ai1 = gbsm(11);
|
||||
ai2 = gbsm(14);
|
||||
sf1 = gbum(1);
|
||||
sf2 = gbum(1);
|
||||
sf3 = gbum(1);
|
||||
sf4 = gbum(1);
|
||||
sf5 = gbum(1);
|
||||
BGDE1E5a = gbsm(10);
|
||||
e5ahs = gbum(2);
|
||||
wn = gbum(12);
|
||||
tow = gbum(20);
|
||||
e5advs=gbum(1);
|
||||
}
|
||||
else if(wtype==2) {
|
||||
iodnav = gbum(10);
|
||||
m0 = gbsm(32);
|
||||
omegadot = gbsm(24);
|
||||
e = gbum(32);
|
||||
sqrtA = gbum(32);
|
||||
omega0 = gbsm(32);
|
||||
idot = gbsm(14);
|
||||
wn = gbum(12);
|
||||
tow = gbum(20);
|
||||
}
|
||||
else if(wtype == 3) {
|
||||
iodnav = gbum(10);
|
||||
i0 = gbsm(32);
|
||||
omega = gbsm(32);
|
||||
deltan = gbsm(16);
|
||||
cuc = gbsm(16);
|
||||
cus = gbsm(16);
|
||||
crc =gbsm(16);
|
||||
crs = gbsm(16);
|
||||
t0e = gbum(14);
|
||||
wn = gbum(12);
|
||||
tow = gbum(20);
|
||||
}
|
||||
else if(wtype == 4) {
|
||||
iodnav = gbum(10);
|
||||
cic = gbsm(16);
|
||||
cis = gbsm(16);
|
||||
|
||||
a0 = gbsm(32);
|
||||
a1 = gbsm(24);
|
||||
|
||||
dtLS= gbsm(8);
|
||||
t0t = gbum(8);
|
||||
wn0t = gbum(8);
|
||||
wnLSF = gbum(8);
|
||||
|
||||
dn = gbum(3);
|
||||
dtLSF = gbsm(8);
|
||||
|
||||
t0g = gbum(8);
|
||||
a0g = gbsm(16);
|
||||
a1g = gbsm(12);
|
||||
wn0g = gbum(6);
|
||||
tow = gbum(20);
|
||||
}
|
||||
return wtype;
|
||||
}
|
||||
|
|
41
galileo.hh
41
galileo.hh
|
@ -8,7 +8,7 @@
|
|||
|
||||
bool getTOWFromInav(std::basic_string_view<uint8_t> inav, uint32_t *satTOW, uint16_t *wn);
|
||||
|
||||
struct GalileoMessage : GPSLikeEphemeris
|
||||
struct GalileoMessage
|
||||
{
|
||||
uint8_t wtype;
|
||||
|
||||
|
@ -31,20 +31,13 @@ struct GalileoMessage : GPSLikeEphemeris
|
|||
int parse(std::basic_string_view<uint8_t> page)
|
||||
{
|
||||
wtype = getbitu(&page[0], 0, 6);
|
||||
if(wtype >= parsers.size()) {
|
||||
// std::cerr<<"Asked for impossible galileo type "<<(int)wtype<<std::endl;
|
||||
return wtype;
|
||||
}
|
||||
|
||||
std::invoke(parsers.at(wtype), this, page);
|
||||
return wtype;
|
||||
}
|
||||
|
||||
int parseFnav(std::basic_string_view<uint8_t> page);
|
||||
|
||||
uint8_t sparetime{0};
|
||||
uint16_t wn{0};
|
||||
uint32_t tow{0};
|
||||
uint16_t wn{0}; // we put the "unrolled" week number here!
|
||||
uint32_t tow{0}; // "last seen"
|
||||
int iodalmanac;
|
||||
int wnalmanac;
|
||||
int t0almanac;
|
||||
|
@ -65,13 +58,13 @@ struct GalileoMessage : GPSLikeEphemeris
|
|||
{
|
||||
}
|
||||
|
||||
uint8_t e5ahs{0}, e5bhs{0}, e1bhs{0};
|
||||
uint8_t e5bhs{0}, e1bhs{0};
|
||||
uint8_t gpshealth{0};
|
||||
uint16_t ai0{0};
|
||||
int16_t ai1{0}, ai2{0};
|
||||
bool sf1{0}, sf2{0}, sf3{0}, sf4{0}, sf5{0};
|
||||
int BGDE1E5a{0}, BGDE1E5b{0};
|
||||
bool e5advs{false}, e5bdvs{false}, e1bdvs{false};
|
||||
bool e5bdvs{false}, e1bdvs{false};
|
||||
bool disturb1{false}, disturb2{false}, disturb3{false}, disturb4{false}, disturb5{false};
|
||||
|
||||
//
|
||||
|
@ -101,11 +94,6 @@ struct GalileoMessage : GPSLikeEphemeris
|
|||
|
||||
uint16_t iodnav;
|
||||
|
||||
int getIOD() const
|
||||
{
|
||||
return iodnav;
|
||||
}
|
||||
|
||||
struct Almanac
|
||||
{
|
||||
int svid{-1};
|
||||
|
@ -138,10 +126,7 @@ struct GalileoMessage : GPSLikeEphemeris
|
|||
double getCrc() const { return 0; } // meters
|
||||
double getCrs() const { return 0; } // meters
|
||||
double getDeltan()const { return 0; } //radians/s
|
||||
int getIOD() const
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
} alma1, alma2, alma3;
|
||||
|
||||
|
||||
|
@ -191,16 +176,14 @@ struct GalileoMessage : GPSLikeEphemeris
|
|||
return t0g * 3600;
|
||||
}
|
||||
|
||||
// pair of nanosecond, nanosecond/s
|
||||
std::pair<double, double> getAtomicOffset(int tow) const
|
||||
{
|
||||
int delta = ephAge(tow, getT0c());
|
||||
// 2^-34 2^-46 2^-59
|
||||
double cur = af0 + ldexp(1.0*delta*af1, -12) + ldexp(1.0*delta*delta*af2, -25);
|
||||
double trend = ldexp(af1, -12) + ldexp(2.0*delta*af2, -25);
|
||||
|
||||
// now in units of 2^-34 seconds, which are ~0.058 nanoseconds each
|
||||
double factor = ldexp(1000000000.0, -34);
|
||||
double factor = ldexp(1000000000, -34);
|
||||
return {factor * cur, factor * trend};
|
||||
}
|
||||
|
||||
|
@ -221,12 +204,10 @@ struct GalileoMessage : GPSLikeEphemeris
|
|||
|
||||
return {factor * cur, factor * trend};
|
||||
}
|
||||
// pair of nanosecond, nanosecond/s
|
||||
|
||||
std::pair<double, double> getGPSOffset(int tow, int wn) const
|
||||
{
|
||||
int dw = (int)(wn%64) - (int)(wn0g%64);
|
||||
if(dw > 31)
|
||||
dw = dw - 64;
|
||||
int dw = (int)(uint8_t)wn - (int)(uint8_t) wn0g;
|
||||
int delta = dw*7*86400 + tow - getT0g(); // NOT ephemeris age tricks
|
||||
|
||||
// 2^-35 2^-51 3600
|
||||
|
@ -270,8 +251,8 @@ struct GalileoMessage : GPSLikeEphemeris
|
|||
sf3 = getbitu(&page[0], 44, 1);
|
||||
sf4 = getbitu(&page[0], 45, 1);
|
||||
sf5 = getbitu(&page[0], 46, 1);
|
||||
BGDE1E5a = getbits(&page[0], 47, 10); // 2^-32 s
|
||||
BGDE1E5b = getbits(&page[0], 57, 10); // 2^-32 s
|
||||
BGDE1E5a = getbits(&page[0], 47, 10);
|
||||
BGDE1E5b = getbits(&page[0], 57, 10);
|
||||
|
||||
e5bhs = getbitu(&page[0], 67, 2);
|
||||
e1bhs = getbitu(&page[0], 69, 2);
|
||||
|
|
421
galmonmon.cc
421
galmonmon.cc
|
@ -1,421 +0,0 @@
|
|||
#include <optional>
|
||||
#include "minicurl.hh"
|
||||
#include <iostream>
|
||||
#include "navmon.hh"
|
||||
#include "fmt/format.h"
|
||||
#include "fmt/printf.h"
|
||||
#include "ext/powerblog/h2o-pp.hh"
|
||||
#include <variant>
|
||||
|
||||
#include "CLI/CLI.hpp"
|
||||
#include "version.hh"
|
||||
|
||||
static char program[]="galmonmon";
|
||||
|
||||
using namespace std;
|
||||
|
||||
extern const char* g_gitHash;
|
||||
|
||||
/*
|
||||
Monitoring the satellites for sensible alerts.
|
||||
|
||||
A satellite transitions state if:
|
||||
* galmon is up
|
||||
* galmon is up to date
|
||||
* We are seeing recent messages for the satellite
|
||||
* For more than 60 seconds this state is different than it was
|
||||
|
||||
A satellite becomes 'unobserved' if:
|
||||
* galmon is up
|
||||
* galmon is up to date
|
||||
* We haven't seen a message since an hour
|
||||
|
||||
A satellite becomes observed again if we have seen it for > 60 seconds.
|
||||
|
||||
At startup, every satellite assumes, without alert, the status we find for it, which is not a transition.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
class StateKeeper
|
||||
{
|
||||
public:
|
||||
typedef std::variant<bool, double, string> var_t;
|
||||
void setBoolNames(string_view name, string_view offName, string_view onName);
|
||||
std::optional<string> reportState(string_view thing, string_view name, var_t state, const std::string& state_text="");
|
||||
std::optional<string> getState(string_view thing, string_view name);
|
||||
|
||||
std::optional<string> getPrevState(string_view thing, string_view name);
|
||||
|
||||
struct State
|
||||
{
|
||||
var_t state;
|
||||
time_t since;
|
||||
string text;
|
||||
};
|
||||
|
||||
std::optional<State> getFullState(string_view thing, string_view name);
|
||||
std::optional<State> getPrevFullState(string_view thing, string_view name);
|
||||
|
||||
private:
|
||||
struct Names
|
||||
{
|
||||
string offName, onName;
|
||||
};
|
||||
map<string, Names> names;
|
||||
|
||||
|
||||
struct ThingState
|
||||
{
|
||||
std::optional<State> prev, live, provisional;
|
||||
};
|
||||
|
||||
map<string, map<string, ThingState> > states;
|
||||
private:
|
||||
std::string getName(string_view name, var_t state)
|
||||
{
|
||||
if(auto ptr = std::get_if<bool>(&state)) {
|
||||
if(*ptr) {
|
||||
return names[(string)name].onName;
|
||||
}
|
||||
else
|
||||
return names[(string)name].offName;
|
||||
}
|
||||
if(auto ptr = std::get_if<string>(&state)) {
|
||||
return *ptr;
|
||||
}
|
||||
if(auto ptr = std::get_if<double>(&state)) {
|
||||
return to_string(*ptr);
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
};
|
||||
|
||||
void StateKeeper::setBoolNames(string_view name, string_view offName, string_view onName)
|
||||
{
|
||||
names[(string)name].offName = offName;
|
||||
names[(string)name].onName = onName;
|
||||
}
|
||||
|
||||
std::optional<string> StateKeeper::getState(string_view thing, string_view name)
|
||||
{
|
||||
if(states.count((string)thing) && states[(string)thing].count((string) name) && states[(string)thing][(string)name].live) {
|
||||
return getName(name, states[(string)thing][(string)name].live->state);
|
||||
}
|
||||
return std::optional<string>();
|
||||
}
|
||||
|
||||
std::optional<StateKeeper::State> StateKeeper::getFullState(string_view thing, string_view name)
|
||||
{
|
||||
if(states.count((string)thing) && states[(string)thing].count((string) name) && states[(string)thing][(string)name].live) {
|
||||
return states[(string)thing][(string)name].live;
|
||||
}
|
||||
return std::optional<StateKeeper::State>();
|
||||
}
|
||||
|
||||
|
||||
std::optional<string> StateKeeper::getPrevState(string_view thing, string_view name)
|
||||
{
|
||||
if(states.count((string)thing) && states[(string)thing].count((string) name) && states[(string)thing][(string)name].prev) {
|
||||
return getName(name, states[(string)thing][(string)name].prev->state);
|
||||
}
|
||||
return std::optional<string>();
|
||||
}
|
||||
|
||||
std::optional<StateKeeper::State> StateKeeper::getPrevFullState(string_view thing, string_view name)
|
||||
{
|
||||
if(states.count((string)thing) && states[(string)thing].count((string) name) && states[(string)thing][(string)name].prev) {
|
||||
return states[(string)thing][(string)name].prev;
|
||||
}
|
||||
return std::optional<StateKeeper::State>();
|
||||
}
|
||||
|
||||
|
||||
std::optional<string> StateKeeper::reportState(string_view thing, string_view name, var_t newstate, const std::string& state_text)
|
||||
{
|
||||
auto& state = states[(string)thing][(string)name];
|
||||
std::optional<string> ret;
|
||||
|
||||
time_t now = time(0);
|
||||
|
||||
if(!state.live) { // we had no state yet
|
||||
state.live = State{newstate, now, state_text};
|
||||
state.provisional.reset(); // for good measure
|
||||
return ret;
|
||||
}
|
||||
else if(state.live->state == newstate) { // confirmation of current state
|
||||
state.live->text = state_text; // update text perhaps
|
||||
state.provisional.reset();
|
||||
return ret;
|
||||
}
|
||||
else if(!state.provisional) { // new provisional state
|
||||
state.provisional = State{newstate, now, state_text};
|
||||
return ret;
|
||||
}
|
||||
else {
|
||||
if(now - state.provisional->since > 60) { // provisional state has been confirmed
|
||||
state.prev = state.live;
|
||||
state.live = state.provisional;
|
||||
state.provisional.reset();
|
||||
return getName(name, state.live->state);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
StateKeeper g_sk;
|
||||
|
||||
void sendTweet(const string& tweet)
|
||||
{
|
||||
string etweet = tweet;
|
||||
//system((string("twurl -X POST /1.1/statuses/update.json -d \"media_ids=1215649475231997953&status=")+etweet+"\" >> twitter.log").c_str());
|
||||
if(system((string("twurl -X POST /1.1/statuses/update.json -d \"status=")+etweet+"\" >> twitter.log").c_str()) < 0) {
|
||||
cout<<"Problem tweeting!"<<endl;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
MiniCurl mc;
|
||||
MiniCurl::MiniCurlHeaders mch;
|
||||
string url="https://galmon.eu/";
|
||||
// string url="http://[::1]:29599/";
|
||||
bool doVERSION{false};
|
||||
|
||||
CLI::App app(program);
|
||||
|
||||
app.add_flag("--version", doVERSION, "show program version and copyright");
|
||||
app.add_option("--url,-u", url, "URL of navparse process to retrieve status from");
|
||||
bool doTweet{false};
|
||||
app.add_flag("--tweet,-t", doTweet, "Actually send out tweets");
|
||||
|
||||
try {
|
||||
app.parse(argc, argv);
|
||||
} catch(const CLI::Error &e) {
|
||||
return app.exit(e);
|
||||
}
|
||||
|
||||
if(doVERSION) {
|
||||
showVersion(program, g_gitHash);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
g_sk.setBoolNames("health", "healthy", "unhealthy");
|
||||
g_sk.setBoolNames("eph-too-old", "ephemeris fresh", "ephemeris aged");
|
||||
g_sk.setBoolNames("silent", "observed", "not observed");
|
||||
g_sk.setBoolNames("osnma", "OFF", "ON");
|
||||
|
||||
std::variant<bool, string> tst;
|
||||
|
||||
auto observers = nlohmann::json::parse(mc.getURL(url+"observers.json"));
|
||||
|
||||
|
||||
// sendTweet("Galmonmon " +string(g_gitHash)+ " started, " + std::to_string(observers.size()) +" observers seen");
|
||||
cout<<("Galmonmon " +string(g_gitHash)+ " started, " + std::to_string(observers.size()) +" observers seen")<<endl;
|
||||
|
||||
string meh="🤔";
|
||||
string unhappy="😬";
|
||||
string alert="🚨";
|
||||
|
||||
for(;;) {
|
||||
try {
|
||||
auto res = mc.getURL(url+"global.json");
|
||||
auto j = nlohmann::json::parse(res);
|
||||
if(!j.count("last-seen") || time(0) - (long) j["last-seen"] > 30) {
|
||||
cout<<"Galmon at "<<url<<" is not current"<<endl;
|
||||
sleep(10);
|
||||
continue;
|
||||
}
|
||||
/*
|
||||
if(auto iter = j.find("last-seen"); iter != j.end()) {
|
||||
cout<<"Galmon behind by " << (time(0) - (long) iter.value()) <<" seconds"<<endl;
|
||||
}
|
||||
*/
|
||||
|
||||
res = mc.getURL(url+"sbas.json");
|
||||
j = nlohmann::json::parse(res);
|
||||
std::optional<string> sbasHealthChange;
|
||||
for(auto iter = j.begin(); iter != j.end(); ++iter) {
|
||||
if(iter.value().count("health")) {
|
||||
string name = sbasName(atoi(iter.key().c_str()));
|
||||
sbasHealthChange = g_sk.reportState(name, "sbas-health", (string)iter.value()["health"]);
|
||||
// cout<<"Setting state for "<< name <<" to "<< (string)iter.value()["health"] << endl;
|
||||
|
||||
|
||||
if(sbasHealthChange) {
|
||||
ostringstream out;
|
||||
out<<"✈️ augmentation system "<<name<<" health changed: "<<*sbasHealthChange;
|
||||
cout<<out.str()<<endl;
|
||||
if(doTweet)
|
||||
sendTweet(out.str());
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res = mc.getURL(url+"svs.json");
|
||||
j = nlohmann::json::parse(res);
|
||||
bool first=true;
|
||||
bool globalOsnma=false;
|
||||
for(const auto& sv : j) {
|
||||
if(!sv.count("gnssid") || !sv.count("fullName") || !sv.count("sigid")) {
|
||||
cout<<"Skipping "<< sv.count("gnssid") <<", "<< sv.count("fullName") <<", " <<sv.count("sigid") <<endl;
|
||||
continue;
|
||||
}
|
||||
int gnssid = sv["gnssid"], sigid = sv["sigid"];
|
||||
string fullName = sv["fullName"];
|
||||
|
||||
if(!(gnssid == 2 && sigid==1) &&
|
||||
!(gnssid == 0 && sigid==0) &&
|
||||
!(gnssid == 3 && sigid==0) &&
|
||||
!(gnssid == 6 && sigid==0))
|
||||
continue;
|
||||
|
||||
int numfresh=0;
|
||||
// we only track "received status" for GPS and Galileo
|
||||
bool notseen= gnssid ==0 || gnssid==2;
|
||||
if(!sv.count("healthissue") || !sv.count("eph-age-m") || !sv.count("sisa") || !sv.count("perrecv")) {
|
||||
// cout<<"Skipping "<<fullName<<" in loop: "<<sv.count("healthissue")<<", "<<sv.count("eph-age-m") << ", "<<sv.count("perrecv")<<endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
for(const auto& recv : sv["perrecv"]) {
|
||||
if(!recv.count("last-seen-s")) {
|
||||
cout<<"Missing last-seen-s"<<endl;
|
||||
continue;
|
||||
}
|
||||
if((int)recv["last-seen-s"] < 60)
|
||||
numfresh++;
|
||||
if((int)recv["last-seen-s"] < 3600)
|
||||
notseen=false;
|
||||
}
|
||||
if(sv.count("osnma") && sv["osnma"]==true)
|
||||
globalOsnma |= 1;
|
||||
|
||||
|
||||
auto healthchange = g_sk.reportState(fullName, "health", sv["healthissue"]!=0);
|
||||
std::optional<string> tooOldChange;
|
||||
if(gnssid == 2)
|
||||
tooOldChange = g_sk.reportState(fullName, "eph-too-old", sv["eph-age-m"] > 105, fmt::sprintf("%.2f", (double)sv["eph-age-m"]));
|
||||
else
|
||||
tooOldChange = g_sk.reportState(fullName, "eph-too-old", sv["eph-age-m"] > 140, fmt::sprintf("%.2f", (double)sv["eph-age-m"]));
|
||||
|
||||
auto seenChange = g_sk.reportState(fullName, "silent", notseen);
|
||||
|
||||
auto sisaChange = g_sk.reportState(fullName, "sisa", (double) sv["sisa-m"], (string)sv["sisa"]);
|
||||
|
||||
double ephdisco = sv.count("latest-disco") ? (double)sv["latest-disco"] : -1.0;
|
||||
auto ephdiscochange = g_sk.reportState(fullName, "eph-disco", ephdisco);
|
||||
if(ephdisco == -1.0)
|
||||
ephdiscochange.reset();
|
||||
|
||||
double timedisco = sv.count("time-disco") ? fabs((double)sv["time-disco"]) : 0.0;
|
||||
auto timediscochange = g_sk.reportState(fullName, "time-disco", timedisco);
|
||||
|
||||
/*
|
||||
cout<<fullName<<": numfresh "<<numfresh << " healthissue "<<sv["healthissue"];
|
||||
cout<<" eph-age-m "<< fmt::sprintf("%.2f", (double)sv["eph-age-m"]);
|
||||
cout<<" not-seen "<<notseen;
|
||||
if(auto val = g_sk.getState(fullName, "eph-too-old"); val) {
|
||||
cout<<" eph-too-old \""<<*val<<"\"";
|
||||
}
|
||||
if(auto val = g_sk.getState(fullName, "health"); val) {
|
||||
cout<<" health \""<<*val<<"\"";
|
||||
}
|
||||
|
||||
if(ephdiscochange) {
|
||||
cout<<fullName <<": ephemeris (orbit description) discontinuity of "<< fmt::sprintf("%.02f", ephdisco)<<" meters"<<endl;
|
||||
}
|
||||
if(timediscochange) {
|
||||
cout<<fullName <<": clock jump of "<< fmt::sprintf("%.02f", timedisco)<<" nanoseconds (= " <<fmt::sprintf("%.01f meters)", timedisco/2.99)<<endl;
|
||||
}
|
||||
*/
|
||||
ostringstream out;
|
||||
|
||||
|
||||
if(healthchange)
|
||||
out<< *healthchange<<" ";
|
||||
if(tooOldChange) {
|
||||
out<< "Ephemeris age: "<<*tooOldChange<<", new value: "<< fmt::sprintf("%.02f", (double)sv["eph-age-m"])<<" minutes, old: ";
|
||||
out<< g_sk.getPrevFullState(fullName, "eph-too-old")->text <<" minutes";
|
||||
}
|
||||
if(seenChange)
|
||||
out<< *seenChange<<" ";
|
||||
|
||||
if(ephdiscochange && (gnssid ==0 || gnssid == 2) && ephdisco > 1.45) {
|
||||
if(ephdisco > 10)
|
||||
out<<alert;
|
||||
else if(ephdisco > 5)
|
||||
out<<unhappy;
|
||||
else
|
||||
out<<meh;
|
||||
|
||||
out<<" ephemeris (orbit description) discontinuity of "<< fmt::sprintf("%.02f", ephdisco)<<" meters"<<endl;
|
||||
}
|
||||
if(timediscochange && (gnssid == 2 && timedisco > 2.5)) {
|
||||
if(timedisco > 10)
|
||||
out<<alert;
|
||||
else if(timedisco > 5)
|
||||
out<<unhappy;
|
||||
else
|
||||
out<<meh;
|
||||
out<<" clock jump of "<< fmt::sprintf("%.02f", timedisco)<<" nanoseconds (= " <<fmt::sprintf("%.01f meters)", timedisco/2.99)<<endl;
|
||||
}
|
||||
|
||||
if(sisaChange) {
|
||||
ostringstream tmp;
|
||||
auto state = g_sk.getFullState(fullName, "sisa");
|
||||
auto prevState = g_sk.getPrevFullState(fullName, "sisa");
|
||||
tmp<< " SISA/URA reported ranging accuracy changed, new: "<<state->text<<", old: " << prevState->text;
|
||||
if(get<double>(state->state) > 3 || get<double>(prevState->state) > 3)
|
||||
out << tmp.str();
|
||||
else
|
||||
cout<<"Not reporting: "<<tmp.str()<<endl;
|
||||
}
|
||||
|
||||
string tweet;
|
||||
if(!out.str().empty()) {
|
||||
if(gnssid == 0)
|
||||
tweet = "GPS";
|
||||
else if(gnssid == 2)
|
||||
tweet = "Galileo";
|
||||
else if(gnssid == 3)
|
||||
tweet ="BeiDou";
|
||||
else if(gnssid== 6)
|
||||
tweet = "GLONASS";
|
||||
tweet += " " + fullName +": ";
|
||||
tweet += out.str();
|
||||
|
||||
|
||||
if(doTweet)
|
||||
sendTweet(tweet);
|
||||
if(first)
|
||||
cout<<"\n";
|
||||
first=false;
|
||||
cout<<humanTimeNow() <<" " << tweet << endl;
|
||||
}
|
||||
}
|
||||
|
||||
auto osnmachange = g_sk.reportState("global", "osnma", globalOsnma);
|
||||
if(osnmachange) {
|
||||
string tweet= "Galileo OSNMA new state: "+*osnmachange;
|
||||
cout<<humanTimeNow()<< " " <<tweet<<endl;
|
||||
if(doTweet) {
|
||||
sendTweet(tweet);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
cout<<".";
|
||||
cout.flush();
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
cerr<<"\nError: "<<e.what()<<endl;
|
||||
}
|
||||
sleep(20);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
#include "githash.h"
|
||||
|
||||
const char* g_gitHash=GIT_HASH;
|
94
glonass.cc
94
glonass.cc
|
@ -1,15 +1,6 @@
|
|||
#include "glonass.hh"
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
using std::cout;
|
||||
using std::endl;
|
||||
|
||||
static const double ae = 6378.136; // km // IERS: 6378136.6
|
||||
static const double mu = 398.6004418E3; // km3/s2 // IERS: 3.986004418
|
||||
static const double J2 = 1082625.75E-9; // IERS: 1.0826359
|
||||
static const double oe = 7.2921151467E-5; // rad/s // IERS: 7.292115
|
||||
|
||||
// this strips out spare bits + parity, and leaves 10 clean 24 bit words
|
||||
std::basic_string<uint8_t> getGlonassMessage(std::basic_string_view<uint8_t> payload)
|
||||
|
@ -56,88 +47,3 @@ uint32_t getGlonassT0e(time_t referencetime, int Tb)
|
|||
tm.tm_sec = 0;
|
||||
return timegm(&tm)-3*3600; // and back to UTC
|
||||
}
|
||||
|
||||
// y[0] .. y[2] --> X, Y, Z
|
||||
// y[3] .. y[5] --> Vx, Vy, Vz
|
||||
static void ff (const double A[3], const double y[6], double f[6])
|
||||
{
|
||||
double X = y[0];
|
||||
double Y = y[1];
|
||||
double Z = y[2];
|
||||
double Vx = y[3];
|
||||
double Vy = y[4];
|
||||
double Vz = y[5];
|
||||
double R = sqrt (X*X + Y*Y + Z*Z);
|
||||
double C0 = pow(R, -3);
|
||||
double C1 = 1.5*J2*ae*ae*pow(R, -5);
|
||||
double C2 = 5*pow(Z/R, 2);
|
||||
double CXY = C0 + C1 * (1 - C2);
|
||||
double CZ = C0 + C1 * (3 - C2);
|
||||
f[0] = Vx;
|
||||
f[1] = Vy;
|
||||
f[2] = Vz;
|
||||
f[3] = (- mu*CXY + oe*oe)*X + 2*oe*Vy + A[0];
|
||||
f[4] = (- mu*CXY + oe*oe)*Y - 2*oe*Vx + A[1];
|
||||
f[5] = - mu*CZ*Z + A[2];
|
||||
}
|
||||
|
||||
static void rk4step (const double A[3], double y[6], double h)
|
||||
{
|
||||
double k1[6], k2[6], k3[6], k4[6], z[6];
|
||||
|
||||
ff (A, y, k1);
|
||||
for (int j = 0; j < 6; j ++)
|
||||
z[j] = y[j] + 0.5*h*k1[j];
|
||||
|
||||
ff (A, z, k2);
|
||||
for (int j = 0; j < 6; j ++)
|
||||
z[j] = y[j] + 0.5*h*k2[j];
|
||||
|
||||
ff (A, z, k3);
|
||||
for (int j = 0; j < 6; j ++)
|
||||
z[j] = y[j] + h*k3[j];
|
||||
|
||||
ff (A, z, k4);
|
||||
for (int j = 0; j < 6; j ++)
|
||||
y[j] = y[j] + h * (k1[j] + 2*(k2[j] + k3[j]) + k4[j]) / 6;
|
||||
}
|
||||
|
||||
|
||||
using Clock = std::chrono::steady_clock;
|
||||
|
||||
static double passedMsec(const Clock::time_point& then, const Clock::time_point& now)
|
||||
{
|
||||
return std::chrono::duration_cast<std::chrono::microseconds>(now - then).count()/1000.0;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static double passedMsec(const Clock::time_point& then)
|
||||
{
|
||||
return passedMsec(then, Clock::now());
|
||||
}
|
||||
#endif
|
||||
|
||||
double getCoordinates(double tow, const GlonassMessage& eph, Point* p)
|
||||
{
|
||||
// auto start = Clock::now();
|
||||
|
||||
double y0[6] = {ldexp(eph.x, -11), ldexp(eph.y, -11), ldexp(eph.z, -11),
|
||||
ldexp(eph.dx, -20), ldexp(eph.dy, -20), ldexp(eph.dz, -20)};
|
||||
double A[3] = {ldexp(eph.ddx, -30), ldexp(eph.ddy, -30), ldexp(eph.ddz, -30)};
|
||||
|
||||
// These all fake
|
||||
uint32_t glotime = eph.getGloTime();
|
||||
uint32_t gloT0e = getGlonassT0e(glotime + 820368000, eph.Tb);
|
||||
uint32_t ephtow = (gloT0e - 820368000) % (7*86400);
|
||||
|
||||
double DT = tow - ephtow;
|
||||
int n = abs (DT / 100) + 1; // integrate in roughly 100 second steps
|
||||
double h = DT / n;
|
||||
for (int j = 0; j < n; j ++)
|
||||
rk4step (A, y0, h);
|
||||
|
||||
*p = Point (1E3*y0[0], 1E3*y0[1], 1E3*y0 [2]);
|
||||
// static double total=0;
|
||||
// cout<<"Took: "<<(total+=passedMsec(start))<<" ms" <<endl;
|
||||
return 0;
|
||||
}
|
||||
|
|
26
glonass.hh
26
glonass.hh
|
@ -4,7 +4,6 @@
|
|||
#include "bits.hh"
|
||||
#include <iostream>
|
||||
#include <math.h>
|
||||
#include "minivec.hh"
|
||||
std::basic_string<uint8_t> getGlonassessage(std::basic_string_view<uint8_t> payload);
|
||||
|
||||
struct GlonassMessage
|
||||
|
@ -53,9 +52,6 @@ struct GlonassMessage
|
|||
double getdY() { return ldexp(dy*1000.0, -20); }
|
||||
double getdZ() { return ldexp(dz*1000.0, -20); }
|
||||
|
||||
// this is there to make doDoppler work, which sadly wants to do
|
||||
// arithmetic to get the age of an ephemeris
|
||||
double getT0e() const { return 0; }
|
||||
|
||||
double getRadius() { return sqrt(getX()*getX() + getY()*getY() + getZ()*getZ()); }
|
||||
|
||||
|
@ -130,26 +126,7 @@ struct GlonassMessage
|
|||
P4 = getbitu(&gstr[0], 85-34, 1);
|
||||
deltaTaun = getbitsglonass(&gstr[0], 85 - 58, 4);
|
||||
}
|
||||
// nanosecond, nanosecond/s pair
|
||||
std::pair<double, double> getUTCOffset(int tow) const
|
||||
{
|
||||
std::pair<double, double> ret;
|
||||
ret.second=0;
|
||||
|
||||
ret.first = 1000000000.0*ldexp(tauc, -31); // this is Glonass-M
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::pair<double, double> getGPSOffset(int tow) const
|
||||
{
|
||||
std::pair<double, double> ret;
|
||||
ret.second=0;
|
||||
ret.first = 1000000000.0*ldexp(taugps, -30);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
uint32_t getGloTime() const;
|
||||
|
||||
uint8_t n4{0}; // counting from 1996 ('n4=1'), this is the 4-year plan index we are currently in
|
||||
|
@ -160,7 +137,7 @@ struct GlonassMessage
|
|||
{
|
||||
n4=getbitu(&gstr[0], 85-36, 5);
|
||||
taugps = getbitsglonass(&gstr[0], 85-31, 22);
|
||||
tauc = getbitsglonass(&gstr[0], 85-69, 32); // check the NEW ICD
|
||||
tauc = getbitsglonass(&gstr[0], 85-69, 32);
|
||||
l_n = getbitu(&gstr[0], 85 - 9, 1);
|
||||
}
|
||||
|
||||
|
@ -218,4 +195,3 @@ struct GlonassMessage
|
|||
|
||||
};
|
||||
uint32_t getGlonassT0e(time_t referencetime, int Tb);
|
||||
double getCoordinates(double tow, const GlonassMessage& eph, Point* p);
|
||||
|
|
81
gndate.cc
81
gndate.cc
|
@ -1,81 +0,0 @@
|
|||
#include "navmon.hh"
|
||||
#include <iostream>
|
||||
#include "CLI/CLI.hpp"
|
||||
#include "version.hh"
|
||||
|
||||
extern const char* g_gitHash;
|
||||
using namespace std;
|
||||
|
||||
int main(int argc, char** argv)
|
||||
try
|
||||
{
|
||||
string program("gndate");
|
||||
CLI::App app(program);
|
||||
string date;
|
||||
int galwn{-1};
|
||||
bool doProgOutput{false};
|
||||
bool doGPSWN{false}, doGALWN{false}, doBEIDOUWN{false}, doVERSION{false}, doUTC{false};
|
||||
app.add_flag("--version", doVERSION, "show program version and copyright");
|
||||
app.add_option("--date,-d", date, "yyyy-mm-dd hh:mm[:ss] hh:mm yyyymmdd hhmm");
|
||||
app.add_option("--date-gal-wn", galwn, "Give data for this Galileo week number");
|
||||
app.add_flag("--utc,-u", doUTC, "Interpret --date,-d as UTC");
|
||||
app.add_flag("--gps-wn", doGPSWN, "Print GPS week number");
|
||||
app.add_flag("--gal-wn", doGALWN, "Print GPS week number");
|
||||
app.add_flag("--prog-output", doProgOutput, "Modulate some date formats for use as parameters to programs");
|
||||
try {
|
||||
app.parse(argc, argv);
|
||||
} catch(const CLI::Error &e) {
|
||||
return app.exit(e);
|
||||
}
|
||||
|
||||
if(doVERSION) {
|
||||
showVersion(program.c_str(), g_gitHash);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
|
||||
time_t now;
|
||||
if(date.empty())
|
||||
now = time(0);
|
||||
else {
|
||||
if(doUTC)
|
||||
setenv("TZ", "UTC", 1);
|
||||
now = parseTime(date);
|
||||
}
|
||||
|
||||
int wn, tow;
|
||||
|
||||
if(galwn >= 0) {
|
||||
time_t week=utcFromGST(galwn, 0);
|
||||
if(doProgOutput)
|
||||
cout<<influxTime(week) << endl;
|
||||
else
|
||||
cout<<humanTime(week)<< " - " << humanTime(week+7*86400) << endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(doGPSWN) {
|
||||
getGPSDateFromUTC(now, wn, tow);
|
||||
cout<<wn<<endl;
|
||||
}
|
||||
else if(doGALWN) {
|
||||
getGalDateFromUTC(now, wn, tow);
|
||||
cout<<wn<<endl;
|
||||
}
|
||||
else if(doBEIDOUWN) {
|
||||
getBeiDouDateFromUTC(now, wn, tow);
|
||||
cout<<wn<<endl;
|
||||
}
|
||||
else {
|
||||
getGPSDateFromUTC(now, wn, tow);
|
||||
cout<<"GPS Week Number (non-wrapped): "<< wn << ", tow " << tow << endl;
|
||||
getGalDateFromUTC(now, wn, tow);
|
||||
cout<<"Galileo Week Number: "<< wn << ", tow " << tow << endl;
|
||||
getBeiDouDateFromUTC(now, wn, tow);
|
||||
cout<<"BeiDou Week Number: "<< wn << ", sow " << tow << endl;
|
||||
|
||||
}
|
||||
}
|
||||
catch(exception& e) {
|
||||
cerr<<"Error: "<<e.what()<<endl;
|
||||
}
|
123
gps.cc
123
gps.cc
|
@ -13,126 +13,3 @@ std::basic_string<uint8_t> getCondensedGPSMessage(std::basic_string_view<uint8_t
|
|||
|
||||
}
|
||||
|
||||
// expects input as 24 bit read to to use messages, returns frame number
|
||||
int GPSState::parseGPSMessage(std::basic_string_view<uint8_t> cond, uint8_t* pageptr)
|
||||
{
|
||||
using namespace std;
|
||||
int frame = getbitu(&cond[0], 24+19, 3);
|
||||
// 10 * 4 bytes in payload now
|
||||
tow = 1.5*(getbitu(&cond[0], 24, 17)*4);
|
||||
// cerr << "Preamble: "<<getbitu(&cond[0], 0, 8) <<", frame: "<< frame<<", truncated TOW: "<<tow<<endl;
|
||||
if(frame == 1) {
|
||||
// word 1, word 2 are TLM and HOW
|
||||
// 2 bits of padding on each word
|
||||
// word3:
|
||||
// 1-10: WN
|
||||
// 11-12: Which codes 00 = invalid, 01 = P-code on, 10 = C/A code on, 11 invalid
|
||||
// 13-16: URA, 0-15 scale
|
||||
// 17-22: 0 is ok
|
||||
// 23-24: MSB of IODC
|
||||
|
||||
// word 8:
|
||||
// 1-8: LSB of IODC
|
||||
// 9-24:
|
||||
|
||||
wn = 2048 + getbitu(&cond[0], 2*24, 10);
|
||||
ura = getbitu(&cond[0], 2*24+12, 4);
|
||||
gpshealth = getbitu(&cond[0], 2*24+16, 6);
|
||||
|
||||
// cerr<<"GPS Week Number: "<< wn <<", URA: "<< (int)ura<<", health: "<<
|
||||
// (int)gpshealth <<endl;
|
||||
|
||||
af2 = getbits(&cond[0], 8*24, 8); // * 2^-55
|
||||
af1 = getbits(&cond[0], 8*24 + 8, 16); // * 2^-43
|
||||
af0 = getbits(&cond[0], 9*24, 22); // * 2^-31
|
||||
t0c = getbitu(&cond[0], 7*24 + 8, 16); // * 16
|
||||
// cerr<<"t0c*16: "<<t0c*16<<", af2: "<< (int)af2 <<", af1: "<< af1 <<", af0: "<<
|
||||
// af0 <<endl;
|
||||
}
|
||||
else if(frame == 2) {
|
||||
gpsiod = getbitu(&cond[0], 2*24, 8);
|
||||
t0e = getbitu(&cond[0], 9*24, 16) * 16.0; // WE SCALE THIS FOR THE USER!!
|
||||
// cerr<<"IODe "<<(int)iod<<", t0e "<< t0e << " = "<< 16* t0e <<"s"<<endl;
|
||||
|
||||
e= getbitu(&cond[0], 5*24+16, 32);
|
||||
// cerr<<"e: "<<ldexp(e, -33)<<", ";
|
||||
|
||||
|
||||
// sqrt(A), 32 bits, 2^-19
|
||||
sqrtA= getbitu(&cond[0], 7*24+ 16, 32);
|
||||
// double sqrtA=ldexp(sqrtA, -19); // 2^-19
|
||||
// cerr<<"Radius: "<<sqrtA*sqrtA<<endl;
|
||||
|
||||
crs = getbits(&cond[0], 2*24 + 8, 16); // 2^-5 meters
|
||||
deltan = getbits(&cond[0], 3*24, 16); // 2^-43 semi-circles/s
|
||||
m0 = getbits(&cond[0], 3*24+16, 32); // 2^-31 semi-circles
|
||||
|
||||
cuc = getbits(&cond[0], 5*24, 16); // 2^-29 RADIANS
|
||||
cus = getbits(&cond[0], 7*24, 16); // 2^-29 RADIANS
|
||||
}
|
||||
else if(frame == 3) {
|
||||
gpsiod = getbitu(&cond[0], 9*24, 8);
|
||||
cic = getbits(&cond[0], 2*24, 16); // 2^-29 RADIANS
|
||||
omega0 = getbits(&cond[0], 2*24 + 16, 32); // 2^-31 semi-circles
|
||||
cis = getbits(&cond[0], 4*24, 16); // 2^-29 radians
|
||||
i0 = getbits(&cond[0], 4*24 + 16, 32); // 2^-31, semicircles
|
||||
|
||||
crc = getbits(&cond[0], 6*24, 16); // 2^-5, meters
|
||||
omega = getbits(&cond[0], 6*24+16, 32); // 2^-31, semi-circles
|
||||
|
||||
omegadot = getbits(&cond[0], 8*24, 24); // 2^-43, semi-circles/s
|
||||
idot = getbits(&cond[0], 9*24+8, 14); // 2^-43, semi-cirlces/s
|
||||
}
|
||||
else if(frame == 4) { // this is a carousel frame
|
||||
int page = getbitu(&cond[0], 2*24 + 2, 6);
|
||||
if(pageptr)
|
||||
*pageptr=0;
|
||||
// cerr<<"Frame 4, page "<<page;
|
||||
if(page == 56) { // 56 is the new 18 somehow? See table 20-V of the ICD
|
||||
if(pageptr)
|
||||
*pageptr=18;
|
||||
a0 = getbits(&cond[0], 6*24 , 32); // 2^-30
|
||||
a1 = getbits(&cond[0], 5*24 , 24); // 2^-50
|
||||
|
||||
t0t = getbitu(&cond[0], 7*24 + 8, 8) * 4096; // WE SCALE THIS FOR THE USER!
|
||||
wn0t = getbitu(&cond[0], 7*24 + 16, 8);
|
||||
|
||||
dtLS = getbits(&cond[0], 8*24, 8);
|
||||
wnLSF= getbitu(&cond[0], 8*24 + 8, 8);
|
||||
dn = getbitu(&cond[0], 8*24 + 16, 8);
|
||||
dtLSF = getbits(&cond[0], 9*24, 8);
|
||||
|
||||
|
||||
// cerr<<": a0: "<<a0<<", a1: "<<a1<<", t0t: "<< t0t * (1<<12) <<", wn0t: "<< wn0t<<", rough offset: "<<ldexp(a0, -30)<<endl;
|
||||
// cerr<<"deltaTLS: "<< (int)dtLS<<", post "<< (int)dtLSF<<endl;
|
||||
return frame; // otherwise pageptr gets overwritten below
|
||||
}
|
||||
//else cerr<<endl;
|
||||
// page 18 contains UTC -> 56
|
||||
// page 25 -> 63
|
||||
// 2-10 -> 25 -> 32 ??
|
||||
}
|
||||
|
||||
if(frame == 5 || frame==4) { // this is a caroussel frame
|
||||
gpsalma.dataid = getbitu(&cond[0], 2*24, 2);
|
||||
gpsalma.sv = getbitu(&cond[0], 2*24+2, 6);
|
||||
|
||||
if(pageptr)
|
||||
*pageptr= gpsalma.sv;
|
||||
|
||||
|
||||
gpsalma.e = getbitu(&cond[0], 2*24 + 8, 16);
|
||||
gpsalma.t0a = getbitu(&cond[0], 3*24, 8);
|
||||
gpsalma.deltai = getbits(&cond[0], 3*24 +8 , 16);
|
||||
gpsalma.omegadot = getbits(&cond[0], 4*24, 16);
|
||||
gpsalma.health = getbitu(&cond[0], 4*24 +16, 8);
|
||||
gpsalma.sqrtA = getbitu(&cond[0], 5*24, 24);
|
||||
gpsalma.Omega0 = getbits(&cond[0], 6*24, 24);
|
||||
gpsalma.omega = getbits(&cond[0], 7*24, 24);
|
||||
gpsalma.M0 = getbits(&cond[0], 8*24, 24);
|
||||
gpsalma.af0 = (getbits(&cond[0], 9*24, 8) << 3) + getbits(&cond[0], 9*24 +19, 3);
|
||||
gpsalma.af1 = getbits(&cond[0], 9*24 + 8, 11);
|
||||
// cerr<<"Frame 5, SV: "<<getbitu(&cond[0], 2*32 + 2 +2, 6)<<endl;
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
|
|
190
gps.hh
190
gps.hh
|
@ -5,12 +5,9 @@
|
|||
#include "bits.hh"
|
||||
#include <iostream>
|
||||
#include <math.h>
|
||||
#include "ephemeris.hh"
|
||||
|
||||
std::basic_string<uint8_t> getCondensedGPSMessage(std::basic_string_view<uint8_t> payload);
|
||||
|
||||
|
||||
struct GPSAlmanac : GPSLikeEphemeris
|
||||
struct GPSAlmanac
|
||||
{
|
||||
int dataid{-1};
|
||||
int sv;
|
||||
|
@ -32,7 +29,7 @@ struct GPSAlmanac : GPSLikeEphemeris
|
|||
{
|
||||
return ldexp(e, -21);
|
||||
}
|
||||
uint32_t getT0e() const
|
||||
double getT0e() const
|
||||
{
|
||||
return ldexp(t0a, 12);
|
||||
}
|
||||
|
@ -74,23 +71,30 @@ struct GPSAlmanac : GPSLikeEphemeris
|
|||
double getCrs() const { return 0; } // meters
|
||||
double getDeltan()const { return 0; } //radians/s
|
||||
|
||||
int getIOD() const { return 0; } // XXX ioda?
|
||||
|
||||
};
|
||||
|
||||
|
||||
struct GPSState : GPSLikeEphemeris
|
||||
struct GPSState
|
||||
{
|
||||
uint32_t t0e;
|
||||
uint32_t e, sqrtA;
|
||||
int32_t m0, omega0, i0, omega, idot, omegadot, deltan;
|
||||
int16_t cuc{0}, cus{0}, crc{0}, crs{0}, cic{0}, cis{0};
|
||||
// 16 seconds
|
||||
uint16_t t0c;
|
||||
struct SVIOD
|
||||
{
|
||||
std::bitset<32> words;
|
||||
int gnssid;
|
||||
uint32_t t0e;
|
||||
uint32_t e, sqrtA;
|
||||
int32_t m0, omega0, i0, omega, idot, omegadot, deltan;
|
||||
|
||||
|
||||
// 2^-31 2^-43
|
||||
int32_t af0 , af1;
|
||||
// ???
|
||||
int8_t af2;
|
||||
int16_t cuc{0}, cus{0}, crc{0}, crs{0}, cic{0}, cis{0};
|
||||
// 16 seconds
|
||||
uint16_t t0c;
|
||||
|
||||
// 2^-31 2^-43
|
||||
int32_t af0 , af1;
|
||||
// ???
|
||||
int8_t af2;
|
||||
uint32_t wn{0}, tow{0};
|
||||
|
||||
double getMu() const
|
||||
{
|
||||
|
@ -116,12 +120,17 @@ struct GPSState : GPSLikeEphemeris
|
|||
double getIdot() const { return ldexp(idot * M_PI, -43); } // radians/s
|
||||
double getOmega() const { return ldexp(omega * M_PI, -31); } // radians
|
||||
|
||||
|
||||
};
|
||||
|
||||
GPSAlmanac gpsalma;
|
||||
|
||||
uint8_t gpshealth{0};
|
||||
uint16_t ai0{0};
|
||||
int16_t ai1{0}, ai2{0};
|
||||
|
||||
int BGDE1E5a{0}, BGDE1E5b{0};
|
||||
|
||||
bool disturb1{false}, disturb2{false}, disturb3{false}, disturb4{false}, disturb5{false};
|
||||
uint16_t wn{0}; // we put the "unrolled" week number here!
|
||||
uint32_t tow{0}; // "last seen"
|
||||
|
@ -133,15 +142,27 @@ struct GPSState : GPSLikeEphemeris
|
|||
uint16_t wnLSF{0};
|
||||
uint8_t dn; // leap second day number
|
||||
// 1 2^-31 2^-43 2^-55 16 second
|
||||
int ura;
|
||||
int ura, af0, af1, af2, t0c; // GPS parameters that should not be here XXX
|
||||
|
||||
int gpsiod{-1};
|
||||
|
||||
int getIOD() const
|
||||
|
||||
std::map<int, SVIOD> iods;
|
||||
SVIOD& getEph(int i) { return iods[i]; } // XXXX gps adaptor
|
||||
void checkCompleteAndClean(int iod)
|
||||
{
|
||||
return gpsiod;
|
||||
if(iods[iod].words[2] && iods[iod].words[3]) {
|
||||
if(iods.size() > 1) {
|
||||
auto tmp = iods[iod];
|
||||
iods.clear();
|
||||
iods[iod] = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
int parseGPSMessage(std::basic_string_view<uint8_t> cond, uint8_t* pageptr=0);
|
||||
bool isComplete(int iod)
|
||||
{
|
||||
return iods[iod].words[2] && iods[iod].words[3];
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
|
@ -182,3 +203,128 @@ std::pair<double, double> getGPSUTCOffset(int tow, int wn, const T& eph)
|
|||
|
||||
|
||||
|
||||
// expects input as 24 bit read to to use messages, returns frame number
|
||||
template<typename T>
|
||||
int parseGPSMessage(std::basic_string_view<uint8_t> cond, T& out, uint8_t* pageptr=0)
|
||||
{
|
||||
using namespace std;
|
||||
int frame = getbitu(&cond[0], 24+19, 3);
|
||||
// 10 * 4 bytes in payload now
|
||||
out.tow = 1.5*(getbitu(&cond[0], 24, 17)*4);
|
||||
// cerr << "Preamble: "<<getbitu(&cond[0], 0, 8) <<", frame: "<< frame<<", truncated TOW: "<<out.tow<<endl;
|
||||
if(frame == 1) {
|
||||
// word 1, word 2 are TLM and HOW
|
||||
// 2 bits of padding on each word
|
||||
// word3:
|
||||
// 1-10: WN
|
||||
// 11-12: Which codes 00 = invalid, 01 = P-code on, 10 = C/A code on, 11 invalid
|
||||
// 13-16: URA, 0-15 scale
|
||||
// 17-22: 0 is ok
|
||||
// 23-24: MSB of IODC
|
||||
|
||||
// word 8:
|
||||
// 1-8: LSB of IODC
|
||||
// 9-24:
|
||||
|
||||
out.wn = 2048 + getbitu(&cond[0], 2*24, 10);
|
||||
out.ura = getbitu(&cond[0], 2*24+12, 4);
|
||||
out.gpshealth = getbitu(&cond[0], 2*24+16, 6);
|
||||
|
||||
// cerr<<"GPS Week Number: "<< out.wn <<", URA: "<< (int)out.ura<<", health: "<<
|
||||
// (int)out.gpshealth <<endl;
|
||||
|
||||
out.af2 = getbits(&cond[0], 8*24, 8); // * 2^-55
|
||||
out.af1 = getbits(&cond[0], 8*24 + 8, 16); // * 2^-43
|
||||
out.af0 = getbits(&cond[0], 9*24, 22); // * 2^-31
|
||||
out.t0c = getbits(&cond[0], 7*24 + 8, 16); // * 16
|
||||
// cerr<<"t0c*16: "<<out.t0c*16<<", af2: "<< (int)out.af2 <<", af1: "<< out.af1 <<", af0: "<<
|
||||
// out.af0 <<endl;
|
||||
}
|
||||
else if(frame == 2) {
|
||||
out.gpsiod = getbitu(&cond[0], 2*24, 8);
|
||||
auto& eph = out.getEph(out.gpsiod);
|
||||
eph.words[2]=1;
|
||||
eph.t0e = getbitu(&cond[0], 9*24, 16) * 16.0; // WE SCALE THIS FOR THE USER!!
|
||||
// cerr<<"IODe "<<(int)iod<<", t0e "<< eph.t0e << " = "<< 16* eph.t0e <<"s"<<endl;
|
||||
|
||||
eph.e= getbitu(&cond[0], 5*24+16, 32);
|
||||
// cerr<<"e: "<<ldexp(eph.e, -33)<<", ";
|
||||
|
||||
|
||||
// sqrt(A), 32 bits, 2^-19
|
||||
eph.sqrtA= getbitu(&cond[0], 7*24+ 16, 32);
|
||||
// double sqrtA=ldexp(eph.sqrtA, -19); // 2^-19
|
||||
// cerr<<"Radius: "<<sqrtA*sqrtA<<endl;
|
||||
|
||||
eph.crs = getbits(&cond[0], 2*24 + 8, 16); // 2^-5 meters
|
||||
eph.deltan = getbits(&cond[0], 3*24, 16); // 2^-43 semi-circles/s
|
||||
eph.m0 = getbits(&cond[0], 3*24+16, 32); // 2^-31 semi-circles
|
||||
|
||||
eph.cuc = getbits(&cond[0], 5*24, 16); // 2^-29 RADIANS
|
||||
eph.cus = getbits(&cond[0], 7*24, 16); // 2^-29 RADIANS
|
||||
out.checkCompleteAndClean(out.gpsiod);
|
||||
}
|
||||
else if(frame == 3) {
|
||||
out.gpsiod = getbitu(&cond[0], 9*24, 8);
|
||||
auto& eph = out.getEph(out.gpsiod);
|
||||
eph.words[3]=1;
|
||||
eph.cic = getbits(&cond[0], 2*24, 16); // 2^-29 RADIANS
|
||||
eph.omega0 = getbits(&cond[0], 2*24 + 16, 32); // 2^-31 semi-circles
|
||||
eph.cis = getbits(&cond[0], 4*24, 16); // 2^-29 radians
|
||||
eph.i0 = getbits(&cond[0], 4*24 + 16, 32); // 2^-31, semicircles
|
||||
|
||||
eph.crc = getbits(&cond[0], 6*24, 16); // 2^-5, meters
|
||||
eph.omega = getbits(&cond[0], 6*24+16, 32); // 2^-31, semi-circles
|
||||
|
||||
eph.omegadot = getbits(&cond[0], 8*24, 24); // 2^-43, semi-circles/s
|
||||
eph.idot = getbits(&cond[0], 9*24+8, 14); // 2^-43, semi-cirlces/s
|
||||
out.checkCompleteAndClean(out.gpsiod);
|
||||
}
|
||||
else if(frame == 4) { // this is a carousel frame
|
||||
int page = getbitu(&cond[0], 2*24 + 2, 6);
|
||||
if(pageptr)
|
||||
*pageptr=0;
|
||||
// cerr<<"Frame 4, page "<<page;
|
||||
if(page == 56) { // 56 is the new 18 somehow? See table 20-V of the ICD
|
||||
if(pageptr)
|
||||
*pageptr=18;
|
||||
out.a0 = getbits(&cond[0], 6*24 , 32); // 2^-30
|
||||
out.a1 = getbits(&cond[0], 5*24 , 24); // 2^-50
|
||||
|
||||
out.t0t = getbitu(&cond[0], 7*24 + 8, 8) * 4096; // WE SCALE THIS FOR THE USER!
|
||||
out.wn0t = getbitu(&cond[0], 7*24 + 16, 8);
|
||||
out.dtLS = getbits(&cond[0], 8*24, 8);
|
||||
out.dtLSF = getbits(&cond[0], 9*24, 8);
|
||||
|
||||
// cerr<<": a0: "<<out.a0<<", a1: "<<out.a1<<", t0t: "<< out.t0t * (1<<12) <<", wn0t: "<< out.wn0t<<", rough offset: "<<ldexp(out.a0, -30)<<endl;
|
||||
// cerr<<"deltaTLS: "<< (int)out.dtLS<<", post "<< (int)out.dtLSF<<endl;
|
||||
}
|
||||
//else cerr<<endl;
|
||||
// page 18 contains UTC -> 56
|
||||
// page 25 -> 63
|
||||
// 2-10 -> 25 -> 32 ??
|
||||
}
|
||||
|
||||
if(frame == 5 || frame==4) { // this is a caroussel frame
|
||||
out.gpsalma.dataid = getbitu(&cond[0], 2*24, 2);
|
||||
out.gpsalma.sv = getbitu(&cond[0], 2*24+2, 6);
|
||||
|
||||
if(pageptr)
|
||||
*pageptr= out.gpsalma.sv;
|
||||
|
||||
|
||||
out.gpsalma.e = getbitu(&cond[0], 2*24 + 8, 16);
|
||||
out.gpsalma.t0a = getbitu(&cond[0], 3*24, 8);
|
||||
out.gpsalma.deltai = getbits(&cond[0], 3*24 +8 , 16);
|
||||
out.gpsalma.omegadot = getbits(&cond[0], 4*24, 16);
|
||||
out.gpsalma.health = getbitu(&cond[0], 4*24 +16, 8);
|
||||
out.gpsalma.sqrtA = getbitu(&cond[0], 5*24, 24);
|
||||
out.gpsalma.Omega0 = getbits(&cond[0], 6*24, 24);
|
||||
out.gpsalma.omega = getbits(&cond[0], 7*24, 24);
|
||||
out.gpsalma.M0 = getbits(&cond[0], 8*24, 24);
|
||||
out.gpsalma.af0 = (getbits(&cond[0], 9*24, 8) << 3) + getbits(&cond[0], 9*24 +19, 3);
|
||||
out.gpsalma.af1 = getbits(&cond[0], 9*24 + 8, 11);
|
||||
// cerr<<"Frame 5, SV: "<<getbitu(&cond[0], 2*32 + 2 +2, 6)<<endl;
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
|
|
20
gpscnav.cc
20
gpscnav.cc
|
@ -1,20 +0,0 @@
|
|||
/*
|
||||
cerr<<"Preamble "<<getbitu(&cnav[0], 0, 8)<<" sv "<<
|
||||
getbitu(&cnav[0], 8, 6) << " type "<< type <<
|
||||
" tow " << tow;
|
||||
|
||||
if(type == 10) {
|
||||
wn = getbitu(&cnav[0], 38, 13);
|
||||
cerr<<" t0p " << 300*getbitu(&cnav[0], 54,11)<< " t0e "<< 300*getbitu(&cnav[0], 70, 11);
|
||||
}
|
||||
else if(type == 11) {
|
||||
cerr<<" t0e "<< 300* getbitu(&cnav[0], 38, 11);
|
||||
|
||||
|
||||
}
|
||||
else if(type == 32) {
|
||||
cerr<< " delta-ut1 " << 1000.0*ldexp(getbits(&cnav[0], 215, 31), -24)<< "ms rate " << 1000.0*ldexp(getbits(&cnav[0], 246, 19), -25) << " te0p " <<
|
||||
16*getbits(&cnav[0], 127, 16);
|
||||
|
||||
}
|
||||
*/
|
227
gpscnav.hh
227
gpscnav.hh
|
@ -1,227 +0,0 @@
|
|||
#pragma once
|
||||
#include <bitset>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include "bits.hh"
|
||||
#include <iostream>
|
||||
#include <math.h>
|
||||
|
||||
struct GPSCNavAlmanac
|
||||
{
|
||||
int dataid{-1};
|
||||
int sv;
|
||||
uint32_t t0a{0};
|
||||
uint32_t e{0}, sqrtA{0};
|
||||
int32_t M0, Omega0, deltai, omega, omegadot;
|
||||
int health;
|
||||
int af0, af1;
|
||||
|
||||
double getMu() const
|
||||
{
|
||||
return 3.986005 * pow(10.0, 14.0);
|
||||
} // m^3/s^2
|
||||
// same for galileo & gps
|
||||
double getOmegaE() const { return 7.2921151467 * pow(10.0, -5.0);} // rad/s
|
||||
|
||||
|
||||
double getE() const
|
||||
{
|
||||
return ldexp(e, -21);
|
||||
}
|
||||
double getT0e() const
|
||||
{
|
||||
return ldexp(t0a, 12);
|
||||
}
|
||||
|
||||
double getI0() const
|
||||
{
|
||||
return M_PI*0.3 + ldexp(M_PI*deltai, -19);
|
||||
}
|
||||
|
||||
double getOmegadot() const
|
||||
{
|
||||
return ldexp(M_PI * omegadot, -38);
|
||||
}
|
||||
|
||||
double getSqrtA() const
|
||||
{
|
||||
return ldexp(sqrtA, -11);
|
||||
}
|
||||
|
||||
double getOmega0() const
|
||||
{
|
||||
return ldexp(M_PI * Omega0, -23);
|
||||
}
|
||||
double getOmega() const
|
||||
{
|
||||
return ldexp(M_PI * omega, -23);
|
||||
}
|
||||
|
||||
double getM0() const
|
||||
{
|
||||
return ldexp(M_PI * M0, -23);
|
||||
}
|
||||
double getIdot() const { return 0; } // radians/s
|
||||
double getCic() const { return 0; } // radians
|
||||
double getCis() const { return 0; } // radians
|
||||
double getCuc() const { return 0; } // radians
|
||||
double getCus() const { return 0; } // radians
|
||||
double getCrc() const { return 0; } // meters
|
||||
double getCrs() const { return 0; } // meters
|
||||
double getDeltan()const { return 0; } //radians/s
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
struct GPSCNavState
|
||||
{
|
||||
uint32_t t0e;
|
||||
uint32_t e, sqrtA;
|
||||
int32_t m0, omega0, i0, omega, idot, omegadot, deltan;
|
||||
|
||||
|
||||
int16_t cuc{0}, cus{0}, crc{0}, crs{0}, cic{0}, cis{0};
|
||||
// 16 seconds
|
||||
uint16_t t0c;
|
||||
|
||||
// 2^-31 2^-43
|
||||
int32_t af0 , af1;
|
||||
// ???
|
||||
int8_t af2;
|
||||
uint32_t wn{0}, tow{0};
|
||||
|
||||
double getMu() const
|
||||
{
|
||||
return 3.986005 * pow(10.0, 14.0);
|
||||
} // m^3/s^2
|
||||
// same for galileo & gps
|
||||
double getOmegaE() const { return 7.2921151467 * pow(10.0, -5.0);} // rad/s
|
||||
|
||||
uint32_t getT0e() const { return t0e; }
|
||||
double getSqrtA() const { return ldexp(sqrtA, -19); }
|
||||
double getE() const { return ldexp(e, -33); }
|
||||
double getCuc() const { return ldexp(cuc, -29); } // radians
|
||||
double getCus() const { return ldexp(cus, -29); } // radians
|
||||
double getCrc() const { return ldexp(crc, -5); } // meters
|
||||
double getCrs() const { return ldexp(crs, -5); } // meters
|
||||
double getM0() const { return ldexp(m0 * M_PI, -31); } // radians
|
||||
double getDeltan()const { return ldexp(deltan *M_PI, -43); } //radians/s
|
||||
double getI0() const { return ldexp(i0 * M_PI, -31); } // radians
|
||||
double getCic() const { return ldexp(cic, -29); } // radians
|
||||
double getCis() const { return ldexp(cis, -29); } // radians
|
||||
double getOmegadot() const { return ldexp(omegadot * M_PI, -43); } // radians/s
|
||||
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
|
||||
|
||||
|
||||
uint8_t gpshealth{0};
|
||||
uint16_t ai0{0};
|
||||
int16_t ai1{0}, ai2{0};
|
||||
|
||||
int BGDE1E5a{0}, BGDE1E5b{0};
|
||||
|
||||
bool disturb1{false}, disturb2{false}, disturb3{false}, disturb4{false}, disturb5{false};
|
||||
//
|
||||
// 2^-30 2^-50 1 8-bit week
|
||||
int32_t a0{0}, a1{0}, t0t{0}, wn0t{0};
|
||||
int32_t a0g{0}, a1g{0}, t0g{0}, wn0g{0};
|
||||
int8_t dtLS{0}, dtLSF{0};
|
||||
uint16_t wnLSF{0};
|
||||
uint8_t dn; // leap second day number
|
||||
|
||||
|
||||
int gpsiod{-1};
|
||||
|
||||
int teop;
|
||||
int deltaUT1;
|
||||
int deltaUT1dot;
|
||||
|
||||
// milliseconds
|
||||
std::pair<double, double> getUT1OffsetMS(int tow)
|
||||
{
|
||||
int delta = ephAge(tow, 16* teop);
|
||||
std::cout<<" tow "<<tow<<" teop "<< 16*teop <<" delta "<<delta<<" " << 1000.0*ldexp(deltaUT1, -24)<<"ms ";
|
||||
double cur = ldexp(deltaUT1 + ldexp(delta * deltaUT1dot / 86400.0, -1), -24);
|
||||
double trend = ldexp(deltaUT1dot, -25);
|
||||
return {1000.0* cur, 1000.0*trend};
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
std::pair<double, double> getGPSCNavAtomicOffset(int tow, const T& eph)
|
||||
{
|
||||
int delta = ephAge(tow, getT0c(eph));
|
||||
double cur = eph.af0 + ldexp(delta*eph.af1, -12) + ldexp(delta*delta*eph.af2, -24);
|
||||
double trend = ldexp(eph.af1, -12) + ldexp(2*delta*eph.af2, -24);
|
||||
|
||||
// now in units of 2^-31 seconds, which are 0.5 nanoseconds each
|
||||
double factor = ldexp(1000000000, -31);
|
||||
return {factor * cur, factor * trend};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::pair<double, double> getGPSCNavUTCOffset(int tow, int wn, const T& eph)
|
||||
{
|
||||
// 2^-30 2^-50 3600
|
||||
// a0 a1 t0t
|
||||
|
||||
int dw = (int)(uint8_t)wn - (int)(uint8_t) eph.wn0t;
|
||||
int delta = dw*7*86400 + tow - eph.t0t; // this is pre-scaled for GPS..
|
||||
|
||||
double cur = eph.a0 + ldexp(1.0*delta*eph.a1, -20);
|
||||
double trend = ldexp(eph.a1, -20);
|
||||
|
||||
// now in units of 2^-30 seconds, which are ~1.1 nanoseconds each
|
||||
|
||||
double factor = ldexp(1000000000, -30);
|
||||
return {factor * cur, factor * trend};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
template<typename T>
|
||||
int parseGPSCNavMessage(std::basic_string_view<uint8_t> msg, T& out)
|
||||
{
|
||||
using namespace std;
|
||||
int type = getbitu(&msg[0], 14, 6);
|
||||
out.tow = 6 * getbitu(&msg[0], 20, 17) - 12;
|
||||
|
||||
if(type == 10) {
|
||||
out.wn = getbitu(&msg[0], 38, 13);
|
||||
// out.ura = getbitu(&msg[0], 65, 5);
|
||||
out.t0e = getbitu(&msg[0], 70, 11); // XXX scale?
|
||||
|
||||
// int32_t deltaSqrtA = getbits(&msg[0], 81, 26);
|
||||
// int32_t SqrtADot = getbits(&msg[0], 107, 25);
|
||||
out.deltan = getbitu(&msg[0], 132, 17); // XXX scale??
|
||||
// int deltandot = getbitu(&msg[0], 149, 23); // XXX scale??
|
||||
// int64_t M0n = getbitu(&msg[0], 172, 33); // XXX scale??
|
||||
|
||||
out.e = getbitu(&msg[0], 205, 33); // XXX scale??
|
||||
out.omega = getbitu(&msg[0], 238, 33); // XXX scale?
|
||||
}
|
||||
else if(type == 11) {
|
||||
out.t0e = getbitu(&msg[0], 38, 11); // XXX scale?
|
||||
out.omega0 = getbitu(&msg[0], 49, 33); // XXX scale?
|
||||
out.i0 = getbitu(&msg[0], 82, 33); // XXX scale?
|
||||
// int64_t deltaOmegaDot = getbitu(&msg[0], 115, 17); // XXX scale?
|
||||
out.idot = getbitu(&msg[0], 132, 15); // XXX scale?
|
||||
out.cis = getbits(&msg[0], 147, 16);// XXX scale?
|
||||
out.cic = getbits(&msg[0], 163, 16);// XXX scale?
|
||||
out.crs = getbits(&msg[0], 179, 24);// XXX scale?
|
||||
out.crc = getbits(&msg[0], 203, 24);// XXX scale?
|
||||
out.cus = getbits(&msg[0], 227, 21);// XXX scale?
|
||||
out.cuc = getbits(&msg[0], 248, 21); // XXX scale?
|
||||
}
|
||||
else if(type == 32) {
|
||||
out.deltaUT1 = getbits(&msg[0], 215, 31);
|
||||
out.deltaUT1dot = getbits(&msg[0], 246, 19);
|
||||
out.teop = getbitu(&msg[0], 127, 16);
|
||||
}
|
||||
return type;
|
||||
}
|
|
@ -1,10 +1,22 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>galmon.eu almanac</title>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" type="text/css" href="style.css">
|
||||
</head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
|
||||
text {
|
||||
font: 12px sans-serif;
|
||||
}
|
||||
|
||||
|
||||
th, td {
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
padding-top: 1px;
|
||||
padding-bottom: 1px;
|
||||
font-family: monospace;
|
||||
}
|
||||
tr:nth-child(even) {background: #CCC}
|
||||
tr:nth-child(odd) {background: #FFF}
|
||||
</style>
|
||||
<body>
|
||||
Last update: <span id="freshness"></span><br/>
|
||||
Note: all distances are in kilometers!
|
||||
|
@ -14,13 +26,13 @@
|
|||
<span id="facts"></span>
|
||||
</p>
|
||||
<p>
|
||||
Information on the status of BeiDou can be found on <a href="http://www.csno-tarc.cn/system/constellation&ce=english">this page</a> from the Chinese Test and Assessment Research Center of China Satellite Navigation Office.
|
||||
Information on the status of BeiDou can be found on <a href="http://www.csno-tarc.cn/system/constellation&ce=english">this page</a> from the Chinese Test and Assessment Research Center of China Satellite Navigation Office.
|
||||
</p>
|
||||
<p>
|
||||
Feedback is very welcome on bert@hubertnet.nl or <a href="https://twitter.com/PowerDNS_Bert">@PowerDNS_Bert</a>.
|
||||
</p>
|
||||
<script src="d3.v4.min.js"></script>
|
||||
<script src="ext/moment-with-locales.js"></script>
|
||||
<script src="almanac.js"></script>
|
||||
<script src="d3.v4.min.js"></script>
|
||||
<script src="ext/moment-with-locales.js"></script>
|
||||
<script src="almanac.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -14,7 +14,7 @@ function maketable(str, arr)
|
|||
enter().
|
||||
append("tr");
|
||||
|
||||
var columns = ["sv", "best-tle", "best-tle-dist", "best-tle-norad", "best-tle-int-desig", "eph-ecefX", "eph-ecefY", "eph-ecefZ", "tle-ecefX", "tle-ecefY", "tle-ecefZ", "eph-latitude", "eph-longitude", "tle-latitude", "tle-longitude", "tle-eciX", "tle-eciY", "tle-eciZ", "t0e", "t", "E", "M0"];
|
||||
var columns = ["sv", "best-tle", "best-tle-dist", "best-tle-norad", "best-tle-int-desig", "eph-ecefX", "eph-ecefY", "eph-ecefZ", "tle-ecefX", "tle-ecefY", "tle-ecefZ", "eph-latitude", "eph-longitude", "tle-latitude", "tle-longitude", "tle-eciX", "tle-eciY", "tle-eciZ", "t0e", "t"];
|
||||
|
||||
// append the header row
|
||||
thead.append("tr")
|
||||
|
|
159
html/doalles.js
159
html/doalles.js
|
@ -14,7 +14,7 @@ function maketable(str, arr)
|
|||
enter().
|
||||
append("tr");
|
||||
|
||||
var columns = ["sv", "best-tle", "iod", "eph-age-m", "latest-disco", "time-disco", "sisa", "health", "alma-dist", "osnma", "sources", "db", "rtcm-eph-delta-cm","rtcm-clock-dclock0", "prres", "elev", "last-seen-s"];
|
||||
var columns = ["sv", "best-tle", "iod", "eph-age-m", "latest-disco", "time-disco", "sisa", "health", "tle-dist", "alma-dist", "delta-utc", "delta-gps", "sources", "db", "delta_hz_corr","prres", "elev", "last-seen-s"];
|
||||
|
||||
// append the header row
|
||||
thead.append("tr")
|
||||
|
@ -25,11 +25,6 @@ function maketable(str, arr)
|
|||
.html(function(column) {
|
||||
if(column == "delta_hz_corr")
|
||||
return "ΔHz";
|
||||
if(column == "rtcm-eph-delta-cm")
|
||||
return "Δrtcm";
|
||||
if(column == "rtcm-clock-dclock0")
|
||||
return "Δclk";
|
||||
|
||||
if(column == "delta-gps")
|
||||
return "ΔGPS ns";
|
||||
if(column == "delta-utc")
|
||||
|
@ -61,28 +56,10 @@ function maketable(str, arr)
|
|||
else if(row["gnssid"] == 6)
|
||||
img='ext/glo.png';
|
||||
|
||||
ret.value = '<img width="16" height="16" src="https://berthub.eu/tmp/'+ img +'"/>';
|
||||
ret.value = '<img width="16" height="16" src="https://ds9a.nl/tmp/'+ img +'"/>';
|
||||
// ret.value="";
|
||||
ret.value += " <a href='sv.html?gnssid="+row.gnssid+"&sv="+row.svid+"&sigid="+row.sigid+"'>"+row.sv+"</a>";
|
||||
ret.value += " <a href='sv.html?gnssid=2&sv="+row.svid+"&sigid="+row.sigid+"'>"+row.sv+"</a>";
|
||||
}
|
||||
else if(column == "rtcm-eph-delta-cm") {
|
||||
if(row[column] != null)
|
||||
ret.value = row[column].toFixed(1)+" cm";
|
||||
else
|
||||
ret.value="";
|
||||
}
|
||||
else if(column == "rtcm-clock-dclock0") {
|
||||
if(row[column] != null) {
|
||||
if(Math.abs(row[column]) > 150)
|
||||
ret.color="#ff2222";
|
||||
else if(Math.abs(row[column]) > 100)
|
||||
ret.color="#ff4444";
|
||||
ret.value = row[column].toFixed(1)+" cm";
|
||||
}
|
||||
else
|
||||
ret.value="";
|
||||
}
|
||||
|
||||
else if(column == "aodc/e") {
|
||||
if(row["aodc"] != null && row["aode"] != null)
|
||||
ret.value = row["aodc"]+"/"+row["aode"];
|
||||
|
@ -111,32 +88,21 @@ function maketable(str, arr)
|
|||
ret.value="";
|
||||
}
|
||||
else if(row[column] != null && (column == "delta_hz_corr" || column =="delta_hz")) {
|
||||
|
||||
ret.value = row[column];
|
||||
}
|
||||
else if((column == "tle-dist")) {
|
||||
if(row["best-tle-dist"] != null) {
|
||||
if(row["best-tle-dist"] != null)
|
||||
ret.value = row["best-tle-dist"].toFixed(1) + " km";
|
||||
|
||||
if (Math.abs(row["best-tle-dist"]) > 10000)
|
||||
ret.color="red";
|
||||
else if (Math.abs(row["best-tle-dist"]) > 1000)
|
||||
ret.color="#ffaaaa";
|
||||
}
|
||||
}
|
||||
else if((column == "alma-dist")) {
|
||||
if(row["alma-dist"] != null)
|
||||
ret.value = row["alma-dist"].toFixed(1) + " km";
|
||||
}
|
||||
|
||||
else if(column == "norad") {
|
||||
ret.value = row["best-tle-norad"];
|
||||
}
|
||||
else if(column == "osnma" && row["osnma"] != null) {
|
||||
if(row["osnma"]==true)
|
||||
ret.value="ON!";
|
||||
else
|
||||
ret.value="off";
|
||||
}
|
||||
|
||||
else if(column == "delta-utc" && row["delta-utc"] != null) {
|
||||
ret.value = row["delta-utc"]+'<span class="CellComment">a0: '+row["a0"]+'<br/>a1: '+row["a1"]+'<br/>wn0t: ' + row["wn0t"]+'<br/>t0t: '+row["t0t"]+'</span>';
|
||||
ret.Class = 'CellWithComment';
|
||||
|
@ -152,12 +118,6 @@ function maketable(str, arr)
|
|||
}
|
||||
else if(column == "best-tle") {
|
||||
ret.value = "<small>"+row[column]+"</small>";
|
||||
if(row["best-tle-dist"] != null) {
|
||||
if (Math.abs(row["best-tle-dist"]) > 10000)
|
||||
ret.color="red";
|
||||
else if (Math.abs(row["best-tle-dist"]) > 1000)
|
||||
ret.color="#ffaaaa";
|
||||
}
|
||||
}
|
||||
else if(column == "gpshealth" && row[column] != null) {
|
||||
if(row[column]==0)
|
||||
|
@ -190,7 +150,7 @@ function maketable(str, arr)
|
|||
if(column == "sisa" && myRe.test(row[column]))
|
||||
ret.color="#ff2222";
|
||||
|
||||
if(column == "sisa" && row[column]=="NO SISA AVAILABLE")
|
||||
if(column == "sisa" && row[column]=="NO SIS AVAILABLE")
|
||||
ret.color="#ff2222";
|
||||
return ret;
|
||||
})
|
||||
|
@ -214,69 +174,35 @@ function updateSats()
|
|||
o.sources="";
|
||||
o.db="";
|
||||
o.elev="";
|
||||
o.delta_hz_corr="";
|
||||
o.sources=0;
|
||||
let prrestot = 0, prresnum=0, dbtot=0, hztot=0, hznum=0;
|
||||
let mindb=1000, maxdb=0;
|
||||
let minelev=90, maxelev=-1;
|
||||
o.hqsources=0;
|
||||
Object.keys(o.perrecv).forEach(function(k) {
|
||||
if(o.perrecv[k]["last-seen-s"] < 30) {
|
||||
o.sources++;
|
||||
if(o.perrecv[k]["last-seen-s"] < 1800) {
|
||||
o.sources = o.sources + '<a href="observer.html?observer=' + k + '">'+k+'</a> ';
|
||||
|
||||
o.db = o.db + o.perrecv[k].db +" ";
|
||||
if(o.perrecv[k].elev != null)
|
||||
o.elev = o.elev + o.perrecv[k].elev.toFixed(0)+" ";
|
||||
else
|
||||
o.elev = o.elev + "? ";
|
||||
|
||||
if(o.delta_hz_corr == null)
|
||||
o.delta_hz_corr ="";
|
||||
if(o.perrecv[k].delta_hz_corr != null)
|
||||
o.delta_hz_corr = o.delta_hz_corr + o.perrecv[k].delta_hz_corr.toFixed(0)+" ";
|
||||
else
|
||||
o.delta_hz_corr = o.delta_hz_corr + "_ ";
|
||||
|
||||
if(o.prres == null)
|
||||
o.prres ="";
|
||||
if(o.perrecv[k].prres != null)
|
||||
o.prres = o.prres + o.perrecv[k].prres.toFixed(0)+" ";
|
||||
else
|
||||
o.prres = o.prres + "_ ";
|
||||
|
||||
|
||||
|
||||
dbtot += o.perrecv[k].db;
|
||||
if(o.perrecv[k].db != null) {
|
||||
let db=o.perrecv[k].db;
|
||||
if(db > maxdb)
|
||||
maxdb = db;
|
||||
if(db <mindb)
|
||||
mindb =db;
|
||||
}
|
||||
if(o.perrecv[k].elev != null) {
|
||||
let elev=o.perrecv[k].elev;
|
||||
if(elev > maxelev)
|
||||
maxelev = elev;
|
||||
if(elev <minelev)
|
||||
minelev =elev;
|
||||
}
|
||||
|
||||
if(o.perrecv[k].qi != null && o.perrecv[k].qi == 7 && o.perrecv[k].elev > 20
|
||||
&& (o.perrecv[k].prres != 0.000 || o.perrecv[k].used)) {
|
||||
o.hqsources++;
|
||||
|
||||
if(o.perrecv[k].delta_hz_corr != null) {
|
||||
hztot += o.perrecv[k].delta_hz_corr;
|
||||
hznum++;
|
||||
}
|
||||
|
||||
if(o.perrecv[k].prres != null) {
|
||||
prrestot+= o.perrecv[k].prres;
|
||||
prresnum++;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if(mindb != 1000)
|
||||
o.db = mindb+" - " +maxdb;
|
||||
else
|
||||
o.db = "-";
|
||||
|
||||
if(maxelev != -1)
|
||||
o.elev = minelev.toFixed(0)+" - " +maxelev.toFixed(0);
|
||||
else
|
||||
o.elev = "-";
|
||||
|
||||
if(o.hqsources > 2) {
|
||||
if(prresnum)
|
||||
o.prres = (prrestot / prresnum).toFixed(2);
|
||||
else
|
||||
o.prres ="-";
|
||||
if(hznum)
|
||||
o.delta_hz_corr = (hztot / hznum).toFixed(2);
|
||||
else
|
||||
o.delta_hz_corr ="-";
|
||||
}
|
||||
|
||||
arr.push(o);
|
||||
});
|
||||
|
||||
|
@ -295,8 +221,6 @@ function updateSats()
|
|||
let wantIt = false;
|
||||
if(d3.select("#GalE1").property("checked") && arr[n].gnssid==2 && arr[n].sigid == 1)
|
||||
wantIt = true;
|
||||
if(d3.select("#GalE5a").property("checked") && arr[n].gnssid==2 && arr[n].sigid == 6)
|
||||
wantIt = true;
|
||||
if(d3.select("#GalE5b").property("checked") && arr[n].gnssid==2 && arr[n].sigid == 5)
|
||||
wantIt = true;
|
||||
if(d3.select("#GPSL1CA").property("checked") && arr[n].gnssid==0 && arr[n].sigid == 0)
|
||||
|
@ -327,7 +251,7 @@ function updateSats()
|
|||
|
||||
function update()
|
||||
{
|
||||
var seconds = 2;
|
||||
var seconds = 20;
|
||||
clearTimeout(repeat);
|
||||
repeat=setTimeout(update, 1000.0*seconds);
|
||||
|
||||
|
@ -336,24 +260,9 @@ function update()
|
|||
|
||||
|
||||
d3.json("global.json", function(d) {
|
||||
var str="Galileo-UTC offset: <b>"+d["gst-utc-offset-ns"].toFixed(2)+"</b> ns";
|
||||
str += ", Galileo-GPS offset: <b>"+d["gst-gps-offset-ns"].toFixed(2)+"</b> ns";
|
||||
str += ", GPS-UTC offset: <b>"+d["gps-utc-offset-ns"].toFixed(2)+" ns</b>";
|
||||
str += ", BeiDou-UTC offset: <b>"+d["beidou-utc-offset-ns"].toFixed(2)+" ns</b>";
|
||||
str += ", GLONASS-UTC offset: <b>"+d["glonass-utc-offset-ns"].toFixed(2)+" ns</b>";
|
||||
str += ", GLONASS-GPS offset: <b>"+d["glonass-gps-offset-ns"].toFixed(2)+" ns</b>";
|
||||
str += ", "+d["leap-seconds"]+"</b> leap seconds (GPS/Galileo)";
|
||||
|
||||
d3.select('#facts').html(str);
|
||||
d3.select('#facts').html("Galileo-UTC offset: <b>"+d["utc-offset-ns"].toFixed(2)+"</b> ns, Galileo-GPS offset: <b>"+d["gps-offset-ns"].toFixed(2)+"</b> ns, GPS UTC offset: <b>"+d["gps-utc-offset-ns"].toFixed(2)+"</b>. "+d["leap-seconds"]+"</b> leap seconds");
|
||||
lastseen = moment(1000*d["last-seen"]);
|
||||
d3.select("#freshness").html(lastseen.fromNow());
|
||||
|
||||
|
||||
str = d["total-live-receivers"]+" receivers active, tracking ";
|
||||
str += d["total-live-svs"] + " satellites via " ;
|
||||
str += d["total-live-signals"] + " signals." ;
|
||||
d3.select("#allstats").text(str);
|
||||
|
||||
});
|
||||
|
||||
d3.json("svs.json", function(d) {
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,46 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
|
||||
<head>
|
||||
<title>galmon.eu geo</title>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" type="text/css" href="geo.css">
|
||||
|
||||
<script src="../d3.v4.min.js"></script>
|
||||
<script src="../ext/topojson.v1.min.js"></script>
|
||||
<script src="../ext/d3-geo-projection.v2.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="galmongeo">
|
||||
<div id="galmoninfo">
|
||||
This is a live map from the <a href="/">galmon.eu</a> project. This map can plot if a fix is possible ('Coverage') or if the positioning, horizontal or vertical precision will be bad (<a href="https://en.wikipedia.org/wiki/Dilution_of_precision_(navigation)">PDOP, HDOP, VDOP</a>). Red blocks indicate a problem even in perfect viewing conditions. Orange means a problem if there are obstructions below 10 degrees above of the horizon, yellow for less than 20 degrees ('urban canyon').
|
||||
<b>These <a href="https://github.com/ahupowerdns/galmon/blob/master/coverage.cc">calculations are very provisional</a> and might be wrong!</b>
|
||||
<div class="centered">
|
||||
<p>
|
||||
<input type="radio" name="kind" id="coverage" onclick="do_timer();" ><label for="coverage">Coverage</label>
|
||||
<input type="radio" name="kind" id="pdop" onclick="do_timer();" ><label for="pdop">PDOP > 10 or no fix</label>
|
||||
<input type="radio" name="kind" id="hdop" onclick="do_timer();" checked><label for="hdop">HDOP > 10 or no fix</label>
|
||||
<input type="radio" name="kind" id="vdop" onclick="do_timer();" ><label for="vdop">VDOP > 10 or no fix</label>
|
||||
</p>
|
||||
<hr/>
|
||||
<p>
|
||||
<input type="checkbox" id="GalE1" onclick="do_timer();" checked> <label for="GalE1">Galileo E1</label>
|
||||
<input type="checkbox" id="GPSL1CA" onclick="do_timer();"> <label for="GPSL1CA">GPS L1C/A</label>
|
||||
<input type="checkbox" id="Beidou" onclick="do_timer();"> <label for="Beidou">Beidou</label>
|
||||
<input type="checkbox" id="Glonass" onclick="do_timer();"> <label for="GloL1OF">Glonass</label>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="combined">
|
||||
<svg id="svgworld"></svg>
|
||||
<svg id="svggraticule"></svg>
|
||||
<svg id="svgobservers"></svg>
|
||||
<svg id="svgalmanac"></svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="coverage.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
|
@ -1,488 +0,0 @@
|
|||
'use strict';
|
||||
//
|
||||
|
||||
var wantGPS=0, wantGalileo=0, wantBeidou=0, wantGlonass=0;
|
||||
//
|
||||
//
|
||||
|
||||
var fileWorld = "world.geojson";
|
||||
var fileAlmanac = "../almanac.json" // "https://galmon.eu/almanac"
|
||||
var fileObservers = "../observers.json" // "https://galmon.eu/observers"
|
||||
|
||||
var projectionChoice = "Fahey";
|
||||
var projectionChoice = "CylindricalStereographic";
|
||||
var projectionChoice = "Equirectangular";
|
||||
//var projectionChoice = "Aitoff";
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
var svgWorld = d3.select("#svgworld");
|
||||
var idWorld = document.getElementById("svgworld");
|
||||
|
||||
var svgGraticule = d3.select("#svggraticule");
|
||||
var idGraticule = document.getElementById("svggraticule");
|
||||
|
||||
var svgObservers = d3.select("#svgobservers");
|
||||
var idObservers = document.getElementById("svgobservers");
|
||||
|
||||
var svgAlmanac = d3.select("#svgalmanac");
|
||||
var idAlmanac = document.getElementById("svgalmanac");
|
||||
|
||||
var geoPath;
|
||||
var aProjection;
|
||||
|
||||
function draw_world(data_world)
|
||||
{
|
||||
// console.log("draw_world() " + data_world.features.length);
|
||||
|
||||
svgWorld.selectAll("path")
|
||||
.data(data_world.features)
|
||||
.enter()
|
||||
.append("path")
|
||||
.attr("class", "countries")
|
||||
.attr("d", geoPath);
|
||||
}
|
||||
|
||||
function draw_graticule()
|
||||
{
|
||||
// Graticule
|
||||
var graticule = d3.geoGraticule();
|
||||
|
||||
// console.log("draw_graticule()");
|
||||
|
||||
svgGraticule.selectAll("path")
|
||||
.data(graticule.lines())
|
||||
.enter()
|
||||
.append("path")
|
||||
.attr("class", "graticule line")
|
||||
.attr("id", function(d) {
|
||||
var c = d.coordinates;
|
||||
if (c[0][0] == c[1][0]) {
|
||||
return (c[0][0] < 0) ? -c[0][0] + "W" : +c[0][0] + "E";
|
||||
} else if (c[0][1] == c[1][1]) {
|
||||
return (c[0][1] < 0) ? -c[0][1] + "S" : c[0][1] + "N";
|
||||
}
|
||||
})
|
||||
.attr("d", geoPath);
|
||||
|
||||
svgGraticule.selectAll('text')
|
||||
.data(graticule.lines())
|
||||
.enter()
|
||||
.append("text")
|
||||
.text(function(d) {
|
||||
var c = d.coordinates;
|
||||
if ((c[0][0] == c[1][0]) && (c[0][0] % 30 == 0)) {
|
||||
return (c[0][0]);
|
||||
} else if (c[0][1] == c[1][1]) {
|
||||
return (c[0][1]);
|
||||
}
|
||||
})
|
||||
.attr("class","label")
|
||||
.attr("style", function(d) {
|
||||
var c = d.coordinates;
|
||||
return (c[0][1] == c[1][1]) ? "text-anchor: end" : "text-anchor: middle";
|
||||
})
|
||||
.attr("dx", function(d) {
|
||||
var c = d.coordinates;
|
||||
return (c[0][1] == c[1][1]) ? -10 : 0;
|
||||
})
|
||||
.attr("dy", function(d) {
|
||||
var c = d.coordinates;
|
||||
return (c[0][1] == c[1][1]) ? 4 : 10;
|
||||
})
|
||||
.attr('transform', function(d) {
|
||||
var c = d.coordinates;
|
||||
return ('translate(' + aProjection(c[0]) + ')')
|
||||
});
|
||||
|
||||
svgGraticule.append("path")
|
||||
.datum(graticule.outline)
|
||||
.attr("class", "graticule outline")
|
||||
.attr("d", geoPath);
|
||||
}
|
||||
|
||||
function get_almanac_valid(data_almanac)
|
||||
{
|
||||
var a=[];
|
||||
|
||||
|
||||
Object.keys(data_almanac).forEach(function(e) {
|
||||
var o = data_almanac[e];
|
||||
o.sv = e;
|
||||
if (o["eph-latitude"] != null && ((wantGalileo && o["gnssid"]==2) || (wantGPS && o["gnssid"]==0) || (wantBeidou && o["gnssid"]==3) ) ) {
|
||||
a.push(o);
|
||||
}
|
||||
});
|
||||
return a;
|
||||
}
|
||||
|
||||
function draw_coverage(data_coverage)
|
||||
{
|
||||
console.log(data_coverage.length);
|
||||
let coverage=[];
|
||||
|
||||
let mode = d3.select('input[name="kind"]:checked').node().id;
|
||||
console.log(mode);
|
||||
|
||||
var offset=4;
|
||||
if(mode=="pdop")
|
||||
offset=4;
|
||||
else if(mode=="hdop")
|
||||
offset=7;
|
||||
else if(mode=="vdop")
|
||||
offset=10;
|
||||
// console.log("Offset is "+offset+ " for mode "+mode);
|
||||
|
||||
|
||||
for(var i =0 ; i < data_coverage.length; ++i) {
|
||||
for(var j = 0 ; j < data_coverage[i][1].length; ++j) {
|
||||
if(mode == "coverage") {
|
||||
let numsats = data_coverage[i][1][j][1];
|
||||
if(numsats < 4)
|
||||
coverage.push({latitude: data_coverage[i][0], longitude: data_coverage[i][1][j][0], numsats: numsats, color: "red", opacity: 0.4});
|
||||
else {
|
||||
numsats = data_coverage[i][1][j][2];
|
||||
if(numsats < 4)
|
||||
coverage.push({latitude: data_coverage[i][0], longitude: data_coverage[i][1][j][0], numsats: numsats, color: "orange", opacity: null});
|
||||
else {
|
||||
numsats = data_coverage[i][1][j][3];
|
||||
if(numsats < 4)
|
||||
coverage.push({latitude: data_coverage[i][0], longitude: data_coverage[i][1][j][0], numsats: numsats, color: "yellow", opacity: 0.12});
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(mode=="pdop" || mode=="hdop" || mode =="vdop") {
|
||||
let dop = data_coverage[i][1][j][offset];
|
||||
if(dop > 100 || dop < 0)
|
||||
coverage.push({latitude: data_coverage[i][0], longitude: data_coverage[i][1][j][0], dop: dop, color: "red", opacity: 0.4});
|
||||
else {
|
||||
dop = data_coverage[i][1][j][offset+1];
|
||||
if(dop > 100 || dop < 0)
|
||||
coverage.push({latitude: data_coverage[i][0], longitude: data_coverage[i][1][j][0], dop: dop, color: "orange", opacity: null});
|
||||
else {
|
||||
dop = data_coverage[i][1][j][offset+2];
|
||||
if(dop > 100 || dop < 0 )
|
||||
coverage.push({latitude: data_coverage[i][0], longitude: data_coverage[i][1][j][0], dop: dop, color: "yellow", opacity: 0.12});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
svgAlmanac.selectAll("rect")
|
||||
.data(coverage)
|
||||
.enter()
|
||||
.append("rect")
|
||||
.attr("class", "sats")
|
||||
.style("opacity", d => d["opacity"])
|
||||
.attr("width", 20)
|
||||
.attr("height", 20)
|
||||
.attr("x", d => aProjection([d["longitude"],d["latitude"]])[0])
|
||||
.attr("y", d => aProjection([d["longitude"],d["latitude"]])[1])
|
||||
.attr("fill", function(d) {
|
||||
return d["color"];
|
||||
});
|
||||
|
||||
|
||||
// console.log(coverage);
|
||||
}
|
||||
|
||||
function draw_almanac(data_almanac)
|
||||
{
|
||||
var arr = get_almanac_valid(data_almanac);
|
||||
|
||||
console.log("draw_almanac() " + arr.length);
|
||||
|
||||
svgAlmanac.selectAll("circle")
|
||||
.data(arr)
|
||||
.enter()
|
||||
.append("circle")
|
||||
.attr("class", "sats")
|
||||
.attr("r", 3)
|
||||
.attr("cx", d => aProjection([d["eph-longitude"],d["eph-latitude"]])[0])
|
||||
.attr("cy", d => aProjection([d["eph-longitude"],d["eph-latitude"]])[1])
|
||||
.attr("fill", function(d) {
|
||||
switch (d.gnssid) {
|
||||
case 0: return "green"; // GPS
|
||||
case 1: return "gray"; // SBAS - not coded
|
||||
case 2: return "blue"; // Galileo
|
||||
case 3: return "red"; // BeiDou
|
||||
case 4: return "gray"; // IMES - not coded
|
||||
case 5: return "gray"; // QZSS - not coded
|
||||
case 6: return "yellow"; // GLONASS
|
||||
default: return "magenta"; // - should not happen
|
||||
}
|
||||
});
|
||||
|
||||
svgAlmanac.selectAll("text")
|
||||
.data(arr)
|
||||
.enter()
|
||||
.append("text")
|
||||
.attr("class", "labels")
|
||||
.text(d => d.sv)
|
||||
.attr("dx", d => 5+aProjection([d["eph-longitude"],d["eph-latitude"]])[0])
|
||||
.attr("dy", d => 5+aProjection([d["eph-longitude"],d["eph-latitude"]])[1])
|
||||
.attr("fill", function(d) {
|
||||
if (d.sisa != null && d.sisa == 255)
|
||||
return "red";
|
||||
if (d.health != null && d.health != 0) // GPS
|
||||
return "red";
|
||||
if (d.e1bhs != null && d.e1bhs != 0) // Galileo
|
||||
return "red";
|
||||
|
||||
if (d.observed==true)
|
||||
return "black";
|
||||
|
||||
return "#666666";
|
||||
})
|
||||
.attr("font-weight", function(d) {
|
||||
if (d.observed==true)
|
||||
return "bold";
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
function draw_observers(data_observers)
|
||||
{
|
||||
console.log("draw_observers() " + data_observers.length);
|
||||
|
||||
svgObservers.selectAll("rect")
|
||||
.data(data_observers)
|
||||
.enter()
|
||||
.append("rect")
|
||||
.attr("class", "sats")
|
||||
.attr("width", 8)
|
||||
.attr("height", 8)
|
||||
.attr("x", d => aProjection([d["longitude"],d["latitude"]])[0]-4)
|
||||
.attr("y", d => aProjection([d["longitude"],d["latitude"]])[1]-4)
|
||||
.attr("fill", function(d) { return "black"; });
|
||||
|
||||
}
|
||||
|
||||
function draw_observers_coverage(data_observers)
|
||||
{
|
||||
var radius = 55; // XXX fix
|
||||
var geoCircle = d3.geoCircle();
|
||||
svgObservers.selectAll("path")
|
||||
.data(data_observers)
|
||||
.enter()
|
||||
.append("path")
|
||||
.attr("class", "coverage")
|
||||
.attr("d", function(r) {
|
||||
// console.log([r["longitude"], r["latitude"]] + " = " + aProjection([r["longitude"], r["latitude"]]));
|
||||
return geoPath(geoCircle.center([r["longitude"],r["latitude"]]).radius(radius)());
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
var display_observers_count = 0;
|
||||
|
||||
function do_update_almanac(error, results)
|
||||
{
|
||||
var data_almanac = results[0];
|
||||
var data_observers = results[1];
|
||||
var data_coverage = results[2];
|
||||
// console.log("do_update_almanac() " + Object.keys(data_almanac).length + " " + data_observers.length);
|
||||
|
||||
if (display_observers_count == 0) {
|
||||
// does not need that much updating!
|
||||
svgObservers.html("");
|
||||
// draw_observers(data_observers);
|
||||
// draw_observers_coverage(data_observers)
|
||||
display_observers_count = 10;
|
||||
}
|
||||
display_observers_count--;
|
||||
|
||||
// We write into the svgalmanac area - so clean it and rewrite it
|
||||
svgAlmanac.html("");
|
||||
draw_almanac(data_almanac);
|
||||
|
||||
draw_coverage(data_coverage);
|
||||
}
|
||||
|
||||
var repeat;
|
||||
|
||||
function do_timer()
|
||||
{
|
||||
var seconds = 60;
|
||||
clearTimeout(repeat);
|
||||
repeat = setTimeout(do_timer, 1000.0*seconds);
|
||||
|
||||
wantGPS = 0;
|
||||
wantGalileo = 0;
|
||||
wantBeidou = 0;
|
||||
wantGlonass = 0;
|
||||
if(d3.select("#GPSL1CA").property("checked"))
|
||||
wantGPS=1;
|
||||
if(d3.select("#GalE1").property("checked"))
|
||||
wantGalileo=1;
|
||||
if(d3.select("#Beidou").property("checked"))
|
||||
wantBeidou=1;
|
||||
if(d3.select("#Glonass").property("checked"))
|
||||
wantGlonass=1;
|
||||
|
||||
var galcovurl="../cov.json?gps="+wantGPS+"&galileo="+wantGalileo+"&beidou="+wantBeidou+"&glonass="+wantGlonass;
|
||||
|
||||
d3.queue(1)
|
||||
.defer(d3.json, fileAlmanac)
|
||||
.defer(d3.json, fileObservers)
|
||||
.defer(d3.json, galcovurl)
|
||||
.awaitAll(do_update_almanac);
|
||||
}
|
||||
|
||||
function set_projection(data_world)
|
||||
{
|
||||
// var aProjection = d3.geoMercator().scale(100).translate([250,250]);
|
||||
// all this complexity is so we can scale to full screen.
|
||||
// see: https://stackoverflow.com/questions/14492284/center-a-map-in-d3-given-a-geojson-object
|
||||
|
||||
var center = [0,0]; // This is very Euro-centric - but that's how these projections works.
|
||||
var scale = 210; // No idea what this does
|
||||
|
||||
var idCombined = document.getElementById("combined");
|
||||
|
||||
svgWorld = d3.select("#svgworld");
|
||||
idWorld = document.getElementById("svgworld");
|
||||
|
||||
svgGraticule = d3.select("#svggraticule");
|
||||
idGraticule = document.getElementById("svggraticule");
|
||||
|
||||
switch(projectionChoice) {
|
||||
default:
|
||||
console.log(projectionChoice + ": not coded");
|
||||
// fall thru to Equirectangular
|
||||
case 'Equirectangular':
|
||||
aProjection = d3.geoEquirectangular()
|
||||
.scale(scale)
|
||||
.translate([idCombined.clientWidth/2,idCombined.clientHeight/2]);
|
||||
break;
|
||||
case 'Aitoff':
|
||||
aProjection = d3.geoAitoff()
|
||||
.scale(scale)
|
||||
.translate([idCombined.clientWidth/2,idCombined.clientHeight/2]);
|
||||
break;
|
||||
case 'CylindricalStereographic':
|
||||
aProjection = d3.geoCylindricalStereographic()
|
||||
.scale(scale)
|
||||
.translate([idCombined.clientWidth/2,idCombined.clientHeight/2]);
|
||||
break;
|
||||
case 'Fahey':
|
||||
aProjection = d3.geoFahey()
|
||||
.scale(scale)
|
||||
.translate([idCombined.clientWidth/2,idCombined.clientHeight/2]);
|
||||
break;
|
||||
case 'Gilbert':
|
||||
aProjection = d3.geoGilbert()
|
||||
.scale(scale)
|
||||
.translate([idCombined.clientWidth/2,idCombined.clientHeight/2]);
|
||||
break;
|
||||
}
|
||||
|
||||
geoPath = d3.geoPath()
|
||||
.projection(aProjection);
|
||||
|
||||
// using the path determine the bounds of the current map and use
|
||||
// these to determine better values for the scale and translation
|
||||
var bounds = geoPath.bounds(data_world);;
|
||||
var hscale = scale*idCombined.clientWidth / (bounds[1][0] - bounds[0][0]);
|
||||
var vscale = scale*idCombined.clientHeight / (bounds[1][1] - bounds[0][1]);
|
||||
scale = (hscale < vscale) ? hscale : vscale;
|
||||
var offset = [
|
||||
idCombined.clientWidth - (bounds[0][0] + bounds[1][0])/2,
|
||||
idCombined.clientHeight - (bounds[0][1] + bounds[1][1])/2
|
||||
];
|
||||
|
||||
if (0) {
|
||||
|
||||
// new projection
|
||||
switch(projectionChoice) {
|
||||
default:
|
||||
console.log(projectionChoice + ": not coded");
|
||||
// fall thru to Equirectangular
|
||||
case 'Equirectangular':
|
||||
aProjection = d3.geoEquirectangular()
|
||||
.center(center)
|
||||
.scale(scale)
|
||||
.translate(offset);
|
||||
break;
|
||||
case 'Aitoff':
|
||||
aProjection = d3.geoAitoff()
|
||||
.center(center) .scale(scale)
|
||||
.translate(offset);
|
||||
break;
|
||||
case 'CylindricalStereographic':
|
||||
aProjection = d3.geoCylindricalStereographic()
|
||||
.center(center)
|
||||
.scale(scale)
|
||||
.translate(offset);
|
||||
break;
|
||||
case 'Fahey':
|
||||
aProjection = d3.geoFahey()
|
||||
.center(center)
|
||||
.scale(scale)
|
||||
.translate(offset);
|
||||
break;
|
||||
case 'Gilbert':
|
||||
aProjection = d3.geoGilbert()
|
||||
.center(center)
|
||||
.scale(scale)
|
||||
.translate(offset);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
console.log("do_draw_world() " + "width=" + idCombined.clientWidth + "," + "height=" + idCombined.clientHeight);
|
||||
|
||||
svgWorld.attr("width", idCombined.clientWidth);
|
||||
svgWorld.attr("height", idCombined.clientHeight);
|
||||
|
||||
svgObservers.attr("width", idCombined.clientWidth);
|
||||
svgObservers.attr("height", idCombined.clientHeight);
|
||||
|
||||
svgGraticule.attr("height", idCombined.clientHeight);
|
||||
svgGraticule.attr("width", idCombined.clientWidth);
|
||||
|
||||
svgAlmanac.attr("height", idCombined.clientHeight);
|
||||
svgAlmanac.attr("width", idCombined.clientWidth);
|
||||
|
||||
}
|
||||
|
||||
function do_draw_world(data_world)
|
||||
{
|
||||
// console.log("do_draw_world()");
|
||||
|
||||
set_projection(data_world);
|
||||
|
||||
svgWorld.html("");
|
||||
draw_world(data_world);
|
||||
|
||||
svgGraticule.html("");
|
||||
draw_graticule();
|
||||
}
|
||||
|
||||
function read_world()
|
||||
{
|
||||
// console.log("read_world()");
|
||||
d3.json(fileWorld, function(result) {
|
||||
var data_world = result;
|
||||
do_draw_world(data_world);
|
||||
// after the world is read in and displayed - then start the timers!
|
||||
// we don't redraw the world!
|
||||
do_timer();
|
||||
});
|
||||
}
|
||||
|
||||
function geo_start()
|
||||
{
|
||||
// console.log("geo_start()");
|
||||
read_world();
|
||||
}
|
||||
|
||||
geo_start();
|
||||
|
||||
// d3.select("body").onresize = do_timer;
|
198
html/geo/geo.css
198
html/geo/geo.css
|
@ -1,18 +1,12 @@
|
|||
body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
float: left;
|
||||
background-color: #ccc;
|
||||
font-family: verdana, helvetica, arial, sans-serif;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-top: 0px;
|
||||
margin-bottom: 5px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
#galmongeo {
|
||||
border: blue 1px solid;
|
||||
margin: 5px;
|
||||
|
@ -25,85 +19,65 @@ h1 {
|
|||
|
||||
#galmoninfo {
|
||||
border-top: blue 1px solid;
|
||||
margin-bottom: 0px;
|
||||
display: block;
|
||||
}
|
||||
#galmontext {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
float: left;
|
||||
font-size: 20px;
|
||||
}
|
||||
#galmonchoice {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
float: right;
|
||||
font-size: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#combined {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
background-color: transparent;
|
||||
height: 620px;
|
||||
// background-color: yellow;
|
||||
height: 700px;
|
||||
width: 100%;
|
||||
//margin-left: auto;
|
||||
//margin-right: auto;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
border: gray 1px solid;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
#svgworld {
|
||||
position: absolute; top: 0; right: 0; bottom: 0; left: 0;
|
||||
// border: blue 1px solid;
|
||||
display: inline-block;
|
||||
// border: green 1px solid;
|
||||
// margin-left: auto;
|
||||
// margin-right: auto;
|
||||
}
|
||||
|
||||
#svggraticule {
|
||||
position: absolute; top: 0; right: 0; bottom: 0; left: 0;
|
||||
display: inline-block;
|
||||
// border: blue 1px solid;
|
||||
display: inline-block;
|
||||
// margin-left: auto;
|
||||
// margin-right: auto;
|
||||
}
|
||||
|
||||
#svgobservers {
|
||||
position: absolute; top: 0; right: 0; bottom: 0; left: 0;
|
||||
display: inline-block;
|
||||
// border: blue 1px solid;
|
||||
display: inline-block;
|
||||
// margin-left: auto;
|
||||
// margin-right: auto;
|
||||
}
|
||||
|
||||
#svgalmanac {
|
||||
position: absolute; top: 0; right: 0; bottom: 0; left: 0;
|
||||
display: inline-block;
|
||||
// border: blue 1px solid;
|
||||
}
|
||||
|
||||
#controls {
|
||||
position: absolute; bottom: 0; right: 0;
|
||||
display: inline-block;
|
||||
margin: 5px;
|
||||
}
|
||||
#rotation {
|
||||
position: absolute; bottom: 0; left: 0;
|
||||
display: inline-block;
|
||||
margin: 5px;
|
||||
// margin-left: auto;
|
||||
// margin-right: auto;
|
||||
}
|
||||
|
||||
svg {
|
||||
//padding: 20px;
|
||||
// border: red 1px solid;
|
||||
// background-color: transparent;
|
||||
padding: 20px;
|
||||
// margin-left: auto;
|
||||
// margin-right: auto;
|
||||
}
|
||||
|
||||
// #Coverage_95: { fill: #800; fill-opacity: 0.2; }
|
||||
// #Coverage_95: { fill: #800; fill-opacity: 0.2; }
|
||||
// #Coverage_95: { fill: #800; fill-opacity: 0.2; }
|
||||
// #Coverage_98: { fill: #800; fill-opacity: 0.2; }
|
||||
// #Coverage_99: { fill: #800; fill-opacity: 0.2; }
|
||||
|
||||
path.countries {
|
||||
background: yellow;
|
||||
stroke-width: 1;
|
||||
stroke: #75739F;
|
||||
fill: #7ECFE6;
|
||||
fill: #5EAFC6;
|
||||
}
|
||||
path.coverage {
|
||||
stroke-width: 1;
|
||||
|
@ -111,42 +85,15 @@ path.coverage {
|
|||
fill: #888;
|
||||
fill-opacity: 0.1;
|
||||
}
|
||||
path.coverage.down {
|
||||
stroke-width: 0;
|
||||
fill: transparent;
|
||||
fill-opacity: 0;
|
||||
}
|
||||
path.observers {
|
||||
stroke-width: 1;
|
||||
stroke: black;
|
||||
fill: black;
|
||||
fill-opacity: .75;
|
||||
}
|
||||
path.observers.down {
|
||||
stroke-width: 1;
|
||||
stroke: red;
|
||||
fill: red;
|
||||
fill-opacity: .75;
|
||||
}
|
||||
text.observers {
|
||||
font-family: courier new, courier;
|
||||
font-style: italic;
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
text.observers.down {
|
||||
color: red;
|
||||
}
|
||||
circle.satellites {
|
||||
circle.sats {
|
||||
stroke-width: 1;
|
||||
stroke: #4F442B;
|
||||
}
|
||||
.radials {
|
||||
stroke-width: 1;
|
||||
stroke: red;
|
||||
fill: none;
|
||||
//fill: #FCBC34;
|
||||
}
|
||||
text.labels {
|
||||
// stroke-width: 1;
|
||||
// stroke: #4F442B;
|
||||
// fill: #FCBC34;
|
||||
font-family: courier new, courier;
|
||||
font-style: italic;
|
||||
}
|
||||
|
@ -178,95 +125,4 @@ path.merged {
|
|||
stroke: #4F442B;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
.tooltip {
|
||||
font-family: courier new, courier;
|
||||
font-style: italic;
|
||||
font-size: 12px;
|
||||
width: 200px;
|
||||
position: relative;
|
||||
background-color: #ddd;
|
||||
border: solid;
|
||||
border-width: 1px;
|
||||
border-radius: 2px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.mybutton {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
padding-left: 35px;
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
cursor: pointer;
|
||||
font-size: 20px;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
.checkmark {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
background-color: #eee;
|
||||
}
|
||||
.mybutton:hover input ~ .checkmark {
|
||||
background-color: #ccc;
|
||||
}
|
||||
.mybutton input {
|
||||
display: none;
|
||||
}
|
||||
.mybutton input:checked ~ .checkmark {
|
||||
background-color: #2196F3;
|
||||
}
|
||||
.mybutton input:checked ~ .checkmark:after {
|
||||
display: block;
|
||||
}
|
||||
.checkmark:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
display: none;
|
||||
}
|
||||
.mybutton .checkmark:after {
|
||||
left: 9px;
|
||||
top: 5px;
|
||||
width: 5px;
|
||||
height: 10px;
|
||||
border: solid white;
|
||||
border-width: 0 3px 3px 0;
|
||||
-webkit-transform: rotate(45deg);
|
||||
-ms-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
.rotatetable {
|
||||
display: table;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0px;
|
||||
}
|
||||
.rotatetable td {
|
||||
vertical-align: middle;
|
||||
padding: 0px 3px 0px 3px;
|
||||
margin: 0px;
|
||||
}
|
||||
.myrotate {
|
||||
cursor: pointer;
|
||||
font-size: 20px;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
#controls {
|
||||
margin: 0px 5px 5px 0px;
|
||||
}
|
||||
#rotation {
|
||||
margin: 0px 0px 5px 5px;
|
||||
border: 1px gray solid;
|
||||
background: #eee;
|
||||
}
|
||||
.centered {
|
||||
margin: auto 0px;
|
||||
text-align: center;
|
||||
}
|
1092
html/geo/geo.js
1092
html/geo/geo.js
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue