Handcrafted theme for those who have not found syntax highlighting useful for themself.
body {
height: 100vh;
margin: 0;
}
.person, .person::before, .person::after, .person *, .person *::after, .person *::before {
position: absolute;
content: '';
}
.grid {
height: 100%;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
}
.portrait {
position: relative;
overflow: hidden;
--black: #00202a;
--grey: #003444;
--light-grey: #547d8f;
--white: #f8f8f8;
--blue: #2348cd;
--dark-blue: #013344;
--light-blue: #0ef3bd;
--skin: #f77676;
--dark-skin: #cc3c40;
--light-skin: #f3af9f;
--yellow: #f6d359;
--dark-yellow: #e9a964;
--red: #f44d5f;
--dark-red: #cc3040;
--light-red: #eab1ae;
}
.portrait:nth-child(1) {
background-color: #fcd2c7;
}
.portrait:nth-child(2) {
background-color: #fcf8db;
}
.portrait:nth-child(3) {
background-color: #f8e8d9;
}
/* LEFT PERSON */
/* neck */
.person_left {
top: 50%; left: 50%;
width: 88px; height: 276px;
background: linear-gradient(86deg, var(--light-skin) 90%, transparent 91%);
translate: -42px -100px;
--animation: 6s ease infinite;
}
/* body */
.person_left::before {
top: 147px; left: -106px;
width: 313px; height: 129px;
background:
/* dark lines */
linear-gradient(-8deg, transparent 108px, var(--dark-red) 109px, var(--dark-red) 110px, transparent 111px) 198px 0 no-repeat,
linear-gradient(-39deg, transparent 60px, var(--dark-red) 61px, var(--dark-red) 62px, transparent 63px),
linear-gradient(-102deg, transparent 74px, var(--dark-red) 75px, var(--dark-red) 76px, transparent 77px),
linear-gradient(-80deg, transparent 106px, var(--dark-red) 107px, var(--dark-red) 108px, transparent 109px),
/* armpit */
conic-gradient(from 3rad, var(--red) 22deg, transparent 0) 25px 49px / 50px 80px no-repeat,
/* coat */
linear-gradient(-79deg, var(--red) 126px, transparent 11px),
/* light lines */
linear-gradient(-8deg, transparent 97px, var(--light-red) 98px, var(--light-red) 99px, transparent 100px),
linear-gradient(108deg, transparent 149px, var(--light-red) 150px, var(--light-red) 151px, transparent 152px) 0 9px no-repeat,
/* shirt */
linear-gradient(-15deg, var(--white) 155px, transparent 125px),
var(--light-skin);
-webkit-mask-image: conic-gradient(from 126deg at 50% -21%, black 109deg, transparent 0);
}
/* top of the head */
.person_left .person__head {
top: -62px; left: 24px;
width: 74px; height: 74px;
background: var(--light-skin);
border-radius: 50%;
transform-origin: bottom center;
animation: leftPersonHead var(--animation);
}
@keyframes leftPersonHead {
0%, 18%, 85%, 100% { rotate: 0deg; }
23% { rotate: -5deg; }
35%, 75% { rotate: 10deg; }
}
/* ear */
.person_left .person__head::before {
top: 37px; left: -78px;
width: 20px; height: 21px;
background: var(--dark-skin);
border-radius: 13px 0 0 0;
transform-origin: top left;
transform: skewX(41deg);
}
/* brows */
.person_left .person__head::after {
top: 24px; left: -64px;
width: 87px; height: 13px;
background:
/* light lines */
linear-gradient(0deg, var(--light-grey) 2px, transparent 3px) 0 0 / 28px 7px no-repeat,
linear-gradient(0deg, var(--light-grey) 2px, transparent 3px) 46px 0 / 41px 7px no-repeat,
/* brows */
linear-gradient(100deg, var(--grey) 31px, transparent 3px) 5px 0 / 32px 100% no-repeat,
linear-gradient(var(--grey) 100%, transparent 3px) 50px 0 / 32px 100% no-repeat;
animation: leftPersonBrows var(--animation);
}
@keyframes leftPersonBrows {
0%, 18%, 85%, 100% { translate: 0; }
23% { translate: 0 5px; }
26%, 75% { translate: 0 -3px; }
}
/* beard */
.person_left .person__beard {
top: 64px; left: -76px;
width: 131px; height: 153px;
background:
/* shadows */
conic-gradient(from 0.2rad at 13% 41%, var(--black) 7%, transparent calc(7% + 0.2%)),
conic-gradient(from -1rad at 93% 36%, var(--black) 14%, transparent calc(14% + 0.2%)),
/* beard shape */
linear-gradient(284deg, var(--grey) 77%, transparent 0) 0 0 / 100px 100% no-repeat,
linear-gradient(83deg, var(--grey) 62.7%, transparent 0) 40px 0 no-repeat;
border-radius: 0 0 15px 24px;
transform-origin: top left;
animation: leftPersonBeard var(--animation);
}
@keyframes leftPersonBeard {
0%, 25%, 85%, 100% { rotate: 7deg; }
35%, 75% { rotate: 0deg; }
}
.person_left .person__beard::before {
top: 42px; left: 31px;
width: 55px; height: 27px;
background:
/* lips */
linear-gradient(-16deg, transparent 18px, var(--dark-skin) 18px, var(--dark-skin) 19px, transparent 20px) 8px 0 / 38px 100% no-repeat,
/* shadows */
conic-gradient(from 190deg at 60% 41%, var(--skin) 18%, transparent 0),
linear-gradient(107deg, var(--skin) 35%, transparent calc(35% + 1px)),
/* skin */
linear-gradient(58deg, var(--light-skin) 81%, transparent calc(81% + 1px));
transform-origin: top left;
border-radius: 0 0 5px 6px;
-webkit-mask-image: conic-gradient(from 83deg at 14% 15%, black 34%, transparent 0);
animation: leftPersonLips var(--animation);
}
@keyframes leftPersonLips {
0%, 25%, 85%, 100% { rotate: -14deg; translate: 0; }
35%, 75% { rotate: -18deg; translate: 0 7px; }
}
/* beard lines */
.person_left .person__beard::after {
top: 0px; left: 0px;
width: 100%; height: 162px;
background:
linear-gradient(79deg, transparent 111px, var(--light-grey) 111px, var(--light-grey) 113px, transparent 113px) 0px 53px / 100% 71px no-repeat,
linear-gradient(87deg, transparent 82px, var(--light-grey) 82px, var(--light-grey) 84px, transparent 84px) 0px 88px / 100% 74px no-repeat,
linear-gradient(89deg, transparent 62px, var(--light-grey) 62px, var(--light-grey) 64px, transparent 64px) 0px 70px / 100% 24px no-repeat,
linear-gradient(95deg, transparent 48px, var(--light-grey) 48px, var(--light-grey) 50px, transparent 50px) 0px 82px / 100% 54px no-repeat,
linear-gradient(97deg, transparent 26px, var(--light-grey) 26px, var(--light-grey) 28px, transparent 28px) 0px 64px / 100% 96px no-repeat;
}
.person_left .person__face {
top: 46px; left: -3px;
width: 50px; height: 45px;
background:
/* undereye circle */
linear-gradient(157deg, transparent 27px, var(--skin) 28px, var(--skin) 29px, transparent 30px) 0 0 / 37px 100% no-repeat,
/* background */
var(--light-skin);
border-radius: 0 0 8px;
transform-origin: top left;
rotate: 23deg;
}
/* nose */
.person_left .person__face::before {
top: 2px; left: -58px;
width: 80px; height: 82px;
background:
/* undereye circle */
linear-gradient(194deg, transparent 55px, var(--dark-skin) 56px, var(--dark-skin) 57px, transparent 58px) 0px 0px / 18px 100% no-repeat,
/* shadows */
linear-gradient(5deg, var(--skin) 24%, transparent calc(22% + 1px)) 0 0 / 40px 100% no-repeat,
linear-gradient(93deg, var(--skin) 33%, transparent calc(33% + 1px)) 0px 9px no-repeat,
/* skin */
linear-gradient(104deg, var(--light-skin) 73px, transparent 74px);
transform-origin: top left;
border-radius: 0 0 0 9px;
rotate: -15deg;
transform: skewY(-23deg);
}
/* eyes */
.person_left .person__face::after {
top: 27px; left: -38px;
width: 8px; height: 8px;
background: var(--grey);
border-radius: 50%;
box-shadow: 46px 0 var(--grey);
rotate: -23deg;
animation: leftPersonEyes var(--animation);
}
@keyframes leftPersonEyes {
0%, 20%, 26%, 100% { scale: 1; }
23% { scale: 1 0.1; }
}
/* CENTER PERSON */
/* body */
.person_center {
top: 50%; left: 50%;
width: 200px; height: 100px;
background:
/* collarbone */
linear-gradient(var(--skin), var(--skin)) 45px 35px / 95px 2px no-repeat,
/* skin */
radial-gradient(var(--light-skin) 70%, transparent 0) 0 -50px / 100% 130% no-repeat;
border-radius: 50%;
translate: -90px 60px;
--animation: 6s ease infinite;
}
/* neck */
.person_center::before {
top: -90px; left: 60px;
width: 80px; height: 100px;
background:
linear-gradient(140deg, var(--skin) 50%, transparent calc(50% + 1px)) no-repeat,
var(--light-skin);
-webkit-mask-image: linear-gradient(80deg, black 80%, transparent calc(80% + 1px));
animation: centerPersonNeck var(--animation);
}
@keyframes centerPersonNeck {
0%, 20%, 80%, 100% { background-position: 0 0; }
30% { background-position: 0 7px; }
40%, 70% { background-position: 0 -5px; }
}
/* ear and hair */
.person_center .person__head {
top: -145px; left: 5px;
width: 50px; height: 50px;
background: var(--dark-skin);
border-radius: 50%;
box-shadow:
/* ear */
135px 0 var(--light-skin),
/* front hair */
120px -40px 0 10px var(--yellow),
80px -60px var(--yellow),
30px -100px 0 15px var(--yellow),
/* back hair */
25px -50px 0 10px var(--dark-yellow),
80px -70px 0 10px var(--dark-yellow);
animation: centerPersonHead var(--animation),
centerPersonHair var(--animation);
}
@keyframes centerPersonHead {
0%, 20%, 80%, 100% { translate: 0 0; }
30% { translate: -2px 10px; }
40%, 70% { translate: -2px -7px; }
}
@keyframes centerPersonHair {
0%, 30%, 80%, 100% {
box-shadow:
/* ear */
135px 0 var(--light-skin),
/* front hair */
120px -40px 0 10px var(--yellow),
80px -60px var(--yellow),
30px -100px 0 15px var(--yellow),
/* back hair */
25px -50px 0 10px var(--dark-yellow),
80px -70px 0 10px var(--dark-yellow);
}
40%, 70% {
box-shadow:
/* ear */
130px 0 var(--light-skin),
/* front hair */
120px -30px 0 10px var(--yellow),
85px -55px var(--yellow),
33px -100px 0 15px var(--yellow),
/* back hair */
20px -45px 0 10px var(--dark-yellow),
75px -55px 0 10px var(--dark-yellow);
}
}
/* head */
.person_center .person__head::before {
top: -30px; left: 25px;
width: 130px; aspect-ratio: 1/1;
background:
/* hair */
linear-gradient(-150deg, var(--yellow) 29%, transparent 0) no-repeat,
/* nose */
linear-gradient(100deg, var(--skin) 45%, transparent calc(45% + 1px)) 0 70px no-repeat,
linear-gradient(100deg, var(--skin) 45%, transparent calc(45% + 1px)) no-repeat,
/* skin */
var(--light-skin);
border-radius: 50%;
animation: centerPersonFace var(--animation),
centerPersonNose var(--animation);
}
@keyframes centerPersonFace {
0%, 30%, 80%, 100% { translate: 0 0; }
40%, 70% { translate: 4px 0; }
}
@keyframes centerPersonNose {
0%, 20%, 80%, 100% {
background-position: 0 0, -13px 70px, -13px 0, 0 0;
}
30% {
background-position: 0 0, -13px 75px, -13px 5px, 0 0;
}
40%, 70% {
background-position: 0 0, -5px 70px, -5px 0, 0 0;
}
}
/* smile */
.person_center .person__head::after {
top: 45px; left: 40px;
width: 60px; height: 23px;
border-radius: 0 0 50% 50%;
box-shadow: inset 0 -13px 0 -10px var(--dark-skin);
animation: centerPersonSmile var(--animation)
}
@keyframes centerPersonSmile {
0%, 20%, 80%, 100% {
transform: translate(0, 0) scale(1) rotate(0);
}
30% {
transform: translate(0px, 5px) scale(1) rotate(0deg);
}
40%, 70% {
transform: translate(15px, -5px) scale(0.8) rotate(-15deg);
}
}
/* eyes */
.person_center .person__face {
top: 15px; left: 35px;
width: 8px; aspect-ratio: 1/1;
background: var(--black);
border-radius: 50%;
box-shadow: 65px 0 var(--black);
animation: centerPersonEyes var(--animation);
}
@keyframes centerPersonEyes {
0%, 20%, 80%, 100% { translate: 0 0; }
30% { translate: 2px 5px; }
40%, 70% { translate: 15px -10px; }
}
/* hair */
.person_center .person__face::before {
top: -85px; left: -20px;
width: 330px; height: 120px;
border-radius: 0 0 0 100%;
box-shadow: inset 120px 0 0 -50px var(--yellow);
animation: centerPersonForelock var(--animation);
}
@keyframes centerPersonForelock {
0%, 20%, 80%, 100% { translate: 0 0; }
30% { translate: 0 -5px; }
40%, 70% { translate: -12px -2px; }
}
/* glasses */
.person_center .person__face::after {
top: -25px; left: -28px;
width: 60px; aspect-ratio: 1/1;
border: 2px solid var(--blue);
border-radius: 50%;
filter: drop-shadow(75px 0 var(--blue));
animation: centerPersonGlasses var(--animation);
}
@keyframes centerPersonGlasses {
0%, 30%, 80%, 100% { translate: 0 0; }
40%, 70% { translate: -5px 5px; }
}
/* RIGHT PERSON */
/* coat */
.person_right {
top: 50%; left: 50%;
width: 224px; height: 69px;
background:
/* line */
linear-gradient(125deg, transparent 120px, var(--light-blue) 121px, var(--light-blue) 122px, transparent 123px) 0 21px no-repeat,
/* fabric */
conic-gradient(from 141deg at 75% 0%, var(--blue) 107deg, transparent 0);
translate: -85px 60px;
--animation-slow: 6s ease infinite;
--animation-fast: 1.5s ease infinite;
}
/* collar */
.person_right::before {
top: 68px; left: -1px;
width: 198px; height: 51px;
background: linear-gradient(196deg, var(--white) 54%, transparent 0);
border-radius: 0 20px 31px 0;
transform-origin: top left;
rotate: -38deg;
z-index: 1;
}
/* line */
.person_right::after {
top: 42px; left: 46px;
width: 154px; height: 2px;
background: var(--light-red);
transform-origin: top left;
rotate: -28deg;
z-index: 1;
}
/* neck */
.person_right .person__neck {
top: -94px; right: 126px;
width: 104px; height: 87px;
background:
/* line */
linear-gradient(120deg, transparent calc(55% - 1px), var(--dark-skin) 55%, var(--dark-skin) calc(55% + 1px), transparent calc(55% + 2px)) 0 9px / 100% 63px no-repeat,
/* skin */
conic-gradient(from 20deg at 13% 58%, var(--dark-skin) 8%, var(--skin) 8% 50%, transparent 0);
transform-origin: top right;
rotate: -30deg;
animation: rightPersonNeck var(--animation-fast);
}
@keyframes rightPersonNeck {
0%, 100% {
background-position: 0 9px, 0 0;
}
50% {
background-position: 0 9px, 0 8px;
}
}
/* hair */
.person_right .person__neck::before {
top: -177px; left: 38px;
width: 110px; height: 110px;
background: var(--dark-blue);
border-radius: 50%;
box-shadow: inset 5px 40px 0 -10px var(--blue);
animation: rightPersonHair var(--animation-fast);
animation-delay: 0.08s;
}
@keyframes rightPersonHair {
0%, 100% { translate: 0 0; }
50% { translate: -15px 8px; }
}
/* ear */
.person_right .person__neck::after {
top: -39px; left: 89px;
width: 48px; height: 48px;
background:
/* pinna */
conic-gradient(from 180deg, var(--skin) 90deg, transparent 0),
radial-gradient(transparent 8px, var(--dark-skin) 9px, var(--dark-skin) 10px, transparent 11px),
/* skin */
linear-gradient(90deg, var(--skin) 59%, var(--light-skin) calc(59% + 1px));
border-radius: 50%;
rotate: 30deg;
}
/* head */
.person_right .person__head {
top: -114px; left: -17px;
width: 150px; height: 106px;
/* shadow */
background: radial-gradient(circle, var(--skin) 70%, transparent 0) -11px 59px / 160px 160px no-repeat, var(--light-skin);
border-radius: 60% 40% 20% 20% / 70% 70% 20% 20%;
transform-origin: bottom right;
animation: rightPersonHead var(--animation-fast);
}
@keyframes rightPersonHead {
0%, 100% {
rotate: -15deg;
translate: -3px 3px;
}
50% {
rotate: -20deg;
translate: 0 0;
}
}
/* hair */
.person_right .person__head::before {
top: 42px; left: 82px;
width: 76px; height: 71px;
background:
/* line */
linear-gradient(93deg, transparent 63px, var(--blue) 64px, var(--blue) 65px, transparent 66px) 0 0 / 100% 57px no-repeat,
/* hair */
conic-gradient(from 59deg at 2% 60%, var(--dark-blue) 63deg, transparent 64deg);
border-radius: 0 0 17px 0;
transform-origin: top left;
rotate: -15deg;
}
/* smile */
.person_right .person__head::after {
top: 27px; left: 13px;
width: 30px; height: 13px;
border-radius: 0 0 60% 0 / 0 0 100% 0;
border-color: var(--grey);
border-width: 0 2px 2px 0;
border-style: solid;
transform-origin: top left;
rotate: 45deg;
animation: rightPersonSmile var(--animation-slow);
}
@keyframes rightPersonSmile {
0%, 50%, 100% {
border-radius: 0 0 60% 0 / 0 0 100% 0;
scale: 1;
translate: 0 0;
rotate: 45deg;
}
55%, 95% {
border-radius: 0 0 100% 0 / 0 0 100% 0;
scale: 0.8;
translate: -1px 8px;
rotate: 35deg;
}
}
/* brow */
.person_right .person__face {
top: 26px; left: 63px;
width: 51px; height: 5px;
background: var(--dark-blue);
rotate: 45deg;
}
/* eye */
.person_right .person__face::before {
top: 11px; left: 0px;
width: 25px; height: 12px;
background: linear-gradient(90deg, var(--black) 50%, var(--white) 50%) 50% 0 / 200% 100%;
border-radius: 0 0 99em 99em;
animation: rightPersonEye var(--animation-slow);
}
@keyframes rightPersonEye {
0%, 50%, 100% {
background-position: 50% 0;
border-radius: 0 0 99em 99em;
scale: 1;
}
52%, 98% {
background-position: 0 0;
border-radius: 0;
scale: 1 0.2;
}
}
/* nose */
.person_right .person__face::after {
top: 16px; left: -34px;
width: 24px; height: 36px;
background: conic-gradient(from 170deg at 73% 0%, var(--dark-skin) 38deg, transparent 0);
border-radius: 0 0 0 4px;
}
#
# NOTE: THIS DOCKERFILE IS GENERATED VIA "update.sh"
#
# PLEASE DO NOT EDIT IT DIRECTLY.
#
FROM nginx:1.24.0-alpine-slim
ENV NJS_VERSION 0.8.0
RUN set -x \
&& apkArch="$(cat /etc/apk/arch)" \
&& nginxPackages=" \
nginx=${NGINX_VERSION}-r${PKG_RELEASE} \
nginx-module-xslt=${NGINX_VERSION}-r${PKG_RELEASE} \
nginx-module-geoip=${NGINX_VERSION}-r${PKG_RELEASE} \
nginx-module-image-filter=${NGINX_VERSION}-r${PKG_RELEASE} \
nginx-module-njs=${NGINX_VERSION}.${NJS_VERSION}-r${PKG_RELEASE} \
" \
# install prerequisites for public key and pkg-oss checks
&& apk add --no-cache --virtual .checksum-deps \
openssl \
&& case "$apkArch" in \
x86_64|aarch64) \
# arches officially built by upstream
set -x \
&& KEY_SHA512="e09fa32f0a0eab2b879ccbbc4d0e4fb9751486eedda75e35fac65802cc9faa266425edf83e261137a2f4d16281ce2c1a5f4502930fe75154723da014214f0655" \
&& wget -O /tmp/nginx_signing.rsa.pub https://nginx.org/keys/nginx_signing.rsa.pub \
&& if echo "$KEY_SHA512 */tmp/nginx_signing.rsa.pub" | sha512sum -c -; then \
echo "key verification succeeded!"; \
mv /tmp/nginx_signing.rsa.pub /etc/apk/keys/; \
else \
echo "key verification failed!"; \
exit 1; \
fi \
&& apk add -X "https://nginx.org/packages/alpine/v$(egrep -o '^[0-9]+\.[0-9]+' /etc/alpine-release)/main" --no-cache $nginxPackages \
;; \
*) \
# we're on an architecture upstream doesn't officially build for
# let's build binaries from the published packaging sources
set -x \
&& tempDir="$(mktemp -d)" \
&& chown nobody:nobody $tempDir \
&& apk add --no-cache --virtual .build-deps \
gcc \
libc-dev \
make \
openssl-dev \
pcre2-dev \
zlib-dev \
linux-headers \
libxslt-dev \
gd-dev \
geoip-dev \
libedit-dev \
bash \
alpine-sdk \
findutils \
&& su nobody -s /bin/sh -c " \
export HOME=${tempDir} \
&& cd ${tempDir} \
&& curl -f -O https://hg.nginx.org/pkg-oss/archive/e5d85b3424bb.tar.gz \
&& PKGOSSCHECKSUM=\"4f33347bf05e7d7dd42a52b6e7af7ec21e3ed71df05a8ec16dd1228425f04e4318d88b1340370ccb6ad02cde590fc102094ddffbb1fc86d2085295a43f02f67b *e5d85b3424bb.tar.gz\" \
&& if [ \"\$(openssl sha512 -r e5d85b3424bb.tar.gz)\" = \"\$PKGOSSCHECKSUM\" ]; then \
echo \"pkg-oss tarball checksum verification succeeded!\"; \
else \
echo \"pkg-oss tarball checksum verification failed!\"; \
exit 1; \
fi \
&& tar xzvf e5d85b3424bb.tar.gz \
&& cd pkg-oss-e5d85b3424bb \
&& cd alpine \
&& make module-geoip module-image-filter module-njs module-xslt \
&& apk index -o ${tempDir}/packages/alpine/${apkArch}/APKINDEX.tar.gz ${tempDir}/packages/alpine/${apkArch}/*.apk \
&& abuild-sign -k ${tempDir}/.abuild/abuild-key.rsa ${tempDir}/packages/alpine/${apkArch}/APKINDEX.tar.gz \
" \
&& cp ${tempDir}/.abuild/abuild-key.rsa.pub /etc/apk/keys/ \
&& apk del --no-network .build-deps \
&& apk add -X ${tempDir}/packages/alpine/ --no-cache $nginxPackages \
;; \
esac \
# remove checksum deps
&& apk del --no-network .checksum-deps \
# if we have leftovers from building, let's purge them (including extra, unnecessary build deps)
&& if [ -n "$tempDir" ]; then rm -rf "$tempDir"; fi \
&& if [ -f "/etc/apk/keys/abuild-key.rsa.pub" ]; then rm -f /etc/apk/keys/abuild-key.rsa.pub; fi \
&& if [ -f "/etc/apk/keys/nginx_signing.rsa.pub" ]; then rm -f /etc/apk/keys/nginx_signing.rsa.pub; fi \
# Bring in curl and ca-certificates to make registering on DNS SD easier
&& apk add --no-cache curl ca-certificates
function fisher --argument-names cmd --description "A plugin manager for Fish"
set --query fisher_path || set --local fisher_path $__fish_config_dir
set --local fisher_version 4.4.4
set --local fish_plugins $__fish_config_dir/fish_plugins
switch "$cmd"
case -v --version
echo "fisher, version $fisher_version"
case "" -h --help
echo "Usage: fisher install <plugins...> Install plugins"
echo " fisher remove <plugins...> Remove installed plugins"
echo " fisher update <plugins...> Update installed plugins"
echo " fisher update Update all installed plugins"
echo " fisher list [<regex>] List installed plugins matching regex"
echo "Options:"
echo " -v, --version Print version"
echo " -h, --help Print this help message"
echo "Variables:"
echo " \$fisher_path Plugin installation path. Default: $__fish_config_dir" | string replace --regex -- $HOME \~
case ls list
string match --entire --regex -- "$argv[2]" $_fisher_plugins
case install update remove
isatty || read --local --null --array stdin && set --append argv $stdin
set --local install_plugins
set --local update_plugins
set --local remove_plugins
set --local arg_plugins $argv[2..-1]
set --local old_plugins $_fisher_plugins
set --local new_plugins
test -e $fish_plugins && set --local file_plugins (string match --regex -- '^[^\s]+$' <$fish_plugins)
if ! set --query argv[2]
if test "$cmd" != update
echo "fisher: Not enough arguments for command: \"$cmd\"" >&2 && return 1
else if ! set --query file_plugins
echo "fisher: \"$fish_plugins\" file not found: \"$cmd\"" >&2 && return 1
end
set arg_plugins $file_plugins
end
for plugin in $arg_plugins
set plugin (test -e "$plugin" && realpath $plugin || string lower -- $plugin)
contains -- "$plugin" $new_plugins || set --append new_plugins $plugin
end
if set --query argv[2]
for plugin in $new_plugins
if contains -- "$plugin" $old_plugins
test "$cmd" = remove &&
set --append remove_plugins $plugin ||
set --append update_plugins $plugin
else if test "$cmd" = install
set --append install_plugins $plugin
else
echo "fisher: Plugin not installed: \"$plugin\"" >&2 && return 1
end
end
else
for plugin in $new_plugins
contains -- "$plugin" $old_plugins &&
set --append update_plugins $plugin ||
set --append install_plugins $plugin
end
for plugin in $old_plugins
contains -- "$plugin" $new_plugins || set --append remove_plugins $plugin
end
end
set --local pid_list
set --local source_plugins
set --local fetch_plugins $update_plugins $install_plugins
set --local fish_path (status fish-path)
echo (set_color --bold)fisher $cmd version $fisher_version(set_color normal)
for plugin in $fetch_plugins
set --local source (command mktemp -d)
set --append source_plugins $source
command mkdir -p $source/{completions,conf.d,themes,functions}
$fish_path --command "
if test -e $plugin
command cp -Rf $plugin/* $source
else
set temp (command mktemp -d)
set repo (string split -- \@ $plugin) || set repo[2] HEAD
if set path (string replace --regex -- '^(https://)?gitlab.com/' '' \$repo[1])
set name (string split -- / \$path)[-1]
set url https://gitlab.com/\$path/-/archive/\$repo[2]/\$name-\$repo[2].tar.gz
else
set url https://api.github.com/repos/\$repo[1]/tarball/\$repo[2]
end
echo Fetching (set_color --underline)\$url(set_color normal)
if command curl -q --silent -L \$url | command tar -xzC \$temp -f - 2>/dev/null
command cp -Rf \$temp/*/* $source
else
echo fisher: Invalid plugin name or host unavailable: \\\"$plugin\\\" >&2
command rm -rf $source
end
command rm -rf \$temp
end
set files $source/* && string match --quiet --regex -- .+\.fish\\\$ \$files
" &
set --append pid_list (jobs --last --pid)
end
wait $pid_list 2>/dev/null
for plugin in $fetch_plugins
if set --local source $source_plugins[(contains --index -- "$plugin" $fetch_plugins)] && test ! -e $source
if set --local index (contains --index -- "$plugin" $install_plugins)
set --erase install_plugins[$index]
else
set --erase update_plugins[(contains --index -- "$plugin" $update_plugins)]
end
end
end
for plugin in $update_plugins $remove_plugins
if set --local index (contains --index -- "$plugin" $_fisher_plugins)
set --local plugin_files_var _fisher_(string escape --style=var -- $plugin)_files
if contains -- "$plugin" $remove_plugins
for name in (string replace --filter --regex -- '.+/conf\.d/([^/]+)\.fish$' '$1' $$plugin_files_var)
emit {$name}_uninstall
end
printf "%s\n" Removing\ (set_color red --bold)$plugin(set_color normal) " "$$plugin_files_var | string replace -- \~ ~
set --erase _fisher_plugins[$index]
end
command rm -rf (string replace -- \~ ~ $$plugin_files_var)
functions --erase (string replace --filter --regex -- '.+/functions/([^/]+)\.fish$' '$1' $$plugin_files_var)
for name in (string replace --filter --regex -- '.+/completions/([^/]+)\.fish$' '$1' $$plugin_files_var)
complete --erase --command $name
end
set --erase $plugin_files_var
end
end
if set --query update_plugins[1] || set --query install_plugins[1]
command mkdir -p $fisher_path/{functions,themes,conf.d,completions}
end
for plugin in $update_plugins $install_plugins
set --local source $source_plugins[(contains --index -- "$plugin" $fetch_plugins)]
set --local files $source/{functions,themes,conf.d,completions}/*
if set --local index (contains --index -- $plugin $install_plugins)
set --local user_files $fisher_path/{functions,themes,conf.d,completions}/*
set --local conflict_files
for file in (string replace -- $source/ $fisher_path/ $files)
contains -- $file $user_files && set --append conflict_files $file
end
if set --query conflict_files[1] && set --erase install_plugins[$index]
echo -s "fisher: Cannot install \"$plugin\": please remove or move conflicting files first:" \n" "$conflict_files >&2
continue
end
end
for file in (string replace -- $source/ "" $files)
command cp -RLf $source/$file $fisher_path/$file
end
set --local plugin_files_var _fisher_(string escape --style=var -- $plugin)_files
set --query files[1] && set --universal $plugin_files_var (string replace -- $source $fisher_path $files | string replace -- ~ \~)
contains -- $plugin $_fisher_plugins || set --universal --append _fisher_plugins $plugin
contains -- $plugin $install_plugins && set --local event install || set --local event update
printf "%s\n" Installing\ (set_color --bold)$plugin(set_color normal) " "$$plugin_files_var | string replace -- \~ ~
for file in (string match --regex -- '.+/[^/]+\.fish$' $$plugin_files_var | string replace -- \~ ~)
source $file
if set --local name (string replace --regex -- '.+conf\.d/([^/]+)\.fish$' '$1' $file)
emit {$name}_$event
end
end
end
command rm -rf $source_plugins
if set --query _fisher_plugins[1]
set --local commit_plugins
for plugin in $file_plugins
contains -- (string lower -- $plugin) (string lower -- $_fisher_plugins) && set --append commit_plugins $plugin
end
for plugin in $_fisher_plugins
contains -- (string lower -- $plugin) (string lower -- $commit_plugins) || set --append commit_plugins $plugin
end
printf "%s\n" $commit_plugins >$fish_plugins
else
set --erase _fisher_plugins
command rm -f $fish_plugins
end
set --local total (count $install_plugins) (count $update_plugins) (count $remove_plugins)
test "$total" != "0 0 0" && echo (string join ", " (
test $total[1] = 0 || echo "Installed $total[1]") (
test $total[2] = 0 || echo "Updated $total[2]") (
test $total[3] = 0 || echo "Removed $total[3]")
) plugin/s
case \*
echo "fisher: Unknown command: \"$cmd\"" >&2 && return 1
end
end
if ! set --query _fisher_upgraded_to_4_4
set --universal _fisher_upgraded_to_4_4
if functions --query _fisher_list
set --query XDG_DATA_HOME[1] || set --local XDG_DATA_HOME ~/.local/share
command rm -rf $XDG_DATA_HOME/fisher
functions --erase _fisher_{list,plugin_parse}
fisher update >/dev/null 2>/dev/null
else
for var in (set --names | string match --entire --regex '^_fisher_.+_files$')
set $var (string replace -- ~ \~ $$var)
end
functions --erase _fisher_fish_postexec
end
end
package kong
import (
"errors"
"fmt"
"io"
"os"
"path/filepath"
"reflect"
"regexp"
"strings"
)
var (
callbackReturnSignature = reflect.TypeOf((*error)(nil)).Elem()
)
func failField(parent reflect.Value, field reflect.StructField, format string, args ...interface{}) error {
name := parent.Type().Name()
if name == "" {
name = "<anonymous struct>"
}
return fmt.Errorf("%s.%s: %s", name, field.Name, fmt.Sprintf(format, args...))
}
// Must creates a new Parser or panics if there is an error.
func Must(ast interface{}, options ...Option) *Kong {
k, err := New(ast, options...)
if err != nil {
panic(err)
}
return k
}
type usageOnError int
const (
shortUsage usageOnError = iota + 1
fullUsage
)
// Kong is the main parser type.
type Kong struct {
// Grammar model.
Model *Application
// Termination function (defaults to os.Exit)
Exit func(int)
Stdout io.Writer
Stderr io.Writer
bindings bindings
loader ConfigurationLoader
resolvers []Resolver
registry *Registry
ignoreFields []*regexp.Regexp
noDefaultHelp bool
usageOnError usageOnError
help HelpPrinter
shortHelp HelpPrinter
helpFormatter HelpValueFormatter
helpOptions HelpOptions
helpFlag *Flag
groups []Group
vars Vars
flagNamer func(string) string
// Set temporarily by Options. These are applied after build().
postBuildOptions []Option
embedded []embedded
dynamicCommands []*dynamicCommand
}
// New creates a new Kong parser on grammar.
//
// See the README (https://github.com/alecthomas/kong) for usage instructions.
func New(grammar interface{}, options ...Option) (*Kong, error) {
k := &Kong{
Exit: os.Exit,
Stdout: os.Stdout,
Stderr: os.Stderr,
registry: NewRegistry().RegisterDefaults(),
vars: Vars{},
bindings: bindings{},
helpFormatter: DefaultHelpValueFormatter,
ignoreFields: make([]*regexp.Regexp, 0),
flagNamer: func(s string) string {
return strings.ToLower(dashedString(s))
},
}
options = append(options, Bind(k))
for _, option := range options {
if err := option.Apply(k); err != nil {
return nil, err
}
}
if k.help == nil {
k.help = DefaultHelpPrinter
}
if k.shortHelp == nil {
k.shortHelp = DefaultShortHelpPrinter
}
model, err := build(k, grammar)
if err != nil {
return k, err
}
model.Name = filepath.Base(os.Args[0])
k.Model = model
k.Model.HelpFlag = k.helpFlag
// Embed any embedded structs.
for _, embed := range k.embedded {
tag, err := parseTagString(strings.Join(embed.tags, " ")) //nolint:govet
if err != nil {
return nil, err
}
tag.Embed = true
v := reflect.Indirect(reflect.ValueOf(embed.strct))
node, err := buildNode(k, v, CommandNode, tag, map[string]bool{})
if err != nil {
return nil, err
}
for _, child := range node.Children {
child.Parent = k.Model.Node
k.Model.Children = append(k.Model.Children, child)
}
k.Model.Flags = append(k.Model.Flags, node.Flags...)
}
// Synthesise command nodes.
for _, dcmd := range k.dynamicCommands {
tag, terr := parseTagString(strings.Join(dcmd.tags, " "))
if terr != nil {
return nil, terr
}
tag.Name = dcmd.name
tag.Help = dcmd.help
tag.Group = dcmd.group
tag.Cmd = true
v := reflect.Indirect(reflect.ValueOf(dcmd.cmd))
err = buildChild(k, k.Model.Node, CommandNode, reflect.Value{}, reflect.StructField{
Name: dcmd.name,
Type: v.Type(),
}, v, tag, dcmd.name, map[string]bool{})
if err != nil {
return nil, err
}
}
for _, option := range k.postBuildOptions {
if err = option.Apply(k); err != nil {
return nil, err
}
}
k.postBuildOptions = nil
if err = k.interpolate(k.Model.Node); err != nil {
return nil, err
}
k.bindings.add(k.vars)
return k, nil
}
type varStack []Vars
func (v *varStack) head() Vars { return (*v)[len(*v)-1] }
func (v *varStack) pop() { *v = (*v)[:len(*v)-1] }
func (v *varStack) push(vars Vars) Vars {
if len(*v) != 0 {
vars = (*v)[len(*v)-1].CloneWith(vars)
}
*v = append(*v, vars)
return vars
}
// Interpolate variables into model.
func (k *Kong) interpolate(node *Node) (err error) {
stack := varStack{}
return Visit(node, func(node Visitable, next Next) error {
switch node := node.(type) {
case *Node:
vars := stack.push(node.Vars())
node.Help, err = interpolate(node.Help, vars, nil)
if err != nil {
return fmt.Errorf("help for %s: %s", node.Path(), err)
}
err = next(nil)
stack.pop()
return err
case *Value:
return next(k.interpolateValue(node, stack.head()))
}
return next(nil)
})
}
func (k *Kong) interpolateValue(value *Value, vars Vars) (err error) {
if len(value.Tag.Vars) > 0 {
vars = vars.CloneWith(value.Tag.Vars)
}
if varsContributor, ok := value.Mapper.(VarsContributor); ok {
vars = vars.CloneWith(varsContributor.Vars(value))
}
if value.Enum, err = interpolate(value.Enum, vars, nil); err != nil {
return fmt.Errorf("enum for %s: %s", value.Summary(), err)
}
updatedVars := map[string]string{
"default": value.Default,
"enum": value.Enum,
}
if value.Default, err = interpolate(value.Default, vars, nil); err != nil {
return fmt.Errorf("default value for %s: %s", value.Summary(), err)
}
if value.Enum, err = interpolate(value.Enum, vars, nil); err != nil {
return fmt.Errorf("enum value for %s: %s", value.Summary(), err)
}
if value.Flag != nil {
for i, env := range value.Flag.Envs {
if value.Flag.Envs[i], err = interpolate(env, vars, nil); err != nil {
return fmt.Errorf("env value for %s: %s", value.Summary(), err)
}
}
value.Tag.Envs = value.Flag.Envs
updatedVars["env"] = ""
if len(value.Flag.Envs) != 0 {
updatedVars["env"] = value.Flag.Envs[0]
}
}
value.Help, err = interpolate(value.Help, vars, updatedVars)
if err != nil {
return fmt.Errorf("help for %s: %s", value.Summary(), err)
}
return nil
}
// Provide additional builtin flags, if any.
func (k *Kong) extraFlags() []*Flag {
if k.noDefaultHelp {
return nil
}
var helpTarget helpValue
value := reflect.ValueOf(&helpTarget).Elem()
helpFlag := &Flag{
Short: 'h',
Value: &Value{
Name: "help",
Help: "Show context-sensitive help.",
OrigHelp: "Show context-sensitive help.",
Target: value,
Tag: &Tag{},
Mapper: k.registry.ForValue(value),
DefaultValue: reflect.ValueOf(false),
},
}
helpFlag.Flag = helpFlag
k.helpFlag = helpFlag
return []*Flag{helpFlag}
}
// Parse arguments into target.
//
// The return Context can be used to further inspect the parsed command-line, to format help, to find the
// selected command, to run command Run() methods, and so on. See Context and README for more information.
//
// Will return a ParseError if a *semantically* invalid command-line is encountered (as opposed to a syntactically
// invalid one, which will report a normal error).
func (k *Kong) Parse(args []string) (ctx *Context, err error) {
ctx, err = Trace(k, args)
if err != nil {
return nil, err
}
if ctx.Error != nil {
return nil, &ParseError{error: ctx.Error, Context: ctx}
}
if err = k.applyHook(ctx, "BeforeReset"); err != nil {
return nil, &ParseError{error: err, Context: ctx}
}
if err = ctx.Reset(); err != nil {
return nil, &ParseError{error: err, Context: ctx}
}
if err = k.applyHook(ctx, "BeforeResolve"); err != nil {
return nil, &ParseError{error: err, Context: ctx}
}
if err = ctx.Resolve(); err != nil {
return nil, &ParseError{error: err, Context: ctx}
}
if err = k.applyHook(ctx, "BeforeApply"); err != nil {
return nil, &ParseError{error: err, Context: ctx}
}
if _, err = ctx.Apply(); err != nil {
return nil, &ParseError{error: err, Context: ctx}
}
if err = ctx.Validate(); err != nil {
return nil, &ParseError{error: err, Context: ctx}
}
if err = k.applyHook(ctx, "AfterApply"); err != nil {
return nil, &ParseError{error: err, Context: ctx}
}
return ctx, nil
}
func (k *Kong) applyHook(ctx *Context, name string) error {
for _, trace := range ctx.Path {
var value reflect.Value
switch {
case trace.App != nil:
value = trace.App.Target
case trace.Argument != nil:
value = trace.Argument.Target
case trace.Command != nil:
value = trace.Command.Target
case trace.Positional != nil:
value = trace.Positional.Target
case trace.Flag != nil:
value = trace.Flag.Value.Target
default:
panic("unsupported Path")
}
method := getMethod(value, name)
if !method.IsValid() {
continue
}
binds := k.bindings.clone()
binds.add(ctx, trace)
binds.add(trace.Node().Vars().CloneWith(k.vars))
binds.merge(ctx.bindings)
if err := callFunction(method, binds); err != nil {
return err
}
}
// Path[0] will always be the app root.
return k.applyHookToDefaultFlags(ctx, ctx.Path[0].Node(), name)
}
// Call hook on any unset flags with default values.
func (k *Kong) applyHookToDefaultFlags(ctx *Context, node *Node, name string) error {
if node == nil {
return nil
}
return Visit(node, func(n Visitable, next Next) error {
node, ok := n.(*Node)
if !ok {
return next(nil)
}
binds := k.bindings.clone().add(ctx).add(node.Vars().CloneWith(k.vars))
for _, flag := range node.Flags {
if !flag.HasDefault || ctx.values[flag.Value].IsValid() || !flag.Target.IsValid() {
continue
}
method := getMethod(flag.Target, name)
if !method.IsValid() {
continue
}
path := &Path{Flag: flag}
if err := callFunction(method, binds.clone().add(path)); err != nil {
return next(err)
}
}
return next(nil)
})
}
func formatMultilineMessage(w io.Writer, leaders []string, format string, args ...interface{}) {
lines := strings.Split(fmt.Sprintf(format, args...), "\n")
leader := ""
for _, l := range leaders {
if l == "" {
continue
}
leader += l + ": "
}
fmt.Fprintf(w, "%s%s\n", leader, lines[0])
for _, line := range lines[1:] {
fmt.Fprintf(w, "%*s%s\n", len(leader), " ", line)
}
}
// Printf writes a message to Kong.Stdout with the application name prefixed.
func (k *Kong) Printf(format string, args ...interface{}) *Kong {
formatMultilineMessage(k.Stdout, []string{k.Model.Name}, format, args...)
return k
}
// Errorf writes a message to Kong.Stderr with the application name prefixed.
func (k *Kong) Errorf(format string, args ...interface{}) *Kong {
formatMultilineMessage(k.Stderr, []string{k.Model.Name, "error"}, format, args...)
return k
}
// Fatalf writes a message to Kong.Stderr with the application name prefixed then exits with a non-zero status.
func (k *Kong) Fatalf(format string, args ...interface{}) {
k.Errorf(format, args...)
k.Exit(1)
}
// FatalIfErrorf terminates with an error message if err != nil.
func (k *Kong) FatalIfErrorf(err error, args ...interface{}) {
if err == nil {
return
}
msg := err.Error()
if len(args) > 0 {
msg = fmt.Sprintf(args[0].(string), args[1:]...) + ": " + err.Error() //nolint
}
// Maybe display usage information.
var parseErr *ParseError
if errors.As(err, &parseErr) {
switch k.usageOnError {
case fullUsage:
_ = k.help(k.helpOptions, parseErr.Context)
fmt.Fprintln(k.Stdout)
case shortUsage:
_ = k.shortHelp(k.helpOptions, parseErr.Context)
fmt.Fprintln(k.Stdout)
}
}
k.Fatalf("%s", msg)
}
// LoadConfig from path using the loader configured via Configuration(loader).
//
// "path" will have ~ and any variables expanded.
func (k *Kong) LoadConfig(path string) (Resolver, error) {
var err error
path = ExpandPath(path)
path, err = interpolate(path, k.vars, nil)
if err != nil {
return nil, err
}
r, err := os.Open(path) //nolint: gas
if err != nil {
return nil, err
}
defer r.Close()
return k.loader(r)
}
module github.com/maaslalani/invoice
go 1.20
require (
github.com/signintech/gopdf v0.18.0
github.com/spf13/cobra v1.7.0
github.com/spf13/viper v1.16.0
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/phpdave11/gofpdi v1.0.14-0.20211212211723-1f10f9844311 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/spf13/afero v1.9.5 // indirect
github.com/spf13/cast v1.5.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/text v0.9.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
)
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
github.com/phpdave11/gofpdi v1.0.14-0.20211212211723-1f10f9844311 h1:zyWXQ6vu27ETMpYsEMAsisQ+GqJ4e1TPvSNfdOPF0no=
github.com/phpdave11/gofpdi v1.0.14-0.20211212211723-1f10f9844311/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/signintech/gopdf v0.18.0 h1:ktQSrhoeQSImPBIIH9Z3vnTJZXCfiGYzgYW2Vy5Ff+c=
github.com/signintech/gopdf v0.18.0/go.mod h1:wrLtZoWaRNrS4hphED0oflFoa6IWkOu6M3nJjm4VbO4=
github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc=
github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
<!DOCTYPE html>
<html class="page" lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Flexbox — Two columns</title>
<link rel="stylesheet" href="page.css">
<link rel="stylesheet" href="news.css">
<style>
/* Columns */
.columns {
display: flex;
justify-content: space-between;
}
.columns__item {
width: calc(50% - 10px);
}
</style>
</head>
<body class="page__body page__body--reasonable">
<div class="columns">
<div class="columns__item">
<article class="news news--first">
<h2 class="news__title">Crop circles have been discovered in England. But they are circles of cuddling Labrador puppies!</h2>
<p class="news__lead">In the English town of Bradstone in the county of Devon, eight Labrador puppies lay down in a circle, huddled together. Alas, one of the pups was too late. He had to climb right on top of his siblings and sleep on their backs. However, it was even softer and warmer!</p>
</article>
</div>
<div class="columns__item">
<article class="news news--second">
<h2 class="news__title">In Kamchatka, a fox stole a tourist’s camera. So now you will see a live feed from its mouth</h2>
<p class="news__lead">Finally, we found a video that would make Emmanuel Lubetsky, who worked on Survivor, Birdman, and Gravity, jealous of the camerawork! Let’s not keep you in suspense: in Kamchatka, a fox stole a camera from an inattentive tourist and made off with it. Accordingly, the view turned out to be literally from inside the fox, with ASMR soundtrack in the form of its loud puffing.</p>
</article>
</div>
</div>
</body>
</html>
[SectionOne]
key = value
integer = 1234
real = 3.14
string1 = 'Case 1'
string2 = "Case 2"
[SectionTwo]
; comment line
key = new value
integer = 5678
real = 3.14
string1 = 'Case 1'
string2 = "Case 2"
string3 = 'Case 3'
import { dequal } from 'dequal';
import { compare, lines } from 'uvu/diff';
function dedent(str) {
str = str.replace(/\r?\n/g, '\n');
let arr = str.match(/^[ \t]*(?=\S)/gm);
let i = 0, min = 1/0, len = (arr||[]).length;
for (; i < len; i++) min = Math.min(min, arr[i].length);
return len && min ? str.replace(new RegExp(`^[ \\t]{${min}}`, 'gm'), '') : str;
}
export class Assertion extends Error {
constructor(opts={}) {
super(opts.message);
this.name = 'Assertion';
this.code = 'ERR_ASSERTION';
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
}
this.details = opts.details || false;
this.generated = !!opts.generated;
this.operator = opts.operator;
this.expects = opts.expects;
this.actual = opts.actual;
}
}
function assert(bool, actual, expects, operator, detailer, backup, msg) {
if (bool) return;
let message = msg || backup;
if (msg instanceof Error) throw msg;
let details = detailer && detailer(actual, expects);
throw new Assertion({ actual, expects, operator, message, details, generated: !msg });
}
export function ok(val, msg) {
assert(!!val, false, true, 'ok', false, 'Expected value to be truthy', msg);
}
export function is(val, exp, msg) {
assert(val === exp, val, exp, 'is', compare, 'Expected values to be strictly equal:', msg);
}
export function equal(val, exp, msg) {
assert(dequal(val, exp), val, exp, 'equal', compare, 'Expected values to be deeply equal:', msg);
}
export function unreachable(msg) {
assert(false, true, false, 'unreachable', false, 'Expected not to be reached!', msg);
}
export function type(val, exp, msg) {
let tmp = typeof val;
assert(tmp === exp, tmp, exp, 'type', false, `Expected "${tmp}" to be "${exp}"`, msg);
}
export function instance(val, exp, msg) {
let name = '`' + (exp.name || exp.constructor.name) + '`';
assert(val instanceof exp, val, exp, 'instance', false, `Expected value to be an instance of ${name}`, msg);
}
export function match(val, exp, msg) {
if (typeof exp === 'string') {
assert(val.includes(exp), val, exp, 'match', false, `Expected value to include "${exp}" substring`, msg);
} else {
assert(exp.test(val), val, exp, 'match', false, `Expected value to match \`${String(exp)}\` pattern`, msg);
}
}
export function snapshot(val, exp, msg) {
val=dedent(val); exp=dedent(exp);
assert(val === exp, val, exp, 'snapshot', lines, 'Expected value to match snapshot:', msg);
}
const lineNums = (x, y) => lines(x, y, 1);
export function fixture(val, exp, msg) {
val=dedent(val); exp=dedent(exp);
assert(val === exp, val, exp, 'fixture', lineNums, 'Expected value to match fixture:', msg);
}
export function throws(blk, exp, msg) {
if (!msg && typeof exp === 'string') {
msg = exp; exp = null;
}
try {
blk();
assert(false, false, true, 'throws', false, 'Expected function to throw', msg);
} catch (err) {
if (err instanceof Assertion) throw err;
if (typeof exp === 'function') {
assert(exp(err), false, true, 'throws', false, 'Expected function to throw matching exception', msg);
} else if (exp instanceof RegExp) {
assert(exp.test(err.message), false, true, 'throws', false, `Expected function to throw exception matching \`${String(exp)}\` pattern`, msg);
}
}
}
// ---
export function not(val, msg) {
assert(!val, true, false, 'not', false, 'Expected value to be falsey', msg);
}
not.ok = not;
is.not = function (val, exp, msg) {
assert(val !== exp, val, exp, 'is.not', false, 'Expected values not to be strictly equal', msg);
}
not.equal = function (val, exp, msg) {
assert(!dequal(val, exp), val, exp, 'not.equal', false, 'Expected values not to be deeply equal', msg);
}
not.type = function (val, exp, msg) {
let tmp = typeof val;
assert(tmp !== exp, tmp, exp, 'not.type', false, `Expected "${tmp}" not to be "${exp}"`, msg);
}
not.instance = function (val, exp, msg) {
let name = '`' + (exp.name || exp.constructor.name) + '`';
assert(!(val instanceof exp), val, exp, 'not.instance', false, `Expected value not to be an instance of ${name}`, msg);
}
not.snapshot = function (val, exp, msg) {
val=dedent(val); exp=dedent(exp);
assert(val !== exp, val, exp, 'not.snapshot', false, 'Expected value not to match snapshot', msg);
}
not.fixture = function (val, exp, msg) {
val=dedent(val); exp=dedent(exp);
assert(val !== exp, val, exp, 'not.fixture', false, 'Expected value not to match fixture', msg);
}
not.match = function (val, exp, msg) {
if (typeof exp === 'string') {
assert(!val.includes(exp), val, exp, 'not.match', false, `Expected value not to include "${exp}" substring`, msg);
} else {
assert(!exp.test(val), val, exp, 'not.match', false, `Expected value not to match \`${String(exp)}\` pattern`, msg);
}
}
not.throws = function (blk, exp, msg) {
if (!msg && typeof exp === 'string') {
msg = exp; exp = null;
}
try {
blk();
} catch (err) {
if (typeof exp === 'function') {
assert(!exp(err), true, false, 'not.throws', false, 'Expected function not to throw matching exception', msg);
} else if (exp instanceof RegExp) {
assert(!exp.test(err.message), true, false, 'not.throws', false, `Expected function not to throw exception matching \`${String(exp)}\` pattern`, msg);
} else if (!exp) {
assert(false, true, false, 'not.throws', false, 'Expected function not to throw', msg);
}
}
}
{
"name": "json-server",
"version": "0.17.4",
"description": "Get a full fake REST API with zero coding in less than 30 seconds",
"main": "./lib/server/index.js",
"bin": "./lib/cli/bin.js",
"files": [
"lib",
"public"
],
"scripts": {
"prepare": "husky install",
"test": "npm run build && cross-env NODE_ENV=test jest",
"start": "babel-node -- src/cli/bin db.json -r routes.json",
"lint": "eslint . --ignore-path .gitignore",
"fix": "npm run lint -- --fix",
"build": "babel src -d lib",
"toc": "markdown-toc -i README.md",
"postversion": "git push && git push --tags",
"prepublish": "npm test && npm run build"
},
"dependencies": {
"body-parser": "^1.19.0",
"chalk": "^4.1.2",
"compression": "^1.7.4",
"connect-pause": "^0.1.1",
"cors": "^2.8.5",
"errorhandler": "^1.5.1",
"express": "^4.17.1",
"express-urlrewrite": "^1.4.0",
"json-parse-helpfulerror": "^1.0.3",
"lodash": "^4.17.21",
"lodash-id": "^0.14.1",
"lowdb": "^1.0.0",
"method-override": "^3.0.0",
"morgan": "^1.10.0",
"nanoid": "^3.1.23",
"please-upgrade-node": "^3.2.0",
"pluralize": "^8.0.0",
"server-destroy": "^1.0.1",
"yargs": "^17.0.1"
},
"devDependencies": {
"@babel/cli": "^7.12.1",
"@babel/core": "^7.12.3",
"@babel/node": "^7.12.6",
"@babel/preset-env": "^7.12.1",
"cross-env": "^7.0.2",
"eslint": "^7.13.0",
"eslint-config-prettier": "^8.3.0",
"eslint-config-standard": "^16.0.1",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.1.0",
"husky": "^6.0.0",
"jest": "^26.6.3",
"markdown-toc": "^1.2.0",
"mkdirp": "^1.0.4",
"npm-run-all": "^4.1.5",
"os-tmpdir": "^2.0.0",
"prettier": "^2.3.2",
"rimraf": "^3.0.2",
"server-ready": "^0.3.1",
"standard": "^17.1.0",
"supertest": "^6.0.1",
"temp-write": "^4.0.0"
},
"repository": {
"type": "git",
"url": "git://github.com/typicode/json-server.git"
},
"keywords": [
"JSON",
"server",
"fake",
"REST",
"API",
"prototyping",
"mock",
"mocking",
"test",
"testing",
"rest",
"data",
"dummy",
"sandbox"
],
"author": "Typicode <[email protected]>",
"license": "MIT",
"bugs": {
"url": "https://github.com/typicode/json-server/issues"
},
"homepage": "https://github.com/typicode/json-server",
"engines": {
"node": ">=12"
},
"jest": {
"testURL": "http://localhost/"
}
}
{
"name": "json-server",
"version": "0.17.4",
"description": "Get a full fake REST API with zero coding in less than 30 seconds",
"main": "./lib/server/index.js",
"bin": "./lib/cli/bin.js",
"files": [
"lib",
"public"
],
"scripts": {
"prepare": "husky install",
"test": "npm run build && cross-env NODE_ENV=test jest",
"start": "babel-node -- src/cli/bin db.json -r routes.json",
"lint": "eslint . --ignore-path .gitignore",
"fix": "npm run lint -- --fix",
"build": "babel src -d lib",
"toc": "markdown-toc -i README.md",
"postversion": "git push && git push --tags",
"prepublish": "npm test && npm run build"
},
"dependencies": {
"body-parser": "^1.19.0",
"chalk": "^4.1.2",
"compression": "^1.7.4",
"connect-pause": "^0.1.1",
"cors": "^2.8.5",
"errorhandler": "^1.5.1",
"express": "^4.17.1",
"express-urlrewrite": "^1.4.0",
"json-parse-helpfulerror": "^1.0.3",
"lodash": "^4.17.21",
"lodash-id": "^0.14.1",
"lowdb": "^1.0.0",
"method-override": "^3.0.0",
"morgan": "^1.10.0",
"nanoid": "^3.1.23",
"please-upgrade-node": "^3.2.0",
"pluralize": "^8.0.0",
"server-destroy": "^1.0.1",
"yargs": "^17.0.1"
},
"devDependencies": {
"@babel/cli": "^7.12.1",
"@babel/core": "^7.12.3",
"@babel/node": "^7.12.6",
"@babel/preset-env": "^7.12.1",
"cross-env": "^7.0.2",
"eslint": "^7.13.0",
"eslint-config-prettier": "^8.3.0",
"eslint-config-standard": "^16.0.1",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.1.0",
"husky": "^6.0.0",
"jest": "^26.6.3",
"markdown-toc": "^1.2.0",
"mkdirp": "^1.0.4",
"npm-run-all": "^4.1.5",
"os-tmpdir": "^2.0.0",
"prettier": "^2.3.2",
"rimraf": "^3.0.2",
"server-ready": "^0.3.1",
"standard": "^17.1.0",
"supertest": "^6.0.1",
"temp-write": "^4.0.0"
},
"repository": {
"type": "git",
"url": "git://github.com/typicode/json-server.git"
},
"keywords": [
"JSON",
"server",
"fake",
"REST",
"API",
"prototyping",
"mock",
"mocking",
"test",
"testing",
"rest",
"data",
"dummy",
"sandbox"
],
"author": "Typicode <[email protected]>",
"license": "MIT",
"bugs": {
"url": "https://github.com/typicode/json-server/issues"
},
"homepage": "https://github.com/typicode/json-server",
"engines": {
"node": ">=12"
},
"jest": {
"testURL": "http://localhost/"
}
}
{
"name": "json-server",
"version": "0.17.4",
"description": "Get a full fake REST API with zero coding in less than 30 seconds",
"main": "./lib/server/index.js",
"bin": "./lib/cli/bin.js",
"files": [
"lib",
"public"
],
"scripts": {
"prepare": "husky install",
"test": "npm run build && cross-env NODE_ENV=test jest",
"start": "babel-node -- src/cli/bin db.json -r routes.json",
"lint": "eslint . --ignore-path .gitignore",
"fix": "npm run lint -- --fix",
"build": "babel src -d lib",
"toc": "markdown-toc -i README.md",
"postversion": "git push && git push --tags",
"prepublish": "npm test && npm run build"
},
"dependencies": {
"body-parser": "^1.19.0",
"chalk": "^4.1.2",
"compression": "^1.7.4",
"connect-pause": "^0.1.1",
"cors": "^2.8.5",
"errorhandler": "^1.5.1",
"express": "^4.17.1",
"express-urlrewrite": "^1.4.0",
"json-parse-helpfulerror": "^1.0.3",
"lodash": "^4.17.21",
"lodash-id": "^0.14.1",
"lowdb": "^1.0.0",
"method-override": "^3.0.0",
"morgan": "^1.10.0",
"nanoid": "^3.1.23",
"please-upgrade-node": "^3.2.0",
"pluralize": "^8.0.0",
"server-destroy": "^1.0.1",
"yargs": "^17.0.1"
},
"devDependencies": {
"@babel/cli": "^7.12.1",
"@babel/core": "^7.12.3",
"@babel/node": "^7.12.6",
"@babel/preset-env": "^7.12.1",
"cross-env": "^7.0.2",
"eslint": "^7.13.0",
"eslint-config-prettier": "^8.3.0",
"eslint-config-standard": "^16.0.1",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.1.0",
"husky": "^6.0.0",
"jest": "^26.6.3",
"markdown-toc": "^1.2.0",
"mkdirp": "^1.0.4",
"npm-run-all": "^4.1.5",
"os-tmpdir": "^2.0.0",
"prettier": "^2.3.2",
"rimraf": "^3.0.2",
"server-ready": "^0.3.1",
"standard": "^17.1.0",
"supertest": "^6.0.1",
"temp-write": "^4.0.0"
},
"repository": {
"type": "git",
"url": "git://github.com/typicode/json-server.git"
},
"keywords": [
"JSON",
"server",
"fake",
"REST",
"API",
"prototyping",
"mock",
"mocking",
"test",
"testing",
"rest",
"data",
"dummy",
"sandbox"
],
"author": "Typicode <[email protected]>",
"license": "MIT",
"bugs": {
"url": "https://github.com/typicode/json-server/issues"
},
"homepage": "https://github.com/typicode/json-server",
"engines": {
"node": ">=12"
},
"jest": {
"testURL": "http://localhost/"
}
}
import _ from 'lodash'
import PropTypes from 'prop-types'
import React, { Component, createRef } from 'react'
import { withRouter, withRouteData } from 'react-static'
import { Grid, Header, Icon, Label, Popup } from 'semantic-ui-react'
import DocsLayout from 'docs/src/components/DocsLayout'
import { docTypes, examplePathToHash } from 'docs/src/utils'
import ComponentDocLinks from './ComponentDocLinks'
import ComponentDocSee from './ComponentDocSee'
import ComponentExamples from './ComponentExamples'
import ComponentProps from './ComponentProps'
import ComponentSidebar from './ComponentSidebar'
import ComponentDocContext from './ComponentDocContext'
const exampleEndStyle = {
textAlign: 'center',
opacity: 0.5,
paddingTop: '50vh',
}
class ComponentDoc extends Component {
state = {}
examplesRef = createRef()
static getDerivedStateFromProps(props, state) {
const resetOccurred = props.displayName !== state.displayName
return {
displayName: props.displayName,
exampleStates: resetOccurred ? {} : state.exampleStates,
}
}
handleExampleVisibility = (examplePath, visible) =>
this.setState((prevState) => ({
exampleStates: {
...prevState.exampleStates,
[examplePath]: visible,
},
}))
handleSidebarItemClick = (e, { examplePath }) => {
const { history, location } = this.props
history.replace(`${location.pathname}#${examplePathToHash(examplePath)}`)
}
render() {
const { componentsInfo, displayName, deprecated, seeTags, sidebarSections } = this.props
const activePath = _.findKey(this.state.exampleStates)
const componentInfo = componentsInfo[displayName]
const contextValue = { ...this.props, onVisibilityChange: this.handleExampleVisibility }
return (
/* TODO: use `title` from context */
<DocsLayout additionalTitle={displayName} sidebar title='Semantic UI React'>
<Grid padded>
<Grid.Row>
<Grid.Column>
<Header
as='h1'
content={
<>
<span>{displayName}</span>
{deprecated && (
<Popup trigger={<Label color='black'>Deprecated</Label>}>
This component is deprecated and will be removed in the next major release.
</Popup>
)}
</>
}
subheader={_.join(componentInfo.docblock.description, ' ')}
/>
<ComponentDocSee seeTags={seeTags} />
{componentInfo.repoPath && (
<ComponentDocLinks
displayName={displayName}
parentDisplayName={componentInfo.parentDisplayName}
repoPath={componentInfo.repoPath}
type={componentInfo.type}
/>
)}
<ComponentProps componentsInfo={componentsInfo} displayName={displayName} />
</Grid.Column>
</Grid.Row>
<Grid.Row columns='equal'>
<Grid.Column>
<div ref={this.examplesRef}>
<ComponentDocContext.Provider value={contextValue}>
<ComponentExamples
displayName={displayName}
examplesExist={componentInfo.examplesExist}
type={componentInfo.type}
/>
</ComponentDocContext.Provider>
</div>
<div style={exampleEndStyle}>
This is the bottom <Icon name='pointing down' />
</div>
</Grid.Column>
<Grid.Column computer={5} largeScreen={4} widescreen={4}>
<ComponentSidebar
activePath={activePath}
examplesRef={this.examplesRef}
onItemClick={this.handleSidebarItemClick}
sections={sidebarSections}
/>
</Grid.Column>
</Grid.Row>
</Grid>
</DocsLayout>
)
}
}
ComponentDoc.propTypes = {
componentsInfo: PropTypes.objectOf(docTypes.componentInfoShape).isRequired,
displayName: PropTypes.string.isRequired,
deprecated: PropTypes.bool.isRequired,
history: PropTypes.object.isRequired,
location: PropTypes.object.isRequired,
seeTags: docTypes.seeTags.isRequired,
sidebarSections: docTypes.sidebarSections.isRequired,
title: PropTypes.string.isRequired,
}
export default withRouteData(withRouter(ComponentDoc))
# ################################################################
# zstd - Makefile
# Copyright (C) Yann Collet 2014-2015
# All rights reserved.
#
# BSD license
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice, this
# list of conditions and the following disclaimer in the documentation and/or
# other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# You can contact the author at :
# - zstd source repository : https://github.com/Cyan4973/zstd
# - Public forum : https://groups.google.com/forum/#!forum/lz4c
# ################################################################
# Version number
export VERSION := 0.4.2
PRGDIR = programs
ZSTDDIR = lib
# Define nul output
ifneq (,$(filter Windows%,$(OS)))
VOID = nul
else
VOID = /dev/null
endif
.PHONY: default all zstdprogram clean install uninstall travis-install test clangtest gpptest armtest usan asan uasan
default: zstdprogram
all:
$(MAKE) -C $(ZSTDDIR) $@
$(MAKE) -C $(PRGDIR) $@
zstdprogram:
$(MAKE) -C $(PRGDIR)
clean:
@$(MAKE) -C $(ZSTDDIR) $@ > $(VOID)
@$(MAKE) -C $(PRGDIR) $@ > $(VOID)
@echo Cleaning completed
#------------------------------------------------------------------------
#make install is validated only for Linux, OSX, kFreeBSD and Hurd targets
ifneq (,$(filter $(shell uname),Linux Darwin GNU/kFreeBSD GNU))
install:
$(MAKE) -C $(ZSTDDIR) $@
$(MAKE) -C $(PRGDIR) $@
uninstall:
$(MAKE) -C $(ZSTDDIR) $@
$(MAKE) -C $(PRGDIR) $@
travis-install:
$(MAKE) install PREFIX=~/install_test_dir
test:
$(MAKE) -C $(PRGDIR) $@
clangtest: clean
clang -v
$(MAKE) all CC=clang MOREFLAGS="-Werror -Wconversion -Wno-sign-conversion"
gpptest: clean
$(MAKE) all CC=g++ CFLAGS="-O3 -Wall -Wextra -Wundef -Wshadow -Wcast-align -Werror"
armtest: clean
$(MAKE) -C $(ZSTDDIR) -e all CC=arm-linux-gnueabi-gcc MOREFLAGS="-Werror"
$(MAKE) -C $(PRGDIR) -e CC=arm-linux-gnueabi-gcc MOREFLAGS="-Werror"
usan: clean
$(MAKE) test CC=clang MOREFLAGS="-g -fsanitize=undefined"
asan: clean
$(MAKE) test CC=clang MOREFLAGS="-g -fsanitize=address"
uasan: clean
$(MAKE) test CC=clang MOREFLAGS="-g -fsanitize=address -fsanitize=undefined"
endif
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# IceCream - Never use print() to debug again
#
# Ansgar Grunseid
# grunseid.com
# [email protected]
#
# License: MIT
#
from __future__ import print_function
import ast
import inspect
import pprint
import sys
from datetime import datetime
import functools
from contextlib import contextmanager
from os.path import basename, realpath
from textwrap import dedent
import colorama
import executing
from pygments import highlight
# See https://gist.github.com/XVilka/8346728 for color support in various
# terminals and thus whether to use Terminal256Formatter or
# TerminalTrueColorFormatter.
from pygments.formatters import Terminal256Formatter
from pygments.lexers import PythonLexer as PyLexer, Python3Lexer as Py3Lexer
from .coloring import SolarizedDark
PYTHON2 = (sys.version_info[0] == 2)
_absent = object()
def bindStaticVariable(name, value):
def decorator(fn):
setattr(fn, name, value)
return fn
return decorator
@bindStaticVariable('formatter', Terminal256Formatter(style=SolarizedDark))
@bindStaticVariable(
'lexer', PyLexer(ensurenl=False) if PYTHON2 else Py3Lexer(ensurenl=False))
def colorize(s):
self = colorize
return highlight(s, self.lexer, self.formatter)
@contextmanager
def supportTerminalColorsInWindows():
# Filter and replace ANSI escape sequences on Windows with equivalent Win32
# API calls. This code does nothing on non-Windows systems.
colorama.init()
yield
colorama.deinit()
def stderrPrint(*args):
print(*args, file=sys.stderr)
def isLiteral(s):
try:
ast.literal_eval(s)
except Exception:
return False
return True
def colorizedStderrPrint(s):
colored = colorize(s)
with supportTerminalColorsInWindows():
stderrPrint(colored)
DEFAULT_PREFIX = 'ic| '
DEFAULT_LINE_WRAP_WIDTH = 70 # Characters.
DEFAULT_CONTEXT_DELIMITER = '- '
DEFAULT_OUTPUT_FUNCTION = colorizedStderrPrint
DEFAULT_ARG_TO_STRING_FUNCTION = pprint.pformat
class NoSourceAvailableError(OSError):
"""
Raised when icecream fails to find or access source code that's
required to parse and analyze. This can happen, for example, when
- ic() is invoked inside a REPL or interactive shell, e.g. from the
command line (CLI) or with python -i.
- The source code is mangled and/or packaged, e.g. with a project
freezer like PyInstaller.
- The underlying source code changed during execution. See
https://stackoverflow.com/a/33175832.
"""
infoMessage = (
'Failed to access the underlying source code for analysis. Was ic() '
'invoked in a REPL (e.g. from the command line), a frozen application '
'(e.g. packaged with PyInstaller), or did the underlying source code '
'change during execution?')
def callOrValue(obj):
return obj() if callable(obj) else obj
class Source(executing.Source):
def get_text_with_indentation(self, node):
result = self.asttokens().get_text(node)
if '\n' in result:
result = ' ' * node.first_token.start[1] + result
result = dedent(result)
result = result.strip()
return result
def prefixLinesAfterFirst(prefix, s):
lines = s.splitlines(True)
for i in range(1, len(lines)):
lines[i] = prefix + lines[i]
return ''.join(lines)
def indented_lines(prefix, string):
lines = string.splitlines()
return [prefix + lines[0]] + [
' ' * len(prefix) + line
for line in lines[1:]
]
def format_pair(prefix, arg, value):
arg_lines = indented_lines(prefix, arg)
value_prefix = arg_lines[-1] + ': '
looksLikeAString = value[0] + value[-1] in ["''", '""']
if looksLikeAString: # Align the start of multiline strings.
value = prefixLinesAfterFirst(' ', value)
value_lines = indented_lines(value_prefix, value)
lines = arg_lines[:-1] + value_lines
return '\n'.join(lines)
def singledispatch(func):
if "singledispatch" not in dir(functools):
def unsupport_py2(*args, **kwargs):
raise NotImplementedError(
"functools.singledispatch is missing in " + sys.version
)
func.register = func.unregister = unsupport_py2
return func
func = functools.singledispatch(func)
# add unregister based on https://stackoverflow.com/a/25951784
closure = dict(zip(func.register.__code__.co_freevars,
func.register.__closure__))
registry = closure['registry'].cell_contents
dispatch_cache = closure['dispatch_cache'].cell_contents
def unregister(cls):
del registry[cls]
dispatch_cache.clear()
func.unregister = unregister
return func
@singledispatch
def argumentToString(obj):
s = DEFAULT_ARG_TO_STRING_FUNCTION(obj)
s = s.replace('\\n', '\n') # Preserve string newlines in output.
return s
class IceCreamDebugger:
_pairDelimiter = ', ' # Used by the tests in tests/.
lineWrapWidth = DEFAULT_LINE_WRAP_WIDTH
contextDelimiter = DEFAULT_CONTEXT_DELIMITER
def __init__(self, prefix=DEFAULT_PREFIX,
outputFunction=DEFAULT_OUTPUT_FUNCTION,
argToStringFunction=argumentToString, includeContext=False,
contextAbsPath=False):
self.enabled = True
self.prefix = prefix
self.includeContext = includeContext
self.outputFunction = outputFunction
self.argToStringFunction = argToStringFunction
self.contextAbsPath = contextAbsPath
def __call__(self, *args):
if self.enabled:
callFrame = inspect.currentframe().f_back
try:
out = self._format(callFrame, *args)
except NoSourceAvailableError as err:
prefix = callOrValue(self.prefix)
out = prefix + 'Error: ' + err.infoMessage
self.outputFunction(out)
if not args: # E.g. ic().
passthrough = None
elif len(args) == 1: # E.g. ic(1).
passthrough = args[0]
else: # E.g. ic(1, 2, 3).
passthrough = args
return passthrough
def format(self, *args):
callFrame = inspect.currentframe().f_back
out = self._format(callFrame, *args)
return out
def _format(self, callFrame, *args):
prefix = callOrValue(self.prefix)
callNode = Source.executing(callFrame).node
if callNode is None:
raise NoSourceAvailableError()
context = self._formatContext(callFrame, callNode)
if not args:
time = self._formatTime()
out = prefix + context + time
else:
if not self.includeContext:
context = ''
out = self._formatArgs(
callFrame, callNode, prefix, context, args)
return out
def _formatArgs(self, callFrame, callNode, prefix, context, args):
source = Source.for_frame(callFrame)
sanitizedArgStrs = [
source.get_text_with_indentation(arg)
for arg in callNode.args]
pairs = list(zip(sanitizedArgStrs, args))
out = self._constructArgumentOutput(prefix, context, pairs)
return out
def _constructArgumentOutput(self, prefix, context, pairs):
def argPrefix(arg):
return '%s: ' % arg
pairs = [(arg, self.argToStringFunction(val)) for arg, val in pairs]
# For cleaner output, if <arg> is a literal, eg 3, "string", b'bytes',
# etc, only output the value, not the argument and the value, as the
# argument and the value will be identical or nigh identical. Ex: with
# ic("hello"), just output
#
# ic| 'hello',
#
# instead of
#
# ic| "hello": 'hello'.
#
pairStrs = [
val if isLiteral(arg) else (argPrefix(arg) + val)
for arg, val in pairs]
allArgsOnOneLine = self._pairDelimiter.join(pairStrs)
multilineArgs = len(allArgsOnOneLine.splitlines()) > 1
contextDelimiter = self.contextDelimiter if context else ''
allPairs = prefix + context + contextDelimiter + allArgsOnOneLine
firstLineTooLong = len(allPairs.splitlines()[0]) > self.lineWrapWidth
if multilineArgs or firstLineTooLong:
# ic| foo.py:11 in foo()
# multilineStr: 'line1
# line2'
#
# ic| foo.py:11 in foo()
# a: 11111111111111111111
# b: 22222222222222222222
if context:
lines = [prefix + context] + [
format_pair(len(prefix) * ' ', arg, value)
for arg, value in pairs
]
# ic| multilineStr: 'line1
# line2'
#
# ic| a: 11111111111111111111
# b: 22222222222222222222
else:
arg_lines = [
format_pair('', arg, value)
for arg, value in pairs
]
lines = indented_lines(prefix, '\n'.join(arg_lines))
# ic| foo.py:11 in foo()- a: 1, b: 2
# ic| a: 1, b: 2, c: 3
else:
lines = [prefix + context + contextDelimiter + allArgsOnOneLine]
return '\n'.join(lines)
def _formatContext(self, callFrame, callNode):
filename, lineNumber, parentFunction = self._getContext(
callFrame, callNode)
if parentFunction != '<module>':
parentFunction = '%s()' % parentFunction
context = '%s:%s in %s' % (filename, lineNumber, parentFunction)
return context
def _formatTime(self):
now = datetime.now()
formatted = now.strftime('%H:%M:%S.%f')[:-3]
return ' at %s' % formatted
def _getContext(self, callFrame, callNode):
lineNumber = callNode.lineno
frameInfo = inspect.getframeinfo(callFrame)
parentFunction = frameInfo.function
filepath = (realpath if self.contextAbsPath else basename)(frameInfo.filename)
return filepath, lineNumber, parentFunction
def enable(self):
self.enabled = True
def disable(self):
self.enabled = False
def configureOutput(self, prefix=_absent, outputFunction=_absent,
argToStringFunction=_absent, includeContext=_absent,
contextAbsPath=_absent):
noParameterProvided = all(
v is _absent for k,v in locals().items() if k != 'self')
if noParameterProvided:
raise TypeError('configureOutput() missing at least one argument')
if prefix is not _absent:
self.prefix = prefix
if outputFunction is not _absent:
self.outputFunction = outputFunction
if argToStringFunction is not _absent:
self.argToStringFunction = argToStringFunction
if includeContext is not _absent:
self.includeContext = includeContext
if contextAbsPath is not _absent:
self.contextAbsPath = contextAbsPath
ic = IceCreamDebugger()
# frozen_string_literal: true
require 'uri'
require 'stringio'
require 'time'
module URI
# Allows the opening of various resources including URIs.
#
# If the first argument responds to the 'open' method, 'open' is called on
# it with the rest of the arguments.
#
# If the first argument is a string that begins with <code>(protocol)://</code>, it is parsed by
# URI.parse. If the parsed object responds to the 'open' method,
# 'open' is called on it with the rest of the arguments.
#
# Otherwise, Kernel#open is called.
#
# OpenURI::OpenRead#open provides URI::HTTP#open, URI::HTTPS#open and
# URI::FTP#open, Kernel#open.
#
# We can accept URIs and strings that begin with http://, https:// and
# ftp://. In these cases, the opened file object is extended by OpenURI::Meta.
def self.open(name, *rest, &block)
if name.respond_to?(:open)
name.open(*rest, &block)
elsif name.respond_to?(:to_str) &&
%r{\A[A-Za-z][A-Za-z0-9+\-\.]*://} =~ name &&
(uri = URI.parse(name)).respond_to?(:open)
uri.open(*rest, &block)
else
super
end
end
singleton_class.send(:ruby2_keywords, :open) if respond_to?(:ruby2_keywords, true)
end
# OpenURI is an easy-to-use wrapper for Net::HTTP, Net::HTTPS and Net::FTP.
#
# == Example
#
# It is possible to open an http, https or ftp URL as though it were a file:
#
# URI.open("http://www.ruby-lang.org/") {|f|
# f.each_line {|line| p line}
# }
#
# The opened file has several getter methods for its meta-information, as
# follows, since it is extended by OpenURI::Meta.
#
# URI.open("http://www.ruby-lang.org/en") {|f|
# f.each_line {|line| p line}
# p f.base_uri # <URI::HTTP:0x40e6ef2 URL:http://www.ruby-lang.org/en/>
# p f.content_type # "text/html"
# p f.charset # "iso-8859-1"
# p f.content_encoding # []
# p f.last_modified # Thu Dec 05 02:45:02 UTC 2002
# }
#
# Additional header fields can be specified by an optional hash argument.
#
# URI.open("http://www.ruby-lang.org/en/",
# "User-Agent" => "Ruby/#{RUBY_VERSION}",
# "From" => "[email protected]",
# "Referer" => "http://www.ruby-lang.org/") {|f|
# # ...
# }
#
# The environment variables such as http_proxy, https_proxy and ftp_proxy
# are in effect by default. Here we disable proxy:
#
# URI.open("http://www.ruby-lang.org/en/", :proxy => nil) {|f|
# # ...
# }
#
# See OpenURI::OpenRead.open and URI.open for more on available options.
#
# URI objects can be opened in a similar way.
#
# uri = URI.parse("http://www.ruby-lang.org/en/")
# uri.open {|f|
# # ...
# }
#
# URI objects can be read directly. The returned string is also extended by
# OpenURI::Meta.
#
# str = uri.read
# p str.base_uri
#
# Author:: Tanaka Akira <[email protected]>
module OpenURI
VERSION = "0.4.1"
Options = {
:proxy => true,
:proxy_http_basic_authentication => true,
:progress_proc => true,
:content_length_proc => true,
:http_basic_authentication => true,
:read_timeout => true,
:open_timeout => true,
:ssl_ca_cert => nil,
:ssl_verify_mode => nil,
:ssl_min_version => nil,
:ssl_max_version => nil,
:ftp_active_mode => false,
:redirect => true,
:encoding => nil,
:max_redirects => 64,
}
def OpenURI.check_options(options) # :nodoc:
options.each {|k, v|
next unless Symbol === k
unless Options.include? k
raise ArgumentError, "unrecognized option: #{k}"
end
}
end
def OpenURI.scan_open_optional_arguments(*rest) # :nodoc:
if !rest.empty? && (String === rest.first || Integer === rest.first)
mode = rest.shift
if !rest.empty? && Integer === rest.first
perm = rest.shift
end
end
return mode, perm, rest
end
def OpenURI.open_uri(name, *rest) # :nodoc:
uri = URI::Generic === name ? name : URI.parse(name)
mode, _, rest = OpenURI.scan_open_optional_arguments(*rest)
options = rest.shift if !rest.empty? && Hash === rest.first
raise ArgumentError.new("extra arguments") if !rest.empty?
options ||= {}
OpenURI.check_options(options)
if /\Arb?(?:\Z|:([^:]+))/ =~ mode
encoding, = $1,Encoding.find($1) if $1
mode = nil
end
if options.has_key? :encoding
if !encoding.nil?
raise ArgumentError, "encoding specified twice"
end
encoding = Encoding.find(options[:encoding])
end
unless mode == nil ||
mode == 'r' || mode == 'rb' ||
mode == File::RDONLY
raise ArgumentError.new("invalid access mode #{mode} (#{uri.class} resource is read only.)")
end
io = open_loop(uri, options)
io.set_encoding(encoding) if encoding
if block_given?
begin
yield io
ensure
if io.respond_to? :close!
io.close! # Tempfile
else
io.close if !io.closed?
end
end
else
io
end
end
def OpenURI.open_loop(uri, options) # :nodoc:
proxy_opts = []
proxy_opts << :proxy_http_basic_authentication if options.include? :proxy_http_basic_authentication
proxy_opts << :proxy if options.include? :proxy
proxy_opts.compact!
if 1 < proxy_opts.length
raise ArgumentError, "multiple proxy options specified"
end
case proxy_opts.first
when :proxy_http_basic_authentication
opt_proxy, proxy_user, proxy_pass = options.fetch(:proxy_http_basic_authentication)
proxy_user = proxy_user.to_str
proxy_pass = proxy_pass.to_str
if opt_proxy == true
raise ArgumentError.new("Invalid authenticated proxy option: #{options[:proxy_http_basic_authentication].inspect}")
end
when :proxy
opt_proxy = options.fetch(:proxy)
proxy_user = nil
proxy_pass = nil
when nil
opt_proxy = true
proxy_user = nil
proxy_pass = nil
end
case opt_proxy
when true
find_proxy = lambda {|u| pxy = u.find_proxy; pxy ? [pxy, nil, nil] : nil}
when nil, false
find_proxy = lambda {|u| nil}
when String
opt_proxy = URI.parse(opt_proxy)
find_proxy = lambda {|u| [opt_proxy, proxy_user, proxy_pass]}
when URI::Generic
find_proxy = lambda {|u| [opt_proxy, proxy_user, proxy_pass]}
else
raise ArgumentError.new("Invalid proxy option: #{opt_proxy}")
end
uri_set = {}
max_redirects = options[:max_redirects]
buf = nil
while true
redirect = catch(:open_uri_redirect) {
buf = Buffer.new
uri.buffer_open(buf, find_proxy.call(uri), options)
nil
}
if redirect
if redirect.relative?
# Although it violates RFC2616, Location: field may have relative
# URI. It is converted to absolute URI using uri as a base URI.
redirect = uri + redirect
end
if !options.fetch(:redirect, true)
raise HTTPRedirect.new(buf.io.status.join(' '), buf.io, redirect)
end
unless OpenURI.redirectable?(uri, redirect)
raise "redirection forbidden: #{uri} -> #{redirect}"
end
if options.include? :http_basic_authentication
# send authentication only for the URI directly specified.
options = options.dup
options.delete :http_basic_authentication
end
uri = redirect
raise "HTTP redirection loop: #{uri}" if uri_set.include? uri.to_s
uri_set[uri.to_s] = true
raise TooManyRedirects.new("Too many redirects", buf.io) if max_redirects && uri_set.size > max_redirects
else
break
end
end
io = buf.io
io.base_uri = uri
io
end
def OpenURI.redirectable?(uri1, uri2) # :nodoc:
# This test is intended to forbid a redirection from http://... to
# file:///etc/passwd, file:///dev/zero, etc. CVE-2011-1521
# https to http redirect is also forbidden intentionally.
# It avoids sending secure cookie or referer by non-secure HTTP protocol.
# (RFC 2109 4.3.1, RFC 2965 3.3, RFC 2616 15.1.3)
# However this is ad hoc. It should be extensible/configurable.
uri1.scheme.downcase == uri2.scheme.downcase ||
(/\A(?:http|ftp)\z/i =~ uri1.scheme && /\A(?:https?|ftp)\z/i =~ uri2.scheme)
end
def OpenURI.open_http(buf, target, proxy, options) # :nodoc:
if proxy
proxy_uri, proxy_user, proxy_pass = proxy
raise "Non-HTTP proxy URI: #{proxy_uri}" if proxy_uri.class != URI::HTTP
end
if target.userinfo
raise ArgumentError, "userinfo not supported. [RFC3986]"
end
header = {}
options.each {|k, v| header[k] = v if String === k }
require 'net/http'
klass = Net::HTTP
if URI::HTTP === target
# HTTP or HTTPS
if proxy
unless proxy_user && proxy_pass
proxy_user, proxy_pass = proxy_uri.userinfo.split(':') if proxy_uri.userinfo
end
if proxy_user && proxy_pass
klass = Net::HTTP::Proxy(proxy_uri.hostname, proxy_uri.port, proxy_user, proxy_pass)
else
klass = Net::HTTP::Proxy(proxy_uri.hostname, proxy_uri.port)
end
end
target_host = target.hostname
target_port = target.port
request_uri = target.request_uri
else
# FTP over HTTP proxy
target_host = proxy_uri.hostname
target_port = proxy_uri.port
request_uri = target.to_s
if proxy_user && proxy_pass
header["Proxy-Authorization"] =
'Basic ' + ["#{proxy_user}:#{proxy_pass}"].pack('m0')
end
end
http = proxy ? klass.new(target_host, target_port) : klass.new(target_host, target_port, nil)
if target.class == URI::HTTPS
require 'net/https'
http.use_ssl = true
http.verify_mode = options[:ssl_verify_mode] || OpenSSL::SSL::VERIFY_PEER
http.min_version = options[:ssl_min_version]
http.max_version = options[:ssl_max_version]
store = OpenSSL::X509::Store.new
if options[:ssl_ca_cert]
Array(options[:ssl_ca_cert]).each do |cert|
if File.directory? cert
store.add_path cert
else
store.add_file cert
end
end
else
store.set_default_paths
end
http.cert_store = store
end
if options.include? :read_timeout
http.read_timeout = options[:read_timeout]
end
if options.include? :open_timeout
http.open_timeout = options[:open_timeout]
end
resp = nil
http.start {
req = Net::HTTP::Get.new(request_uri, header)
if options.include? :http_basic_authentication
user, pass = options[:http_basic_authentication]
req.basic_auth user, pass
end
http.request(req) {|response|
resp = response
if options[:content_length_proc] && Net::HTTPSuccess === resp
if resp.key?('Content-Length')
options[:content_length_proc].call(resp['Content-Length'].to_i)
else
options[:content_length_proc].call(nil)
end
end
resp.read_body {|str|
buf << str
if options[:progress_proc] && Net::HTTPSuccess === resp
options[:progress_proc].call(buf.size)
end
str.clear
}
}
}
io = buf.io
io.rewind
io.status = [resp.code, resp.message]
resp.each_name {|name| buf.io.meta_add_field2 name, resp.get_fields(name) }
case resp
when Net::HTTPSuccess
when Net::HTTPMovedPermanently, # 301
Net::HTTPFound, # 302
Net::HTTPSeeOther, # 303
Net::HTTPTemporaryRedirect, # 307
Net::HTTPPermanentRedirect # 308
begin
loc_uri = URI.parse(resp['location'])
rescue URI::InvalidURIError
raise OpenURI::HTTPError.new(io.status.join(' ') + ' (Invalid Location URI)', io)
end
throw :open_uri_redirect, loc_uri
else
raise OpenURI::HTTPError.new(io.status.join(' '), io)
end
end
class HTTPError < StandardError
def initialize(message, io)
super(message)
@io = io
end
attr_reader :io
end
# Raised on redirection,
# only occurs when +redirect+ option for HTTP is +false+.
class HTTPRedirect < HTTPError
def initialize(message, io, uri)
super(message, io)
@uri = uri
end
attr_reader :uri
end
class TooManyRedirects < HTTPError
end
class Buffer # :nodoc: all
def initialize
@io = StringIO.new
@size = 0
end
attr_reader :size
StringMax = 10240
def <<(str)
@io << str
@size += str.length
if StringIO === @io && StringMax < @size
require 'tempfile'
io = Tempfile.new('open-uri')
io.binmode
Meta.init io, @io if Meta === @io
io << @io.string
@io = io
end
end
def io
Meta.init @io unless Meta === @io
@io
end
end
# :stopdoc:
RE_LWS = /[\r\n\t ]+/n
RE_TOKEN = %r{[^\x00- ()<>@,;:\\"/\[\]?={}\x7f]+}n
RE_QUOTED_STRING = %r{"(?:[\r\n\t !#-\[\]-~\x80-\xff]|\\[\x00-\x7f])*"}n
RE_PARAMETERS = %r{(?:;#{RE_LWS}?#{RE_TOKEN}#{RE_LWS}?=#{RE_LWS}?(?:#{RE_TOKEN}|#{RE_QUOTED_STRING})#{RE_LWS}?)*}n
# :startdoc:
# Mixin for holding meta-information.
module Meta
def Meta.init(obj, src=nil) # :nodoc:
obj.extend Meta
obj.instance_eval {
@base_uri = nil
@meta = {} # name to string. legacy.
@metas = {} # name to array of strings.
}
if src
obj.status = src.status
obj.base_uri = src.base_uri
src.metas.each {|name, values|
obj.meta_add_field2(name, values)
}
end
end
# returns an Array that consists of status code and message.
attr_accessor :status
# returns a URI that is the base of relative URIs in the data.
# It may differ from the URI supplied by a user due to redirection.
attr_accessor :base_uri
# returns a Hash that represents header fields.
# The Hash keys are downcased for canonicalization.
# The Hash values are a field body.
# If there are multiple field with same field name,
# the field values are concatenated with a comma.
attr_reader :meta
# returns a Hash that represents header fields.
# The Hash keys are downcased for canonicalization.
# The Hash value are an array of field values.
attr_reader :metas
def meta_setup_encoding # :nodoc:
charset = self.charset
enc = nil
if charset
begin
enc = Encoding.find(charset)
rescue ArgumentError
end
end
enc = Encoding::ASCII_8BIT unless enc
if self.respond_to? :force_encoding
self.force_encoding(enc)
elsif self.respond_to? :string
self.string.force_encoding(enc)
else # Tempfile
self.set_encoding enc
end
end
def meta_add_field2(name, values) # :nodoc:
name = name.downcase
@metas[name] = values
@meta[name] = values.join(', ')
meta_setup_encoding if name == 'content-type'
end
def meta_add_field(name, value) # :nodoc:
meta_add_field2(name, [value])
end
# returns a Time that represents the Last-Modified field.
def last_modified
if vs = @metas['last-modified']
v = vs.join(', ')
Time.httpdate(v)
else
nil
end
end
def content_type_parse # :nodoc:
vs = @metas['content-type']
# The last (?:;#{RE_LWS}?)? matches extra ";" which violates RFC2045.
if vs && %r{\A#{RE_LWS}?(#{RE_TOKEN})#{RE_LWS}?/(#{RE_TOKEN})#{RE_LWS}?(#{RE_PARAMETERS})(?:;#{RE_LWS}?)?\z}no =~ vs.join(', ')
type = $1.downcase
subtype = $2.downcase
parameters = []
$3.scan(/;#{RE_LWS}?(#{RE_TOKEN})#{RE_LWS}?=#{RE_LWS}?(?:(#{RE_TOKEN})|(#{RE_QUOTED_STRING}))/no) {|att, val, qval|
if qval
val = qval[1...-1].gsub(/[\r\n\t !#-\[\]-~\x80-\xff]+|(\\[\x00-\x7f])/n) { $1 ? $1[1,1] : $& }
end
parameters << [att.downcase, val]
}
["#{type}/#{subtype}", *parameters]
else
nil
end
end
# returns "type/subtype" which is MIME Content-Type.
# It is downcased for canonicalization.
# Content-Type parameters are stripped.
def content_type
type, *_ = content_type_parse
type || 'application/octet-stream'
end
# returns a charset parameter in Content-Type field.
# It is downcased for canonicalization.
#
# If charset parameter is not given but a block is given,
# the block is called and its result is returned.
# It can be used to guess charset.
#
# If charset parameter and block is not given,
# nil is returned except text type.
# In that case, "utf-8" is returned as defined by RFC6838 4.2.1
def charset
type, *parameters = content_type_parse
if pair = parameters.assoc('charset')
pair.last.downcase
elsif block_given?
yield
elsif type && %r{\Atext/} =~ type
"utf-8" # RFC6838 4.2.1
else
nil
end
end
# Returns a list of encodings in Content-Encoding field as an array of
# strings.
#
# The encodings are downcased for canonicalization.
def content_encoding
vs = @metas['content-encoding']
if vs && %r{\A#{RE_LWS}?#{RE_TOKEN}#{RE_LWS}?(?:,#{RE_LWS}?#{RE_TOKEN}#{RE_LWS}?)*}o =~ (v = vs.join(', '))
v.scan(RE_TOKEN).map {|content_coding| content_coding.downcase}
else
[]
end
end
end
# Mixin for HTTP and FTP URIs.
module OpenRead
# OpenURI::OpenRead#open provides `open' for URI::HTTP and URI::FTP.
#
# OpenURI::OpenRead#open takes optional 3 arguments as:
#
# OpenURI::OpenRead#open([mode [, perm]] [, options]) [{|io| ... }]
#
# OpenURI::OpenRead#open returns an IO-like object if block is not given.
# Otherwise it yields the IO object and return the value of the block.
# The IO object is extended with OpenURI::Meta.
#
# +mode+ and +perm+ are the same as Kernel#open.
#
# However, +mode+ must be read mode because OpenURI::OpenRead#open doesn't
# support write mode (yet).
# Also +perm+ is ignored because it is meaningful only for file creation.
#
# +options+ must be a hash.
#
# Each option with a string key specifies an extra header field for HTTP.
# I.e., it is ignored for FTP without HTTP proxy.
#
# The hash may include other options, where keys are symbols:
#
# [:proxy]
# Synopsis:
# :proxy => "http://proxy.foo.com:8000/"
# :proxy => URI.parse("http://proxy.foo.com:8000/")
# :proxy => true
# :proxy => false
# :proxy => nil
#
# If :proxy option is specified, the value should be String, URI,
# boolean or nil.
#
# When String or URI is given, it is treated as proxy URI.
#
# When true is given or the option itself is not specified,
# environment variable `scheme_proxy' is examined.
# `scheme' is replaced by `http', `https' or `ftp'.
#
# When false or nil is given, the environment variables are ignored and
# connection will be made to a server directly.
#
# [:proxy_http_basic_authentication]
# Synopsis:
# :proxy_http_basic_authentication =>
# ["http://proxy.foo.com:8000/", "proxy-user", "proxy-password"]
# :proxy_http_basic_authentication =>
# [URI.parse("http://proxy.foo.com:8000/"),
# "proxy-user", "proxy-password"]
#
# If :proxy option is specified, the value should be an Array with 3
# elements. It should contain a proxy URI, a proxy user name and a proxy
# password. The proxy URI should be a String, an URI or nil. The proxy
# user name and password should be a String.
#
# If nil is given for the proxy URI, this option is just ignored.
#
# If :proxy and :proxy_http_basic_authentication is specified,
# ArgumentError is raised.
#
# [:http_basic_authentication]
# Synopsis:
# :http_basic_authentication=>[user, password]
#
# If :http_basic_authentication is specified,
# the value should be an array which contains 2 strings:
# username and password.
# It is used for HTTP Basic authentication defined by RFC 2617.
#
# [:content_length_proc]
# Synopsis:
# :content_length_proc => lambda {|content_length| ... }
#
# If :content_length_proc option is specified, the option value procedure
# is called before actual transfer is started.
# It takes one argument, which is expected content length in bytes.
#
# If two or more transfers are performed by HTTP redirection, the
# procedure is called only once for the last transfer.
#
# When expected content length is unknown, the procedure is called with
# nil. This happens when the HTTP response has no Content-Length header.
#
# [:progress_proc]
# Synopsis:
# :progress_proc => lambda {|size| ...}
#
# If :progress_proc option is specified, the proc is called with one
# argument each time when `open' gets content fragment from network.
# The argument +size+ is the accumulated transferred size in bytes.
#
# If two or more transfer is done by HTTP redirection, the procedure
# is called only one for a last transfer.
#
# :progress_proc and :content_length_proc are intended to be used for
# progress bar.
# For example, it can be implemented as follows using Ruby/ProgressBar.
#
# pbar = nil
# open("http://...",
# :content_length_proc => lambda {|t|
# if t && 0 < t
# pbar = ProgressBar.new("...", t)
# pbar.file_transfer_mode
# end
# },
# :progress_proc => lambda {|s|
# pbar.set s if pbar
# }) {|f| ... }
#
# [:read_timeout]
# Synopsis:
# :read_timeout=>nil (no timeout)
# :read_timeout=>10 (10 second)
#
# :read_timeout option specifies a timeout of read for http connections.
#
# [:open_timeout]
# Synopsis:
# :open_timeout=>nil (no timeout)
# :open_timeout=>10 (10 second)
#
# :open_timeout option specifies a timeout of open for http connections.
#
# [:ssl_ca_cert]
# Synopsis:
# :ssl_ca_cert=>filename or an Array of filenames
#
# :ssl_ca_cert is used to specify CA certificate for SSL.
# If it is given, default certificates are not used.
#
# [:ssl_verify_mode]
# Synopsis:
# :ssl_verify_mode=>mode
#
# :ssl_verify_mode is used to specify openssl verify mode.
#
# [:ssl_min_version]
# Synopsis:
# :ssl_min_version=>:TLS1_2
#
# :ssl_min_version option specifies the minimum allowed SSL/TLS protocol
# version. See also OpenSSL::SSL::SSLContext#min_version=.
#
# [:ssl_max_version]
# Synopsis:
# :ssl_max_version=>:TLS1_2
#
# :ssl_max_version option specifies the maximum allowed SSL/TLS protocol
# version. See also OpenSSL::SSL::SSLContext#max_version=.
#
# [:ftp_active_mode]
# Synopsis:
# :ftp_active_mode=>bool
#
# <tt>:ftp_active_mode => true</tt> is used to make ftp active mode.
# Ruby 1.9 uses passive mode by default.
# Note that the active mode is default in Ruby 1.8 or prior.
#
# [:redirect]
# Synopsis:
# :redirect=>bool
#
# +:redirect+ is true by default. <tt>:redirect => false</tt> is used to
# disable all HTTP redirects.
#
# OpenURI::HTTPRedirect exception raised on redirection.
# Using +true+ also means that redirections between http and ftp are
# permitted.
#
def open(*rest, &block)
OpenURI.open_uri(self, *rest, &block)
end
# OpenURI::OpenRead#read([ options ]) reads a content referenced by self and
# returns the content as string.
# The string is extended with OpenURI::Meta.
# The argument +options+ is same as OpenURI::OpenRead#open.
def read(options={})
self.open(options) {|f|
str = f.read
Meta.init str, f
str
}
end
end
end
module URI
class HTTP
def buffer_open(buf, proxy, options) # :nodoc:
OpenURI.open_http(buf, self, proxy, options)
end
include OpenURI::OpenRead
end
class FTP
def buffer_open(buf, proxy, options) # :nodoc:
if proxy
OpenURI.open_http(buf, self, proxy, options)
return
end
begin
require 'net/ftp'
rescue LoadError
abort "net/ftp is not found. You may need to `gem install net-ftp` to install net/ftp."
end
path = self.path
path = path.sub(%r{\A/}, '%2F') # re-encode the beginning slash because uri library decodes it.
directories = path.split(%r{/}, -1)
directories.each {|d|
d.gsub!(/%([0-9A-Fa-f][0-9A-Fa-f])/) { [$1].pack("H2") }
}
unless filename = directories.pop
raise ArgumentError, "no filename: #{self.inspect}"
end
directories.each {|d|
if /[\r\n]/ =~ d
raise ArgumentError, "invalid directory: #{d.inspect}"
end
}
if /[\r\n]/ =~ filename
raise ArgumentError, "invalid filename: #{filename.inspect}"
end
typecode = self.typecode
if typecode && /\A[aid]\z/ !~ typecode
raise ArgumentError, "invalid typecode: #{typecode.inspect}"
end
# The access sequence is defined by RFC 1738
ftp = Net::FTP.new
ftp.connect(self.hostname, self.port)
ftp.passive = !options[:ftp_active_mode]
# todo: extract user/passwd from .netrc.
user = 'anonymous'
passwd = nil
user, passwd = self.userinfo.split(/:/) if self.userinfo
ftp.login(user, passwd)
directories.each {|cwd|
ftp.voidcmd("CWD #{cwd}")
}
if typecode
# xxx: typecode D is not handled.
ftp.voidcmd("TYPE #{typecode.upcase}")
end
if options[:content_length_proc]
options[:content_length_proc].call(ftp.size(filename))
end
ftp.retrbinary("RETR #{filename}", 4096) { |str|
buf << str
options[:progress_proc].call(buf.size) if options[:progress_proc]
}
ftp.close
buf.io.rewind
end
include OpenURI::OpenRead
end
end
#![allow(clippy::self_named_module_files)] // false positive in `commands/build.rs`
use cargo::core::shell::Shell;
use cargo::util::network::http::http_handle;
use cargo::util::network::http::needs_custom_http_transport;
use cargo::util::{self, closest_msg, command_prelude, CargoResult};
use cargo_util::{ProcessBuilder, ProcessError};
use cargo_util_schemas::manifest::StringOrVec;
use std::collections::BTreeMap;
use std::env;
use std::ffi::OsStr;
use std::fs;
use std::path::{Path, PathBuf};
mod cli;
mod commands;
use crate::command_prelude::*;
fn main() {
let _guard = setup_logger();
let mut gctx = match GlobalContext::default() {
Ok(gctx) => gctx,
Err(e) => {
let mut shell = Shell::new();
cargo::exit_with_error(e.into(), &mut shell)
}
};
let result = if let Some(lock_addr) = cargo::ops::fix_get_proxy_lock_addr() {
cargo::ops::fix_exec_rustc(&gctx, &lock_addr).map_err(|e| CliError::from(e))
} else {
let _token = cargo::util::job::setup();
cli::main(&mut gctx)
};
match result {
Err(e) => cargo::exit_with_error(e, &mut *gctx.shell()),
Ok(()) => {}
}
}
fn setup_logger() -> Option<ChromeFlushGuard> {
use tracing_subscriber::prelude::*;
let env = tracing_subscriber::EnvFilter::from_env("CARGO_LOG");
let fmt_layer = tracing_subscriber::fmt::layer()
.with_timer(tracing_subscriber::fmt::time::Uptime::default())
.with_ansi(std::io::IsTerminal::is_terminal(&std::io::stderr()))
.with_writer(std::io::stderr)
.with_filter(env);
let (profile_layer, profile_guard) = chrome_layer();
let registry = tracing_subscriber::registry()
.with(fmt_layer)
.with(profile_layer);
registry.init();
tracing::trace!(start = humantime::format_rfc3339(std::time::SystemTime::now()).to_string());
profile_guard
}
#[cfg(target_has_atomic = "64")]
type ChromeFlushGuard = tracing_chrome::FlushGuard;
#[cfg(target_has_atomic = "64")]
fn chrome_layer<S>() -> (
Option<tracing_chrome::ChromeLayer<S>>,
Option<ChromeFlushGuard>,
)
where
S: tracing::Subscriber
+ for<'span> tracing_subscriber::registry::LookupSpan<'span>
+ Send
+ Sync,
{
#![allow(clippy::disallowed_methods)]
if env_to_bool(std::env::var_os("CARGO_LOG_PROFILE").as_deref()) {
let capture_args =
env_to_bool(std::env::var_os("CARGO_LOG_PROFILE_CAPTURE_ARGS").as_deref());
let (layer, guard) = tracing_chrome::ChromeLayerBuilder::new()
.include_args(capture_args)
.build();
(Some(layer), Some(guard))
} else {
(None, None)
}
}
#[cfg(not(target_has_atomic = "64"))]
type ChromeFlushGuard = ();
#[cfg(not(target_has_atomic = "64"))]
fn chrome_layer() -> (
Option<tracing_subscriber::layer::Identity>,
Option<ChromeFlushGuard>,
) {
(None, None)
}
#[cfg(target_has_atomic = "64")]
fn env_to_bool(os: Option<&OsStr>) -> bool {
match os.and_then(|os| os.to_str()) {
Some("1") | Some("true") => true,
_ => false,
}
}
/// Table for defining the aliases which come builtin in `Cargo`.
/// The contents are structured as: `(alias, aliased_command, description)`.
const BUILTIN_ALIASES: [(&str, &str, &str); 6] = [
("b", "build", "alias: build"),
("c", "check", "alias: check"),
("d", "doc", "alias: doc"),
("r", "run", "alias: run"),
("t", "test", "alias: test"),
("rm", "remove", "alias: remove"),
];
/// Function which contains the list of all of the builtin aliases and it's
/// corresponding execs represented as &str.
fn builtin_aliases_execs(cmd: &str) -> Option<&(&str, &str, &str)> {
BUILTIN_ALIASES.iter().find(|alias| alias.0 == cmd)
}
/// Resolve the aliased command from the [`GlobalContext`] with a given command string.
///
/// The search fallback chain is:
///
/// 1. Get the aliased command as a string.
/// 2. If an `Err` occurs (missing key, type mismatch, or any possible error),
/// try to get it as an array again.
/// 3. If still cannot find any, finds one insides [`BUILTIN_ALIASES`].
fn aliased_command(gctx: &GlobalContext, command: &str) -> CargoResult<Option<Vec<String>>> {
let alias_name = format!("alias.{}", command);
let user_alias = match gctx.get_string(&alias_name) {
Ok(Some(record)) => Some(
record
.val
.split_whitespace()
.map(|s| s.to_string())
.collect(),
),
Ok(None) => None,
Err(_) => gctx.get::<Option<Vec<String>>>(&alias_name)?,
};
let result = user_alias.or_else(|| {
builtin_aliases_execs(command).map(|command_str| vec![command_str.1.to_string()])
});
Ok(result)
}
/// List all runnable commands
fn list_commands(gctx: &GlobalContext) -> BTreeMap<String, CommandInfo> {
let prefix = "cargo-";
let suffix = env::consts::EXE_SUFFIX;
let mut commands = BTreeMap::new();
for dir in search_directories(gctx) {
let entries = match fs::read_dir(dir) {
Ok(entries) => entries,
_ => continue,
};
for entry in entries.filter_map(|e| e.ok()) {
let path = entry.path();
let Some(filename) = path.file_name().and_then(|s| s.to_str()) else {
continue;
};
let Some(name) = filename
.strip_prefix(prefix)
.and_then(|s| s.strip_suffix(suffix))
else {
continue;
};
if is_executable(entry.path()) {
commands.insert(
name.to_string(),
CommandInfo::External { path: path.clone() },
);
}
}
}
for cmd in commands::builtin() {
commands.insert(
cmd.get_name().to_string(),
CommandInfo::BuiltIn {
about: cmd.get_about().map(|s| s.to_string()),
},
);
}
// Add the builtin_aliases and them descriptions to the
// `commands` `BTreeMap`.
for command in &BUILTIN_ALIASES {
commands.insert(
command.0.to_string(),
CommandInfo::BuiltIn {
about: Some(command.2.to_string()),
},
);
}
// Add the user-defined aliases
if let Ok(aliases) = gctx.get::<BTreeMap<String, StringOrVec>>("alias") {
for (name, target) in aliases.iter() {
commands.insert(
name.to_string(),
CommandInfo::Alias {
target: target.clone(),
},
);
}
}
// `help` is special, so it needs to be inserted separately.
commands.insert(
"help".to_string(),
CommandInfo::BuiltIn {
about: Some("Displays help for a cargo subcommand".to_string()),
},
);
commands
}
fn find_external_subcommand(gctx: &GlobalContext, cmd: &str) -> Option<PathBuf> {
let command_exe = format!("cargo-{}{}", cmd, env::consts::EXE_SUFFIX);
search_directories(gctx)
.iter()
.map(|dir| dir.join(&command_exe))
.find(|file| is_executable(file))
}
fn execute_external_subcommand(gctx: &GlobalContext, cmd: &str, args: &[&OsStr]) -> CliResult {
let path = find_external_subcommand(gctx, cmd);
let command = match path {
Some(command) => command,
None => {
let script_suggestion = if gctx.cli_unstable().script
&& std::path::Path::new(cmd).is_file()
{
let sep = std::path::MAIN_SEPARATOR;
format!("\n\tTo run the file `{cmd}`, provide a relative path like `.{sep}{cmd}`")
} else {
"".to_owned()
};
let err = if cmd.starts_with('+') {
anyhow::format_err!(
"no such command: `{cmd}`\n\n\t\
Cargo does not handle `+toolchain` directives.\n\t\
Did you mean to invoke `cargo` through `rustup` instead?{script_suggestion}",
)
} else {
let suggestions = list_commands(gctx);
let did_you_mean = closest_msg(cmd, suggestions.keys(), |c| c);
anyhow::format_err!(
"no such command: `{cmd}`{did_you_mean}\n\n\t\
View all installed commands with `cargo --list`\n\t\
Find a package to install `{cmd}` with `cargo search cargo-{cmd}`{script_suggestion}",
)
};
return Err(CliError::new(err, 101));
}
};
execute_subcommand(gctx, Some(&command), args)
}
fn execute_internal_subcommand(gctx: &GlobalContext, args: &[&OsStr]) -> CliResult {
execute_subcommand(gctx, None, args)
}
// This function is used to execute a subcommand. It is used to execute both
// internal and external subcommands.
// If `cmd_path` is `None`, then the subcommand is an internal subcommand.
fn execute_subcommand(
gctx: &GlobalContext,
cmd_path: Option<&PathBuf>,
args: &[&OsStr],
) -> CliResult {
let cargo_exe = gctx.cargo_exe()?;
let mut cmd = match cmd_path {
Some(cmd_path) => ProcessBuilder::new(cmd_path),
None => ProcessBuilder::new(&cargo_exe),
};
cmd.env(cargo::CARGO_ENV, cargo_exe).args(args);
if let Some(client) = gctx.jobserver_from_env() {
cmd.inherit_jobserver(client);
}
let err = match cmd.exec_replace() {
Ok(()) => return Ok(()),
Err(e) => e,
};
if let Some(perr) = err.downcast_ref::<ProcessError>() {
if let Some(code) = perr.code {
return Err(CliError::code(code));
}
}
Err(CliError::new(err, 101))
}
#[cfg(unix)]
fn is_executable<P: AsRef<Path>>(path: P) -> bool {
use std::os::unix::prelude::*;
fs::metadata(path)
.map(|metadata| metadata.is_file() && metadata.permissions().mode() & 0o111 != 0)
.unwrap_or(false)
}
#[cfg(windows)]
fn is_executable<P: AsRef<Path>>(path: P) -> bool {
path.as_ref().is_file()
}
fn search_directories(gctx: &GlobalContext) -> Vec<PathBuf> {
let mut path_dirs = if let Some(val) = gctx.get_env_os("PATH") {
env::split_paths(&val).collect()
} else {
vec![]
};
let home_bin = gctx.home().clone().into_path_unlocked().join("bin");
// If any of that PATH elements contains `home_bin`, do not
// add it again. This is so that the users can control priority
// of it using PATH, while preserving the historical
// behavior of preferring it over system global directories even
// when not in PATH at all.
// See https://github.com/rust-lang/cargo/issues/11020 for details.
//
// Note: `p == home_bin` will ignore trailing slash, but we don't
// `canonicalize` the paths.
if !path_dirs.iter().any(|p| p == &home_bin) {
path_dirs.insert(0, home_bin);
};
path_dirs
}
/// Initialize libgit2.
#[tracing::instrument(skip_all)]
fn init_git(gctx: &GlobalContext) {
// Disabling the owner validation in git can, in theory, lead to code execution
// vulnerabilities. However, libgit2 does not launch executables, which is the foundation of
// the original security issue. Meanwhile, issues with refusing to load git repos in
// `CARGO_HOME` for example will likely be very frustrating for users. So, we disable the
// validation.
//
// For further discussion of Cargo's current interactions with git, see
//
// https://github.com/rust-lang/rfcs/pull/3279
//
// and in particular the subsection on "Git support".
//
// Note that we only disable this when Cargo is run as a binary. If Cargo is used as a library,
// this code won't be invoked. Instead, developers will need to explicitly disable the
// validation in their code. This is inconvenient, but won't accidentally open consuming
// applications up to security issues if they use git2 to open repositories elsewhere in their
// code.
unsafe {
git2::opts::set_verify_owner_validation(false)
.expect("set_verify_owner_validation should never fail");
}
init_git_transports(gctx);
}
/// Configure libgit2 to use libcurl if necessary.
///
/// If the user has a non-default network configuration, then libgit2 will be
/// configured to use libcurl instead of the built-in networking support so
/// that those configuration settings can be used.
#[tracing::instrument(skip_all)]
fn init_git_transports(gctx: &GlobalContext) {
match needs_custom_http_transport(gctx) {
Ok(true) => {}
_ => return,
}
let handle = match http_handle(gctx) {
Ok(handle) => handle,
Err(..) => return,
};
// The unsafety of the registration function derives from two aspects:
//
// 1. This call must be synchronized with all other registration calls as
// well as construction of new transports.
// 2. The argument is leaked.
//
// We're clear on point (1) because this is only called at the start of this
// binary (we know what the state of the world looks like) and we're mostly
// clear on point (2) because we'd only free it after everything is done
// anyway
unsafe {
git2_curl::register(handle);
}
}
#!/bin/bash
##===- bolt/utils/bughunter.sh - Help locate BOLT bugs -------*- Script -*-===##
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
# details.
#
##===----------------------------------------------------------------------===##
#
# This script attempts to narrow down llvm-bolt bug to a single function in the
# input binary.
#
# If such a function is found, llvm-bolt could be run just on this function
# to mitigate debugging process.
#
# The following envvars are used by this script:
#
# BOLT - path to llvm-bolt
#
# BOLT_OPTIONS - options to be used by llvm-bolt
#
# INPUT_BINARY - input for llvm-bolt
#
# PRE_COMMAND - command to execute prior to running optimized binary
#
# POST_COMMAND - command to filter results of running optimized binary
#
# TIMEOUT_OR_CMD - optional timeout or command on optimized binary command
# if the value is a number with an optional trailing letter
# [smhd] it is considered a paramter to "timeout",
# otherwise it's a shell command that wraps the optimized
# binary command.
#
# COMMAND_LINE - command line options to run optimized binary with
#
# IGNORE_ERROR - ignore error codes returned from optimized binary
#
# GOLD_FILE - file containing expected output from optimized binary
#
# FUNC_NAMES - if set, path to an initial list of function names to
# search. Otherwise, nm is used on the original binary.
#
# OFFLINE - if set, bughunter will produce the binaries but will not
# run them, and will depend on you telling whether it
# succeeded or not.
#
# MAX_FUNCS - if set, use -max-funcs to narrow down the offending
# function. if non-zero, start -max-funcs at $MAX_FUNCS
# otherwise, count the number of symbols in the binary.
#
# MAX_FUNCS_FLAG - BOLT command line option to use for MAX_FUNCS search.
# Default is -max-funcs. Can also be used for relocation
# debugging, e.g. -max-data-relocations.
#
# VERBOSE - if non-empty, set the script to echo mode.
#
##===----------------------------------------------------------------------===##
BOLT=${BOLT:=llvm-bolt}
ulimit -c 0
set -o pipefail
if [[ -n "$VERBOSE" ]]; then
set -x
fi
if [[ ! -x $INPUT_BINARY ]] ; then
echo "INPUT_BINARY must be set to an executable file"
exit 1
fi
if [[ -z "$PRE_COMMAND" ]] ; then
PRE_COMMAND=':'
fi
if [[ -z "$POST_COMMAND" ]] ; then
POST_COMMAND='cat'
fi
if [[ -n "$TIMEOUT_OR_CMD" && $TIMEOUT_OR_CMD =~ ^[0-9]+[smhd]?$ ]] ; then
TIMEOUT_OR_CMD="timeout -s KILL $TIMEOUT_OR_CMD"
fi
if [[ -z "$MAX_FUNCS_FLAG" ]] ; then
MAX_FUNCS_FLAG="-max-funcs"
fi
OPTIMIZED_BINARY=$(mktemp -t -u --suffix=.bolt $(basename ${INPUT_BINARY}).XXX)
OUTPUT_FILE="${OPTIMIZED_BINARY}.out"
BOLT_LOG=$(mktemp -t -u --suffix=.log boltXXX)
if [[ -z $OFFLINE ]]; then
echo "Verify input binary passes"
echo " INPUT_BINARY: $PRE_COMMAND && $TIMEOUT_OR_CMD $INPUT_BINARY $COMMAND_LINE |& $POST_COMMAND >& $OUTPUT_FILE"
($PRE_COMMAND && $TIMEOUT_OR_CMD $INPUT_BINARY $COMMAND_LINE |& $POST_COMMAND >& $OUTPUT_FILE)
STATUS=$?
if [[ "$IGNORE_ERROR" == "1" ]]; then
FAIL=0
else
FAIL=$STATUS
fi
if [[ -e "$GOLD_FILE" ]] ; then
cmp -s "$OUTPUT_FILE" "$GOLD_FILE"
FAIL=$?
fi
if [[ $FAIL -ne "0" ]] ; then
echo " Warning: input binary failed"
else
echo " Input binary passes."
fi
fi
echo "Verify optimized binary fails"
($BOLT $BOLT_OPTIONS $INPUT_BINARY -o $OPTIMIZED_BINARY >& $BOLT_LOG)
FAIL=$?
if [[ $FAIL -eq "0" ]]; then
if [[ -z $OFFLINE ]]; then
echo " OPTIMIZED_BINARY: $PRE_COMMAND && $TIMEOUT_OR_CMD $OPTIMIZED_BINARY $COMMAND_LINE |& $POST_COMMAND >& $OUTPUT_FILE"
($PRE_COMMAND && $TIMEOUT_OR_CMD $OPTIMIZED_BINARY $COMMAND_LINE |& $POST_COMMAND >& $OUTPUT_FILE)
STATUS=$?
if [[ "$IGNORE_ERROR" == "1" ]]; then
FAIL=0
else
FAIL=$STATUS
fi
if [[ -e "$GOLD_FILE" ]] ; then
cmp -s "$OUTPUT_FILE" "$GOLD_FILE"
FAIL=$?
fi
else
echo "Did it pass? Type the return code [0 = pass, 1 = fail]"
read -n1 PASS
fi
if [[ $FAIL -eq "0" ]] ; then
echo " Warning: optimized binary passes."
else
echo " Optimized binary fails as expected."
fi
else
echo " Bolt crashes while generating optimized binary."
fi
# Collect function names
FF=$(mktemp -t -u --suffix=.txt func-names.XXX)
nm --defined-only -p $INPUT_BINARY | grep " [TtWw] " | cut -d ' ' -f 3 | egrep -v "\._" | egrep -v '^$' | sort -u > $FF
# Use function names or numbers
if [[ -z "$MAX_FUNCS" ]] ; then
# Do binary search on function names
if [[ -n "$FUNC_NAMES" ]]; then
FF=$FUNC_NAMES
fi
NUM_FUNCS=$(wc -l $FF | cut -d ' ' -f 1)
HALF=$(expr \( $NUM_FUNCS + 1 \) / 2)
PREFIX=$(mktemp -t -u --suffix=.txt func-names.XXX)
FF0=$PREFIX\aa
FF1=$PREFIX\ab
split -a 2 -l $HALF $FF $PREFIX
FF=$FF0
NUM_FUNCS=$(wc -l $FF | cut -d ' ' -f 1)
CONTINUE=$(expr $NUM_FUNCS \> 0)
else
P=0
if [[ "$MAX_FUNCS" -eq "0" ]]; then
Q=$(wc -l $FF | cut -d ' ' -f 1)
else
Q=$MAX_FUNCS
fi
I=$Q
CONTINUE=$(expr \( $Q - $P \) \> 1)
fi
ITER=0
while [[ "$CONTINUE" -ne "0" ]] ; do
rm -f $OPTIMIZED_BINARY
if [[ -z "$MAX_FUNCS" ]] ; then
echo "Iteration $ITER, trying $FF / $HALF functions"
SEARCH_OPT="-funcs-file-no-regex=$FF"
else
I=$(expr \( $Q + $P \) / 2)
echo "Iteration $ITER, P=$P, Q=$Q, I=$I"
SEARCH_OPT="$MAX_FUNCS_FLAG=$I"
fi
echo " BOLT: $BOLT $BOLT_OPTIONS $INPUT_BINARY $SEARCH_OPT -o $OPTIMIZED_BINARY >& $BOLT_LOG"
($BOLT $BOLT_OPTIONS $INPUT_BINARY $SEARCH_OPT -o $OPTIMIZED_BINARY >& $BOLT_LOG)
FAIL=$?
echo " BOLT failure=$FAIL"
rm -f $OUTPUT_FILE
if [[ $FAIL -eq "0" ]] ; then
if [[ -z $OFFLINE ]]; then
echo " OPTIMIZED_BINARY: $PRE_COMMAND && $TIMEOUT_OR_CMD $OPTIMIZED_BINARY $COMMAND_LINE |& $POST_COMMAND >& $OUTPUT_FILE"
($PRE_COMMAND && $TIMEOUT_OR_CMD $OPTIMIZED_BINARY $COMMAND_LINE |& $POST_COMMAND >& $OUTPUT_FILE)
STATUS=$?
if [[ "$IGNORE_ERROR" == "1" ]]; then
FAIL=0
else
FAIL=$STATUS
fi
if [[ -e "$GOLD_FILE" ]] ; then
cmp -s "$OUTPUT_FILE" "$GOLD_FILE"
FAIL=$?
fi
echo " OPTIMIZED_BINARY failure=$FAIL"
else
echo "Did it pass? Type the return code [0 = pass, 1 = fail]"
read -n1 PASS
fi
else
FAIL=1
fi
if [[ -z "$MAX_FUNCS" ]] ; then
if [[ $FAIL -eq "0" ]] ; then
if [[ "$FF" == "$FF1" ]]; then
NUM_FUNCS=0
break;
fi
FF=$FF1
NUM_FUNCS=$(wc -l $FF | cut -d ' ' -f 1)
else
HALF=$(expr \( $NUM_FUNCS + 1 \) / 2)
PREFIX=$(mktemp -t -u --suffix=.txt func-names.XXX)
split -a 2 -l $HALF $FF $PREFIX
FF0=$PREFIX\aa
FF1=$PREFIX\ab
FF=$FF0
NUM_FUNCS=$(wc -l $FF | cut -d ' ' -f 1)
if [[ $NUM_FUNCS -eq "1" && ! -e $FF1 ]]; then
break;
fi
fi
CONTINUE=$(expr $NUM_FUNCS \> 0)
else
if [[ $FAIL -eq "0" ]] ; then
P=$I
else
Q=$I
fi
FF=$I
HALF=$I
CONTINUE=$(expr \( $Q - $P \) \> 1)
fi
ITER=$(expr $ITER + 1)
done
if [[ -z "$MAX_FUNCS" ]] ; then
if [[ "$NUM_FUNCS" -ne "0" ]] ; then
FAILED="The function(s) that failed are in $FF"
fi
else
if [[ $P -ne $Q ]] ; then
FF=$(grep "processing ending" $BOLT_LOG | sed -e "s/BOLT-INFO: processing ending on \(.*\)/\1/g" | tail -1)
FAILED="The item that failed is $FF @ $Q"
fi
fi
if [[ -n "$FAILED" ]] ; then
echo "$FAILED"
echo "To reproduce, run: $BOLT $BOLT_OPTIONS $INPUT_BINARY $SEARCH_OPT -o $OPTIMIZED_BINARY"
else
echo "Unable to reproduce bug."
fi
rm $OPTIMIZED_BINARY $OUTPUT_FILE $BOLT_LOG
-- https://github.com/prisma/database-schema-examples/tree/master/postgres/basic-twitter#basic-twitter
CREATE TABLE tweet (
id BIGINT NOT NULL PRIMARY KEY,
text TEXT NOT NULL,
is_sent BOOLEAN NOT NULL DEFAULT TRUE,
owner_id BIGINT
);
INSERT INTO tweet(id, text, owner_id)
VALUES (1, '#sqlx is pretty cool!', 1);
--
CREATE TABLE tweet_reply (
id BIGINT NOT NULL PRIMARY KEY,
tweet_id BIGINT NOT NULL,
text TEXT NOT NULL,
owner_id BIGINT,
CONSTRAINT tweet_id_fk FOREIGN KEY (tweet_id) REFERENCES tweet(id)
);
INSERT INTO tweet_reply(id, tweet_id, text, owner_id)
VALUES (1, 1, 'Yeah! #sqlx is indeed pretty cool!', 1);
--
CREATE TABLE accounts (
id INTEGER NOT NULL PRIMARY KEY,
name TEXT NOT NULL,
is_active BOOLEAN
);
INSERT INTO accounts(id, name, is_active)
VALUES (1, 'Herp Derpinson', 1);
CREATE VIEW accounts_view as
SELECT *
FROM accounts;
--
CREATE TABLE products (
product_no INTEGER,
name TEXT,
price NUMERIC,
CONSTRAINT price_greater_than_zero CHECK (price > 0)
);
//
// ControlChoiceViewController.swift
// Lunar
//
// Created by Alin Panaitiu on 01.10.2021.
// Copyright © 2021 Alin. All rights reserved.
//
import Cocoa
import Combine
import Foundation
// MARK: - GradientView
final class GradientView: NSView {
override var wantsDefaultClipping: Bool { false }
@IBInspectable var firstColor: NSColor = .clear {
didSet {
updateView()
}
}
@IBInspectable var secondColor: NSColor = .clear {
didSet {
updateView()
}
}
func updateView() {
let layer = CAGradientLayer()
layer.colors = [firstColor, secondColor].map(\.cgColor)
layer.startPoint = CGPoint(x: 0, y: 0)
layer.endPoint = CGPoint(x: 0.5, y: 0.45)
if let blur = CIFilter(name: "CIGaussianBlur", parameters: ["inputRadius": 20]) {
layer.filters = [blur]
}
layer.masksToBounds = false
self.layer = layer
}
}
// MARK: - LunarTestViewController
final class LunarTestViewController: NSViewController {
@IBOutlet var label: NSTextField!
@objc dynamic var lunarTestText = "Checking Monitor"
var taskKey = "lunarTestHighlighter"
var lunarTestHighlighter: Repeater?
override func viewDidAppear() {
let end = {
for d in DC.displays.values {
d.testWindowController?.close()
d.testWindowController = nil
}
}
lunarTestHighlighter = Repeater(
every: 2,
name: taskKey,
onFinish: end,
onCancel: end
) { [weak self] in
guard let self else {
end()
return
}
label.transition(1.5, easing: .easeInOutCubic)
if label.textColor == .white {
label.textColor = darkMauve
} else {
label.textColor = .white
}
}
}
override func viewDidLoad() {
label.textColor = .white
}
}
// MARK: - ControlReadResult
struct ControlReadResult {
static var onlyBrightnessWorked = ControlReadResult(brightness: true, contrast: false, volume: false)
static var allWorked = ControlReadResult(brightness: true, contrast: true, volume: true)
static var noneWorked = ControlReadResult(brightness: false, contrast: false, volume: false)
let brightness: Bool
let contrast: Bool
let volume: Bool
var all: Bool { brightness && contrast }
}
// MARK: - ControlWriteResult
struct ControlWriteResult {
static var onlyBrightnessWorked = ControlWriteResult(brightness: true, contrast: false, volume: false)
static var allWorked = ControlWriteResult(brightness: true, contrast: true, volume: true)
static var noneWorked = ControlWriteResult(brightness: false, contrast: false, volume: false)
let brightness: Bool
let contrast: Bool
let volume: Bool
var all: Bool { brightness && contrast }
}
// MARK: - ControlResult
struct ControlResult {
static var onlyBrightnessWorked = ControlResult(type: .appleNative, read: .onlyBrightnessWorked, write: .onlyBrightnessWorked)
static var allWorked = ControlResult(type: .ddc, read: .allWorked, write: .allWorked)
static var noneWorked = ControlResult(type: .ddc, read: .noneWorked, write: .noneWorked)
let type: DisplayControl
let read: ControlReadResult
let write: ControlWriteResult
var all: Bool { read.all && write.all }
}
// MARK: - ControlChoiceViewController
final class ControlChoiceViewController: NSViewController {
var cancelled = false
@IBOutlet var displayImage: DisplayImage?
@IBOutlet var _controlButton: NSButton!
@IBOutlet var _ddcBlockersButton: NSButton!
@IBOutlet var actionLabel: NSTextField!
@IBOutlet var actionInfo: NSTextField!
@IBOutlet var displayName: DisplayName!
@IBOutlet var brightnessField: ScrollableTextField!
@IBOutlet var contrastField: ScrollableTextField!
@IBOutlet var brightnessCaption: NSTextField!
@IBOutlet var contrastCaption: NSTextField!
@IBOutlet var brightnessReadResult: NSTextField!
@IBOutlet var contrastReadResult: NSTextField!
@IBOutlet var volumeReadResult: NSTextField!
@IBOutlet var brightnessWriteResult: NSTextField!
@IBOutlet var contrastWriteResult: NSTextField!
@IBOutlet var volumeWriteResult: NSTextField!
@IBOutlet var volumeSlider: VolumeSlider!
@IBOutlet var actionButton: Button!
@IBOutlet var noButton: Button!
@IBOutlet var yesButton: Button!
@IBOutlet var skipButton: Button!
var observers: Set<AnyCancellable> = []
@objc dynamic var progress = 0.0
@objc dynamic var volume: Double = 50
var actionLabelColor = white.blended(withFraction: 0.2, of: lunarYellow)
var actionInfoColor = white
var wakeObserver: Cancellable?
var screenObserver: Cancellable?
var semaphore = DispatchSemaphore(value: 0, name: "Control Choice")
var currentDisplay: Display?
var displayProgressStep: Double = 0
var controlProgressStep: Double = 0
var progressForLastDisplay: Double = 0
var progressForDisplay: Double = 0
var didAppear = false
var controlButton: HelpButton? { _controlButton as? HelpButton }
var ddcBlockersButton: OnboardingHelpButton? { _ddcBlockersButton as? OnboardingHelpButton }
func info(_ text: String, color: NSColor) {
mainThread {
actionLabel.stringValue = text
actionLabel.transition(0.9, easing: .easeOutCubic)
actionLabel.textColor = color
}
mainAsyncAfter(ms: 1000) { [weak self] in
guard let self else { return }
actionLabel.transition(1.0, easing: .easeOutCubic)
actionLabel.textColor = actionLabelColor
}
}
func hideAction() {
mainThread {
if let ddcBlockersButton {
ddcBlockersButton.transition(0.8, easing: .easeOutExpo)
ddcBlockersButton.alphaValue = 0.0
ddcBlockersButton.close()
ddcBlockersButton.isEnabled = false
ddcBlockersButton.isHidden = true
}
actionInfo.transition(0.8, easing: .easeOutExpo)
actionInfo.alphaValue = 0.0
actionButton.transition(0.8, easing: .easeOutExpo)
actionButton.alphaValue = 0.0
actionButton.isEnabled = false
actionButton.onClick = nil
}
}
func hideQuestion() {
mainThread {
if let ddcBlockersButton {
ddcBlockersButton.transition(0.8, easing: .easeOutExpo)
ddcBlockersButton.alphaValue = 0.0
ddcBlockersButton.close()
ddcBlockersButton.isEnabled = false
ddcBlockersButton.isHidden = true
}
actionInfo.transition(0.8, easing: .easeOutExpo)
actionInfo.alphaValue = 0.0
noButton.transition(0.8, easing: .easeOutExpo)
noButton.alphaValue = 0.0
noButton.isEnabled = false
noButton.onClick = nil
yesButton.transition(0.8, easing: .easeOutExpo)
yesButton.alphaValue = 0.0
yesButton.isEnabled = false
yesButton.onClick = nil
}
}
func waitForAction(_ text: String, buttonColor: NSColor, buttonText: NSAttributedString, action: @escaping (() -> Void)) {
mainAsyncAfter(ms: 1100) { [weak self] in
guard let self else { return }
actionInfo.transition(0.5, easing: .easeInOutExpo)
actionInfo.textColor = actionInfoColor
}
mainThread {
actionInfo.stringValue = text
actionInfo.transition(0.8, easing: .easeOutCubic)
actionInfo.alphaValue = 1.0
actionInfo.transition(1.0, easing: .easeOutCubic)
actionInfo.textColor = peach
actionButton.bg = buttonColor
actionButton.attributedTitle = buttonText
actionButton.isEnabled = true
actionButton.transition(0.8, easing: .easeOutCubic)
actionButton.alphaValue = 1.0
}
actionButton.onClick = { [weak self] in
log.info("Clicked '\(buttonText.string)' on '\(text)'")
guard let self else {
self?.semaphore.signal()
return
}
semaphore.signal()
hideAction()
}
semaphore.wait(for: 0)
guard !cancelled else { return }
action()
}
func askQuestion(
_ question: String,
yesButtonText: String = "Yes",
noButtonText: String = "No",
ddcBlockerText: String? = nil,
answer: @escaping ((Bool) -> Void)
) {
mainAsyncAfter(ms: 1100) { [weak self] in
guard let self else { return }
actionInfo.transition(0.8, easing: .easeOutCubic)
actionInfo.textColor = actionInfoColor
}
mainThread {
actionInfo.stringValue = question
actionInfo.transition(0.8, easing: .easeOutCubic)
actionInfo.alphaValue = 1.0
actionInfo.transition(1.0, easing: .easeOutCubic)
actionInfo.textColor = peach
noButton.transition(0.8, easing: .easeOutCubic)
noButton.alphaValue = 1.0
noButton.isEnabled = true
noButton.attributedTitle = noButtonText.withTextColor(white)
yesButton.transition(0.8, easing: .easeOutCubic)
yesButton.alphaValue = 1.0
yesButton.isEnabled = true
yesButton.attributedTitle = yesButtonText.withTextColor(white)
if let ddcBlockerText, let ddcBlockersButton {
ddcBlockersButton.helpText = ddcBlockerText
ddcBlockersButton.isEnabled = true
ddcBlockersButton.isHidden = false
ddcBlockersButton.transition(0.8, easing: .easeOutCubic)
ddcBlockersButton.alphaValue = 1.0
ddcBlockersButton.open(edge: .maxX)
}
}
var answerResult = false
noButton.onClick = { [weak self] in
log.info("Answered 'no' to '\(question)'")
guard let self else {
self?.semaphore.signal()
return
}
answerResult = false
semaphore.signal()
hideQuestion()
}
yesButton.onClick = { [weak self] in
log.info("Answered 'yes' to '\(question)'")
guard let self else {
self?.semaphore.signal()
return
}
answerResult = true
semaphore.signal()
hideQuestion()
}
semaphore.wait(for: 0)
guard !cancelled else { return }
answer(answerResult)
}
func setBrightness(_ brightness: Brightness) {
mainThread {
if brightnessField.alphaValue == 0 {
brightnessField.transition(1.0, easing: .easeOutExpo)
brightnessField.alphaValue = 1.0
brightnessCaption.transition(1.0, easing: .easeOutExpo)
brightnessCaption.alphaValue = 1.0
}
}
for br in stride(
from: brightnessField.integerValue,
through: brightness.i,
by: brightnessField.integerValue < brightness.i ? 1 : -1
) {
mainThread { brightnessField.integerValue = br }
guard wait(0.01) else { return }
}
}
func setContrast(_ contrast: Contrast) {
mainThread {
if contrastField.alphaValue == 0 {
contrastField.transition(1.0, easing: .easeOutExpo)
contrastField.alphaValue = 1.0
contrastCaption.transition(1.0, easing: .easeOutExpo)
contrastCaption.alphaValue = 1.0
}
}
for cr in stride(from: contrastField.integerValue, through: contrast.i, by: contrastField.integerValue < contrast.i ? 1 : -1) {
mainThread { contrastField.integerValue = cr }
guard wait(0.01) else { return }
}
}
func setControl(_ control: DisplayControl, display: Display) {
mainThread {
guard !cancelled, let button = controlButton else { return }
switch control {
case .appleNative:
button.bg = .clear
button.attributedTitle = "Using Apple Native Protocol".withTextColor(green)
button.helpText = NATIVE_CONTROLS_HELP_TEXT
case .ddc:
button.bg = .clear
button.attributedTitle = "Using DDC Protocol".withTextColor(peach)
button.helpText = HARDWARE_CONTROLS_HELP_TEXT
case .network:
button.bg = .clear
button.attributedTitle = "Using DDC-over-Network Protocol".withTextColor(blue.highlight(withLevel: 0.2) ?? blue)
button.helpText = NETWORK_CONTROLS_HELP_TEXT
case .gamma:
display.enabledControls[.gamma] = true
button.bg = .clear
if display.supportsGamma {
button.attributedTitle = "Using Gamma Approximation".withTextColor(red)
button.helpText = SOFTWARE_CONTROLS_HELP_TEXT
} else {
button.attributedTitle = "Using Dark Overlay".withTextColor(red)
button.helpText = SOFTWARE_OVERLAY_HELP_TEXT
}
default:
break
}
button.isEnabled = true
button.isHidden = false
button.transition(1.0, easing: .easeOutExpo)
button.alphaValue = 1.0
}
}
func setVolume(_ volume: UInt16) {
mainThread {
if volumeSlider.alphaValue == 0 {
volumeSlider.isEnabled = true
volumeSlider.isHidden = false
volumeSlider.transition(1.0, easing: .easeOutExpo)
volumeSlider.alphaValue = 1.0
}
}
for vol in stride(from: self.volume.i, through: volume.i, by: self.volume.i < volume.i ? 4 : -4) {
mainThread { self.volume = vol.d }
guard wait(0.02) else { return }
}
}
func setResult(_ resultControl: NSTextField, text: String, color: NSColor, transitionSpeed: TimeInterval = 1.5, alpha: Double = 1.0) {
mainThread {
resultControl.stringValue = text
resultControl.textColor = color
resultControl.transition(transitionSpeed)
resultControl.alphaValue = alpha
}
}
func testControl(_ control: Control, for display: Display) -> ControlResult {
hideDisplayValues()
hideAction()
hideQuestion()
let readWorked = testControlRead(control, for: display)
setControlProgress(0.5)
guard wait(1) else { return .allWorked }
guard readWorked.all else {
info("", color: peach)
// var writeWorked = ControlWriteResult.noneWorked
// waitForAction(
// "Reading didn't work for some values\nWrite the missing values manually and click the Continue button",
// buttonColor: lunarYellow, buttonText: "Continue".withTextColor(mauve)
// ) { [weak self] in
// guard let self else { return }
// writeWorked = self.testControlWrite(control, for: display)
// }
let writeWorked = testControlWrite(control, for: display)
return ControlResult(type: control.displayControl, read: readWorked, write: writeWorked)
}
info("Starting write tests", color: peach)
guard wait(3) else { return .allWorked }
let writeWorked = testControlWrite(control, for: display)
return ControlResult(type: control.displayControl, read: readWorked, write: writeWorked)
}
func testControlWrite(_ control: Control, for display: Display) -> ControlWriteResult {
// #if DEBUG
// return .noneWorked
// #endif
let currentBrightness: UInt16 = brightnessField.integerValue == 0 ? 50 : brightnessField.integerValue.u16
let currentContrast: UInt16 = contrastField.integerValue == 0 ? 50 : contrastField.integerValue.u16
let currentVolume: UInt16 = volume == 0 ? 50 : volume.u16
var brightnessWriteWorked = false
var contrastWriteWorked = false
var volumeWriteWorked = false
if let control = control as? DDCControl {
control.ignoreFaults = true
}
defer {
if let control = control as? DDCControl {
control.ignoreFaults = false
}
}
let setWriteProgress = { value in self.setControlProgress(0.5 + (0.5 * value)) }
info("Writing brightness", color: peach)
setWriteProgress(0.1)
guard wait(1.1) else { return .allWorked }
display.withBrightnessTransition(.slow) {
display.enabledControls[control.displayControl] = true
var newBr: UInt16 = currentBrightness != 0 ? (control.isSoftware ? 25 : 0) : 50
var oldBr: UInt16 = currentBrightness
let write1Worked = control.setBrightness(
newBr,
oldValue: oldBr,
force: true, transition: .smooth,
onChange: { [weak self] br in self?.setBrightness(br) }
)
if control.isSoftware, !display.supportsGamma {
setBrightness(newBr)
}
guard wait(4) else { return }
setWriteProgress(0.15)
oldBr = newBr
newBr = 75
let write2Worked = control.setBrightness(
newBr,
oldValue: oldBr,
force: true, transition: .smooth,
onChange: { [weak self] br in self?.setBrightness(br) }
)
if control.isSoftware, !display.supportsGamma {
setBrightness(newBr)
}
guard wait(4) else { return }
setWriteProgress(0.2)
oldBr = newBr
newBr = control.isSoftware ? 100 : 67
let write3Worked = control.setBrightness(
newBr,
oldValue: oldBr,
force: true, transition: .smooth,
onChange: { [weak self] br in self?.setBrightness(br) }
)
if control.isSoftware, !display.supportsGamma {
setBrightness(newBr)
}
brightnessWriteWorked = (write1Worked.i + write2Worked.i + write3Worked.i) >= 2
setWriteProgress(0.25)
}
guard !control.isSoftware || display.supportsGamma else {
setContrast(100)
setVolume(50)
setResult(contrastWriteResult, text: "Write not supported", color: peach)
setResult(volumeWriteResult, text: "Write not supported", color: peach)
return ControlWriteResult(brightness: true, contrast: false, volume: false)
}
if brightnessWriteWorked {
setResult(brightnessWriteResult, text: "Write seemed to work", color: peach)
askQuestion(
control.isSoftware
? "Was there any change in brightness on the tested display?"
: "Was there any change in brightness on the tested display?\nThe brightness value in the monitor settings should now be set to 67"
) { [weak self] itWorked in
guard let self else { return }
if itWorked {
setResult(brightnessWriteResult, text: "Write worked", color: green)
} else {
brightnessWriteWorked = false
setResult(brightnessWriteResult, text: "Failed to write", color: red)
}
}
} else {
setResult(brightnessWriteResult, text: "Failed to write", color: red)
}
guard wait(1.1) else { return .allWorked }
guard !control.isSoftware else {
setContrast(100)
setVolume(50)
setResult(contrastWriteResult, text: "Write not supported", color: peach)
setResult(volumeWriteResult, text: "Write not supported", color: peach)
return ControlWriteResult(brightness: true, contrast: false, volume: false)
}
setWriteProgress(0.3)
info("Writing contrast", color: peach)
guard wait(1.1) else { return .allWorked }
display.withBrightnessTransition {
setWriteProgress(0.35)
var newCr: UInt16 = currentContrast != 0 ? 0 : 50
var oldCr: UInt16 = currentContrast
let write1Worked = control.setContrast(
newCr,
oldValue: oldCr,
transition: .smooth,
onChange: { [weak self] br in self?.setContrast(br) }
)
guard wait(4) else { return }
setWriteProgress(0.4)
oldCr = newCr
newCr = 75
let write2Worked = control.setContrast(
newCr,
oldValue: oldCr,
transition: .smooth,
onChange: { [weak self] br in self?.setContrast(br) }
)
guard wait(4) else { return }
setWriteProgress(0.45)
oldCr = newCr
newCr = 67
let write3Worked = control.setContrast(
newCr,
oldValue: oldCr,
transition: .smooth,
onChange: { [weak self] br in self?.setContrast(br) }
)
contrastWriteWorked = (write1Worked.i + write2Worked.i + write3Worked.i) >= 2
setWriteProgress(0.5)
}
if contrastWriteWorked {
setResult(contrastWriteResult, text: "Write seemed to work", color: peach)
askQuestion(
"Was there any change in contrast on the tested display?\nThe contrast value in the monitor settings should now be set to 67"
) { [weak self] itWorked in
guard let self else { return }
if itWorked {
setResult(contrastWriteResult, text: "Write worked", color: green)
} else {
contrastWriteWorked = false
setResult(contrastWriteResult, text: "Failed to write", color: red)
}
}
} else {
setResult(contrastWriteResult, text: "Failed to write", color: red)
}
guard wait(1.1) else { return .allWorked }
setWriteProgress(0.55)
info("Writing volume", color: peach)
guard wait(1.1) else { return .allWorked }
setVolume(0)
let write1Worked = control.setVolume(0)
guard wait(1) else { return .allWorked }
setWriteProgress(0.6)
setVolume(75)
let write2Worked = control.setVolume(75)
guard wait(1) else { return .allWorked }
setWriteProgress(0.65)
setVolume(23)
let write3Worked = control.setVolume(23)
volumeWriteWorked = (write1Worked.i + write2Worked.i + write3Worked.i) >= 2
setWriteProgress(0.7)
if volumeWriteWorked {
setResult(volumeWriteResult, text: "Write seemed to work", color: peach)
askQuestion(
"Was there any change in volume on the tested display?\nThe volume value in the monitor settings should now be set to 23"
) { [weak self] itWorked in
guard let self else { return }
if itWorked {
setResult(volumeWriteResult, text: "Write worked", color: white)
} else {
volumeWriteWorked = false
setResult(volumeWriteResult, text: "Failed to write", color: peach)
}
}
} else {
setResult(volumeWriteResult, text: "Failed to write", color: red)
}
guard wait(1.1) else { return .allWorked }
setWriteProgress(0.8)
info("Setting values before test", color: peach)
guard wait(2) else { return .allWorked }
_ = control.setBrightness(currentBrightness, oldValue: nil, force: true, transition: .smooth, onChange: nil)
setBrightness(currentBrightness)
setWriteProgress(0.85)
guard wait(0.5) else { return .allWorked }
_ = control.setContrast(currentContrast, oldValue: nil, transition: .smooth, onChange: nil)
setContrast(currentContrast)
setWriteProgress(0.9)
guard wait(0.5) else { return .allWorked }
_ = control.setVolume(currentVolume)
setVolume(currentVolume)
setWriteProgress(0.95)
guard wait(0.5) else { return .allWorked }
return ControlWriteResult(brightness: brightnessWriteWorked, contrast: contrastWriteWorked, volume: volumeWriteWorked)
}
func wait(_ seconds: TimeInterval) -> Bool {
// #if DEBUG
// return !cancelled
// #endif
Thread.sleep(forTimeInterval: seconds)
return !cancelled
}
func testControlRead(_ control: Control, for _: Display) -> ControlReadResult {
var brightnessReadWorked = false
var contrastReadWorked = false
var volumeReadWorked = false
let setReadProgress = { value in self.setControlProgress(0.5 * value) }
guard !control.isSoftware else {
setBrightness(100)
setContrast(100)
setVolume(50)
setResult(brightnessReadResult, text: "Read not supported", color: peach)
setResult(contrastReadResult, text: "Read not supported", color: peach)
setResult(volumeReadResult, text: "Read not supported", color: peach)
return .allWorked
}
info("Reading brightness", color: peach)
guard wait(1.1) else { return .allWorked }
var brightnessThatWasRead: UInt16 = 0
if let br = (control.getBrightness() ?? control.getBrightness()), !(control is AppleNativeControl) || br <= 100 {
brightnessReadWorked = true
setBrightness(br)
brightnessThatWasRead = br
guard wait(1.1) else { return .allWorked }
setReadProgress(0.15)
setResult(brightnessReadResult, text: "Read worked", color: green)
guard wait(1.1) else { return .allWorked }
let _ = control.setBrightness(br, oldValue: nil, force: true, transition: .instant, onChange: nil)
} else {
setBrightness(0)
guard wait(1.1) else { return .allWorked }
setReadProgress(0.15)
// setResult(brightnessReadResult, text: "Failed to read", color: red)
guard wait(1.1) else { return .allWorked }
}
setReadProgress(0.33)
info("Reading contrast", color: peach)
guard wait(1.1) else { return .allWorked }
if let cr = (control.getContrast() ?? control.getContrast()) {
contrastReadWorked = true
setContrast(cr)
guard wait(1.1) else { return .allWorked }
setReadProgress(0.45)
setResult(contrastReadResult, text: "Read worked", color: green)
guard wait(1.1) else { return .allWorked }
if brightnessReadWorked {
let _ = control.setBrightness(brightnessThatWasRead, oldValue: nil, force: true, transition: .instant, onChange: nil)
} else {
let _ = control.setContrast(cr, oldValue: nil, transition: .instant, onChange: nil)
}
} else {
setContrast(0)
guard wait(1.1) else { return .allWorked }
setReadProgress(0.45)
// setResult(contrastReadResult, text: "Failed to read", color: red)
guard wait(1.1) else { return .allWorked }
}
setReadProgress(0.66)
info("Reading volume", color: peach)
guard wait(1.1) else { return .allWorked }
if let vol = (control.getVolume() ?? control.getVolume()) {
volumeReadWorked = true
setVolume(vol)
guard wait(1.1) else { return .allWorked }
setReadProgress(0.8)
setResult(volumeReadResult, text: "Read worked", color: white)
guard wait(1.1) else { return .allWorked }
} else {
setVolume(0)
guard wait(1.1) else { return .allWorked }
setReadProgress(0.8)
// setResult(volumeReadResult, text: "Failed to read", color: peach)
guard wait(1.1) else { return .allWorked }
}
setReadProgress(0.9)
return ControlReadResult(brightness: brightnessReadWorked, contrast: contrastReadWorked, volume: volumeReadWorked)
}
func hideDisplayValues() {
mainThread {
brightnessField.transition(0.5, easing: .easeOutExpo)
brightnessField.alphaValue = 0
brightnessCaption.transition(0.5, easing: .easeOutExpo)
brightnessCaption.alphaValue = 0
contrastField.transition(0.5, easing: .easeOutExpo)
contrastField.alphaValue = 0
contrastCaption.transition(0.5, easing: .easeOutExpo)
contrastCaption.alphaValue = 0
brightnessReadResult.transition(0.5, easing: .easeOutExpo)
brightnessReadResult.alphaValue = 0
brightnessWriteResult.transition(0.5, easing: .easeOutExpo)
brightnessWriteResult.alphaValue = 0
contrastReadResult.transition(0.5, easing: .easeOutExpo)
contrastReadResult.alphaValue = 0
contrastWriteResult.transition(0.5, easing: .easeOutExpo)
contrastWriteResult.alphaValue = 0
volumeSlider.transition(0.5, easing: .easeOutExpo)
volumeSlider.alphaValue = 0
volumeReadResult.transition(0.5, easing: .easeOutExpo)
volumeReadResult.alphaValue = 0
volumeWriteResult.transition(0.5, easing: .easeOutExpo)
volumeWriteResult.alphaValue = 0
}
}
func queueChange(_ change: @escaping () -> Void) {
mainThread {
guard let wc = view.window?.windowController as? OnboardWindowController else {
return
}
wc.changes.append(change)
}
}
func next() {
guard let wc = view.window?.windowController as? OnboardWindowController else { return }
wc.applyChanges()
wc.pageController?.navigateForward(self)
}
func showTestWindow() {
guard let d = currentDisplay else { return }
mainThread {
createWindow(
"testWindowController",
controller: &d.testWindowController,
screen: d.nsScreen,
show: true,
backgroundColor: .clear,
level: .screenSaver,
fillScreen: false,
stationary: true
)
}
wakeObserver = wakeObserver ?? NSWorkspace.shared.notificationCenter
.publisher(for: NSWorkspace.screensDidWakeNotification, object: nil)
.debounce(for: .seconds(2), scheduler: RunLoop.main)
.sink { [weak self] _ in
self?.showTestWindow()
}
screenObserver = screenObserver ?? NotificationCenter.default
.publisher(for: NSApplication.didChangeScreenParametersNotification, object: nil)
.debounce(for: .seconds(2), scheduler: RunLoop.main)
.sink { [weak self] _ in
self?.showTestWindow()
}
}
func networkProblemText(_ display: Display, control _: NetworkControl) -> String {
var url = "http://<your-rpi-ip>:3485/displays"
if let networkController = NetworkControl.controllersForDisplay[display.serial], let controlURL = networkController.url {
url = controlURL.deletingLastPathComponent().appendingPathComponent("displays").absoluteString
}
let infoDict = display.infoDictionary
let year = (infoDict[kDisplayYearOfManufacture] as? Int64) ?? 2017
let serial = (infoDict[kDisplaySerialNumber] as? Int64) ?? 314_041
let product = (infoDict[kDisplayProductID] as? Int64) ?? 23304
let vendor = (infoDict[kDisplayVendorID] as? Int64)?.s ?? "GSM"
let name = display.edidName
return """
##### If you have technical knowledge about `SSH` and `curl`, you can check the following:
* See if the controller is reachable using this curl command: `curl \(url)`
* You should get a response similar to the one below:
Display 1
I2C bus: /dev/i2c-2
EDID synopsis:
Mfg id: \(vendor)
Model: \(name)
Product code: \(product)
Serial number:
Binary serial number: \(serial)
Manufacture year: \(year)
EDID version: 1.3
VCP version: 2.1
* If you get `Invalid Display` try turning your monitor off then turn it on after a few seconds
* If you get `Display not found`, make sure your Pi is running an OS with a desktop environment, and the desktop is visible when the Pi HDMI input is active
* Check that the server is running
* SSH into your Pi
* Run `sudo systemctl status ddcutil-server`
* Check if `ddcutil` can correctly identify and control your monitor
* SSH into your Pi
* Run `ddcutil detect` _(you should get the same response as for the `curl` command)_
* Cycle through a few brightness values to see if there's any change:
* `ddcutil setvcp 0x10 25`
* `ddcutil setvcp 0x10 100`
* `ddcutil setvcp 0x10 50`
"""
}
func setDisplayProgress(_ value: Double, controlStep: Double) {
mainThread {
progress = progressForLastDisplay + displayProgressStep * value
controlProgressStep = controlStep
progressForDisplay = value
}
}
func setControlProgress(_ value: Double) {
mainThread {
progress = progressForLastDisplay + displayProgressStep * progressForDisplay + value *
(controlProgressStep * displayProgressStep * progressForDisplay)
}
}
func listenForDisplayConnections() {
NotificationCenter.default
.publisher(for: displayListChanged, object: nil)
.debounce(for: .seconds(2), scheduler: RunLoop.main)
.sink { [weak self] _ in
self?.cancelled = true
OnboardPageController.task?.cancel()
log.debug("Restarting Onboarding after display list change")
guard let w = self?.view.window else { return }
_ = askBool(
message: "Display list changed",
info: """
The list of connected displays has changed
and the onboarding process can't continue.
Do you want to restart the onboarding process?
""",
okButton: "Restart Onboarding",
cancelButton: "Skip Onboarding",
window: w,
onCompletion: { [weak self] (restart: Bool) in
guard let self, let w = view.window, let wc = w.windowController as? OnboardWindowController else {
return
}
cancelled = true
OnboardPageController.task?.cancel()
semaphore.signal()
wc.changes = []
w.close()
wc.close()
for d in DC.displays.values {
d.testWindowController?.close()
d.testWindowController = nil
}
if restart {
mainAsyncAfter(ms: 1000) { appDelegate!.onboard() }
}
},
unique: true,
waitTimeout: 5.minutes
)
}.store(in: &observers)
}
override func viewDidAppear() {
guard !didAppear else { return }
didAppear = true
uiCrumb("Control Tester")
cancelled = false
adaptiveModeDisabledByDiagnostics = false
if DC.adaptiveModeKey != .manual {
DC.disable()
adaptiveModeDisabledByDiagnostics = true
}
listenForDisplayConnections()
if let wc = view.window?.windowController as? OnboardWindowController {
wc.setupSkipButton(skipButton, text: useOnboardingForDiagnostics ? "Stop Diagnostics" : nil) { [weak self] in
self?.cancelled = true
for d in DC.displays.values {
d.testWindowController?.close()
d.testWindowController = nil
}
self?.wakeObserver?.cancel()
self?.wakeObserver = nil
self?.screenObserver?.cancel()
self?.screenObserver = nil
self?.semaphore.signal()
guard useOnboardingForDiagnostics else { return }
self?.skipButton.isEnabled = false
self?.skipButton.attributedTitle = "Stopping".withTextColor(.black)
mainAsyncAfter(ms: 1500) {
self?.view.window?.close()
if adaptiveModeDisabledByDiagnostics {
DC.enable()
}
}
}
}
OnboardPageController.task = concurrentQueue.asyncAfter(ms: 10, name: ONBOARDING_TASK_KEY) { [weak self] in
let displays = DC.externalDisplaysForTest
guard let self, !self.cancelled, !OnboardPageController.task.isCancelled, let firstDisplay = displays.first else {
return
}
mainThread {
self.displayName.transition(0.5, easing: .easeOutExpo)
self.displayName.alphaValue = 1.0
self.displayName.display = firstDisplay
self.setControl(firstDisplay.getBestControl(reapply: false).displayControl, display: firstDisplay)
}
waitForAction(
"Lunar will try to read and then change the monitor brightness, contrast and volume\nIf Lunar can't revert the changes, you can do that by using the monitor's physical buttons",
buttonColor: lunarYellow, buttonText: "Continue".withTextColor(mauve)
) {}
displayProgressStep = 1.0 / displays.count.d
for (i, d) in displays.enumerated() {
testDisplay(d, index: i)
}
queueChange {
DC.externalActiveDisplays.forEach { $0.resetControl() }
}
mainThread {
for d in DC.displays.values {
d.testWindowController?.close()
d.testWindowController = nil
}
if useOnboardingForDiagnostics, self.cancelled { return }
self.next()
}
}
}
func testDisplay(_ d: Display, index: Int) {
for d in DC.displays.values {
d.testWindowController?.close()
d.testWindowController = nil
}
guard !cancelled else { return }
progressForLastDisplay = displayProgressStep * index.d
setDisplayProgress(0, controlStep: 0.25)
defer { self.progress = self.displayProgressStep * (index + 1).d }
currentDisplay = d
// showTestWindow()
mainThread {
self.displayName.transition(0.5, easing: .easeOutExpo)
self.displayName.alphaValue = 1.0
self.displayName.display = d
}
let networkControl = NetworkControl(display: d)
let appleNativeControl = AppleNativeControl(display: d)
let ddcControl = DDCControl(display: d)
let gammaControl = GammaControl(display: d)
var result = ControlResult.noneWorked
defer { d.controlResult = result }
if appleNativeControl.isAvailable(), !cancelled {
setControl(.appleNative, display: d)
result = testControl(appleNativeControl, for: d)
setDisplayProgress(0.25, controlStep: 0.25)
if !result.write.brightness, d.isLEDCinema || d.isCinema {
var nextStep = true
askQuestion(
"On Cinema displays, you need to plug in the USB cable to get brightness controls\nYou can try that now and retry the test, or continue to the next step",
yesButtonText: "Next", noButtonText: "Retry"
) { nextStep = $0 }
if nextStep {
queueChange {
d.enabledControls[.appleNative] = false
d.save()
}
} else {
setDisplayProgress(0, controlStep: 0.25)
result = testControl(appleNativeControl, for: d)
setDisplayProgress(0.25, controlStep: 0.25)
}
}
} else { setDisplayProgress(0.25, controlStep: 0.25) }
if result.write.all {
waitForAction(
"The monitor is fully functional and ready to be used with Lunar",
buttonColor: lunarYellow, buttonText: "Next step".withTextColor(mauve)
) {}
return
} else if result.write.brightness {
waitForAction(
"The monitor supports brightness controls and is ready to be used with Lunar",
buttonColor: lunarYellow, buttonText: "Next step".withTextColor(mauve)
) {}
return
}
ddcBlockersButton?.md.code.fontSize = 15
if ddcControl.isAvailable(), !cancelled {
setControl(.ddc, display: d)
result = testControl(ddcControl, for: d)
setDisplayProgress(0.5, controlStep: 0.25)
var nextStep = false
while !result.write.brightness, !nextStep {
askQuestion(
"Some monitor settings can block brightness controls\nYou can try changing the listed settings and then retry the test",
yesButtonText: "Next", noButtonText: "Retry",
ddcBlockerText: d.possibleDDCBlockers()
) { nextStep = $0 }
if nextStep {
// let ddcEnabled = result.write.volume || result.write.contrast
queueChange {
d.enabledControls[.ddc] = false
d.enabledControls[.gamma] = true
d.save()
}
} else {
setDisplayProgress(0.25, controlStep: 0.25)
result = testControl(ddcControl, for: d)
setDisplayProgress(0.5, controlStep: 0.25)
}
}
} else { setDisplayProgress(0.5, controlStep: 0.25) }
if result.write.all {
waitForAction(
"The monitor is fully functional and ready to be used with Lunar",
buttonColor: lunarYellow, buttonText: "Next step".withTextColor(mauve)
) {}
return
} else if result.write.brightness {
waitForAction(
"The monitor supports brightness controls and is ready to be used with Lunar\nUnfortunately, not all monitors support \(result.write.volume ? "contrast" : "volume") controls",
buttonColor: lunarYellow, buttonText: "Next step".withTextColor(mauve)
) {}
return
}
ddcBlockersButton?.md.code.fontSize = 13
if networkControl.isAvailable(), !cancelled {
setControl(.network, display: d)
result = testControl(networkControl, for: d)
setDisplayProgress(0.75, controlStep: 0.25)
var nextStep = false
while !result.write.brightness, !nextStep {
askQuestion(
"The assigned network controller wasn't able to control this monitor",
yesButtonText: "Next", noButtonText: "Retry",
ddcBlockerText: networkProblemText(d, control: networkControl)
) { nextStep = $0 }
if !nextStep {
setDisplayProgress(0.5, controlStep: 0.25)
result = testControl(ddcControl, for: d)
setDisplayProgress(0.75, controlStep: 0.25)
}
}
} else { setDisplayProgress(0.75, controlStep: 0.1) }
if result.write.all {
waitForAction(
"The monitor is fully functional and ready to be used with Lunar",
buttonColor: lunarYellow, buttonText: "Next step".withTextColor(mauve)
) {}
return
} else if result.write.brightness {
waitForAction(
"The monitor supports brightness controls and is ready to be used with Lunar",
buttonColor: lunarYellow, buttonText: "Next step".withTextColor(mauve)
) {}
return
}
setControl(.gamma, display: d)
result = testControl(gammaControl, for: d)
setDisplayProgress(0.8, controlStep: 0.1)
if d.supportsGamma, !result.write.brightness {
d.useOverlay = true
setControl(.gamma, display: d)
result = testControl(gammaControl, for: d)
setDisplayProgress(0.9, controlStep: 0.1)
}
waitForAction(
"The monitor only supports software brightness dimming\nThe monitor's brightness should be set to its highest values manually using its physical buttons",
buttonColor: lunarYellow, buttonText: "Next step".withTextColor(mauve)
) {}
}
override func viewDidLoad() {
super.viewDidLoad()
hideDisplayValues()
hideAction()
hideQuestion()
noButton.bg = red
noButton.attributedTitle = "No".withTextColor(white)
yesButton.bg = green
yesButton.attributedTitle = "Yes".withTextColor(white)
displayImage?.cornerRadius = 16
displayImage?.standColor = peach
displayImage?.screenColor = rouge
actionLabel.textColor = actionLabelColor
actionInfo.alphaValue = 0.0
volumeSlider.minValue = 0
volumeSlider.maxValue = 100
volumeSlider.isEnabled = false
volumeSlider.isHidden = true
if let controlButton {
controlButton.isEnabled = false
controlButton.isHidden = true
}
if let ddcBlockersButton {
ddcBlockersButton.isEnabled = false
ddcBlockersButton.isHidden = true
}
POPOVERS["help"]!?.appearance = NSAppearance(named: .vibrantDark)
}
}
[package]
name = "mise"
version = "2025.3.7"
edition = "2024"
description = "The front-end to your dev env"
authors = ["Jeff Dickey (@jdx)"]
homepage = "https://mise.jdx.dev"
documentation = "https://mise.jdx.dev"
repository = "https://github.com/jdx/mise"
readme = "README.md"
license = "MIT"
keywords = ["mise"]
categories = ["command-line-utilities"]
include = [
"/Cargo.lock",
"/LICENSE",
"/README.md",
"/build.rs",
"/completions/*",
"/minisign.pub",
"/registry.toml",
"/settings.toml",
"/zipsign.pub",
"/src/**/*.rs",
"/src/assets/**",
"/src/plugins/core/assets/**",
"/aqua-registry/pkgs/**/registry.yaml",
]
build = "build.rs"
rust-version = "1.85"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[[bin]]
name = "mise"
path = "src/main.rs"
#[[bench]]
#name = "config_bench"
#harness = false
[profile.serious]
inherits = "release"
lto = true
[profile.dev.package.backtrace]
opt-level = 3
[dependencies]
base64 = "0.22"
bzip2 = "0.5"
calm_io = "0.1"
chrono = { version = "0.4", default-features = false, features = [
"std",
"clock",
] }
ci_info = "0.14"
clap = { version = "4", features = ["env", "derive", "string"] }
clap_mangen = { version = "0.2", optional = true }
color-eyre = "0.6"
color-print = "0.3"
comfy-table = "7.1.3"
confique = { version = "0.3", default-features = false }
console = "0.15"
contracts = "0.6"
crossbeam-channel = "0.5"
demand = "1"
digest = "0.10.7"
dotenvy = "0.15"
duct = "0.13"
either = { version = "1", features = ["serde"] }
homedir = "0.3"
# expr-lang = { path = "../expr-lang" }
expr-lang = "0.3"
eyre = "0.6"
filetime = "0.2"
flate2 = "1"
fslock = "0.2.1"
glob = "0.3"
globset = "0.4"
heck = "0.5"
indenter = "0.3"
indexmap = { version = "2", features = ["serde"] }
indicatif = { version = "0.17", features = ["default", "improved_unicode"] }
indoc = "2"
itertools = "0.14"
junction = "1"
log = "0.4"
minisign-verify = "0.2"
md-5 = "0.10"
num_cpus = "1"
number_prefix = "0.4"
once_cell = "1"
openssl = { version = "0.10", optional = true }
os-release = "0.1"
path-absolutize = "3"
petgraph = "0.7"
rand = "0.9"
rayon = "1"
regex = "1"
reqwest = { version = "0.12", default-features = false, features = [
"json",
"gzip",
"zstd",
"charset",
"http2",
"macos-system-configuration",
] }
rmp-serde = "1"
rops = { version = "0.1", default-features = false, features = [
"aes-gcm",
"sha2",
"yaml",
"json",
"age",
] }
serde = "1"
serde_derive = "1"
serde_ignored = "0.1"
serde_json = "1"
serde_yaml = "0.9"
sha1 = "0.10"
sha2 = "0.10"
shell-escape = "0.1"
shell-words = "1"
signal-hook = "0.3"
siphasher = "1"
strum = { version = "0.26", features = ["derive"] }
sys-info = "0.9"
tabled = { version = "0.17", features = ["ansi"] }
taplo = "0.13"
tar = "0.4"
tempfile = "3"
tera = "1"
terminal_size = "0.4"
thiserror = "2"
tokio = { version = "1", features = ["io-std", "rt", "time"] }
toml = { version = "0.8", features = ["parse"] }
toml_edit = { version = "0.22", features = ["parse"] }
ubi = { version = "0.6.1", default-features = false }
url = "2"
usage-lib = { version = "2", features = ["clap", "docs"] }
versions = { version = "6", features = ["serde"] }
# vfox = { path = "../vfox.rs" }
vfox = { version = "1", default-features = false }
walkdir = "2"
which = "7"
xx = { version = "2", features = ["glob"] }
xz2 = "0.1"
zip = { version = "2", default-features = false, features = ["deflate"] }
zstd = "0.13"
gix = { version = "<1", features = ["worktree-mutation"] }
jiff = "0.2"
[target.'cfg(unix)'.dependencies]
exec = "0.3"
nix = { version = "0.29", features = ["signal", "user"] }
self_update = { version = "0.42", optional = true, default-features = false, features = [
"archive-tar",
"compression-flate2",
"signatures",
] }
[target.'cfg(windows)'.dependencies]
self_update = { version = "0.42", optional = true, default-features = false, features = [
"archive-zip",
"signatures",
] }
sevenz-rust = "0.6"
[build-dependencies]
built = { version = "0.7", features = ["chrono"] }
cfg_aliases = "0.2"
heck = "0.5"
toml = "0.8"
indexmap = "2"
[dev-dependencies]
ctor = "0.4"
insta = { version = "1", features = ["filters", "json"] }
mockito = "1.6.1"
pretty_assertions = "1"
test-log = "0.2"
[features]
default = ["native-tls", "vfox/vendored-lua", "self_update"]
native-tls = [
"reqwest/native-tls",
"ubi/native-tls",
"gix/blocking-http-transport-reqwest-native-tls",
"vfox/native-tls",
]
rustls = [
"reqwest/rustls-tls",
"self_update/rustls",
"ubi/rustls-tls",
"gix/blocking-http-transport-reqwest-rust-tls",
"vfox/rustls",
]
rustls-native-roots = [
"reqwest/rustls-tls-native-roots",
"self_update/rustls",
"ubi/rustls-tls-native-roots",
"vfox/rustls-native-roots",
]
[package.metadata.binstall]
bin-dir = "mise/bin/mise"
[package.metadata.binstall.overrides.aarch64-apple-darwin]
pkg-url = "{ repo }/releases/download/v{ version }/mise-v{version}-macos-arm64{ archive-suffix }"
[package.metadata.binstall.overrides.x86_64-apple-darwin]
pkg-url = "{ repo }/releases/download/v{ version }/mise-v{version}-macos-x64{ archive-suffix }"
[package.metadata.binstall.overrides.aarch64-unknown-linux-gnu]
pkg-url = "{ repo }/releases/download/v{ version }/mise-v{version}-linux-arm64{ archive-suffix }"
[package.metadata.binstall.overrides.x86_64-unknown-linux-gnu]
pkg-url = "{ repo }/releases/download/v{ version }/mise-v{version}-linux-x64{ archive-suffix }"
[package.metadata.binstall.overrides.armv7-unknown-linux-gnueabihf]
pkg-url = "{ repo }/releases/download/v{ version }/mise-v{version}-linux-armv7{ archive-suffix }"
[package.metadata.cargo-machete]
ignored = ["built", "openssl", "cfg_aliases"]
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = [
'cfg(coverage,coverage_nightly)',
] }
[lints.clippy]
borrowed_box = "allow"
import { relative } from 'pathe'
import { parseAstAsync } from 'vite'
import { ancestor as walkAst } from 'acorn-walk'
import type { RawSourceMap } from 'vite-node'
import { calculateSuiteHash, generateHash, interpretTaskModes, someTasksAreOnly } from '@vitest/runner/utils'
import type { File, Suite, Test } from '../types'
import type { WorkspaceProject } from '../node/workspace'
interface ParsedFile extends File {
start: number
end: number
}
interface ParsedTest extends Test {
start: number
end: number
}
interface ParsedSuite extends Suite {
start: number
end: number
}
interface LocalCallDefinition {
start: number
end: number
name: string
type: 'suite' | 'test'
mode: 'run' | 'skip' | 'only' | 'todo'
task: ParsedSuite | ParsedFile | ParsedTest
}
export interface FileInformation {
file: File
filepath: string
parsed: string
map: RawSourceMap | null
definitions: LocalCallDefinition[]
}
export async function collectTests(ctx: WorkspaceProject, filepath: string): Promise<null | FileInformation> {
const request = await ctx.vitenode.transformRequest(filepath, filepath)
if (!request)
return null
const ast = await parseAstAsync(request.code)
const testFilepath = relative(ctx.config.root, filepath)
const file: ParsedFile = {
filepath,
type: 'suite',
id: generateHash(`${testFilepath}${ctx.config.name || ''}`),
name: testFilepath,
mode: 'run',
tasks: [],
start: ast.start,
end: ast.end,
projectName: ctx.getName(),
meta: { typecheck: true },
}
const definitions: LocalCallDefinition[] = []
const getName = (callee: any): string | null => {
if (!callee)
return null
if (callee.type === 'Identifier')
return callee.name
if (callee.type === 'MemberExpression') {
// direct call as `__vite_ssr_exports_0__.test()`
if (callee.object?.name?.startsWith('__vite_ssr_'))
return getName(callee.property)
// call as `__vite_ssr__.test.skip()`
return getName(callee.object?.property)
}
return null
}
walkAst(ast as any, {
CallExpression(node) {
const { callee } = node as any
const name = getName(callee)
if (!name)
return
if (!['it', 'test', 'describe', 'suite'].includes(name))
return
const { arguments: [{ value: message }] } = node as any
const property = callee?.property?.name
let mode = (!property || property === name) ? 'run' : property
if (!['run', 'skip', 'todo', 'only', 'skipIf', 'runIf'].includes(mode))
throw new Error(`${name}.${mode} syntax is not supported when testing types`)
// cannot statically analyze, so we always skip it
if (mode === 'skipIf' || mode === 'runIf')
mode = 'skip'
definitions.push({
start: node.start,
end: node.end,
name: message,
type: (name === 'it' || name === 'test') ? 'test' : 'suite',
mode,
} as LocalCallDefinition)
},
})
let lastSuite: ParsedSuite = file
const updateLatestSuite = (index: number) => {
while (lastSuite.suite && lastSuite.end < index)
lastSuite = lastSuite.suite as ParsedSuite
return lastSuite
}
definitions.sort((a, b) => a.start - b.start).forEach((definition) => {
const latestSuite = updateLatestSuite(definition.start)
let mode = definition.mode
if (latestSuite.mode !== 'run') // inherit suite mode, if it's set
mode = latestSuite.mode
if (definition.type === 'suite') {
const task: ParsedSuite = {
type: definition.type,
id: '',
suite: latestSuite,
file,
tasks: [],
mode,
name: definition.name,
end: definition.end,
start: definition.start,
projectName: ctx.getName(),
meta: {
typecheck: true,
},
}
definition.task = task
latestSuite.tasks.push(task)
lastSuite = task
return
}
const task: ParsedTest = {
type: definition.type,
id: '',
suite: latestSuite,
file,
mode,
context: {} as any, // not used in typecheck
name: definition.name,
end: definition.end,
start: definition.start,
meta: {
typecheck: true,
},
}
definition.task = task
latestSuite.tasks.push(task)
})
calculateSuiteHash(file)
const hasOnly = someTasksAreOnly(file)
interpretTaskModes(file, ctx.config.testNamePattern, hasOnly, false, ctx.config.allowOnly)
return {
file,
parsed: request.code,
filepath,
map: request.map as RawSourceMap | null,
definitions,
}
}
/* eslint-disable testing-library/no-node-access */
import * as React from "react";
import { userEvent, within } from "@storybook/testing-library";
import { expect } from "@storybook/jest";
import * as Tokens from "@washingtonpost/wpds-theme/src/tokens";
import * as PopoverPrimitive from "@radix-ui/react-popover";
import { Popover } from "./";
import { styled, theme, globalCss } from "@washingtonpost/wpds-theme";
import { ChevronRight } from "@washingtonpost/wpds-assets";
import type { ComponentMeta, ComponentStory } from "@storybook/react";
export default {
title: "Popover",
component: Popover.Content,
subcomponents: {
Root: Popover.Root,
Portal: Popover.Portal,
Trigger: Popover.Trigger,
Anchor: Popover.Anchor,
},
} as ComponentMeta<typeof Popover.Content>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const Template: ComponentStory<any> = (args) => {
const triggerRef = React.useRef(null);
const [parentElement, setParentElement] = React.useState();
React.useEffect(() => {
triggerRef?.current &&
setParentElement(triggerRef.current["parentElement"]);
}, [setParentElement]);
return (
<Popover.Root>
<Popover.Trigger ref={triggerRef}>Trigger</Popover.Trigger>
<Popover.Portal container={parentElement}>
<Popover.Content {...args}></Popover.Content>
</Popover.Portal>
</Popover.Root>
);
};
export const Default = Template.bind({});
Default.args = {
children:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Diam non eget consequat pretium.",
density: "default",
sideOffset: "050",
side: "bottom",
align: "center",
};
Default.argTypes = {
density: {
control: "select",
options: ["default", "compact"],
},
sideOffset: {
options: Object.keys(Tokens.sizes),
mapping: theme.sizes,
control: "select",
},
};
Default.parameters = {
chromatic: { disableSnapshot: true },
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const AnchorTemplate: ComponentStory<any> = (args) => {
const triggerRef = React.useRef(null);
const [parentElement, setParentElement] = React.useState();
React.useEffect(() => {
triggerRef?.current &&
setParentElement(triggerRef.current["parentElement"]);
}, [setParentElement]);
return (
<Popover.Root>
<Popover.Trigger ref={triggerRef}>Trigger</Popover.Trigger>
<Popover.Portal container={parentElement}>
<Popover.Content {...args}></Popover.Content>
</Popover.Portal>
<Popover.Anchor asChild>
<div
style={{
position: "absolute",
bottom: 0,
color: theme.colors.primary.value,
}}
>
Anchor
</div>
</Popover.Anchor>
</Popover.Root>
);
};
export const Anchor = AnchorTemplate.bind({});
Anchor.args = {
children:
"Anchors allow for Popups to be triggered and positioned independently",
};
Anchor.parameters = { chromatic: { disableSnapshot: true } };
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const CloseButtonTemplate: ComponentStory<any> = (args) => {
const triggerRef = React.useRef(null);
const [parentElement, setParentElement] = React.useState();
React.useEffect(() => {
triggerRef?.current &&
setParentElement(triggerRef.current["parentElement"]);
}, [setParentElement]);
return (
<Popover.Root>
<Popover.Trigger ref={triggerRef}>Trigger</Popover.Trigger>
<Popover.Portal container={parentElement}>
<Popover.Content
{...args}
onInteractOutside={(e) => {
e.preventDefault();
}}
>
<div style={{ marginInlineEnd: theme.sizes["175"].value }}>
{args.children}
</div>
<Popover.Close />
</Popover.Content>
</Popover.Portal>
</Popover.Root>
);
};
export const CloseButton = CloseButtonTemplate.bind({});
CloseButton.args = {
children: "An optional close button may be included",
};
CloseButton.parameters = { chromatic: { disableSnapshot: true } };
const SideMenu = styled("menu", {
backgroundColor: theme.colors.gray40,
listStyle: "none",
marginBlock: 0,
paddingInlineStart: 0,
width: "250px",
minHeight: "100vh",
});
const MenuLink = styled("a", {
color: theme.colors.onPrimary,
display: "flex",
alignItems: "center",
fontSize: theme.fontSizes["087"],
fontWeight: theme.fontWeights.bold,
textDecoration: "none",
paddingBlock: theme.space["100"],
paddingInline: theme.space["125"],
"&:hover": {
backgroundColor: theme.colors.gray0,
},
"&:focus-visible": { outline: "none" },
"& :first-child": {
flex: 1,
},
});
const SubMenu = styled("menu", {
listStyle: "none",
marginBlock: 0,
paddingInlineStart: 0,
});
const SubMenuLink = styled("a", {
color: theme.colors.primary,
display: "block",
fontSize: theme.fontSizes["087"],
fontWeight: theme.fontWeights.bold,
paddingBlock: theme.space["100"],
paddingInline: theme.space["125"],
textDecoration: "none",
"&:hover": {
backgroundColor: theme.colors.gray500,
},
});
const Arrow = () => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="12"
height="7"
fill="none"
viewBox="0 0 12 7"
>
<path fill={theme.colors.gray300.value} d="M0 1h12L6 7 0 1z"></path>
<path
fill={theme.colors.onPrimary.value}
d="M1 1h10L6 6 1 1zM0 0h12v1H0V0z"
></path>
</svg>
);
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const MenuTemplate: ComponentStory<any> = () => {
const [menuOpen, setMenuOpen] = React.useState(false);
return (
<div style={{ width: "calc(100% + 4rem)", marginBlock: "-2rem" }}>
<SideMenu>
<li>
<MenuLink href="">Home Page</MenuLink>
</li>
<li
onMouseEnter={() => setMenuOpen(true)}
onMouseLeave={() => setMenuOpen(false)}
>
<Popover.Root
open={menuOpen}
onOpenChange={(open) => setMenuOpen(open)}
>
<Popover.Trigger asChild>
<MenuLink href="https://www.washingtonpost.com/politics/?itid=nb_front_politics">
<span>Politics</span>{" "}
<ChevronRight width={theme.sizes["125"].value} />
</MenuLink>
</Popover.Trigger>
<Popover.Content
side="right"
sideOffset={0}
css={{
"&:focus-visible": { outline: "none" },
"& span": { lineHeight: 0 },
}}
>
<SubMenu>
<li>
<SubMenuLink href="https://www.washingtonpost.com/elections/election-2024/">
Election 2024
</SubMenuLink>
</li>
<li>
<SubMenuLink href="https://www.washingtonpost.com/politics/courts-law/?itid=nb_politics_courts-and-law">
Courts and Law
</SubMenuLink>
</li>
<li>
<SubMenuLink href="https://www.washingtonpost.com/news/fact-checker/?itid=nb_politics_fact-checker">
Fact Checker
</SubMenuLink>
</li>
</SubMenu>
<PopoverPrimitive.Arrow asChild>
<Arrow />
</PopoverPrimitive.Arrow>
</Popover.Content>
</Popover.Root>
</li>
</SideMenu>
</div>
);
};
export const Menu = MenuTemplate.bind({});
Menu.parameters = { chromatic: { disableSnapshot: true } };
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const FullPopover: ComponentStory<any> = ({ side, align }) => (
<Popover.Root open={true}>
<Popover.Trigger asChild>
<u
style={{
border: `1px solid ${theme.colors.gray300}`,
color: theme.colors.gray100.value,
paddingInline: theme.space["025"].value,
}}
tabIndex={0}
>
Trigger
</u>
</Popover.Trigger>
<Popover.Content
side={side}
align={align}
width={114}
css={{
animation: "unset",
}}
>
{side} {align}
</Popover.Content>
</Popover.Root>
);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const ChromaticTemplate: ComponentStory<any> = () => {
const side = ["bottom", "top", "right", "left"];
const align = ["start", "center", "end"];
return (
<div
style={{
display: "grid",
gridTemplateRows: "auto auto auto",
gridTemplateColumns: "auto auto",
gridTemplateAreas: "'bottom bottom' 'top top' 'right left'",
width: "100%",
}}
>
{side.map((s) => {
let direction = "row";
if (s === "right" || s === "left") {
direction = "column";
}
let alignItems = "flex-start";
if (s === "left") {
alignItems = "flex-end";
}
return (
<div
key={s}
style={
{
display: "flex",
flexDirection: direction,
gridArea: s,
justifyContent: "space-between",
alignItems: alignItems,
} as React.CSSProperties
}
>
{align.map((a) => {
let p = "0";
if (s === "bottom") {
p = "0 0 54px 0";
} else if (s === "top") {
p = "54px 0 0 0";
} else {
p = "20px 0 20px 0";
}
return (
<div key={s + a} style={{ padding: p }}>
<FullPopover side={s} align={a} />
</div>
);
})}
</div>
);
})}
</div>
);
};
export const Chromatic = ChromaticTemplate.bind({});
Chromatic.parameters = {
docs: { disable: true },
chromatic: { delay: 1000 },
};
const ecGlobalCss = globalCss({
":not(.wpds-dark) > .ec-grid": {
marginInlineStart: "-2rem",
},
".wpds-dark > .ec-grid": {
marginInlineEnd: "-2rem",
},
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const EdgeAndCornerCasesTemplate: ComponentStory<any> = () => {
ecGlobalCss();
return (
<div
className="ec-grid"
style={{
display: "grid",
gridTemplateRows: "auto 1fr auto",
gridTemplateColumns: "auto 1fr auto",
width: "calc(100% + 4rem)",
height: `100vh`,
marginBlockStart: "-2rem",
}}
>
<div>
<FullPopover side="left" align="end" />
</div>
<div style={{ display: "flex", justifyContent: "center" }}>
<FullPopover side="top" align="center" />
</div>
<div>
<FullPopover side="right" align="end" />
</div>
<div style={{ display: "flex", alignItems: "center" }}>
<FullPopover side="left" align="center" />
</div>
<div></div>
<div style={{ display: "flex", alignItems: "center" }}>
<FullPopover side="right" align="center" />
</div>
<div>
<FullPopover side="left" align="start" />
</div>
<div style={{ display: "flex", justifyContent: "center" }}>
<FullPopover side="bottom" align="center" />
</div>
<div>
<FullPopover side="right" align="start" />
</div>
</div>
);
};
export const EdgeAndCornerCases = EdgeAndCornerCasesTemplate.bind({});
EdgeAndCornerCases.parameters = {
docs: { disable: true },
chromatic: { delay: 3000 },
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const InteractionsTemplate: ComponentStory<any> = () => (
<Popover.Root>
<Popover.Trigger>Trigger</Popover.Trigger>
<Popover.Content>Popover Content</Popover.Content>
</Popover.Root>
);
export const Interactions = InteractionsTemplate.bind({});
Interactions.parameters = {
chromatic: { disableSnapshot: true },
};
// Function to emulate pausing between interactions
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
Interactions.play = async ({ canvasElement }) => {
const canvas = within(canvasElement);
const trigger = canvas.getByRole("button");
await userEvent.click(trigger);
await sleep(500);
const content = canvas.getByRole("dialog");
await expect(content).toBeVisible();
await userEvent.click(document.body);
await sleep(500);
await expect(content).not.toBeInTheDocument();
};
# Copyright 2023 Google Inc. All Rights Reserved.
#
# Distributed under MIT license.
# See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
# Workflow for building the release binaries.
name: Release build / deploy
on:
push:
branches:
- master
- v*.*.*
release:
types: [ published ]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
windows_build:
name: Windows Build (vcpkg / ${{ matrix.triplet }})
runs-on: [windows-2022]
strategy:
fail-fast: false
matrix:
include:
- triplet: x86-windows-dynamic
arch: '-A Win32'
build_shared_libs: 'ON'
- triplet: x64-windows-dynamic
arch: '-A x64'
build_shared_libs: 'ON'
- triplet: x86-windows-static
arch: '-A Win32'
build_shared_libs: 'OFF'
- triplet: x64-windows-static
arch: '-A x64'
build_shared_libs: 'OFF'
env:
VCPKG_VERSION: '2022.11.14'
VCPKG_ROOT: vcpkg
VCPKG_DISABLE_METRICS: 1
steps:
- name: Checkout the source
uses: actions/checkout@v3
with:
submodules: false
fetch-depth: 1
- uses: actions/cache@v3
id: cache-vcpkg
with:
path: vcpkg
key: release-${{ runner.os }}-vcpkg-${{ env.VCPKG_VERSION }}-${{ matrix.triplet }}
- name: Download vcpkg
if: steps.cache-vcpkg.outputs.cache-hit != 'true'
# wget doesn't seem to work under bash.
shell: 'powershell'
run: |
C:\msys64\usr\bin\wget.exe -nv `
https://github.com/microsoft/vcpkg/archive/refs/tags/${{ env.VCPKG_VERSION }}.zip `
-O vcpkg.zip
- name: Bootstrap vcpkg
if: steps.cache-vcpkg.outputs.cache-hit != 'true'
shell: 'bash'
run: |
set -x
unzip -q vcpkg.zip
rm -rf ${VCPKG_ROOT}
mv vcpkg-${VCPKG_VERSION} ${VCPKG_ROOT}
${VCPKG_ROOT}/bootstrap-vcpkg.sh
- name: Configure
shell: 'bash'
run: |
set -x
mkdir out
cmake -Bout -H. ${{ matrix.arch }} \
-DBUILD_TESTING=OFF \
-DBUILD_SHARED_LIBS=${{ matrix.build_shared_libs }} \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=`pwd`/prefix \
-DCMAKE_TOOLCHAIN_FILE=${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake \
-DVCPKG_TARGET_TRIPLET=${{ matrix.triplet }} \
#
- name: Build
shell: 'bash'
run: |
set -x
cmake --build out --config Release
- name: Install
shell: 'bash'
run: |
set -x
cmake --build out --config Release --target install
cp LICENSE prefix/bin/LICENSE.brotli
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: brotli-${{matrix.triplet}}
path: |
prefix/bin/*
- name: Package release zip
if: github.event_name == 'release'
shell: 'powershell'
run: |
Compress-Archive -Path prefix\bin\* `
-DestinationPath brotli-${{matrix.triplet}}.zip
- name: Upload binaries to release
if: github.event_name == 'release'
uses: AButler/[email protected]
with:
files: brotli-${{matrix.triplet}}.zip
repo-token: ${{ secrets.GITHUB_TOKEN }}
Early on in my programming journey, syntax highlighting aided my understanding the coding. However, as I gained more experience, I found myself relying less on it. Eventually, I realized that the abundance of colors was very straining on my eyes and made it difficult to focus and concentrate.
Moondust is crafted to reduce eye strain, allowing to focus on code. It challenges the conventional approach of highlighting keywords and typical structures. Instead of drawing excessive attention to them, this theme brings business logic to the forefront.
For a more in-depth discussion on the issues associated with the conventional approach to syntax highlighting, recommend reading the articles A case against syntax highlighting and Syntax highlighting is a waste of an information channel.
Moondust is tailored for each syntax individually. At present, it supports CSS, Dockerfile, fish, Go, Go Module, Go Sum, HTML, INI, JavaScript, JSON, JSON with Comments, JSON Lines, JSX, Makefile, Python, Ruby, Rust, Shell Script, SQL, Swift, TOML, TypeScript, TSX, and YAML.
In the initial phase, Moondust was primarily a modification of the GitHub Theme. I owe a great deal of gratitude to the Primer team for their work.
A special shout-out goes to the entire community that focuses on creating minimalistic, two-color, monochrome themes, particularly Monochrome. It was a delight for me to delve into your work, and it is heartening to realize that our aesthetic preferences align closely.