forked from vieter-v/vieter
Compare commits
311 Commits
api-testin
...
dev
Author | SHA1 | Date |
---|---|---|
Jef Roosens | 1a992806fa | |
Jef Roosens | 22577d3411 | |
Jef Roosens | 47c0f0405b | |
Jef Roosens | 076ee24b1b | |
Jef Roosens | de8764b281 | |
Jef Roosens | b278ebd73f | |
Jef Roosens | afb38256ac | |
Jef Roosens | ac3a89500b | |
Jef Roosens | 8a76860363 | |
Jef Roosens | 7595eb7bbe | |
Jef Roosens | 37f368b769 | |
Jef Roosens | 094634084b | |
Jef Roosens | 3b24ad0f2c | |
Jef Roosens | 69cc2404db | |
Jef Roosens | f423dcf26b | |
Jef Roosens | beae2cebd2 | |
Jef Roosens | a3a83a94ae | |
Jef Roosens | 455f3b5f41 | |
Jef Roosens | 4dc82515f4 | |
Jef Roosens | bff817ccd9 | |
Jef Roosens | 8a08423907 | |
Jef Roosens | b9598ca046 | |
Jef Roosens | 91a976c634 | |
Jef Roosens | b3a119f221 | |
Jef Roosens | e10b450abd | |
Jef Roosens | 8f32888dff | |
Jef Roosens | da370f42fd | |
Jef Roosens | 0d6ca8d3e4 | |
Jef Roosens | 434c4eb558 | |
Jef Roosens | 3b320ac7c3 | |
Jef Roosens | 8d14d5c3fd | |
Jef Roosens | 6ca53ce534 | |
Jef Roosens | ba89110eab | |
Jef Roosens | ad19bc660a | |
Jef Roosens | beb90d5756 | |
Jef Roosens | bfd28d6f70 | |
GreekStapler | 8432f5915d | |
Jef Roosens | 5176266ca1 | |
Jef Roosens | b5ff50066b | |
Jef Roosens | 398e2bd9eb | |
Jef Roosens | 39a026fdb3 | |
Jef Roosens | b0fe6b7384 | |
Jef Roosens | c9edb55abc | |
Jef Roosens | f8f611f5c5 | |
Jef Roosens | 60d5fb77e0 | |
Jef Roosens | 849bf54979 | |
Jef Roosens | 4ed4ef4a27 | |
Jef Roosens | 4ca2521937 | |
Jef Roosens | c0f58ddc77 | |
Jef Roosens | 8a0214babe | |
Jef Roosens | 1c70bce9e4 | |
Jef Roosens | 6738f8de67 | |
Jef Roosens | 4635127ba2 | |
Jef Roosens | bb4406404d | |
Jef Roosens | cac74db086 | |
Jef Roosens | b7af051103 | |
Jef Roosens | 641cf22669 | |
Jef Roosens | 3342eedfa4 | |
Jef Roosens | dc517c23c5 | |
Jef Roosens | be3762835d | |
Jef Roosens | ab6da78738 | |
Jef Roosens | 2c93316688 | |
Jef Roosens | 0a3e883f4d | |
Jef Roosens | ab81eebd87 | |
Jef Roosens | b66d1161ed | |
Jef Roosens | 26796f2228 | |
Jef Roosens | 09c61143b0 | |
Jef Roosens | a9ad3088bb | |
Jef Roosens | af409011e6 | |
Jef Roosens | 8b72a9fc0f | |
Jef Roosens | f9bb4b81de | |
Jef Roosens | 300c5490a6 | |
Jef Roosens | b067f9c589 | |
Jef Roosens | 1797c0f560 | |
Jef Roosens | 946d9acd59 | |
Jef Roosens | 402fef475a | |
Jef Roosens | 894323ddcb | |
Jef Roosens | fe3e6e2bab | |
Jef Roosens | af4c9e1d00 | |
Jef Roosens | 0604de26c4 | |
Jef Roosens | 489931eaa8 | |
Jef Roosens | 1ce7b9d571 | |
Jef Roosens | a48358fd75 | |
Jef Roosens | dbbe5c1e51 | |
Jef Roosens | b634775ca3 | |
Jef Roosens | 0727d0fd25 | |
Jef Roosens | 0bd5158608 | |
Jef Roosens | bfe1aafcf1 | |
Jef Roosens | 60cb91c18c | |
Jef Roosens | 51df1874f5 | |
Jef Roosens | d7a04c6ebf | |
Jef Roosens | 2cc3e8404e | |
Jef Roosens | 6a208dbe6c | |
Jef Roosens | f6c5e7c246 | |
Jef Roosens | 8a2f720bdf | |
Jef Roosens | d3151863ee | |
Jef Roosens | 03f2240ff6 | |
Jef Roosens | 5cbfc0ebcb | |
Jef Roosens | 6342789921 | |
Jef Roosens | e742d3de6d | |
Jef Roosens | b6168a3060 | |
Jef Roosens | 882a9a60a9 | |
Jef Roosens | 3611123f45 | |
Jef Roosens | 6f23d690a7 | |
Jef Roosens | 7ef8d4b846 | |
Jef Roosens | 5bab1f77f0 | |
Jef Roosens | 0a5c4295e0 | |
Jef Roosens | c57de4d8ee | |
Jef Roosens | 9a49d96e20 | |
Jef Roosens | 6281ef7607 | |
Jef Roosens | 9e11237ff9 | |
Jef Roosens | 71c77e90bc | |
Jef Roosens | 54f40b7638 | |
Jef Roosens | 9493796160 | |
Jef Roosens | 3636dd92db | |
Jef Roosens | 17e58c91ed | |
Jef Roosens | fc4dc30f74 | |
Jef Roosens | 5542be0418 | |
Jef Roosens | 9a552f5302 | |
Jef Roosens | aff6dff06a | |
Jef Roosens | 3095daed7d | |
Jef Roosens | cc9dcb3058 | |
Jef Roosens | 96a9798d3f | |
Jef Roosens | 161341a108 | |
Jef Roosens | a2fda0d4b7 | |
Jef Roosens | 23632be7a4 | |
Jef Roosens | 22fd6e395b | |
Jef Roosens | ed29102717 | |
Jef Roosens | ae29fe5ef8 | |
Jef Roosens | 66928216e5 | |
Jef Roosens | 7ef7dcd725 | |
Jef Roosens | e9fdbc9426 | |
Jef Roosens | 15c2d72743 | |
Jef Roosens | f34eefd59b | |
Jef Roosens | ae98c3e717 | |
Jef Roosens | 559ef3e505 | |
Jef Roosens | 847d77b2bc | |
Jef Roosens | 95d32e2d51 | |
Jef Roosens | 575c04189d | |
Jef Roosens | 39e2d12827 | |
Jef Roosens | 851a446a95 | |
Jef Roosens | fab8ca20b8 | |
Jef Roosens | 8a08788935 | |
Jef Roosens | cf67b46df0 | |
Jef Roosens | b6cd2f0bc2 | |
Jef Roosens | 210508f1ee | |
Jef Roosens | 7b59277931 | |
Jef Roosens | 3e0a2584fa | |
Jef Roosens | 9dfdfbf724 | |
Jef Roosens | 272f14b264 | |
Jef Roosens | 4887af26d3 | |
Jef Roosens | 9268ef0302 | |
Jef Roosens | e23635a1d3 | |
Jef Roosens | cc5df95a1a | |
Jef Roosens | e7b45bf251 | |
Jef Roosens | 3a73ea0632 | |
Jef Roosens | 78b0918df7 | |
Jef Roosens | 50918da672 | |
Jef Roosens | ba3b00572b | |
Jef Roosens | 68b7e5e71e | |
Jef Roosens | 6283cbea9c | |
Jef Roosens | 49ddb312de | |
Jef Roosens | 8a2b121cc7 | |
Jef Roosens | 0f6630b940 | |
Jef Roosens | 0d0fb323f2 | |
Jef Roosens | 1a940f2f98 | |
Jef Roosens | 487b235727 | |
Jef Roosens | 1b7cabdd74 | |
Jef Roosens | 6336d801d3 | |
Jef Roosens | 25d87fb5e6 | |
Jef Roosens | af4fbc4ccc | |
Jef Roosens | c8fc4c6a96 | |
Jef Roosens | 9dd9222a69 | |
Jef Roosens | a4c2508fe7 | |
Jef Roosens | e58ac49680 | |
Jef Roosens | 461f227169 | |
Jef Roosens | d060366dcb | |
Jef Roosens | 39eb03077e | |
Jef Roosens | 1ac76ac452 | |
Jef Roosens | 4200f5c8de | |
Jef Roosens | 424b0651e9 | |
Jef Roosens | 5e11a91f3d | |
Jef Roosens | 449656eb97 | |
Jef Roosens | 8f91c1fde5 | |
Jef Roosens | bd07964509 | |
Jef Roosens | bb5643bb03 | |
Jef Roosens | 10ad8297fb | |
Jef Roosens | a8d647cca3 | |
Jef Roosens | 1b7c14e7dc | |
Jef Roosens | f81039d2bb | |
Jef Roosens | 3d38df6d03 | |
Jef Roosens | cf94b64400 | |
Jef Roosens | fcdcf9c5ca | |
Jef Roosens | 9727b86203 | |
Jef Roosens | 102a7f8899 | |
Jef Roosens | faec08f846 | |
Jef Roosens | 4d581da7bf | |
Jef Roosens | 6b79f7b5ed | |
Jef Roosens | 3a5ac5d32b | |
Jef Roosens | 339267e6b2 | |
Jef Roosens | 233dd20345 | |
Jef Roosens | ae04fe63a7 | |
Jef Roosens | 592241c743 | |
Jef Roosens | 44696fc11b | |
Jef Roosens | 4866cfa635 | |
Jef Roosens | 12805d713c | |
Jef Roosens | 03df62bbc4 | |
Jef Roosens | ec128539d2 | |
Jef Roosens | 9dc8db4d54 | |
Jef Roosens | 0855d9efd8 | |
Jef Roosens | f4b2109533 | |
Jef Roosens | 440d1753da | |
Jef Roosens | ddccceb336 | |
Jef Roosens | c15f4a482f | |
Jef Roosens | c341d7a024 | |
Jef Roosens | a9ddfd8ec8 | |
Jef Roosens | 0ab39a334d | |
Jef Roosens | d7d77afe09 | |
Jef Roosens | 4ecf6a11c4 | |
Jef Roosens | 5df5850044 | |
Jef Roosens | 95441bdea0 | |
Jef Roosens | aea83c38ef | |
Jef Roosens | fc6d3909d2 | |
Jef Roosens | e734e658a0 | |
Jef Roosens | 7ad5830e9f | |
Jef Roosens | 7c2f892162 | |
Jef Roosens | 4870edde51 | |
Jef Roosens | a02b70e9a5 | |
Jef Roosens | 1d034e670e | |
Jef Roosens | 2abbc11118 | |
Jef Roosens | 0e8e7223ab | |
Jef Roosens | f9a6d585dd | |
Jef Roosens | ae50e29597 | |
Jef Roosens | e5582a2d54 | |
Jef Roosens | 329e819e15 | |
Jef Roosens | 06df2c21f0 | |
Jef Roosens | 48e2ae7645 | |
Jef Roosens | 9f753f9c93 | |
Jef Roosens | ec92b16a73 | |
Jef Roosens | aded6d438a | |
Jef Roosens | edd71b41c2 | |
Jef Roosens | f0565c4168 | |
Jef Roosens | a4ffc2c0e3 | |
Jef Roosens | 401e0291e3 | |
Jef Roosens | a39c1aa5eb | |
Jef Roosens | 4f32dec5b5 | |
Jef Roosens | 31e903ebeb | |
Jef Roosens | 596da100b6 | |
Jef Roosens | 0d5704ba15 | |
Jef Roosens | cdb88e1620 | |
Jef Roosens | 768da5b790 | |
Jef Roosens | bd4bb9a9fb | |
Jef Roosens | c0b739035b | |
Jef Roosens | 7f6e9e636c | |
Jef Roosens | 96d0c2f1eb | |
Jef Roosens | 0233b8559d | |
Jef Roosens | 2fc25f1afe | |
Jef Roosens | 6bd5b7cb48 | |
Jef Roosens | 5e81dadce3 | |
Jef Roosens | 1e079143cd | |
Jef Roosens | 7627b28bcf | |
Jef Roosens | 0de5ffb45d | |
Jef Roosens | 29e6d8d071 | |
Jef Roosens | 67c4d19921 | |
Jef Roosens | 73d2d4b08f | |
Jef Roosens | 92f73ad364 | |
Jef Roosens | 889d5a0884 | |
Jef Roosens | 3c87e60293 | |
Jef Roosens | 850cba6ab9 | |
Jef Roosens | d4c803c41c | |
Jef Roosens | 055b168ff1 | |
Jef Roosens | 1d3c7a1651 | |
Jef Roosens | 97cdaa18e1 | |
Jef Roosens | ce67208fbd | |
Jef Roosens | e041682fea | |
Jef Roosens | 79fd9c1f87 | |
Jef Roosens | 1811ebbe3f | |
Jef Roosens | f22ed29631 | |
Jef Roosens | 92cbea69d6 | |
Jef Roosens | da46b8b4ae | |
Jef Roosens | 4c97489f8a | |
Jef Roosens | dd9958ea28 | |
Jef Roosens | 473c7ec06b | |
Jef Roosens | c01d849b9e | |
Jef Roosens | 5c5c2f87e0 | |
Jef Roosens | b91d7b3159 | |
Jef Roosens | 5f21e256ee | |
Jef Roosens | 53f5b68d08 | |
Jef Roosens | 06bab98a88 | |
Jef Roosens | ad207bdb70 | |
Jef Roosens | c018aad143 | |
LordMZTE | 0bac221aee | |
Jef Roosens | 78fc3afcd3 | |
Jef Roosens | cae44fb593 | |
Jef Roosens | 3821ed29fd | |
Jef Roosens | 5a5f7f8346 | |
Jef Roosens | ea4c4fce16 | |
Jef Roosens | e79d18100f | |
Jef Roosens | 4b172cb5d8 | |
Jef Roosens | 27aa215eff | |
Jef Roosens | 7e5f0c5a53 | |
Jef Roosens | 30cce4fa72 | |
Jef Roosens | 5f7d7c4780 | |
Jef Roosens | 5b016df85d | |
Jef Roosens | fa6603bd45 | |
Jef Roosens | 407b226955 | |
Jef Roosens | f42d3fd8b0 | |
Jef Roosens | 139142fcec | |
Jef Roosens | 393e641a76 | |
Jef Roosens | 7e01dbafec | |
Jef Roosens | 58c1ecd25e |
|
@ -0,0 +1,4 @@
|
|||
# To stay consistent with the V formatting style, we use tabs
|
||||
UseTab: Always
|
||||
IndentWidth: 4
|
||||
TabWidth: 4
|
|
@ -5,6 +5,5 @@ root = true
|
|||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
[*.v]
|
||||
# vfmt wants it :(
|
||||
[*.{v,c,h}]
|
||||
indent_style = tab
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
*.c
|
||||
vieter.c
|
||||
/data/
|
||||
|
||||
# Build artifacts
|
||||
|
@ -26,3 +26,8 @@ gdb.txt
|
|||
|
||||
# Generated docs
|
||||
_docs/
|
||||
docs/resources/_gen/
|
||||
/man/
|
||||
|
||||
# VLS logs
|
||||
vls.log
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
[submodule "docs/themes/hugo-book"]
|
||||
path = docs/themes/hugo-book
|
||||
url = https://github.com/alex-shpak/hugo-book
|
||||
[submodule "src/libvieter"]
|
||||
path = src/libvieter
|
||||
url = https://git.rustybever.be/vieter-v/libvieter
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
# These checks already get performed on the feature branches
|
||||
branches:
|
||||
exclude: [ main ]
|
||||
platform: 'linux/amd64'
|
||||
|
||||
pipeline:
|
||||
lint:
|
||||
image: 'chewingbever/vlang:latest'
|
||||
pull: true
|
||||
commands:
|
||||
- make lint
|
||||
when:
|
||||
event: [ pull_request ]
|
|
@ -1,17 +0,0 @@
|
|||
matrix:
|
||||
PLATFORM:
|
||||
- 'linux/amd64'
|
||||
- 'linux/arm64'
|
||||
|
||||
branches:
|
||||
exclude: [ main ]
|
||||
platform: ${PLATFORM}
|
||||
|
||||
pipeline:
|
||||
test:
|
||||
image: 'chewingbever/vlang:latest'
|
||||
pull: true
|
||||
commands:
|
||||
- make test
|
||||
when:
|
||||
event: [pull_request]
|
|
@ -0,0 +1,40 @@
|
|||
matrix:
|
||||
PLATFORM:
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
|
||||
platform: ${PLATFORM}
|
||||
branches: [main]
|
||||
skip_clone: true
|
||||
|
||||
pipeline:
|
||||
build:
|
||||
image: 'git.rustybever.be/vieter-v/vieter-builder'
|
||||
pull: true
|
||||
commands:
|
||||
# Add the vieter repository so we can use the compiler
|
||||
- echo -e '[vieter]\nServer = https://arch.r8r.be/$repo/$arch\nSigLevel = Optional' >> /etc/pacman.conf
|
||||
# Update packages
|
||||
- pacman -Syu --noconfirm
|
||||
# Create non-root user to perform build & switch to their home
|
||||
- groupadd -g 1000 builder
|
||||
- useradd -mg builder builder
|
||||
- chown -R builder:builder "$PWD"
|
||||
- "echo 'builder ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers"
|
||||
- su builder
|
||||
# Due to a bug with the V compiler, we can't just use the PKGBUILD from
|
||||
# inside the repo
|
||||
- curl -OL "https://git.rustybever.be/vieter-v/vieter/raw/tag/$CI_COMMIT_TAG/PKGBUILD"
|
||||
- makepkg -s --noconfirm --needed
|
||||
when:
|
||||
event: tag
|
||||
|
||||
publish:
|
||||
image: 'curlimages/curl'
|
||||
commands:
|
||||
# Publish the package
|
||||
- 'for pkg in $(ls -1 *.pkg*); do curl -XPOST -T "$pkg" -H "X-API-KEY: $VIETER_API_KEY" https://arch.r8r.be/vieter/publish; done'
|
||||
secrets:
|
||||
- vieter_api_key
|
||||
when:
|
||||
event: tag
|
|
@ -9,7 +9,8 @@ skip_clone: true
|
|||
|
||||
pipeline:
|
||||
build:
|
||||
image: 'menci/archlinuxarm:base-devel'
|
||||
image: 'git.rustybever.be/vieter-v/vieter-builder'
|
||||
pull: true
|
||||
commands:
|
||||
# Add the vieter repository so we can use the compiler
|
||||
- echo -e '[vieter]\nServer = https://arch.r8r.be/$repo/$arch\nSigLevel = Optional' >> /etc/pacman.conf
|
||||
|
@ -23,7 +24,7 @@ pipeline:
|
|||
- su builder
|
||||
# Due to a bug with the V compiler, we can't just use the PKGBUILD from
|
||||
# inside the repo
|
||||
- curl -OL https://git.rustybever.be/vieter/vieter/raw/branch/dev/PKGBUILD
|
||||
- curl -o PKGBUILD -L https://git.rustybever.be/vieter-v/vieter/raw/branch/dev/PKGBUILD.dev
|
||||
- makepkg -s --noconfirm --needed
|
||||
when:
|
||||
event: push
|
|
@ -1,3 +1,6 @@
|
|||
variables:
|
||||
- &vlang_image 'git.rustybever.be/vieter/vlang:5d4c9dc9fc11bf8648541c934adb64f27cb94e37-alpine3.17'
|
||||
|
||||
matrix:
|
||||
PLATFORM:
|
||||
- 'linux/amd64'
|
||||
|
@ -6,10 +9,19 @@ matrix:
|
|||
platform: ${PLATFORM}
|
||||
|
||||
pipeline:
|
||||
debug:
|
||||
image: 'chewingbever/vlang:latest'
|
||||
install-modules:
|
||||
image: *vlang_image
|
||||
pull: true
|
||||
commands:
|
||||
- export VMODULES=$PWD/.vmodules
|
||||
- 'cd src && v install'
|
||||
when:
|
||||
event: [push, pull_request]
|
||||
|
||||
debug:
|
||||
image: *vlang_image
|
||||
commands:
|
||||
- export VMODULES=$PWD/.vmodules
|
||||
- make
|
||||
when:
|
||||
event: [pull_request]
|
||||
|
@ -17,11 +29,11 @@ pipeline:
|
|||
exclude: [main]
|
||||
|
||||
prod:
|
||||
image: 'chewingbever/vlang:latest'
|
||||
pull: true
|
||||
image: *vlang_image
|
||||
environment:
|
||||
- LDFLAGS=-lz -lbz2 -llzma -lexpat -lzstd -llz4 -lsqlite3 -static
|
||||
commands:
|
||||
- export VMODULES=$PWD/.vmodules
|
||||
# Apparently this -D is *very* important
|
||||
- CFLAGS='-DGC_THREADS=1' make prod
|
||||
# Make sure the binary is actually statically built
|
||||
|
@ -35,7 +47,7 @@ pipeline:
|
|||
event: [push, pull_request]
|
||||
|
||||
upload:
|
||||
image: 'chewingbever/vlang:latest'
|
||||
image: *vlang_image
|
||||
secrets: [ s3_username, s3_password ]
|
||||
commands:
|
||||
# https://gist.github.com/JustinTimperio/7c7115f87b775618637d67ac911e595f
|
||||
|
@ -45,7 +57,7 @@ pipeline:
|
|||
|
||||
- export OBJ_PATH="/vieter/commits/$CI_COMMIT_SHA/vieter-$(echo '${PLATFORM}' | sed 's:/:-:g')"
|
||||
- export SIG_STRING="PUT\n\n$CONTENT_TYPE\n$DATE\n$OBJ_PATH"
|
||||
- export SIGNATURE=`echo -en $SIG_STRING | openssl sha1 -hmac $S3_PASSWORD -binary | base64`
|
||||
- export SIGNATURE="$(echo -en $SIG_STRING | openssl dgst -sha1 -hmac $S3_PASSWORD -binary | base64)"
|
||||
- >
|
||||
curl
|
||||
--silent
|
|
@ -11,7 +11,9 @@ pipeline:
|
|||
- 'docker_password'
|
||||
settings:
|
||||
repo: 'chewingbever/vieter'
|
||||
tag: 'dev'
|
||||
tags:
|
||||
- 'dev'
|
||||
- ${CI_COMMIT_SHA}
|
||||
platforms: [ 'linux/arm64/v8', 'linux/amd64' ]
|
||||
build_args_from_env:
|
||||
- 'CI_COMMIT_SHA'
|
|
@ -1,41 +1,49 @@
|
|||
variables:
|
||||
- &vlang_image 'git.rustybever.be/vieter/vlang:5d4c9dc9fc11bf8648541c934adb64f27cb94e37-alpine3.17'
|
||||
|
||||
platform: 'linux/amd64'
|
||||
branches:
|
||||
exclude: [ main ]
|
||||
|
||||
pipeline:
|
||||
docs:
|
||||
image: 'klakegg/hugo:alpine'
|
||||
image: 'klakegg/hugo:ext-alpine'
|
||||
group: 'generate'
|
||||
commands:
|
||||
- apk add git
|
||||
- make docs
|
||||
- 'cd docs/public && tar czvf ../../docs.tar.gz *'
|
||||
|
||||
api-docs:
|
||||
image: 'chewingbever/vlang:latest'
|
||||
image: *vlang_image
|
||||
pull: true
|
||||
group: 'generate'
|
||||
commands:
|
||||
- make api-docs
|
||||
- 'cd src/_docs && tar czvf ../../api-docs.tar.gz *'
|
||||
|
||||
deploy-docs:
|
||||
slate-docs:
|
||||
image: 'slatedocs/slate:v2.13.0'
|
||||
group: 'generate'
|
||||
# Slate requires a specific directory to run in
|
||||
commands:
|
||||
- cd docs/api
|
||||
- bundle exec middleman build --clean
|
||||
|
||||
archive:
|
||||
image: 'alpine'
|
||||
commands:
|
||||
- cp -r docs/api/build docs/public/api
|
||||
- 'cd docs/public && tar czvf ../../docs.tar.gz *'
|
||||
- 'cd ../../src/_docs && tar czvf ../../api-docs.tar.gz *'
|
||||
when:
|
||||
event: push
|
||||
branch: dev
|
||||
|
||||
deploy:
|
||||
image: 'curlimages/curl'
|
||||
group: 'deploy'
|
||||
secrets:
|
||||
- 'site_api_key'
|
||||
commands:
|
||||
- 'curl -XPOST --fail -s -H "Authorization: Bearer $SITE_API_KEY" -T docs.tar.gz https://rustybever.be/api/deploy?dir=docs-vieter'
|
||||
when:
|
||||
event: push
|
||||
branch: dev
|
||||
|
||||
deploy-api-docs:
|
||||
image: 'curlimages/curl'
|
||||
group: 'deploy'
|
||||
secrets:
|
||||
- 'site_api_key'
|
||||
commands:
|
||||
- 'curl -XPOST --fail -s -H "Authorization: Bearer $SITE_API_KEY" -T api-docs.tar.gz https://rustybever.be/api/deploy?dir=api-docs-vieter'
|
||||
when:
|
||||
event: push
|
|
@ -1,3 +1,6 @@
|
|||
variables:
|
||||
- &vlang_image 'git.rustybever.be/vieter/vlang:5d4c9dc9fc11bf8648541c934adb64f27cb94e37-alpine3.17'
|
||||
|
||||
platform: 'linux/amd64'
|
||||
branches: [ 'main' ]
|
||||
depends_on:
|
||||
|
@ -8,12 +11,13 @@ skip_clone: true
|
|||
|
||||
pipeline:
|
||||
prepare:
|
||||
image: 'chewingbever/vlang:latest'
|
||||
image: *vlang_image
|
||||
pull: true
|
||||
secrets: [ s3_username, s3_password ]
|
||||
commands:
|
||||
- mc alias set s3/ https://s3.rustybever.be "$S3_USERNAME" "$S3_PASSWORD"
|
||||
- mc cp -r "s3/vieter/commits/$CI_COMMIT_SHA" .
|
||||
- mv "$CI_COMMIT_SHA"/vieter-* .
|
||||
when:
|
||||
event: tag
|
||||
|
||||
|
@ -23,9 +27,8 @@ pipeline:
|
|||
- gitea_release_api_key
|
||||
settings:
|
||||
base_url: https://git.rustybever.be
|
||||
files: ${CI_COMMIT_SHA}/*
|
||||
files: vieter-*
|
||||
checksum:
|
||||
- md5
|
||||
- sha256
|
||||
title: ${CI_COMMIT_TAG}
|
||||
when:
|
|
@ -0,0 +1,27 @@
|
|||
variables:
|
||||
- &vlang_image 'git.rustybever.be/vieter/vlang:5d4c9dc9fc11bf8648541c934adb64f27cb94e37-alpine3.17'
|
||||
|
||||
# These checks already get performed on the feature branches
|
||||
branches:
|
||||
exclude: [ main ]
|
||||
platform: 'linux/amd64'
|
||||
|
||||
pipeline:
|
||||
# vfmt seems to get confused if these aren't present
|
||||
install-modules:
|
||||
image: *vlang_image
|
||||
pull: true
|
||||
commands:
|
||||
- export VMODULES=$PWD/.vmodules
|
||||
- 'cd src && v install'
|
||||
when:
|
||||
event: [pull_request]
|
||||
|
||||
lint:
|
||||
image: *vlang_image
|
||||
pull: true
|
||||
commands:
|
||||
- export VMODULES=$PWD/.vmodules
|
||||
- make lint
|
||||
when:
|
||||
event: [pull_request]
|
|
@ -0,0 +1,45 @@
|
|||
variables:
|
||||
- &vlang_image 'git.rustybever.be/vieter/vlang:5d4c9dc9fc11bf8648541c934adb64f27cb94e37-alpine3.17'
|
||||
|
||||
platform: 'linux/amd64'
|
||||
branches:
|
||||
exclude: [ main ]
|
||||
|
||||
depends_on:
|
||||
- build
|
||||
|
||||
pipeline:
|
||||
install-modules:
|
||||
image: *vlang_image
|
||||
pull: true
|
||||
commands:
|
||||
- export VMODULES=$PWD/.vmodules
|
||||
- 'cd src && v install'
|
||||
|
||||
generate:
|
||||
image: *vlang_image
|
||||
commands:
|
||||
# - curl -o vieter -L "https://s3.rustybever.be/vieter/commits/$CI_COMMIT_SHA/vieter-linux-amd64"
|
||||
# - chmod +x vieter
|
||||
- export VMODULES=$PWD/.vmodules
|
||||
- make
|
||||
- ./vieter man man
|
||||
- cd man
|
||||
|
||||
# Generate an HTML page from each man page
|
||||
- for f in $(ls -1 *.1); do mandoc -Thtml -O style=mandoc.css,man=%N.%S.html $f > "$f.html"; done
|
||||
|
||||
# Download the mandoc.css file from the official site
|
||||
- curl -o mandoc.css -L https://mandoc.bsd.lv/mandoc.css
|
||||
|
||||
- tar czvf ../man.tar.gz *.html mandoc.css
|
||||
|
||||
deploy:
|
||||
image: 'curlimages/curl'
|
||||
secrets:
|
||||
- 'site_api_key'
|
||||
commands:
|
||||
- 'curl -XPOST --fail -s -H "Authorization: Bearer $SITE_API_KEY" -T man.tar.gz https://rustybever.be/api/deploy?dir=man-vieter'
|
||||
when:
|
||||
event: push
|
||||
branch: dev
|
|
@ -0,0 +1,30 @@
|
|||
variables:
|
||||
- &vlang_image 'git.rustybever.be/vieter/vlang:5d4c9dc9fc11bf8648541c934adb64f27cb94e37-alpine3.17'
|
||||
|
||||
matrix:
|
||||
PLATFORM:
|
||||
- 'linux/amd64'
|
||||
- 'linux/arm64'
|
||||
|
||||
branches:
|
||||
exclude: [ main ]
|
||||
platform: ${PLATFORM}
|
||||
|
||||
pipeline:
|
||||
install-modules:
|
||||
image: *vlang_image
|
||||
pull: true
|
||||
commands:
|
||||
- export VMODULES=$PWD/.vmodules
|
||||
- 'cd src && v install'
|
||||
when:
|
||||
event: [pull_request]
|
||||
|
||||
test:
|
||||
image: *vlang_image
|
||||
pull: true
|
||||
commands:
|
||||
- export VMODULES=$PWD/.vmodules
|
||||
- make test
|
||||
when:
|
||||
event: [pull_request]
|
174
CHANGELOG.md
174
CHANGELOG.md
|
@ -5,7 +5,173 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.3.0-alpha.1](https://git.rustybever.be/vieter/vieter/src/tag/0.3.0-alpha.1)
|
||||
## [Unreleased](https://git.rustybever.be/vieter-v/vieter/src/branch/dev)
|
||||
|
||||
## [0.6.0](https://git.rustybever.be/vieter-v/vieter/src/tag/0.6.0)
|
||||
|
||||
### Added
|
||||
|
||||
* Metrics endpoint for Prometheus integration
|
||||
* Search in list of targets using API & CLI
|
||||
* Allow filtering targets by arch value
|
||||
* Configurable global timeout for builds
|
||||
|
||||
### Changed
|
||||
|
||||
* Rewrote cron expression logic in C
|
||||
* Updated codebase to V commit after 0.3.3
|
||||
* Agents now use worker threads and no longer spawn a new thread for every
|
||||
build
|
||||
|
||||
### Fixed
|
||||
|
||||
* Package upload now fails if TCP connection is closed before all bytes have
|
||||
been received
|
||||
|
||||
### Removed
|
||||
|
||||
* Deprecated cron daemon
|
||||
|
||||
## [0.5.0](https://git.rustybever.be/vieter-v/vieter/src/tag/0.5.0)
|
||||
|
||||
### Added
|
||||
|
||||
* CLI commands for removing packages, arch-repos & repositories
|
||||
|
||||
## [0.5.0-rc.2](https://git.rustybever.be/vieter-v/vieter/src/tag/0.5.0-rc.2)
|
||||
|
||||
### Added
|
||||
|
||||
* API route for removing logs & accompanying CLI command
|
||||
* Daemon for periodically removing old logs
|
||||
* CLI flag to filter logs by specific exit codes
|
||||
|
||||
### Changed
|
||||
|
||||
* Use `--long-option` instead of `-long-option` for CLI
|
||||
|
||||
## [0.5.0-rc.1](https://git.rustybever.be/vieter-v/vieter/src/tag/0.5.0-rc.1)
|
||||
|
||||
### Added
|
||||
|
||||
* Allow specifying subdirectory inside Git repository
|
||||
* Added option to deploy using agent-server architecture instead of cron daemon
|
||||
* Allow scheduling builds on the server from the CLI tool instead of building
|
||||
them locally
|
||||
* Allow force-building packages, meaning the build won't check if the
|
||||
repository is already up to date
|
||||
|
||||
### Changed
|
||||
|
||||
* Migrated codebase to V 0.3.2
|
||||
* Cron expression parser now uses bitfields instead of bool arrays
|
||||
|
||||
### Fixed
|
||||
|
||||
* Arch value for target is now properly set if not provided
|
||||
* Allow NULL values for branch in database
|
||||
* Endpoint for adding targets now returns the correct id
|
||||
* CLI now correctly errors and doesn't error when sending requests
|
||||
* Fixed possible infinite loop when removing old build images
|
||||
* Check whether build image still exists before starting build
|
||||
* Don't run makepkg `prepare()` function twice
|
||||
* Don't buffer stdout in Docker containers
|
||||
|
||||
## [0.4.0](https://git.rustybever.be/vieter-v/vieter/src/tag/0.4.0)
|
||||
|
||||
### Added
|
||||
|
||||
* Server port can now be configured
|
||||
* Targets now have a 'kind' field describing whether it's a Git repository or a
|
||||
URL to a PKGBUILD
|
||||
* Targets with kind 'url' can provide a direct URL to a PKGBUILD instead of
|
||||
providing a Git repository
|
||||
* CLI commands for searching the AUR & directly adding packages
|
||||
* HTTP routes for removing packages, arch-repos & repos
|
||||
* All endpoints serving files now support HTTP byte range requests
|
||||
* Better CLI UX
|
||||
* When adding targets, the ID of the created target is returned
|
||||
* The `-r` flag only shows raw data of action
|
||||
* When adding a target, only ID is shown and not surrounding text
|
||||
* Tabled output returns a tab-separated list (easy to script using
|
||||
`cut`)
|
||||
|
||||
### Changed
|
||||
|
||||
* Moved all API routes under `/v1` namespace
|
||||
* Renamed `vieter repos` to `vieter targets`
|
||||
* Renamed `/api/v1/repos` namespace to `/api/v1/targets`
|
||||
* Branch name for 'git' targets is now optional; if not provided, the
|
||||
repository will be cloned with the default branch
|
||||
* Build containers now explicitely set the PATH variable
|
||||
* Refactor of web framework
|
||||
* API endpoints now return id of newly created entries
|
||||
* Repo POST requests now return information on published package
|
||||
* `api` can no longer be used as a repository name
|
||||
* CLI client now allows setting values to an empty value
|
||||
|
||||
### Removed
|
||||
|
||||
* md5 hashes are no longer calculated for packages
|
||||
|
||||
## [0.3.0](https://git.rustybever.be/vieter-v/vieter/src/tag/0.3.0)
|
||||
|
||||
Nothing besides bumping the versions.
|
||||
|
||||
## [0.3.0-rc.1](https://git.rustybever.be/vieter-v/vieter/src/tag/0.3.0-rc.1)
|
||||
|
||||
### Added
|
||||
|
||||
* Database migrations
|
||||
* Improved GitRepo & BuildLog API
|
||||
* Pagination using `limit` & `offset` query params
|
||||
* GitRepo: filter by repo
|
||||
* BuildLog: filter by start & end date, repo, exit code & arch
|
||||
* CLI flags to take advantage of above API improvements
|
||||
* Added CLI command to generate all man pages
|
||||
* PKGBUILDs now install man pages
|
||||
* Hosted CLI man pages ([vieter(1)](https://rustybever.be/man/vieter/vieter.1.html))
|
||||
* Proper HTTP API docs ([link](https://rustybever.be/docs/vieter/api/))
|
||||
|
||||
### Changed
|
||||
|
||||
* Packages from target repo are available during builds
|
||||
* This can be used as a basic way to support AUR dependencies, by adding
|
||||
the dependencies to the same repository
|
||||
* Every build now updates its packages first instead of solely relying on the
|
||||
updated builder image
|
||||
* Build logs now show commands being executed
|
||||
|
||||
### Fixed
|
||||
|
||||
* `POST /api/logs` now correctly uses epoch timestamps instead of strings
|
||||
|
||||
## [0.3.0-alpha.2](https://git.rustybever.be/vieter-v/vieter/src/tag/0.3.0-alpha.2)
|
||||
|
||||
### Added
|
||||
|
||||
* Web API for adding & querying build logs
|
||||
* CLI commands to access build logs API
|
||||
* Cron build logs are uploaded to above API
|
||||
* Proper ASCII table output in CLI
|
||||
* `vieter repos build id` command to run builds locally
|
||||
|
||||
### Removed
|
||||
|
||||
* `vieter build` command
|
||||
* This command was used alongside cron for periodic builds, but this has
|
||||
been replaced by `vieter cron`
|
||||
|
||||
### Changed
|
||||
|
||||
* `vieter build` command now only builds a single repository & uploads the
|
||||
build logs
|
||||
* Official Arch packages are now split between `vieter` & `vieter-git`
|
||||
* `vieter` is the latest release
|
||||
* `vieter-git` is the latest commit on the dev branch
|
||||
* Full refactor of Docker socket code
|
||||
|
||||
## [0.3.0-alpha.1](https://git.rustybever.be/vieter-v/vieter/src/tag/0.3.0-alpha.1)
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -24,7 +190,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
* Binary no longer panics when an env var is missing
|
||||
|
||||
## [0.2.0](https://git.rustybever.be/vieter/vieter/src/tag/0.2.0)
|
||||
## [0.2.0](https://git.rustybever.be/vieter-v/vieter/src/tag/0.2.0)
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -58,13 +224,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
* Packages with unknown fields in .PKGINFO are now allowed
|
||||
* Old packages are now properly removed
|
||||
|
||||
## [0.1.0](https://git.rustybever.be/vieter/vieter/src/tag/0.1.0)
|
||||
## [0.1.0](https://git.rustybever.be/vieter-v/vieter/src/tag/0.1.0)
|
||||
|
||||
### Changed
|
||||
|
||||
* Improved logging
|
||||
|
||||
## [0.1.0-rc.1](https://git.rustybever.be/vieter/vieter/src/tag/0.1.0-rc.1)
|
||||
## [0.1.0-rc.1](https://git.rustybever.be/vieter-v/vieter/src/tag/0.1.0-rc.1)
|
||||
|
||||
### Added
|
||||
|
||||
|
|
12
Dockerfile
12
Dockerfile
|
@ -1,4 +1,4 @@
|
|||
FROM chewingbever/vlang:latest AS builder
|
||||
FROM git.rustybever.be/chewing_bever/vlang:0.3.2 AS builder
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
ARG CI_COMMIT_SHA
|
||||
|
@ -23,6 +23,7 @@ RUN if [ -n "${CI_COMMIT_SHA}" ]; then \
|
|||
"https://s3.rustybever.be/vieter/commits/${CI_COMMIT_SHA}/vieter-$(echo "${TARGETPLATFORM}" | sed 's:/:-:g')" && \
|
||||
chmod +x vieter ; \
|
||||
else \
|
||||
cd src && v install && cd .. && \
|
||||
LDFLAGS='-lz -lbz2 -llzma -lexpat -lzstd -llz4 -lsqlite3 -static' make prod && \
|
||||
mv pvieter vieter ; \
|
||||
fi
|
||||
|
@ -36,15 +37,8 @@ ENV PATH=/bin \
|
|||
|
||||
COPY --from=builder /app/dumb-init /app/vieter /bin/
|
||||
|
||||
HEALTHCHECK --interval=30s \
|
||||
--timeout=3s \
|
||||
--start-period=5s \
|
||||
CMD /bin/wget --spider http://localhost:8000/health || exit 1
|
||||
|
||||
RUN mkdir /data && \
|
||||
chown -R www-data:www-data /data && \
|
||||
mkdir -p '/var/spool/cron/crontabs' && \
|
||||
echo '0 3 * * * /bin/vieter build' | crontab -
|
||||
chown -R www-data:www-data /data
|
||||
|
||||
WORKDIR /data
|
||||
|
||||
|
|
45
Makefile
45
Makefile
|
@ -1,16 +1,20 @@
|
|||
# =====CONFIG=====
|
||||
SRC_DIR := src
|
||||
SOURCES != find '$(SRC_DIR)' -iname '*.v'
|
||||
SRCS != find '$(SRC_DIR)' -iname '*.v'
|
||||
|
||||
V_PATH ?= v
|
||||
V := $(V_PATH) -showcc -gc boehm
|
||||
V := $(V_PATH) -showcc -gc boehm -d use_openssl -skip-unused
|
||||
|
||||
all: vieter
|
||||
|
||||
|
||||
# =====COMPILATION=====
|
||||
.PHONY: libvieter
|
||||
libvieter:
|
||||
make -C '$(SRC_DIR)/libvieter' CFLAGS='-O3'
|
||||
|
||||
# Regular binary
|
||||
vieter: $(SOURCES)
|
||||
vieter: $(SOURCES) libvieter
|
||||
$(V) -g -o vieter $(SRC_DIR)
|
||||
|
||||
# Debug build using gcc
|
||||
|
@ -18,7 +22,7 @@ vieter: $(SOURCES)
|
|||
# multi-threaded and causes issues when running vieter inside gdb.
|
||||
.PHONY: debug
|
||||
debug: dvieter
|
||||
dvieter: $(SOURCES)
|
||||
dvieter: $(SOURCES) libvieter
|
||||
$(V_PATH) -showcc -keepc -cg -o dvieter $(SRC_DIR)
|
||||
|
||||
# Run the debug build inside gdb
|
||||
|
@ -29,12 +33,12 @@ gdb: dvieter
|
|||
# Optimised production build
|
||||
.PHONY: prod
|
||||
prod: pvieter
|
||||
pvieter: $(SOURCES)
|
||||
pvieter: $(SOURCES) libvieter
|
||||
$(V) -o pvieter -prod $(SRC_DIR)
|
||||
|
||||
# Only generate C code
|
||||
.PHONY: c
|
||||
c: $(SOURCES)
|
||||
c: $(SOURCES) libvieter
|
||||
$(V) -o vieter.c $(SRC_DIR)
|
||||
|
||||
|
||||
|
@ -60,8 +64,14 @@ api-docs:
|
|||
rm -rf '$(SRC_DIR)/_docs'
|
||||
cd '$(SRC_DIR)' && v doc -all -f html -m -readme .
|
||||
|
||||
.PHONY: man
|
||||
man: vieter
|
||||
rm -rf man
|
||||
./vieter man man
|
||||
|
||||
|
||||
# =====OTHER=====
|
||||
# Linting
|
||||
.PHONY: lint
|
||||
lint:
|
||||
$(V) fmt -verify $(SRC_DIR)
|
||||
|
@ -69,34 +79,33 @@ lint:
|
|||
$(V_PATH) missdoc -p $(SRC_DIR)
|
||||
@ [ $$($(V_PATH) missdoc -p $(SRC_DIR) | wc -l) = 0 ]
|
||||
|
||||
# Format the V codebase
|
||||
|
||||
# Formatting
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
$(V) fmt -w $(SRC_DIR)
|
||||
|
||||
|
||||
# Testing
|
||||
.PHONY: test
|
||||
test:
|
||||
$(V) test $(SRC_DIR)
|
||||
test: libvieter
|
||||
$(V) -g test $(SRC_DIR)
|
||||
|
||||
# Build & patch the V compiler
|
||||
.PHONY: v
|
||||
v: v/v
|
||||
v/v:
|
||||
git clone --single-branch https://git.rustybever.be/Chewing_Bever/v v
|
||||
make -C v
|
||||
|
||||
# Cleaning
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf 'data' 'vieter' 'dvieter' 'pvieter' 'vieter.c' 'dvieterctl' 'vieterctl' 'pkg' 'src/vieter' *.pkg.tar.zst 'suvieter' 'afvieter' '$(SRC_DIR)/_docs' 'docs/public'
|
||||
rm -rf 'data' 'vieter' 'dvieter' 'pvieter' 'vieter.c' 'pkg' 'src/vieter' *.pkg.tar.zst 'suvieter' 'afvieter' '$(SRC_DIR)/_docs' 'docs/public'
|
||||
make -C '$(SRC_DIR)/libvieter' clean
|
||||
|
||||
|
||||
# =====EXPERIMENTAL=====
|
||||
.PHONY: autofree
|
||||
autofree: afvieter
|
||||
afvieter: $(SOURCES)
|
||||
$(V_PATH) -showcc -autofree -o afvieter $(SRC_DIR)
|
||||
$(V) -showcc -autofree -o afvieter $(SRC_DIR)
|
||||
|
||||
.PHONY: skip-unused
|
||||
skip-unused: suvieter
|
||||
suvieter: $(SOURCES)
|
||||
$(V_PATH) -showcc -skip-unused -o suvieter $(SRC_DIR)
|
||||
$(V) -skip-unused -o suvieter $(SRC_DIR)
|
||||
|
|
44
PKGBUILD
44
PKGBUILD
|
@ -1,32 +1,52 @@
|
|||
# vim: ft=bash
|
||||
# Maintainer: Jef Roosens
|
||||
|
||||
pkgbase='vieter'
|
||||
pkgname='vieter'
|
||||
pkgver=0.2.0.r25.g20112b8
|
||||
pkgver='0.6.0'
|
||||
pkgrel=1
|
||||
depends=('glibc' 'openssl' 'libarchive' 'gc' 'sqlite')
|
||||
makedepends=('git' 'gcc' 'vieter-v')
|
||||
pkgdesc="Lightweight Arch repository server & package build system"
|
||||
depends=('glibc' 'openssl' 'libarchive' 'sqlite')
|
||||
makedepends=('git' 'vieter-vlang')
|
||||
arch=('x86_64' 'aarch64')
|
||||
url='https://git.rustybever.be/vieter/vieter'
|
||||
url='https://git.rustybever.be/vieter-v/vieter'
|
||||
license=('AGPL3')
|
||||
source=($pkgname::git+https://git.rustybever.be/vieter/vieter#branch=dev)
|
||||
md5sums=('SKIP')
|
||||
source=(
|
||||
"$pkgname::git+https://git.rustybever.be/vieter-v/vieter#tag=${pkgver//_/-}"
|
||||
"libvieter::git+https://git.rustybever.be/vieter-v/libvieter"
|
||||
)
|
||||
md5sums=('SKIP' 'SKIP')
|
||||
|
||||
pkgver() {
|
||||
cd "$pkgname"
|
||||
prepare() {
|
||||
cd "${pkgname}"
|
||||
|
||||
git describe --long --tags | sed 's/^v//;s/\([^-]*-g\)/r\1/;s/-/./g'
|
||||
# Add the libvieter submodule
|
||||
git submodule init
|
||||
git config submodules.src/libvieter.url "${srcdir}/libvieter"
|
||||
git -c protocol.file.allow=always submodule update
|
||||
|
||||
export VMODULES="${srcdir}/.vmodules"
|
||||
|
||||
cd src && v install
|
||||
}
|
||||
|
||||
build() {
|
||||
export VMODULES="$srcdir/.vmodules"
|
||||
|
||||
cd "$pkgname"
|
||||
|
||||
make prod
|
||||
|
||||
# The default CFLAGS for some reason causes vieter to segfault if used
|
||||
# inside the PKGBUILD. As a workaround, we use tcc to build a debug build
|
||||
# that does work, so we can generate the manpages.
|
||||
CFLAGS= LDFLAGS= make man
|
||||
}
|
||||
|
||||
package() {
|
||||
pkgdesc="Vieter is a lightweight implementation of an Arch repository server."
|
||||
|
||||
install -dm755 "$pkgdir/usr/bin"
|
||||
install -Dm755 "$pkgbase/pvieter" "$pkgdir/usr/bin/vieter"
|
||||
install -Dm755 "$pkgname/pvieter" "$pkgdir/usr/bin/vieter"
|
||||
|
||||
install -dm755 "$pkgdir/usr/share/man/man1"
|
||||
install -Dm644 "$pkgname/man"/*.1 "$pkgdir/usr/share/man/man1"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
# vim: ft=bash
|
||||
# Maintainer: Jef Roosens
|
||||
|
||||
pkgbase='vieter-git'
|
||||
pkgname='vieter-git'
|
||||
pkgver=0.2.0.r25.g20112b8
|
||||
pkgrel=1
|
||||
pkgdesc="Lightweight Arch repository server & package build system (development version)"
|
||||
depends=('glibc' 'openssl' 'libarchive' 'sqlite')
|
||||
makedepends=('git' 'vieter-vlang')
|
||||
arch=('x86_64' 'aarch64')
|
||||
url='https://git.rustybever.be/vieter-v/vieter'
|
||||
license=('AGPL3')
|
||||
source=(
|
||||
"${pkgname}::git+https://git.rustybever.be/vieter-v/vieter#branch=dev"
|
||||
"libvieter::git+https://git.rustybever.be/vieter-v/libvieter"
|
||||
)
|
||||
md5sums=('SKIP' 'SKIP')
|
||||
provides=('vieter')
|
||||
conflicts=('vieter')
|
||||
|
||||
pkgver() {
|
||||
cd "${pkgname}"
|
||||
|
||||
git describe --long --tags | sed 's/^v//;s/\([^-]*-g\)/r\1/;s/-/./g'
|
||||
}
|
||||
|
||||
prepare() {
|
||||
cd "${pkgname}"
|
||||
|
||||
# Add the libvieter submodule
|
||||
git submodule init
|
||||
git config submodules.src/libvieter.url "${srcdir}/libvieter"
|
||||
git -c protocol.file.allow=always submodule update
|
||||
|
||||
export VMODULES="${srcdir}/.vmodules"
|
||||
|
||||
cd src && v install
|
||||
}
|
||||
|
||||
build() {
|
||||
export VMODULES="${srcdir}/.vmodules"
|
||||
|
||||
cd "${pkgname}"
|
||||
|
||||
make prod
|
||||
|
||||
# The default CFLAGS for some reason causes vieter to segfault if used
|
||||
# inside the PKGBUILD. As a workaround, we use tcc to build a debug build
|
||||
# that does work, so we can generate the manpages.
|
||||
CFLAGS= LDFLAGS= make man
|
||||
}
|
||||
|
||||
package() {
|
||||
install -dm755 "${pkgdir}/usr/bin"
|
||||
install -Dm755 "${pkgname}/pvieter" "${pkgdir}/usr/bin/vieter"
|
||||
|
||||
install -dm755 "${pkgdir}/usr/share/man/man1"
|
||||
install -Dm644 "${pkgname}/man"/*.1 "${pkgdir}/usr/share/man/man1"
|
||||
}
|
80
README.md
80
README.md
|
@ -1,11 +1,12 @@
|
|||
# Vieter
|
||||
|
||||
## Documentation
|
||||
|
||||
I host documentation for Vieter over at https://rustybever.be/docs/vieter/. API
|
||||
documentation for the current codebase can be found at
|
||||
https://rustybever.be/api-docs/vieter/.
|
||||
|
||||
For more information, questions or just a chat, there's
|
||||
[#vieter:rustybever.be](https://matrix.to/#/#vieter:rustybever.be) on Matrix!
|
||||
|
||||
## Overview
|
||||
|
||||
Vieter is a restart of the Pieter project. The goal is to create a simple,
|
||||
|
@ -20,14 +21,8 @@ quicker.
|
|||
I chose [V](https://vlang.io/) as I've been very intrigued by this language for
|
||||
a while now. I wanted a fast language that I could code while relaxing, without
|
||||
having to exert too much mental effort & V seemed like the right choice for
|
||||
that.
|
||||
|
||||
### Compiler
|
||||
|
||||
Vieter compiles with the standard Vlang compiler. However, I do maintain a
|
||||
[mirror](https://git.rustybever.be/Chewing_Bever/v). This is to ensure my CI
|
||||
does not break without reason, as I control when & how frequently the mirror is
|
||||
updated to reflect the official repository.
|
||||
that. Sadly, this didn't quite turn out the way I expected, but I'm sticking
|
||||
with it anyways ;p
|
||||
|
||||
## Features
|
||||
|
||||
|
@ -41,17 +36,62 @@ updated to reflect the official repository.
|
|||
|
||||
## Building
|
||||
|
||||
In order to build Vieter, you'll need a couple of libraries:
|
||||
Besides a V installer, Vieter also requires the following libraries to work:
|
||||
|
||||
* An installation of V
|
||||
* gc
|
||||
* libarchive
|
||||
* openssl
|
||||
* sqlite3
|
||||
|
||||
**NOTE**: if you encounter any issues compiling Vieter using the absolute
|
||||
latest version of V, it might be because my mirror is missing a specific commit
|
||||
that causes issues. For this reason, the `make v` command exists which will
|
||||
clone my compiler in the `v` directory & build it. Afterwards, you can use this
|
||||
compiler with make by prepending all make commands with `V_PATH=v/v`. If you do
|
||||
encounter this issue, please let me know so I can update my mirror & the
|
||||
codebase to fix it!
|
||||
Vieter also depends on some external V modules which you can install using `cd
|
||||
src && v install`. Make sure to keep these dependencies up to date using `v
|
||||
update`.
|
||||
|
||||
### Compiler
|
||||
|
||||
V is developed using a specific compiler commit that is usually updated
|
||||
whenever a new version is released. Information on this can be found in the
|
||||
[tools](https://git.rustybever.be/vieter-v/tools) repository.
|
||||
|
||||
## Contributing
|
||||
|
||||
If you wish to contribute to the project, please take note of the following:
|
||||
|
||||
* Rebase instead of merging whenever possible, e.g. when updating your branch
|
||||
with the dev branch.
|
||||
* Please follow the
|
||||
[Conventional Commits](https://www.conventionalcommits.org/) style for your
|
||||
commit messages.
|
||||
|
||||
### Writing documentation
|
||||
|
||||
The `docs` directory contains a Hugo site consisting of all user &
|
||||
administrator documentation. `docs/api` on the other hand is a
|
||||
[Slate](https://github.com/slatedocs/slate) project describing the HTTP web
|
||||
API.
|
||||
|
||||
To modify the Hugo documentation, you'll need to install Hugo. Afterwards, you
|
||||
can use the following commands inside the `docs` directory:
|
||||
|
||||
```sh
|
||||
# Build the documentation
|
||||
hugo
|
||||
|
||||
# Host an auto-refreshing web server with the documentation. Important to note
|
||||
# is that the files will be at `http://localhost:1313/docs/vieter` instead of
|
||||
# just `http://localhost:1313/`
|
||||
hugo server
|
||||
```
|
||||
|
||||
For the Slate docs, I personally just start a docker container:
|
||||
|
||||
```sh
|
||||
docker run \
|
||||
--rm \
|
||||
-p 4567:4567 \
|
||||
--name slate \
|
||||
-v $(pwd)/docs/api/source:/srv/slate/source slatedocs/slate serve
|
||||
```
|
||||
|
||||
This will make the Slate docs available at http://localhost:4567. Sadly, this
|
||||
server doesn't auto-refresh, so you'll have to manually refresh your browser
|
||||
every time you make a change.
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
.git/
|
||||
.github/
|
||||
build/
|
||||
.editorconfig
|
||||
.gitattributes
|
||||
.gitignore
|
||||
CHANGELOG.md
|
||||
CODE_OF_CONDUCT.md
|
||||
deploy.sh
|
||||
font-selection.json
|
||||
README.md
|
||||
Vagrantfile
|
|
@ -0,0 +1,18 @@
|
|||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# Top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.rb]
|
||||
charset = utf-8
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
|
@ -0,0 +1 @@
|
|||
source/javascripts/lib/* linguist-vendored
|
|
@ -0,0 +1,27 @@
|
|||
*.gem
|
||||
*.rbc
|
||||
.bundle
|
||||
.config
|
||||
coverage
|
||||
InstalledFiles
|
||||
lib/bundler/man
|
||||
pkg
|
||||
rdoc
|
||||
spec/reports
|
||||
test/tmp
|
||||
test/version_tmp
|
||||
tmp
|
||||
*.DS_STORE
|
||||
build/
|
||||
.cache
|
||||
.vagrant
|
||||
.sass-cache
|
||||
|
||||
# YARD artifacts
|
||||
.yardoc
|
||||
_yardoc
|
||||
doc/
|
||||
.idea/
|
||||
|
||||
# Vagrant artifacts
|
||||
ubuntu-*-console.log
|
|
@ -0,0 +1,13 @@
|
|||
ruby '>= 2.6'
|
||||
source 'https://rubygems.org'
|
||||
|
||||
# Middleman
|
||||
gem 'middleman', '~> 4.4'
|
||||
gem 'middleman-syntax', '~> 3.2'
|
||||
gem 'middleman-autoprefixer', '~> 3.0'
|
||||
gem 'middleman-sprockets', '~> 4.1'
|
||||
gem 'rouge', '~> 3.21'
|
||||
gem 'redcarpet', '~> 3.5.0'
|
||||
gem 'nokogiri', '~> 1.13.3'
|
||||
gem 'sass'
|
||||
gem 'webrick'
|
|
@ -0,0 +1,145 @@
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
activesupport (6.1.4.1)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
tzinfo (~> 2.0)
|
||||
zeitwerk (~> 2.3)
|
||||
addressable (2.8.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
autoprefixer-rails (10.2.5.0)
|
||||
execjs (< 2.8.0)
|
||||
backports (3.21.0)
|
||||
coffee-script (2.4.1)
|
||||
coffee-script-source
|
||||
execjs
|
||||
coffee-script-source (1.12.2)
|
||||
concurrent-ruby (1.1.9)
|
||||
contracts (0.13.0)
|
||||
dotenv (2.7.6)
|
||||
erubis (2.7.0)
|
||||
execjs (2.7.0)
|
||||
fast_blank (1.0.1)
|
||||
fastimage (2.2.5)
|
||||
ffi (1.15.4)
|
||||
haml (5.2.2)
|
||||
temple (>= 0.8.0)
|
||||
tilt
|
||||
hamster (3.0.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
hashie (3.6.0)
|
||||
i18n (1.6.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
kramdown (2.3.1)
|
||||
rexml
|
||||
listen (3.0.8)
|
||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||
rb-inotify (~> 0.9, >= 0.9.7)
|
||||
memoist (0.16.2)
|
||||
middleman (4.4.2)
|
||||
coffee-script (~> 2.2)
|
||||
haml (>= 4.0.5)
|
||||
kramdown (>= 2.3.0)
|
||||
middleman-cli (= 4.4.2)
|
||||
middleman-core (= 4.4.2)
|
||||
middleman-autoprefixer (3.0.0)
|
||||
autoprefixer-rails (~> 10.0)
|
||||
middleman-core (>= 4.0.0)
|
||||
middleman-cli (4.4.2)
|
||||
thor (>= 0.17.0, < 2.0)
|
||||
middleman-core (4.4.2)
|
||||
activesupport (>= 6.1, < 7.0)
|
||||
addressable (~> 2.4)
|
||||
backports (~> 3.6)
|
||||
bundler (~> 2.0)
|
||||
contracts (~> 0.13.0)
|
||||
dotenv
|
||||
erubis
|
||||
execjs (~> 2.0)
|
||||
fast_blank
|
||||
fastimage (~> 2.0)
|
||||
hamster (~> 3.0)
|
||||
hashie (~> 3.4)
|
||||
i18n (~> 1.6.0)
|
||||
listen (~> 3.0.0)
|
||||
memoist (~> 0.14)
|
||||
padrino-helpers (~> 0.15.0)
|
||||
parallel
|
||||
rack (>= 1.4.5, < 3)
|
||||
sassc (~> 2.0)
|
||||
servolux
|
||||
tilt (~> 2.0.9)
|
||||
toml
|
||||
uglifier (~> 3.0)
|
||||
webrick
|
||||
middleman-sprockets (4.1.1)
|
||||
middleman-core (~> 4.0)
|
||||
sprockets (>= 3.0)
|
||||
middleman-syntax (3.2.0)
|
||||
middleman-core (>= 3.2)
|
||||
rouge (~> 3.2)
|
||||
mini_portile2 (2.8.0)
|
||||
minitest (5.14.4)
|
||||
nokogiri (1.13.4)
|
||||
mini_portile2 (~> 2.8.0)
|
||||
racc (~> 1.4)
|
||||
padrino-helpers (0.15.1)
|
||||
i18n (>= 0.6.7, < 2)
|
||||
padrino-support (= 0.15.1)
|
||||
tilt (>= 1.4.1, < 3)
|
||||
padrino-support (0.15.1)
|
||||
parallel (1.21.0)
|
||||
parslet (2.0.0)
|
||||
public_suffix (4.0.6)
|
||||
racc (1.6.0)
|
||||
rack (2.2.3)
|
||||
rb-fsevent (0.11.0)
|
||||
rb-inotify (0.10.1)
|
||||
ffi (~> 1.0)
|
||||
redcarpet (3.5.1)
|
||||
rexml (3.2.5)
|
||||
rouge (3.28.0)
|
||||
sass (3.7.4)
|
||||
sass-listen (~> 4.0.0)
|
||||
sass-listen (4.0.0)
|
||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||
rb-inotify (~> 0.9, >= 0.9.7)
|
||||
sassc (2.4.0)
|
||||
ffi (~> 1.9)
|
||||
servolux (0.13.0)
|
||||
sprockets (3.7.2)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (> 1, < 3)
|
||||
temple (0.8.2)
|
||||
thor (1.1.0)
|
||||
tilt (2.0.10)
|
||||
toml (0.3.0)
|
||||
parslet (>= 1.8.0, < 3.0.0)
|
||||
tzinfo (2.0.4)
|
||||
concurrent-ruby (~> 1.0)
|
||||
uglifier (3.2.0)
|
||||
execjs (>= 0.3.0, < 3)
|
||||
webrick (1.7.0)
|
||||
zeitwerk (2.5.1)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
middleman (~> 4.4)
|
||||
middleman-autoprefixer (~> 3.0)
|
||||
middleman-sprockets (~> 4.1)
|
||||
middleman-syntax (~> 3.2)
|
||||
nokogiri (~> 1.13.3)
|
||||
redcarpet (~> 3.5.0)
|
||||
rouge (~> 3.21)
|
||||
sass
|
||||
webrick
|
||||
|
||||
RUBY VERSION
|
||||
ruby 2.7.2p137
|
||||
|
||||
BUNDLED WITH
|
||||
2.2.22
|
|
@ -0,0 +1,63 @@
|
|||
# Unique header generation
|
||||
require './lib/unique_head.rb'
|
||||
|
||||
# Markdown
|
||||
set :markdown_engine, :redcarpet
|
||||
set :markdown,
|
||||
fenced_code_blocks: true,
|
||||
smartypants: true,
|
||||
disable_indented_code_blocks: true,
|
||||
prettify: true,
|
||||
strikethrough: true,
|
||||
tables: true,
|
||||
with_toc_data: true,
|
||||
no_intra_emphasis: true,
|
||||
renderer: UniqueHeadCounter
|
||||
|
||||
# Assets
|
||||
set :css_dir, 'stylesheets'
|
||||
set :js_dir, 'javascripts'
|
||||
set :images_dir, 'images'
|
||||
set :fonts_dir, 'fonts'
|
||||
|
||||
# Activate the syntax highlighter
|
||||
activate :syntax
|
||||
ready do
|
||||
require './lib/monokai_sublime_slate.rb'
|
||||
require './lib/multilang.rb'
|
||||
end
|
||||
|
||||
activate :sprockets
|
||||
|
||||
activate :autoprefixer do |config|
|
||||
config.browsers = ['last 2 version', 'Firefox ESR']
|
||||
config.cascade = false
|
||||
config.inline = true
|
||||
end
|
||||
|
||||
# Github pages require relative links
|
||||
activate :relative_assets
|
||||
set :relative_links, true
|
||||
|
||||
# Build Configuration
|
||||
configure :build do
|
||||
# We do want to hash woff and woff2 as there's a bug where woff2 will use
|
||||
# woff asset hash which breaks things. Trying to use a combination of ignore and
|
||||
# rewrite_ignore does not work as it conflicts weirdly with relative_assets. Disabling
|
||||
# the .woff2 extension only does not work as .woff will still activate it so have to
|
||||
# have both. See https://github.com/slatedocs/slate/issues/1171 for more details.
|
||||
activate :asset_hash, :exts => app.config[:asset_extensions] - %w[.woff .woff2]
|
||||
# If you're having trouble with Middleman hanging, commenting
|
||||
# out the following two lines has been known to help
|
||||
activate :minify_css
|
||||
activate :minify_javascript
|
||||
# activate :gzip
|
||||
end
|
||||
|
||||
# Deploy Configuration
|
||||
# If you want Middleman to listen on a different port, you can set that below
|
||||
set :port, 4567
|
||||
|
||||
helpers do
|
||||
require './lib/toc_data.rb'
|
||||
end
|
|
@ -0,0 +1,148 @@
|
|||
{
|
||||
"IcoMoonType": "selection",
|
||||
"icons": [
|
||||
{
|
||||
"icon": {
|
||||
"paths": [
|
||||
"M438.857 73.143q119.429 0 220.286 58.857t159.714 159.714 58.857 220.286-58.857 220.286-159.714 159.714-220.286 58.857-220.286-58.857-159.714-159.714-58.857-220.286 58.857-220.286 159.714-159.714 220.286-58.857zM512 785.714v-108.571q0-8-5.143-13.429t-12.571-5.429h-109.714q-7.429 0-13.143 5.714t-5.714 13.143v108.571q0 7.429 5.714 13.143t13.143 5.714h109.714q7.429 0 12.571-5.429t5.143-13.429zM510.857 589.143l10.286-354.857q0-6.857-5.714-10.286-5.714-4.571-13.714-4.571h-125.714q-8 0-13.714 4.571-5.714 3.429-5.714 10.286l9.714 354.857q0 5.714 5.714 10t13.714 4.286h105.714q8 0 13.429-4.286t6-10z"
|
||||
],
|
||||
"attrs": [],
|
||||
"isMulticolor": false,
|
||||
"tags": [
|
||||
"exclamation-circle"
|
||||
],
|
||||
"defaultCode": 61546,
|
||||
"grid": 14
|
||||
},
|
||||
"attrs": [],
|
||||
"properties": {
|
||||
"id": 100,
|
||||
"order": 4,
|
||||
"prevSize": 28,
|
||||
"code": 58880,
|
||||
"name": "exclamation-sign",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"iconIdx": 0
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
"paths": [
|
||||
"M585.143 786.286v-91.429q0-8-5.143-13.143t-13.143-5.143h-54.857v-292.571q0-8-5.143-13.143t-13.143-5.143h-182.857q-8 0-13.143 5.143t-5.143 13.143v91.429q0 8 5.143 13.143t13.143 5.143h54.857v182.857h-54.857q-8 0-13.143 5.143t-5.143 13.143v91.429q0 8 5.143 13.143t13.143 5.143h256q8 0 13.143-5.143t5.143-13.143zM512 274.286v-91.429q0-8-5.143-13.143t-13.143-5.143h-109.714q-8 0-13.143 5.143t-5.143 13.143v91.429q0 8 5.143 13.143t13.143 5.143h109.714q8 0 13.143-5.143t5.143-13.143zM877.714 512q0 119.429-58.857 220.286t-159.714 159.714-220.286 58.857-220.286-58.857-159.714-159.714-58.857-220.286 58.857-220.286 159.714-159.714 220.286-58.857 220.286 58.857 159.714 159.714 58.857 220.286z"
|
||||
],
|
||||
"attrs": [],
|
||||
"isMulticolor": false,
|
||||
"tags": [
|
||||
"info-circle"
|
||||
],
|
||||
"defaultCode": 61530,
|
||||
"grid": 14
|
||||
},
|
||||
"attrs": [],
|
||||
"properties": {
|
||||
"id": 85,
|
||||
"order": 3,
|
||||
"name": "info-sign",
|
||||
"prevSize": 28,
|
||||
"code": 58882
|
||||
},
|
||||
"setIdx": 0,
|
||||
"iconIdx": 2
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
"paths": [
|
||||
"M733.714 419.429q0-16-10.286-26.286l-52-51.429q-10.857-10.857-25.714-10.857t-25.714 10.857l-233.143 232.571-129.143-129.143q-10.857-10.857-25.714-10.857t-25.714 10.857l-52 51.429q-10.286 10.286-10.286 26.286 0 15.429 10.286 25.714l206.857 206.857q10.857 10.857 25.714 10.857 15.429 0 26.286-10.857l310.286-310.286q10.286-10.286 10.286-25.714zM877.714 512q0 119.429-58.857 220.286t-159.714 159.714-220.286 58.857-220.286-58.857-159.714-159.714-58.857-220.286 58.857-220.286 159.714-159.714 220.286-58.857 220.286 58.857 159.714 159.714 58.857 220.286z"
|
||||
],
|
||||
"attrs": [],
|
||||
"isMulticolor": false,
|
||||
"tags": [
|
||||
"check-circle"
|
||||
],
|
||||
"defaultCode": 61528,
|
||||
"grid": 14
|
||||
},
|
||||
"attrs": [],
|
||||
"properties": {
|
||||
"id": 83,
|
||||
"order": 9,
|
||||
"prevSize": 28,
|
||||
"code": 58886,
|
||||
"name": "ok-sign"
|
||||
},
|
||||
"setIdx": 0,
|
||||
"iconIdx": 6
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
"paths": [
|
||||
"M658.286 475.429q0-105.714-75.143-180.857t-180.857-75.143-180.857 75.143-75.143 180.857 75.143 180.857 180.857 75.143 180.857-75.143 75.143-180.857zM950.857 950.857q0 29.714-21.714 51.429t-51.429 21.714q-30.857 0-51.429-21.714l-196-195.429q-102.286 70.857-228 70.857-81.714 0-156.286-31.714t-128.571-85.714-85.714-128.571-31.714-156.286 31.714-156.286 85.714-128.571 128.571-85.714 156.286-31.714 156.286 31.714 128.571 85.714 85.714 128.571 31.714 156.286q0 125.714-70.857 228l196 196q21.143 21.143 21.143 51.429z"
|
||||
],
|
||||
"width": 951,
|
||||
"attrs": [],
|
||||
"isMulticolor": false,
|
||||
"tags": [
|
||||
"search"
|
||||
],
|
||||
"defaultCode": 61442,
|
||||
"grid": 14
|
||||
},
|
||||
"attrs": [],
|
||||
"properties": {
|
||||
"id": 2,
|
||||
"order": 1,
|
||||
"prevSize": 28,
|
||||
"code": 58887,
|
||||
"name": "icon-search"
|
||||
},
|
||||
"setIdx": 0,
|
||||
"iconIdx": 7
|
||||
}
|
||||
],
|
||||
"height": 1024,
|
||||
"metadata": {
|
||||
"name": "slate",
|
||||
"license": "SIL OFL 1.1"
|
||||
},
|
||||
"preferences": {
|
||||
"showGlyphs": true,
|
||||
"showQuickUse": true,
|
||||
"showQuickUse2": true,
|
||||
"showSVGs": true,
|
||||
"fontPref": {
|
||||
"prefix": "icon-",
|
||||
"metadata": {
|
||||
"fontFamily": "slate",
|
||||
"majorVersion": 1,
|
||||
"minorVersion": 0,
|
||||
"description": "Based on FontAwesome",
|
||||
"license": "SIL OFL 1.1"
|
||||
},
|
||||
"metrics": {
|
||||
"emSize": 1024,
|
||||
"baseline": 6.25,
|
||||
"whitespace": 50
|
||||
},
|
||||
"resetPoint": 58880,
|
||||
"showSelector": false,
|
||||
"selector": "class",
|
||||
"classSelector": ".icon",
|
||||
"showMetrics": false,
|
||||
"showMetadata": true,
|
||||
"showVersion": true,
|
||||
"ie7": false
|
||||
},
|
||||
"imagePref": {
|
||||
"prefix": "icon-",
|
||||
"png": true,
|
||||
"useClassSelector": true,
|
||||
"color": 4473924,
|
||||
"bgColor": 16777215
|
||||
},
|
||||
"historySize": 100,
|
||||
"showCodes": true,
|
||||
"gridSize": 16,
|
||||
"showLiga": false
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
# -*- coding: utf-8 -*- #
|
||||
# frozen_string_literal: true
|
||||
|
||||
# this is based on https://github.com/rouge-ruby/rouge/blob/master/lib/rouge/themes/monokai_sublime.rb
|
||||
# but without the added background, and changed styling for JSON keys to be soft_yellow instead of white
|
||||
|
||||
module Rouge
|
||||
module Themes
|
||||
class MonokaiSublimeSlate < CSSTheme
|
||||
name 'monokai.sublime.slate'
|
||||
|
||||
palette :black => '#000000'
|
||||
palette :bright_green => '#a6e22e'
|
||||
palette :bright_pink => '#f92672'
|
||||
palette :carmine => '#960050'
|
||||
palette :dark => '#49483e'
|
||||
palette :dark_grey => '#888888'
|
||||
palette :dark_red => '#aa0000'
|
||||
palette :dimgrey => '#75715e'
|
||||
palette :emperor => '#555555'
|
||||
palette :grey => '#999999'
|
||||
palette :light_grey => '#aaaaaa'
|
||||
palette :light_violet => '#ae81ff'
|
||||
palette :soft_cyan => '#66d9ef'
|
||||
palette :soft_yellow => '#e6db74'
|
||||
palette :very_dark => '#1e0010'
|
||||
palette :whitish => '#f8f8f2'
|
||||
palette :orange => '#f6aa11'
|
||||
palette :white => '#ffffff'
|
||||
|
||||
style Generic::Heading, :fg => :grey
|
||||
style Literal::String::Regex, :fg => :orange
|
||||
style Generic::Output, :fg => :dark_grey
|
||||
style Generic::Prompt, :fg => :emperor
|
||||
style Generic::Strong, :bold => false
|
||||
style Generic::Subheading, :fg => :light_grey
|
||||
style Name::Builtin, :fg => :orange
|
||||
style Comment::Multiline,
|
||||
Comment::Preproc,
|
||||
Comment::Single,
|
||||
Comment::Special,
|
||||
Comment, :fg => :dimgrey
|
||||
style Error,
|
||||
Generic::Error,
|
||||
Generic::Traceback, :fg => :carmine
|
||||
style Generic::Deleted,
|
||||
Generic::Inserted,
|
||||
Generic::Emph, :fg => :dark
|
||||
style Keyword::Constant,
|
||||
Keyword::Declaration,
|
||||
Keyword::Reserved,
|
||||
Name::Constant,
|
||||
Keyword::Type, :fg => :soft_cyan
|
||||
style Literal::Number::Float,
|
||||
Literal::Number::Hex,
|
||||
Literal::Number::Integer::Long,
|
||||
Literal::Number::Integer,
|
||||
Literal::Number::Oct,
|
||||
Literal::Number,
|
||||
Literal::String::Char,
|
||||
Literal::String::Escape,
|
||||
Literal::String::Symbol, :fg => :light_violet
|
||||
style Literal::String::Doc,
|
||||
Literal::String::Double,
|
||||
Literal::String::Backtick,
|
||||
Literal::String::Heredoc,
|
||||
Literal::String::Interpol,
|
||||
Literal::String::Other,
|
||||
Literal::String::Single,
|
||||
Literal::String, :fg => :soft_yellow
|
||||
style Name::Attribute,
|
||||
Name::Class,
|
||||
Name::Decorator,
|
||||
Name::Exception,
|
||||
Name::Function, :fg => :bright_green
|
||||
style Name::Variable::Class,
|
||||
Name::Namespace,
|
||||
Name::Entity,
|
||||
Name::Builtin::Pseudo,
|
||||
Name::Variable::Global,
|
||||
Name::Variable::Instance,
|
||||
Name::Variable,
|
||||
Text::Whitespace,
|
||||
Text,
|
||||
Name, :fg => :white
|
||||
style Name::Label, :fg => :bright_pink
|
||||
style Operator::Word,
|
||||
Name::Tag,
|
||||
Keyword,
|
||||
Keyword::Namespace,
|
||||
Keyword::Pseudo,
|
||||
Operator, :fg => :bright_pink
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,16 @@
|
|||
module Multilang
|
||||
def block_code(code, full_lang_name)
|
||||
if full_lang_name
|
||||
parts = full_lang_name.split('--')
|
||||
rouge_lang_name = (parts) ? parts[0] : "" # just parts[0] here causes null ref exception when no language specified
|
||||
super(code, rouge_lang_name).sub("highlight #{rouge_lang_name}") do |match|
|
||||
match + " tab-" + full_lang_name
|
||||
end
|
||||
else
|
||||
super(code, full_lang_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require 'middleman-core/renderers/redcarpet'
|
||||
Middleman::Renderers::MiddlemanRedcarpetHTML.send :include, Multilang
|
|
@ -0,0 +1,22 @@
|
|||
# Nested unique header generation
|
||||
require 'middleman-core/renderers/redcarpet'
|
||||
|
||||
class NestingUniqueHeadCounter < Middleman::Renderers::MiddlemanRedcarpetHTML
|
||||
def initialize
|
||||
super
|
||||
@@headers_history = {} if !defined?(@@headers_history)
|
||||
end
|
||||
|
||||
def header(text, header_level)
|
||||
friendly_text = text.gsub(/<[^>]*>/,"").parameterize
|
||||
@@headers_history[header_level] = text.parameterize
|
||||
|
||||
if header_level > 1
|
||||
for i in (header_level - 1).downto(1)
|
||||
friendly_text.prepend("#{@@headers_history[i]}-") if @@headers_history.key?(i)
|
||||
end
|
||||
end
|
||||
|
||||
return "<h#{header_level} id='#{friendly_text}'>#{text}</h#{header_level}>"
|
||||
end
|
||||
end
|
|
@ -0,0 +1,31 @@
|
|||
require 'nokogiri'
|
||||
|
||||
def toc_data(page_content)
|
||||
html_doc = Nokogiri::HTML::DocumentFragment.parse(page_content)
|
||||
|
||||
# get a flat list of headers
|
||||
headers = []
|
||||
html_doc.css('h1, h2, h3').each do |header|
|
||||
headers.push({
|
||||
id: header.attribute('id').to_s,
|
||||
content: header.children,
|
||||
title: header.children.to_s.gsub(/<[^>]*>/, ''),
|
||||
level: header.name[1].to_i,
|
||||
children: []
|
||||
})
|
||||
end
|
||||
|
||||
[3,2].each do |header_level|
|
||||
header_to_nest = nil
|
||||
headers = headers.reject do |header|
|
||||
if header[:level] == header_level
|
||||
header_to_nest[:children].push header if header_to_nest
|
||||
true
|
||||
else
|
||||
header_to_nest = header if header[:level] < header_level
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
headers
|
||||
end
|
|
@ -0,0 +1,24 @@
|
|||
# Unique header generation
|
||||
require 'middleman-core/renderers/redcarpet'
|
||||
require 'digest'
|
||||
class UniqueHeadCounter < Middleman::Renderers::MiddlemanRedcarpetHTML
|
||||
def initialize
|
||||
super
|
||||
@head_count = {}
|
||||
end
|
||||
def header(text, header_level)
|
||||
friendly_text = text.gsub(/<[^>]*>/,"").parameterize
|
||||
if friendly_text.strip.length == 0
|
||||
# Looks like parameterize removed the whole thing! It removes many unicode
|
||||
# characters like Chinese and Russian. To get a unique URL, let's just
|
||||
# URI escape the whole header
|
||||
friendly_text = Digest::SHA1.hexdigest(text)[0,10]
|
||||
end
|
||||
@head_count[friendly_text] ||= 0
|
||||
@head_count[friendly_text] += 1
|
||||
if @head_count[friendly_text] > 1
|
||||
friendly_text += "-#{@head_count[friendly_text]}"
|
||||
end
|
||||
return "<h#{header_level} id='#{friendly_text}'>#{text}</h#{header_level}>"
|
||||
end
|
||||
end
|
|
@ -0,0 +1,248 @@
|
|||
#!/usr/bin/env bash
|
||||
set -o errexit #abort if any command fails
|
||||
|
||||
me=$(basename "$0")
|
||||
|
||||
help_message="\
|
||||
Usage: $me [<options>] <command> [<command-options>]
|
||||
Run commands related to the slate process.
|
||||
|
||||
Commands:
|
||||
|
||||
serve Run the middleman server process, useful for
|
||||
development.
|
||||
build Run the build process.
|
||||
deploy Will build and deploy files to branch. Use
|
||||
--no-build to only deploy.
|
||||
|
||||
Global Options:
|
||||
|
||||
-h, --help Show this help information.
|
||||
-v, --verbose Increase verbosity. Useful for debugging.
|
||||
|
||||
Deploy options:
|
||||
-e, --allow-empty Allow deployment of an empty directory.
|
||||
-m, --message MESSAGE Specify the message used when committing on the
|
||||
deploy branch.
|
||||
-n, --no-hash Don't append the source commit's hash to the deploy
|
||||
commit's message.
|
||||
--no-build Do not build the source files.
|
||||
"
|
||||
|
||||
|
||||
run_serve() {
|
||||
exec bundle exec middleman serve --watcher-force-polling
|
||||
}
|
||||
|
||||
run_build() {
|
||||
bundle exec middleman build --clean
|
||||
}
|
||||
|
||||
parse_args() {
|
||||
# Set args from a local environment file.
|
||||
if [ -e ".env" ]; then
|
||||
source .env
|
||||
fi
|
||||
|
||||
command=
|
||||
|
||||
# Parse arg flags
|
||||
# If something is exposed as an environment variable, set/overwrite it
|
||||
# here. Otherwise, set/overwrite the internal variable instead.
|
||||
while : ; do
|
||||
if [[ $1 = "-h" || $1 = "--help" ]]; then
|
||||
echo "$help_message"
|
||||
exit 0
|
||||
elif [[ $1 = "-v" || $1 = "--verbose" ]]; then
|
||||
verbose=true
|
||||
shift
|
||||
elif [[ $1 = "-e" || $1 = "--allow-empty" ]]; then
|
||||
allow_empty=true
|
||||
shift
|
||||
elif [[ ( $1 = "-m" || $1 = "--message" ) && -n $2 ]]; then
|
||||
commit_message=$2
|
||||
shift 2
|
||||
elif [[ $1 = "-n" || $1 = "--no-hash" ]]; then
|
||||
GIT_DEPLOY_APPEND_HASH=false
|
||||
shift
|
||||
elif [[ $1 = "--no-build" ]]; then
|
||||
no_build=true
|
||||
shift
|
||||
elif [[ $1 = "serve" || $1 = "build" || $1 = "deploy" ]]; then
|
||||
if [ ! -z "${command}" ]; then
|
||||
>&2 echo "You can only specify one command."
|
||||
exit 1
|
||||
fi
|
||||
command=$1
|
||||
shift
|
||||
elif [ -z $1 ]; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "${command}" ]; then
|
||||
>&2 echo "Command not specified."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Set internal option vars from the environment and arg flags. All internal
|
||||
# vars should be declared here, with sane defaults if applicable.
|
||||
|
||||
# Source directory & target branch.
|
||||
deploy_directory=build
|
||||
deploy_branch=gh-pages
|
||||
|
||||
#if no user identity is already set in the current git environment, use this:
|
||||
default_username=${GIT_DEPLOY_USERNAME:-deploy.sh}
|
||||
default_email=${GIT_DEPLOY_EMAIL:-}
|
||||
|
||||
#repository to deploy to. must be readable and writable.
|
||||
repo=origin
|
||||
|
||||
#append commit hash to the end of message by default
|
||||
append_hash=${GIT_DEPLOY_APPEND_HASH:-true}
|
||||
}
|
||||
|
||||
main() {
|
||||
enable_expanded_output
|
||||
|
||||
if ! git diff --exit-code --quiet --cached; then
|
||||
echo Aborting due to uncommitted changes in the index >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
commit_title=`git log -n 1 --format="%s" HEAD`
|
||||
commit_hash=` git log -n 1 --format="%H" HEAD`
|
||||
|
||||
#default commit message uses last title if a custom one is not supplied
|
||||
if [[ -z $commit_message ]]; then
|
||||
commit_message="publish: $commit_title"
|
||||
fi
|
||||
|
||||
#append hash to commit message unless no hash flag was found
|
||||
if [ $append_hash = true ]; then
|
||||
commit_message="$commit_message"$'\n\n'"generated from commit $commit_hash"
|
||||
fi
|
||||
|
||||
previous_branch=`git rev-parse --abbrev-ref HEAD`
|
||||
|
||||
if [ ! -d "$deploy_directory" ]; then
|
||||
echo "Deploy directory '$deploy_directory' does not exist. Aborting." >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
# must use short form of flag in ls for compatibility with macOS and BSD
|
||||
if [[ -z `ls -A "$deploy_directory" 2> /dev/null` && -z $allow_empty ]]; then
|
||||
echo "Deploy directory '$deploy_directory' is empty. Aborting. If you're sure you want to deploy an empty tree, use the --allow-empty / -e flag." >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
if git ls-remote --exit-code $repo "refs/heads/$deploy_branch" ; then
|
||||
# deploy_branch exists in $repo; make sure we have the latest version
|
||||
|
||||
disable_expanded_output
|
||||
git fetch --force $repo $deploy_branch:$deploy_branch
|
||||
enable_expanded_output
|
||||
fi
|
||||
|
||||
# check if deploy_branch exists locally
|
||||
if git show-ref --verify --quiet "refs/heads/$deploy_branch"
|
||||
then incremental_deploy
|
||||
else initial_deploy
|
||||
fi
|
||||
|
||||
restore_head
|
||||
}
|
||||
|
||||
initial_deploy() {
|
||||
git --work-tree "$deploy_directory" checkout --orphan $deploy_branch
|
||||
git --work-tree "$deploy_directory" add --all
|
||||
commit+push
|
||||
}
|
||||
|
||||
incremental_deploy() {
|
||||
#make deploy_branch the current branch
|
||||
git symbolic-ref HEAD refs/heads/$deploy_branch
|
||||
#put the previously committed contents of deploy_branch into the index
|
||||
git --work-tree "$deploy_directory" reset --mixed --quiet
|
||||
git --work-tree "$deploy_directory" add --all
|
||||
|
||||
set +o errexit
|
||||
diff=$(git --work-tree "$deploy_directory" diff --exit-code --quiet HEAD --)$?
|
||||
set -o errexit
|
||||
case $diff in
|
||||
0) echo No changes to files in $deploy_directory. Skipping commit.;;
|
||||
1) commit+push;;
|
||||
*)
|
||||
echo git diff exited with code $diff. Aborting. Staying on branch $deploy_branch so you can debug. To switch back to main, use: git symbolic-ref HEAD refs/heads/main && git reset --mixed >&2
|
||||
return $diff
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
commit+push() {
|
||||
set_user_id
|
||||
git --work-tree "$deploy_directory" commit -m "$commit_message"
|
||||
|
||||
disable_expanded_output
|
||||
#--quiet is important here to avoid outputting the repo URL, which may contain a secret token
|
||||
git push --quiet $repo $deploy_branch
|
||||
enable_expanded_output
|
||||
}
|
||||
|
||||
#echo expanded commands as they are executed (for debugging)
|
||||
enable_expanded_output() {
|
||||
if [ $verbose ]; then
|
||||
set -o xtrace
|
||||
set +o verbose
|
||||
fi
|
||||
}
|
||||
|
||||
#this is used to avoid outputting the repo URL, which may contain a secret token
|
||||
disable_expanded_output() {
|
||||
if [ $verbose ]; then
|
||||
set +o xtrace
|
||||
set -o verbose
|
||||
fi
|
||||
}
|
||||
|
||||
set_user_id() {
|
||||
if [[ -z `git config user.name` ]]; then
|
||||
git config user.name "$default_username"
|
||||
fi
|
||||
if [[ -z `git config user.email` ]]; then
|
||||
git config user.email "$default_email"
|
||||
fi
|
||||
}
|
||||
|
||||
restore_head() {
|
||||
if [[ $previous_branch = "HEAD" ]]; then
|
||||
#we weren't on any branch before, so just set HEAD back to the commit it was on
|
||||
git update-ref --no-deref HEAD $commit_hash $deploy_branch
|
||||
else
|
||||
git symbolic-ref HEAD refs/heads/$previous_branch
|
||||
fi
|
||||
|
||||
git reset --mixed
|
||||
}
|
||||
|
||||
filter() {
|
||||
sed -e "s|$repo|\$repo|g"
|
||||
}
|
||||
|
||||
sanitize() {
|
||||
"$@" 2> >(filter 1>&2) | filter
|
||||
}
|
||||
|
||||
parse_args "$@"
|
||||
|
||||
if [ "${command}" = "serve" ]; then
|
||||
run_serve
|
||||
elif [[ "${command}" = "build" ]]; then
|
||||
run_build
|
||||
elif [[ ${command} = "deploy" ]]; then
|
||||
if [[ ${no_build} != true ]]; then
|
||||
run_build
|
||||
fi
|
||||
main "$@"
|
||||
fi
|
Binary file not shown.
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<metadata>Generated by IcoMoon</metadata>
|
||||
<defs>
|
||||
<font id="slate" horiz-adv-x="1024">
|
||||
<font-face units-per-em="1024" ascent="960" descent="-64" />
|
||||
<missing-glyph horiz-adv-x="1024" />
|
||||
<glyph unicode=" " d="" horiz-adv-x="512" />
|
||||
<glyph unicode="" d="M438.857 877.714q119.429 0 220.286-58.857t159.714-159.714 58.857-220.286-58.857-220.286-159.714-159.714-220.286-58.857-220.286 58.857-159.714 159.714-58.857 220.286 58.857 220.286 159.714 159.714 220.286 58.857zM512 165.143v108.571q0 8-5.143 13.429t-12.571 5.429h-109.714q-7.429 0-13.143-5.714t-5.714-13.143v-108.571q0-7.429 5.714-13.143t13.143-5.714h109.714q7.429 0 12.571 5.429t5.143 13.429zM510.857 361.714l10.286 354.857q0 6.857-5.714 10.286-5.714 4.571-13.714 4.571h-125.714q-8 0-13.714-4.571-5.714-3.429-5.714-10.286l9.714-354.857q0-5.714 5.714-10t13.714-4.286h105.714q8 0 13.429 4.286t6 10z" />
|
||||
<glyph unicode="" d="M585.143 164.571v91.429q0 8-5.143 13.143t-13.143 5.143h-54.857v292.571q0 8-5.143 13.143t-13.143 5.143h-182.857q-8 0-13.143-5.143t-5.143-13.143v-91.429q0-8 5.143-13.143t13.143-5.143h54.857v-182.857h-54.857q-8 0-13.143-5.143t-5.143-13.143v-91.429q0-8 5.143-13.143t13.143-5.143h256q8 0 13.143 5.143t5.143 13.143zM512 676.571v91.429q0 8-5.143 13.143t-13.143 5.143h-109.714q-8 0-13.143-5.143t-5.143-13.143v-91.429q0-8 5.143-13.143t13.143-5.143h109.714q8 0 13.143 5.143t5.143 13.143zM877.714 438.857q0-119.429-58.857-220.286t-159.714-159.714-220.286-58.857-220.286 58.857-159.714 159.714-58.857 220.286 58.857 220.286 159.714 159.714 220.286 58.857 220.286-58.857 159.714-159.714 58.857-220.286z" />
|
||||
<glyph unicode="" d="M733.714 531.428q0 16-10.286 26.286l-52 51.429q-10.857 10.857-25.714 10.857t-25.714-10.857l-233.143-232.571-129.143 129.143q-10.857 10.857-25.714 10.857t-25.714-10.857l-52-51.429q-10.286-10.286-10.286-26.286 0-15.429 10.286-25.714l206.857-206.857q10.857-10.857 25.714-10.857 15.429 0 26.286 10.857l310.286 310.286q10.286 10.286 10.286 25.714zM877.714 438.857q0-119.429-58.857-220.286t-159.714-159.714-220.286-58.857-220.286 58.857-159.714 159.714-58.857 220.286 58.857 220.286 159.714 159.714 220.286 58.857 220.286-58.857 159.714-159.714 58.857-220.286z" />
|
||||
<glyph unicode="" d="M658.286 475.428q0 105.714-75.143 180.857t-180.857 75.143-180.857-75.143-75.143-180.857 75.143-180.857 180.857-75.143 180.857 75.143 75.143 180.857zM950.857 0q0-29.714-21.714-51.429t-51.429-21.714q-30.857 0-51.429 21.714l-196 195.429q-102.286-70.857-228-70.857-81.714 0-156.286 31.714t-128.571 85.714-85.714 128.571-31.714 156.286 31.714 156.286 85.714 128.571 128.571 85.714 156.286 31.714 156.286-31.714 128.571-85.714 85.714-128.571 31.714-156.286q0-125.714-70.857-228l196-196q21.143-21.143 21.143-51.429z" horiz-adv-x="951" />
|
||||
</font></defs></svg>
|
After Width: | Height: | Size: 2.9 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 96 B |
|
@ -0,0 +1,78 @@
|
|||
# Jobs
|
||||
|
||||
<aside class="notice">
|
||||
|
||||
All routes in this section require authentication.
|
||||
|
||||
</aside>
|
||||
|
||||
## Manually schedule a job
|
||||
|
||||
```shell
|
||||
curl \
|
||||
-H 'X-Api-Key: secret' \
|
||||
https://example.com/api/v1/jobs/queue?target=10&force&arch=x86_64
|
||||
```
|
||||
|
||||
Manually schedule a job on the server.
|
||||
|
||||
### HTTP Request
|
||||
|
||||
`POST /api/v1/jobs/queue`
|
||||
|
||||
### Query Parameters
|
||||
|
||||
Parameter | Description
|
||||
--------- | -----------
|
||||
target | Id of target to schedule build for
|
||||
arch | Architecture to build on
|
||||
force | Whether it's a forced build (true if present)
|
||||
|
||||
## Poll for new jobs
|
||||
|
||||
<aside class="warning">
|
||||
|
||||
This endpoint is used by the agents and should not be used manually. It's just
|
||||
here for completeness. Requests to this endpoint modify the build queue,
|
||||
meaning manual requests can cause builds to be skipped.
|
||||
|
||||
</aside>
|
||||
|
||||
```shell
|
||||
curl \
|
||||
-H 'X-Api-Key: secret' \
|
||||
https://example.com/api/v1/jobs/poll?arch=x86_64&max=2
|
||||
```
|
||||
|
||||
> JSON output format
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "",
|
||||
"data": [
|
||||
{
|
||||
"target_id": 1,
|
||||
"kind": "git",
|
||||
"url": "https://aur.archlinux.org/discord-ptb.git",
|
||||
"branch": "master",
|
||||
"path": "",
|
||||
"repo": "bur",
|
||||
"base_image": "archlinux:base-devel",
|
||||
"force": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Poll the server for new builds.
|
||||
|
||||
### HTTP Request
|
||||
|
||||
`GET /api/v1/jobs/poll`
|
||||
|
||||
### Query Parameters
|
||||
|
||||
Parameter | Description
|
||||
--------- | -----------
|
||||
arch | For which architecture to receive jobs
|
||||
max | How many jobs to receive at most
|
|
@ -0,0 +1,172 @@
|
|||
# Build Logs
|
||||
|
||||
<aside class="notice">
|
||||
|
||||
All routes in this section require authentication.
|
||||
|
||||
</aside>
|
||||
|
||||
Endpoints for interacting with stored build logs.
|
||||
|
||||
## List logs
|
||||
|
||||
```shell
|
||||
curl \
|
||||
-H 'X-Api-Key: secret' \
|
||||
https://example.com/api/v1/logs?offset=10&limit=20
|
||||
```
|
||||
|
||||
> JSON output format
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "",
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"target_id": 3,
|
||||
"start_time": 1652008554,
|
||||
"end_time": 1652008559,
|
||||
"arch": "x86_64",
|
||||
"exit_code": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Retrieve a list of build logs.
|
||||
|
||||
### HTTP Request
|
||||
|
||||
`GET /api/v1/logs`
|
||||
|
||||
### Query Parameters
|
||||
|
||||
Parameter | Description
|
||||
--------- | -----------
|
||||
limit | Maximum amount of results to return.
|
||||
offset | Offset of results.
|
||||
target | Only return builds for this target id.
|
||||
before | Only return logs started before this time (UTC epoch)
|
||||
after | Only return logs started after this time (UTC epoch)
|
||||
arch | Only return logs built on this architecture
|
||||
exit_codes | Comma-separated list of exit codes to limit result to; using `!` as a prefix makes it exclude that value. For example, `1,2` only returns logs with status code 1 or 2, while `!1,!2` returns those that don't have 1 or 2 as the result.
|
||||
|
||||
|
||||
## Get build log
|
||||
|
||||
```shell
|
||||
curl \
|
||||
-H 'X-Api-Key: secret' \
|
||||
https://example.com/api/v1/logs/1
|
||||
```
|
||||
|
||||
> JSON output format
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"target_id": 3,
|
||||
"start_time": 1652008554,
|
||||
"end_time": 1652008559,
|
||||
"arch": "x86_64",
|
||||
"exit_code": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Retrieve info about a specific build log.
|
||||
|
||||
### HTTP Request
|
||||
|
||||
`GET /api/v1/logs/:id`
|
||||
|
||||
### URL Parameters
|
||||
|
||||
Parameter | Description
|
||||
--------- | -----------
|
||||
id | ID of requested log
|
||||
|
||||
## Get log contents
|
||||
|
||||
```shell
|
||||
curl \
|
||||
-H 'X-Api-Key: secret' \
|
||||
https://example.com/api/v1/logs/15/content
|
||||
```
|
||||
|
||||
Retrieve the contents of a build log. The response is the build log in
|
||||
plaintext.
|
||||
|
||||
### HTTP Request
|
||||
|
||||
`GET /api/v1/logs/:id/content`
|
||||
|
||||
### URL Parameters
|
||||
|
||||
Parameter | Description
|
||||
--------- | -----------
|
||||
id | ID of requested log
|
||||
|
||||
## Publish build log
|
||||
|
||||
> JSON output format
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "",
|
||||
"data": {
|
||||
"id": 15
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<aside class="warning">
|
||||
|
||||
This endpoint is used by the agents and should not be used manually unless you
|
||||
know what you're doing. It's just here for completeness.
|
||||
|
||||
</aside>
|
||||
|
||||
Publish a new build log to the server.
|
||||
|
||||
### HTTP Request
|
||||
|
||||
`POST /api/v1/logs`
|
||||
|
||||
### Query parameters
|
||||
|
||||
Parameter | Description
|
||||
--------- | -----------
|
||||
startTime | Start time of the build (UTC epoch)
|
||||
endTime | End time of the build (UTC epoch)
|
||||
arch | Architecture on which the build was done
|
||||
exitCode | Exit code of the build container
|
||||
target | id of target this build is for
|
||||
|
||||
### Request body
|
||||
|
||||
Plaintext contents of the build log.
|
||||
|
||||
## Remove a build log
|
||||
|
||||
```shell
|
||||
curl \
|
||||
-XDELETE \
|
||||
-H 'X-Api-Key: secret' \
|
||||
https://example.com/api/v1/logs/1
|
||||
```
|
||||
|
||||
Remove a build log from the server.
|
||||
|
||||
### HTTP Request
|
||||
|
||||
`DELETE /api/v1/logs/:id`
|
||||
|
||||
### URL Parameters
|
||||
|
||||
Parameter | Description
|
||||
--------- | -----------
|
||||
id | id of log to remove
|
|
@ -0,0 +1,179 @@
|
|||
# Repository
|
||||
|
||||
Besides providing a RESTful API, the Vieter server is also a Pacman-compatible
|
||||
repository server. This section describes the various routes that make this
|
||||
possible.
|
||||
|
||||
## Get a package archive or database file
|
||||
|
||||
```shell
|
||||
curl -L https://example.com/bur/x86_64/tuxedo-keyboard-3.0.10-1-x86_64.pkg.tar.zst
|
||||
```
|
||||
|
||||
This endpoint is really the entire repository. It serves both the package
|
||||
archives & the database files for a specific arch-repo. It has three different
|
||||
behaviors, depending on `filename`:
|
||||
|
||||
* If the file extension is one of `.db`, `.files`, `.db.tar.gz` or
|
||||
`.files.tar.gz`, it tries to serve the requested database file.
|
||||
* If the filename contains `.pkg`, it serves the package file.
|
||||
* Otherwise, it assumes `filename` is the name & version of a package inside
|
||||
the repository (e.g. `vieter-0.3.0_alpha.2-1`) & serves that package's `desc`
|
||||
file from inside the database archive.
|
||||
|
||||
<aside class="notice">
|
||||
|
||||
The final option might sound a little strange, but it's used by the build
|
||||
system to determine whether a package needs to be rebuilt.
|
||||
|
||||
</aside>
|
||||
|
||||
### HTTP Request
|
||||
|
||||
`GET /:repo/:arch/:filename`
|
||||
|
||||
### URL Parameters
|
||||
|
||||
Parameter | Description
|
||||
--------- | -----------
|
||||
repo | Repository containing the package
|
||||
arch | Arch-repo containing the package
|
||||
filename | actual filename to request
|
||||
|
||||
## Check whether file exists
|
||||
|
||||
```shell
|
||||
curl -L https://example.com/bur/x86_64/tuxedo-keyboard-3.0.10-1-x86_64.pkg.tar.zst
|
||||
```
|
||||
|
||||
The above request can also be performed as a HEAD request. The behavior is the
|
||||
same, except no data is returned besides an error 404 if the file doesn't exist
|
||||
& an error 200 otherwise.
|
||||
|
||||
### HTTP Request
|
||||
|
||||
`GET /:repo/:arch/:filename`
|
||||
|
||||
### URL Parameters
|
||||
|
||||
Parameter | Description
|
||||
--------- | -----------
|
||||
repo | Repository containing the package
|
||||
arch | Arch-repo containing the package
|
||||
filename | actual filename to request
|
||||
|
||||
## Publish package
|
||||
|
||||
<aside class="notice">
|
||||
|
||||
This endpoint requests authentication.
|
||||
|
||||
</aside>
|
||||
|
||||
```shell
|
||||
curl \
|
||||
-H 'X-Api-Key: secret' \
|
||||
-XPOST \
|
||||
-T tuxedo-keyboard-3.0.10-1-x86_64.pkg.tar.zst \
|
||||
https://example.com/some-repo/publish
|
||||
```
|
||||
|
||||
This endpoint allows you to publish a new package archive to a given repo.
|
||||
|
||||
If the package's architecture is not `any`, it is added to that specific
|
||||
arch-repo. Otherwise, it is added to the configured default architecture & any
|
||||
other already present arch-repos.
|
||||
|
||||
### HTTP Request
|
||||
|
||||
`POST /:repo/publish`
|
||||
|
||||
### URL Parameters
|
||||
|
||||
Parameter | Description
|
||||
--------- | -----------
|
||||
repo | Repository to publish package to
|
||||
|
||||
## Remove package from arch-repo
|
||||
|
||||
<aside class="notice">
|
||||
|
||||
This endpoint requests authentication.
|
||||
|
||||
</aside>
|
||||
|
||||
```shell
|
||||
curl \
|
||||
-H 'X-Api-Key: secret' \
|
||||
-XDELETE \
|
||||
https://example.com/vieter/x86_64/mike
|
||||
```
|
||||
|
||||
This endpoint allows you to remove a package from a given arch-repo.
|
||||
|
||||
### HTTP Request
|
||||
|
||||
`DELETE /:repo/:arch/:pkg`
|
||||
|
||||
### URL Parameters
|
||||
|
||||
Parameter | Description
|
||||
--------- | -----------
|
||||
repo | Repository to delete package from
|
||||
arch | Specific arch-repo to remove package from
|
||||
pkg | Name of package to remove (without any version information)
|
||||
|
||||
## Remove arch-repo
|
||||
|
||||
<aside class="notice">
|
||||
|
||||
This endpoint requests authentication.
|
||||
|
||||
</aside>
|
||||
|
||||
```shell
|
||||
curl \
|
||||
-H 'X-Api-Key: secret' \
|
||||
-XDELETE \
|
||||
https://example.com/vieter/x86_64
|
||||
```
|
||||
|
||||
This endpoint allows removing an entire arch-repo.
|
||||
|
||||
### HTTP Request
|
||||
|
||||
`DELETE /:repo/:arch`
|
||||
|
||||
### URL Parameters
|
||||
|
||||
Parameter | Description
|
||||
--------- | -----------
|
||||
repo | Repository to delete arch-repo from
|
||||
arch | Specific architecture to remove
|
||||
|
||||
## Remove repo
|
||||
|
||||
<aside class="notice">
|
||||
|
||||
This endpoint requests authentication.
|
||||
|
||||
</aside>
|
||||
|
||||
```shell
|
||||
curl \
|
||||
-H 'X-Api-Key: secret' \
|
||||
-XDELETE \
|
||||
https://example.com/vieter
|
||||
```
|
||||
|
||||
This endpoint allows removing an entire repo.
|
||||
|
||||
### HTTP Request
|
||||
|
||||
`DELETE /:repo`
|
||||
|
||||
### URL Parameters
|
||||
|
||||
Parameter | Description
|
||||
--------- | -----------
|
||||
repo | Repository to delete
|
|
@ -0,0 +1,181 @@
|
|||
# Targets
|
||||
|
||||
<aside class="notice">
|
||||
|
||||
All routes in this section require authentication.
|
||||
|
||||
</aside>
|
||||
|
||||
Endpoints for interacting with the list of targets stored on the server.
|
||||
|
||||
## List targets
|
||||
|
||||
```shell
|
||||
curl \
|
||||
-H 'X-Api-Key: secret' \
|
||||
https://example.com/api/v1/targets?offset=10&limit=20
|
||||
```
|
||||
|
||||
> JSON output format
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "",
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"kind": "git",
|
||||
"url": "https://aur.archlinux.org/discord-ptb.git",
|
||||
"branch": "master",
|
||||
"path" : "",
|
||||
"repo": "bur",
|
||||
"schedule": "",
|
||||
"arch": [
|
||||
{
|
||||
"id": 1,
|
||||
"target_id": 1,
|
||||
"value": "x86_64"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Retrieve a list of targets.
|
||||
|
||||
### HTTP Request
|
||||
|
||||
`GET /api/v1/targets`
|
||||
|
||||
### Query Parameters
|
||||
|
||||
Parameter | Description
|
||||
--------- | -----------
|
||||
limit | Maximum amount of results to return.
|
||||
offset | Offset of results.
|
||||
repo | Limit results to targets that publish to the given repo.
|
||||
query | Only return targets that have this substring in their URL, path or branch.
|
||||
arch | Only return targets that publish to this arch.
|
||||
|
||||
## Get specific target
|
||||
|
||||
```shell
|
||||
curl \
|
||||
-H 'X-Api-Key: secret' \
|
||||
https://example.com/api/v1/targets/1
|
||||
```
|
||||
|
||||
> JSON output format
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"kind": "git",
|
||||
"url": "https://aur.archlinux.org/discord-ptb.git",
|
||||
"branch": "master",
|
||||
"path": "",
|
||||
"repo": "bur",
|
||||
"schedule": "0 2",
|
||||
"arch": [
|
||||
{
|
||||
"id": 1,
|
||||
"target_id": 1,
|
||||
"value": "x86_64"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Get info about a specific target.
|
||||
|
||||
### HTTP Request
|
||||
|
||||
`GET /api/v1/targets/:id`
|
||||
|
||||
### URL Parameters
|
||||
|
||||
Parameter | Description
|
||||
--------- | -----------
|
||||
id | id of requested target
|
||||
|
||||
## Create a new target
|
||||
|
||||
> JSON output format
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "",
|
||||
"data": {
|
||||
"id": 15
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Create a new target with the given data.
|
||||
|
||||
### HTTP Request
|
||||
|
||||
`POST /api/v1/targets`
|
||||
|
||||
### Query Parameters
|
||||
|
||||
Parameter | Description
|
||||
--------- | -----------
|
||||
kind | Kind of target to add; one of 'git', 'url'.
|
||||
url | URL of the Git repository.
|
||||
branch | Branch of the Git repository.
|
||||
path | Subdirectory inside Git repository to use.
|
||||
repo | Vieter repository to publish built packages to.
|
||||
schedule | Cron build schedule (syntax explained [here](https://rustybever.be/docs/vieter/usage/builds/schedule/))
|
||||
arch | Comma-separated list of architectures to build package on.
|
||||
|
||||
## Modify a target
|
||||
|
||||
Modify the data of an existing target.
|
||||
|
||||
### HTTP Request
|
||||
|
||||
`PATCH /api/v1/targets/:id`
|
||||
|
||||
### URL Parameters
|
||||
|
||||
Parameter | Description
|
||||
--------- | -----------
|
||||
id | id of target to modify
|
||||
|
||||
### Query Parameters
|
||||
|
||||
Parameter | Description
|
||||
--------- | -----------
|
||||
kind | Kind of target; one of 'git', 'url'.
|
||||
url | URL of the Git repository.
|
||||
branch | Branch of the Git repository.
|
||||
path | Subdirectory inside Git repository to use.
|
||||
repo | Vieter repository to publish built packages to.
|
||||
schedule | Cron build schedule
|
||||
arch | Comma-separated list of architectures to build package on.
|
||||
|
||||
## Remove a target
|
||||
|
||||
```shell
|
||||
curl \
|
||||
-XDELETE \
|
||||
-H 'X-Api-Key: secret' \
|
||||
https://example.com/api/v1/targets/1
|
||||
```
|
||||
|
||||
Remove a target from the server.
|
||||
|
||||
### HTTP Request
|
||||
|
||||
`DELETE /api/v1/targets/:id`
|
||||
|
||||
### URL Parameters
|
||||
|
||||
Parameter | Description
|
||||
--------- | -----------
|
||||
id | id of target to remove
|
|
@ -0,0 +1,40 @@
|
|||
---
|
||||
title: API Reference
|
||||
|
||||
language_tabs: # must be one of https://git.io/vQNgJ
|
||||
- shell: cURL
|
||||
|
||||
toc_footers:
|
||||
- <a href='https://github.com/slatedocs/slate'>Documentation Powered by Slate</a>
|
||||
|
||||
includes:
|
||||
- repository
|
||||
- targets
|
||||
- logs
|
||||
- jobs
|
||||
|
||||
search: true
|
||||
|
||||
code_clipboard: true
|
||||
|
||||
meta:
|
||||
- name: description
|
||||
content: Documentation for the Vieter API
|
||||
---
|
||||
|
||||
# Introduction
|
||||
|
||||
Welcome to the Vieter API documentation! Here, you can find everything related
|
||||
to interacting with Vieter's HTTP API.
|
||||
|
||||
# Authentication
|
||||
|
||||
```shell
|
||||
curl -H 'X-Api-Key: secret' https://example.com/api/some/path
|
||||
```
|
||||
|
||||
> Don't forget to replace `secret` with your Vieter instance's secret.
|
||||
|
||||
Authentication is done by passing the HTTP header `X-Api-Key: secret` along
|
||||
with each request, where `secret` is replaced with your Vieter server's
|
||||
configured secret.
|
|
@ -0,0 +1,2 @@
|
|||
//= require ./all_nosearch
|
||||
//= require ./app/_search
|
|
@ -0,0 +1,27 @@
|
|||
//= require ./lib/_energize
|
||||
//= require ./app/_copy
|
||||
//= require ./app/_toc
|
||||
//= require ./app/_lang
|
||||
|
||||
function adjustLanguageSelectorWidth() {
|
||||
const elem = $('.dark-box > .lang-selector');
|
||||
elem.width(elem.parent().width());
|
||||
}
|
||||
|
||||
$(function() {
|
||||
loadToc($('#toc'), '.toc-link', '.toc-list-h2', 10);
|
||||
setupLanguages($('body').data('languages'));
|
||||
$('.content').imagesLoaded( function() {
|
||||
window.recacheHeights();
|
||||
window.refreshToc();
|
||||
});
|
||||
|
||||
$(window).resize(function() {
|
||||
adjustLanguageSelectorWidth();
|
||||
});
|
||||
adjustLanguageSelectorWidth();
|
||||
});
|
||||
|
||||
window.onpopstate = function() {
|
||||
activateLanguage(getLanguageFromQueryString());
|
||||
};
|
|
@ -0,0 +1,15 @@
|
|||
function copyToClipboard(container) {
|
||||
const el = document.createElement('textarea');
|
||||
el.value = container.textContent.replace(/\n$/, '');
|
||||
document.body.appendChild(el);
|
||||
el.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(el);
|
||||
}
|
||||
|
||||
function setupCodeCopy() {
|
||||
$('pre.highlight').prepend('<div class="copy-clipboard"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>Copy to Clipboard</title><path d="M18 6v-6h-18v18h6v6h18v-18h-6zm-12 10h-4v-14h14v4h-10v10zm16 6h-14v-14h14v14z"></path></svg></div>');
|
||||
$('.copy-clipboard').on('click', function() {
|
||||
copyToClipboard(this.parentNode.children[1]);
|
||||
});
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
//= require ../lib/_jquery
|
||||
|
||||
/*
|
||||
Copyright 2008-2013 Concur Technologies, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
not use this file except in compliance with the License. You may obtain
|
||||
a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
License for the specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
;(function () {
|
||||
'use strict';
|
||||
|
||||
var languages = [];
|
||||
|
||||
window.setupLanguages = setupLanguages;
|
||||
window.activateLanguage = activateLanguage;
|
||||
window.getLanguageFromQueryString = getLanguageFromQueryString;
|
||||
|
||||
function activateLanguage(language) {
|
||||
if (!language) return;
|
||||
if (language === "") return;
|
||||
|
||||
$(".lang-selector a").removeClass('active');
|
||||
$(".lang-selector a[data-language-name='" + language + "']").addClass('active');
|
||||
for (var i=0; i < languages.length; i++) {
|
||||
$(".highlight.tab-" + languages[i]).hide();
|
||||
$(".lang-specific." + languages[i]).hide();
|
||||
}
|
||||
$(".highlight.tab-" + language).show();
|
||||
$(".lang-specific." + language).show();
|
||||
|
||||
window.recacheHeights();
|
||||
|
||||
// scroll to the new location of the position
|
||||
if ($(window.location.hash).get(0)) {
|
||||
$(window.location.hash).get(0).scrollIntoView(true);
|
||||
}
|
||||
}
|
||||
|
||||
// parseURL and stringifyURL are from https://github.com/sindresorhus/query-string
|
||||
// MIT licensed
|
||||
// https://github.com/sindresorhus/query-string/blob/7bee64c16f2da1a326579e96977b9227bf6da9e6/license
|
||||
function parseURL(str) {
|
||||
if (typeof str !== 'string') {
|
||||
return {};
|
||||
}
|
||||
|
||||
str = str.trim().replace(/^(\?|#|&)/, '');
|
||||
|
||||
if (!str) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return str.split('&').reduce(function (ret, param) {
|
||||
var parts = param.replace(/\+/g, ' ').split('=');
|
||||
var key = parts[0];
|
||||
var val = parts[1];
|
||||
|
||||
key = decodeURIComponent(key);
|
||||
// missing `=` should be `null`:
|
||||
// http://w3.org/TR/2012/WD-url-20120524/#collect-url-parameters
|
||||
val = val === undefined ? null : decodeURIComponent(val);
|
||||
|
||||
if (!ret.hasOwnProperty(key)) {
|
||||
ret[key] = val;
|
||||
} else if (Array.isArray(ret[key])) {
|
||||
ret[key].push(val);
|
||||
} else {
|
||||
ret[key] = [ret[key], val];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}, {});
|
||||
};
|
||||
|
||||
function stringifyURL(obj) {
|
||||
return obj ? Object.keys(obj).sort().map(function (key) {
|
||||
var val = obj[key];
|
||||
|
||||
if (Array.isArray(val)) {
|
||||
return val.sort().map(function (val2) {
|
||||
return encodeURIComponent(key) + '=' + encodeURIComponent(val2);
|
||||
}).join('&');
|
||||
}
|
||||
|
||||
return encodeURIComponent(key) + '=' + encodeURIComponent(val);
|
||||
}).join('&') : '';
|
||||
};
|
||||
|
||||
// gets the language set in the query string
|
||||
function getLanguageFromQueryString() {
|
||||
if (location.search.length >= 1) {
|
||||
var language = parseURL(location.search).language;
|
||||
if (language) {
|
||||
return language;
|
||||
} else if (jQuery.inArray(location.search.substr(1), languages) != -1) {
|
||||
return location.search.substr(1);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// returns a new query string with the new language in it
|
||||
function generateNewQueryString(language) {
|
||||
var url = parseURL(location.search);
|
||||
if (url.language) {
|
||||
url.language = language;
|
||||
return stringifyURL(url);
|
||||
}
|
||||
return language;
|
||||
}
|
||||
|
||||
// if a button is clicked, add the state to the history
|
||||
function pushURL(language) {
|
||||
if (!history) { return; }
|
||||
var hash = window.location.hash;
|
||||
if (hash) {
|
||||
hash = hash.replace(/^#+/, '');
|
||||
}
|
||||
history.pushState({}, '', '?' + generateNewQueryString(language) + '#' + hash);
|
||||
|
||||
// save language as next default
|
||||
if (localStorage) {
|
||||
localStorage.setItem("language", language);
|
||||
}
|
||||
}
|
||||
|
||||
function setupLanguages(l) {
|
||||
var defaultLanguage = null;
|
||||
if (localStorage) {
|
||||
defaultLanguage = localStorage.getItem("language");
|
||||
}
|
||||
|
||||
languages = l;
|
||||
|
||||
var presetLanguage = getLanguageFromQueryString();
|
||||
if (presetLanguage) {
|
||||
// the language is in the URL, so use that language!
|
||||
activateLanguage(presetLanguage);
|
||||
|
||||
if (localStorage) {
|
||||
localStorage.setItem("language", presetLanguage);
|
||||
}
|
||||
} else if ((defaultLanguage !== null) && (jQuery.inArray(defaultLanguage, languages) != -1)) {
|
||||
// the language was the last selected one saved in localstorage, so use that language!
|
||||
activateLanguage(defaultLanguage);
|
||||
} else {
|
||||
// no language selected, so use the default
|
||||
activateLanguage(languages[0]);
|
||||
}
|
||||
}
|
||||
|
||||
// if we click on a language tab, activate that language
|
||||
$(function() {
|
||||
$(".lang-selector a").on("click", function() {
|
||||
var language = $(this).data("language-name");
|
||||
pushURL(language);
|
||||
activateLanguage(language);
|
||||
return false;
|
||||
});
|
||||
});
|
||||
})();
|
|
@ -0,0 +1,102 @@
|
|||
//= require ../lib/_lunr
|
||||
//= require ../lib/_jquery
|
||||
//= require ../lib/_jquery.highlight
|
||||
;(function () {
|
||||
'use strict';
|
||||
|
||||
var content, searchResults;
|
||||
var highlightOpts = { element: 'span', className: 'search-highlight' };
|
||||
var searchDelay = 0;
|
||||
var timeoutHandle = 0;
|
||||
var index;
|
||||
|
||||
function populate() {
|
||||
index = lunr(function(){
|
||||
|
||||
this.ref('id');
|
||||
this.field('title', { boost: 10 });
|
||||
this.field('body');
|
||||
this.pipeline.add(lunr.trimmer, lunr.stopWordFilter);
|
||||
var lunrConfig = this;
|
||||
|
||||
$('h1, h2').each(function() {
|
||||
var title = $(this);
|
||||
var body = title.nextUntil('h1, h2');
|
||||
lunrConfig.add({
|
||||
id: title.prop('id'),
|
||||
title: title.text(),
|
||||
body: body.text()
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
determineSearchDelay();
|
||||
}
|
||||
|
||||
$(populate);
|
||||
$(bind);
|
||||
|
||||
function determineSearchDelay() {
|
||||
if (index.tokenSet.toArray().length>5000) {
|
||||
searchDelay = 300;
|
||||
}
|
||||
}
|
||||
|
||||
function bind() {
|
||||
content = $('.content');
|
||||
searchResults = $('.search-results');
|
||||
|
||||
$('#input-search').on('keyup',function(e) {
|
||||
var wait = function() {
|
||||
return function(executingFunction, waitTime){
|
||||
clearTimeout(timeoutHandle);
|
||||
timeoutHandle = setTimeout(executingFunction, waitTime);
|
||||
};
|
||||
}();
|
||||
wait(function(){
|
||||
search(e);
|
||||
}, searchDelay);
|
||||
});
|
||||
}
|
||||
|
||||
function search(event) {
|
||||
|
||||
var searchInput = $('#input-search')[0];
|
||||
|
||||
unhighlight();
|
||||
searchResults.addClass('visible');
|
||||
|
||||
// ESC clears the field
|
||||
if (event.keyCode === 27) searchInput.value = '';
|
||||
|
||||
if (searchInput.value) {
|
||||
var results = index.search(searchInput.value).filter(function(r) {
|
||||
return r.score > 0.0001;
|
||||
});
|
||||
|
||||
if (results.length) {
|
||||
searchResults.empty();
|
||||
$.each(results, function (index, result) {
|
||||
var elem = document.getElementById(result.ref);
|
||||
searchResults.append("<li><a href='#" + result.ref + "'>" + $(elem).text() + "</a></li>");
|
||||
});
|
||||
highlight.call(searchInput);
|
||||
} else {
|
||||
searchResults.html('<li></li>');
|
||||
$('.search-results li').text('No Results Found for "' + searchInput.value + '"');
|
||||
}
|
||||
} else {
|
||||
unhighlight();
|
||||
searchResults.removeClass('visible');
|
||||
}
|
||||
}
|
||||
|
||||
function highlight() {
|
||||
if (this.value) content.highlight(this.value, highlightOpts);
|
||||
}
|
||||
|
||||
function unhighlight() {
|
||||
content.unhighlight(highlightOpts);
|
||||
}
|
||||
})();
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
//= require ../lib/_jquery
|
||||
//= require ../lib/_imagesloaded.min
|
||||
;(function () {
|
||||
'use strict';
|
||||
|
||||
var htmlPattern = /<[^>]*>/g;
|
||||
var loaded = false;
|
||||
|
||||
var debounce = function(func, waitTime) {
|
||||
var timeout = false;
|
||||
return function() {
|
||||
if (timeout === false) {
|
||||
setTimeout(function() {
|
||||
func();
|
||||
timeout = false;
|
||||
}, waitTime);
|
||||
timeout = true;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var closeToc = function() {
|
||||
$(".toc-wrapper").removeClass('open');
|
||||
$("#nav-button").removeClass('open');
|
||||
};
|
||||
|
||||
function loadToc($toc, tocLinkSelector, tocListSelector, scrollOffset) {
|
||||
var headerHeights = {};
|
||||
var pageHeight = 0;
|
||||
var windowHeight = 0;
|
||||
var originalTitle = document.title;
|
||||
|
||||
var recacheHeights = function() {
|
||||
headerHeights = {};
|
||||
pageHeight = $(document).height();
|
||||
windowHeight = $(window).height();
|
||||
|
||||
$toc.find(tocLinkSelector).each(function() {
|
||||
var targetId = $(this).attr('href');
|
||||
if (targetId[0] === "#") {
|
||||
headerHeights[targetId] = $("#" + $.escapeSelector(targetId.substring(1))).offset().top;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var refreshToc = function() {
|
||||
var currentTop = $(document).scrollTop() + scrollOffset;
|
||||
|
||||
if (currentTop + windowHeight >= pageHeight) {
|
||||
// at bottom of page, so just select last header by making currentTop very large
|
||||
// this fixes the problem where the last header won't ever show as active if its content
|
||||
// is shorter than the window height
|
||||
currentTop = pageHeight + 1000;
|
||||
}
|
||||
|
||||
var best = null;
|
||||
for (var name in headerHeights) {
|
||||
if ((headerHeights[name] < currentTop && headerHeights[name] > headerHeights[best]) || best === null) {
|
||||
best = name;
|
||||
}
|
||||
}
|
||||
|
||||
// Catch the initial load case
|
||||
if (currentTop == scrollOffset && !loaded) {
|
||||
best = window.location.hash;
|
||||
loaded = true;
|
||||
}
|
||||
|
||||
var $best = $toc.find("[href='" + best + "']").first();
|
||||
if (!$best.hasClass("active")) {
|
||||
// .active is applied to the ToC link we're currently on, and its parent <ul>s selected by tocListSelector
|
||||
// .active-expanded is applied to the ToC links that are parents of this one
|
||||
$toc.find(".active").removeClass("active");
|
||||
$toc.find(".active-parent").removeClass("active-parent");
|
||||
$best.addClass("active");
|
||||
$best.parents(tocListSelector).addClass("active").siblings(tocLinkSelector).addClass('active-parent');
|
||||
$best.siblings(tocListSelector).addClass("active");
|
||||
$toc.find(tocListSelector).filter(":not(.active)").slideUp(150);
|
||||
$toc.find(tocListSelector).filter(".active").slideDown(150);
|
||||
if (window.history.replaceState) {
|
||||
window.history.replaceState(null, "", best);
|
||||
}
|
||||
var thisTitle = $best.data("title");
|
||||
if (thisTitle !== undefined && thisTitle.length > 0) {
|
||||
document.title = thisTitle.replace(htmlPattern, "") + " – " + originalTitle;
|
||||
} else {
|
||||
document.title = originalTitle;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var makeToc = function() {
|
||||
recacheHeights();
|
||||
refreshToc();
|
||||
|
||||
$("#nav-button").click(function() {
|
||||
$(".toc-wrapper").toggleClass('open');
|
||||
$("#nav-button").toggleClass('open');
|
||||
return false;
|
||||
});
|
||||
$(".page-wrapper").click(closeToc);
|
||||
$(".toc-link").click(closeToc);
|
||||
|
||||
// reload immediately after scrolling on toc click
|
||||
$toc.find(tocLinkSelector).click(function() {
|
||||
setTimeout(function() {
|
||||
refreshToc();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
$(window).scroll(debounce(refreshToc, 200));
|
||||
$(window).resize(debounce(recacheHeights, 200));
|
||||
};
|
||||
|
||||
makeToc();
|
||||
|
||||
window.recacheHeights = recacheHeights;
|
||||
window.refreshToc = refreshToc;
|
||||
}
|
||||
|
||||
window.loadToc = loadToc;
|
||||
})();
|
|
@ -0,0 +1,169 @@
|
|||
/**
|
||||
* energize.js v0.1.0
|
||||
*
|
||||
* Speeds up click events on mobile devices.
|
||||
* https://github.com/davidcalhoun/energize.js
|
||||
*/
|
||||
|
||||
(function() { // Sandbox
|
||||
/**
|
||||
* Don't add to non-touch devices, which don't need to be sped up
|
||||
*/
|
||||
if(!('ontouchstart' in window)) return;
|
||||
|
||||
var lastClick = {},
|
||||
isThresholdReached, touchstart, touchmove, touchend,
|
||||
click, closest;
|
||||
|
||||
/**
|
||||
* isThresholdReached
|
||||
*
|
||||
* Compare touchstart with touchend xy coordinates,
|
||||
* and only fire simulated click event if the coordinates
|
||||
* are nearby. (don't want clicking to be confused with a swipe)
|
||||
*/
|
||||
isThresholdReached = function(startXY, xy) {
|
||||
return Math.abs(startXY[0] - xy[0]) > 5 || Math.abs(startXY[1] - xy[1]) > 5;
|
||||
};
|
||||
|
||||
/**
|
||||
* touchstart
|
||||
*
|
||||
* Save xy coordinates when the user starts touching the screen
|
||||
*/
|
||||
touchstart = function(e) {
|
||||
this.startXY = [e.touches[0].clientX, e.touches[0].clientY];
|
||||
this.threshold = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* touchmove
|
||||
*
|
||||
* Check if the user is scrolling past the threshold.
|
||||
* Have to check here because touchend will not always fire
|
||||
* on some tested devices (Kindle Fire?)
|
||||
*/
|
||||
touchmove = function(e) {
|
||||
// NOOP if the threshold has already been reached
|
||||
if(this.threshold) return false;
|
||||
|
||||
this.threshold = isThresholdReached(this.startXY, [e.touches[0].clientX, e.touches[0].clientY]);
|
||||
};
|
||||
|
||||
/**
|
||||
* touchend
|
||||
*
|
||||
* If the user didn't scroll past the threshold between
|
||||
* touchstart and touchend, fire a simulated click.
|
||||
*
|
||||
* (This will fire before a native click)
|
||||
*/
|
||||
touchend = function(e) {
|
||||
// Don't fire a click if the user scrolled past the threshold
|
||||
if(this.threshold || isThresholdReached(this.startXY, [e.changedTouches[0].clientX, e.changedTouches[0].clientY])) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and fire a click event on the target element
|
||||
* https://developer.mozilla.org/en/DOM/event.initMouseEvent
|
||||
*/
|
||||
var touch = e.changedTouches[0],
|
||||
evt = document.createEvent('MouseEvents');
|
||||
evt.initMouseEvent('click', true, true, window, 0, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);
|
||||
evt.simulated = true; // distinguish from a normal (nonsimulated) click
|
||||
e.target.dispatchEvent(evt);
|
||||
};
|
||||
|
||||
/**
|
||||
* click
|
||||
*
|
||||
* Because we've already fired a click event in touchend,
|
||||
* we need to listed for all native click events here
|
||||
* and suppress them as necessary.
|
||||
*/
|
||||
click = function(e) {
|
||||
/**
|
||||
* Prevent ghost clicks by only allowing clicks we created
|
||||
* in the click event we fired (look for e.simulated)
|
||||
*/
|
||||
var time = Date.now(),
|
||||
timeDiff = time - lastClick.time,
|
||||
x = e.clientX,
|
||||
y = e.clientY,
|
||||
xyDiff = [Math.abs(lastClick.x - x), Math.abs(lastClick.y - y)],
|
||||
target = closest(e.target, 'A') || e.target, // needed for standalone apps
|
||||
nodeName = target.nodeName,
|
||||
isLink = nodeName === 'A',
|
||||
standAlone = window.navigator.standalone && isLink && e.target.getAttribute("href");
|
||||
|
||||
lastClick.time = time;
|
||||
lastClick.x = x;
|
||||
lastClick.y = y;
|
||||
|
||||
/**
|
||||
* Unfortunately Android sometimes fires click events without touch events (seen on Kindle Fire),
|
||||
* so we have to add more logic to determine the time of the last click. Not perfect...
|
||||
*
|
||||
* Older, simpler check: if((!e.simulated) || standAlone)
|
||||
*/
|
||||
if((!e.simulated && (timeDiff < 500 || (timeDiff < 1500 && xyDiff[0] < 50 && xyDiff[1] < 50))) || standAlone) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if(!standAlone) return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Special logic for standalone web apps
|
||||
* See http://stackoverflow.com/questions/2898740/iphone-safari-web-app-opens-links-in-new-window
|
||||
*/
|
||||
if(standAlone) {
|
||||
window.location = target.getAttribute("href");
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an energize-focus class to the targeted link (mimics :focus behavior)
|
||||
* TODO: test and/or remove? Does this work?
|
||||
*/
|
||||
if(!target || !target.classList) return;
|
||||
target.classList.add("energize-focus");
|
||||
window.setTimeout(function(){
|
||||
target.classList.remove("energize-focus");
|
||||
}, 150);
|
||||
};
|
||||
|
||||
/**
|
||||
* closest
|
||||
* @param {HTMLElement} node current node to start searching from.
|
||||
* @param {string} tagName the (uppercase) name of the tag you're looking for.
|
||||
*
|
||||
* Find the closest ancestor tag of a given node.
|
||||
*
|
||||
* Starts at node and goes up the DOM tree looking for a
|
||||
* matching nodeName, continuing until hitting document.body
|
||||
*/
|
||||
closest = function(node, tagName){
|
||||
var curNode = node;
|
||||
|
||||
while(curNode !== document.body) { // go up the dom until we find the tag we're after
|
||||
if(!curNode || curNode.nodeName === tagName) { return curNode; } // found
|
||||
curNode = curNode.parentNode; // not found, so keep going up
|
||||
}
|
||||
|
||||
return null; // not found
|
||||
};
|
||||
|
||||
/**
|
||||
* Add all delegated event listeners
|
||||
*
|
||||
* All the events we care about bubble up to document,
|
||||
* so we can take advantage of event delegation.
|
||||
*
|
||||
* Note: no need to wait for DOMContentLoaded here
|
||||
*/
|
||||
document.addEventListener('touchstart', touchstart, false);
|
||||
document.addEventListener('touchmove', touchmove, false);
|
||||
document.addEventListener('touchend', touchend, false);
|
||||
document.addEventListener('click', click, true); // TODO: why does this use capture?
|
||||
|
||||
})();
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* jQuery Highlight plugin
|
||||
*
|
||||
* Based on highlight v3 by Johann Burkard
|
||||
* http://johannburkard.de/blog/programming/javascript/highlight-javascript-text-higlighting-jquery-plugin.html
|
||||
*
|
||||
* Code a little bit refactored and cleaned (in my humble opinion).
|
||||
* Most important changes:
|
||||
* - has an option to highlight only entire words (wordsOnly - false by default),
|
||||
* - has an option to be case sensitive (caseSensitive - false by default)
|
||||
* - highlight element tag and class names can be specified in options
|
||||
*
|
||||
* Usage:
|
||||
* // wrap every occurrance of text 'lorem' in content
|
||||
* // with <span class='highlight'> (default options)
|
||||
* $('#content').highlight('lorem');
|
||||
*
|
||||
* // search for and highlight more terms at once
|
||||
* // so you can save some time on traversing DOM
|
||||
* $('#content').highlight(['lorem', 'ipsum']);
|
||||
* $('#content').highlight('lorem ipsum');
|
||||
*
|
||||
* // search only for entire word 'lorem'
|
||||
* $('#content').highlight('lorem', { wordsOnly: true });
|
||||
*
|
||||
* // don't ignore case during search of term 'lorem'
|
||||
* $('#content').highlight('lorem', { caseSensitive: true });
|
||||
*
|
||||
* // wrap every occurrance of term 'ipsum' in content
|
||||
* // with <em class='important'>
|
||||
* $('#content').highlight('ipsum', { element: 'em', className: 'important' });
|
||||
*
|
||||
* // remove default highlight
|
||||
* $('#content').unhighlight();
|
||||
*
|
||||
* // remove custom highlight
|
||||
* $('#content').unhighlight({ element: 'em', className: 'important' });
|
||||
*
|
||||
*
|
||||
* Copyright (c) 2009 Bartek Szopka
|
||||
*
|
||||
* Licensed under MIT license.
|
||||
*
|
||||
*/
|
||||
|
||||
jQuery.extend({
|
||||
highlight: function (node, re, nodeName, className) {
|
||||
if (node.nodeType === 3) {
|
||||
var match = node.data.match(re);
|
||||
if (match) {
|
||||
var highlight = document.createElement(nodeName || 'span');
|
||||
highlight.className = className || 'highlight';
|
||||
var wordNode = node.splitText(match.index);
|
||||
wordNode.splitText(match[0].length);
|
||||
var wordClone = wordNode.cloneNode(true);
|
||||
highlight.appendChild(wordClone);
|
||||
wordNode.parentNode.replaceChild(highlight, wordNode);
|
||||
return 1; //skip added node in parent
|
||||
}
|
||||
} else if ((node.nodeType === 1 && node.childNodes) && // only element nodes that have children
|
||||
!/(script|style)/i.test(node.tagName) && // ignore script and style nodes
|
||||
!(node.tagName === nodeName.toUpperCase() && node.className === className)) { // skip if already highlighted
|
||||
for (var i = 0; i < node.childNodes.length; i++) {
|
||||
i += jQuery.highlight(node.childNodes[i], re, nodeName, className);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
jQuery.fn.unhighlight = function (options) {
|
||||
var settings = { className: 'highlight', element: 'span' };
|
||||
jQuery.extend(settings, options);
|
||||
|
||||
return this.find(settings.element + "." + settings.className).each(function () {
|
||||
var parent = this.parentNode;
|
||||
parent.replaceChild(this.firstChild, this);
|
||||
parent.normalize();
|
||||
}).end();
|
||||
};
|
||||
|
||||
jQuery.fn.highlight = function (words, options) {
|
||||
var settings = { className: 'highlight', element: 'span', caseSensitive: false, wordsOnly: false };
|
||||
jQuery.extend(settings, options);
|
||||
|
||||
if (words.constructor === String) {
|
||||
words = [words];
|
||||
}
|
||||
words = jQuery.grep(words, function(word, i){
|
||||
return word != '';
|
||||
});
|
||||
words = jQuery.map(words, function(word, i) {
|
||||
return word.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
||||
});
|
||||
if (words.length == 0) { return this; };
|
||||
|
||||
var flag = settings.caseSensitive ? "" : "i";
|
||||
var pattern = "(" + words.join("|") + ")";
|
||||
if (settings.wordsOnly) {
|
||||
pattern = "\\b" + pattern + "\\b";
|
||||
}
|
||||
var re = new RegExp(pattern, flag);
|
||||
|
||||
return this.each(function () {
|
||||
jQuery.highlight(this, re, settings.element, settings.className);
|
||||
});
|
||||
};
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,137 @@
|
|||
<%#
|
||||
Copyright 2008-2013 Concur Technologies, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
not use this file except in compliance with the License. You may obtain
|
||||
a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
License for the specific language governing permissions and limitations
|
||||
under the License.
|
||||
%>
|
||||
<% language_tabs = current_page.data.language_tabs || [] %>
|
||||
<% page_content = yield %>
|
||||
<%
|
||||
if current_page.data.includes
|
||||
current_page.data.includes.each do |include|
|
||||
page_content += partial("includes/#{include}")
|
||||
end
|
||||
end
|
||||
%>
|
||||
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<% if current_page.data.key?('meta') %>
|
||||
<% current_page.data.meta.each do |meta| %>
|
||||
<meta
|
||||
<% meta.each do |key, value| %>
|
||||
<%= "#{key}=\"#{value}\"" %>
|
||||
<% end %>
|
||||
>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<title><%= current_page.data.title || "API Documentation" %></title>
|
||||
|
||||
<style media="screen">
|
||||
<%= Rouge::Themes::MonokaiSublimeSlate.render(:scope => '.highlight') %>
|
||||
</style>
|
||||
<style media="print">
|
||||
* {
|
||||
transition:none!important;
|
||||
}
|
||||
<%= Rouge::Themes::Base16::Solarized.render(:scope => '.highlight') %>
|
||||
</style>
|
||||
<%= stylesheet_link_tag :screen, media: :screen %>
|
||||
<%= stylesheet_link_tag :print, media: :print %>
|
||||
<% if current_page.data.search %>
|
||||
<%= javascript_include_tag "all" %>
|
||||
<% else %>
|
||||
<%= javascript_include_tag "all_nosearch" %>
|
||||
<% end %>
|
||||
|
||||
<% if current_page.data.code_clipboard %>
|
||||
<script>
|
||||
$(function() { setupCodeCopy(); });
|
||||
</script>
|
||||
<% end %>
|
||||
</head>
|
||||
|
||||
<body class="<%= page_classes %>" data-languages="<%=h language_tabs.map{ |lang| lang.is_a?(Hash) ? lang.keys.first : lang }.to_json %>">
|
||||
<a href="#" id="nav-button">
|
||||
<span>
|
||||
NAV
|
||||
<%= image_tag('navbar.png') %>
|
||||
</span>
|
||||
</a>
|
||||
<div class="toc-wrapper">
|
||||
<%= image_tag "logo.png", class: 'logo' %>
|
||||
<% if language_tabs.any? %>
|
||||
<div class="lang-selector">
|
||||
<% language_tabs.each do |lang| %>
|
||||
<% if lang.is_a? Hash %>
|
||||
<a href="#" data-language-name="<%= lang.keys.first %>"><%= lang.values.first %></a>
|
||||
<% else %>
|
||||
<a href="#" data-language-name="<%= lang %>"><%= lang %></a>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if current_page.data.search %>
|
||||
<div class="search">
|
||||
<input type="text" class="search" id="input-search" placeholder="Search">
|
||||
</div>
|
||||
<ul class="search-results"></ul>
|
||||
<% end %>
|
||||
<ul id="toc" class="toc-list-h1">
|
||||
<% toc_data(page_content).each do |h1| %>
|
||||
<li>
|
||||
<a href="#<%= h1[:id] %>" class="toc-h1 toc-link" data-title="<%= h1[:title] %>"><%= h1[:content] %></a>
|
||||
<% if h1[:children].length > 0 %>
|
||||
<ul class="toc-list-h2">
|
||||
<% h1[:children].each do |h2| %>
|
||||
<li>
|
||||
<a href="#<%= h2[:id] %>" class="toc-h2 toc-link" data-title="<%= h2[:title] %>"><%= h2[:content] %></a>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% end %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% if current_page.data.toc_footers %>
|
||||
<ul class="toc-footer">
|
||||
<% current_page.data.toc_footers.each do |footer| %>
|
||||
<li><%= footer %></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="page-wrapper">
|
||||
<div class="dark-box"></div>
|
||||
<div class="content">
|
||||
<%= page_content %>
|
||||
</div>
|
||||
<div class="dark-box">
|
||||
<% if language_tabs.any? %>
|
||||
<div class="lang-selector">
|
||||
<% language_tabs.each do |lang| %>
|
||||
<% if lang.is_a? Hash %>
|
||||
<a href="#" data-language-name="<%= lang.keys.first %>"><%= lang.values.first %></a>
|
||||
<% else %>
|
||||
<a href="#" data-language-name="<%= lang %>"><%= lang %></a>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,38 @@
|
|||
@font-face {
|
||||
font-family: 'slate';
|
||||
src:font-url('slate.eot?-syv14m');
|
||||
src:font-url('slate.eot?#iefix-syv14m') format('embedded-opentype'),
|
||||
font-url('slate.woff2?-syv14m') format('woff2'),
|
||||
font-url('slate.woff?-syv14m') format('woff'),
|
||||
font-url('slate.ttf?-syv14m') format('truetype'),
|
||||
font-url('slate.svg?-syv14m#slate') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
%icon {
|
||||
font-family: 'slate';
|
||||
speak: none;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
%icon-exclamation-sign {
|
||||
@extend %icon;
|
||||
content: "\e600";
|
||||
}
|
||||
%icon-info-sign {
|
||||
@extend %icon;
|
||||
content: "\e602";
|
||||
}
|
||||
%icon-ok-sign {
|
||||
@extend %icon;
|
||||
content: "\e606";
|
||||
}
|
||||
%icon-search {
|
||||
@extend %icon;
|
||||
content: "\e607";
|
||||
}
|
|
@ -0,0 +1,427 @@
|
|||
/*! normalize.css v3.0.2 | MIT License | git.io/normalize */
|
||||
|
||||
/**
|
||||
* 1. Set default font family to sans-serif.
|
||||
* 2. Prevent iOS text size adjust after orientation change, without disabling
|
||||
* user zoom.
|
||||
*/
|
||||
|
||||
html {
|
||||
font-family: sans-serif; /* 1 */
|
||||
-ms-text-size-adjust: 100%; /* 2 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove default margin.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* HTML5 display definitions
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Correct `block` display not defined for any HTML5 element in IE 8/9.
|
||||
* Correct `block` display not defined for `details` or `summary` in IE 10/11
|
||||
* and Firefox.
|
||||
* Correct `block` display not defined for `main` in IE 11.
|
||||
*/
|
||||
|
||||
article,
|
||||
aside,
|
||||
details,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
main,
|
||||
menu,
|
||||
nav,
|
||||
section,
|
||||
summary {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct `inline-block` display not defined in IE 8/9.
|
||||
* 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
|
||||
*/
|
||||
|
||||
audio,
|
||||
canvas,
|
||||
progress,
|
||||
video {
|
||||
display: inline-block; /* 1 */
|
||||
vertical-align: baseline; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent modern browsers from displaying `audio` without controls.
|
||||
* Remove excess height in iOS 5 devices.
|
||||
*/
|
||||
|
||||
audio:not([controls]) {
|
||||
display: none;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address `[hidden]` styling not present in IE 8/9/10.
|
||||
* Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.
|
||||
*/
|
||||
|
||||
[hidden],
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Links
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the gray background color from active links in IE 10.
|
||||
*/
|
||||
|
||||
a {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Improve readability when focused and also mouse hovered in all browsers.
|
||||
*/
|
||||
|
||||
a:active,
|
||||
a:hover {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
/* Text-level semantics
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Address styling not present in IE 8/9/10/11, Safari, and Chrome.
|
||||
*/
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: 1px dotted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address styling not present in Safari and Chrome.
|
||||
*/
|
||||
|
||||
dfn {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address variable `h1` font-size and margin within `section` and `article`
|
||||
* contexts in Firefox 4+, Safari, and Chrome.
|
||||
*/
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address styling not present in IE 8/9.
|
||||
*/
|
||||
|
||||
mark {
|
||||
background: #ff0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address inconsistent and variable font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent `sub` and `sup` affecting `line-height` in all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
/* Embedded content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove border when inside `a` element in IE 8/9/10.
|
||||
*/
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct overflow not hidden in IE 9/10/11.
|
||||
*/
|
||||
|
||||
svg:not(:root) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Grouping content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Address margin not present in IE 8/9 and Safari.
|
||||
*/
|
||||
|
||||
figure {
|
||||
margin: 1em 40px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address differences between Firefox and other browsers.
|
||||
*/
|
||||
|
||||
hr {
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Contain overflow in all browsers.
|
||||
*/
|
||||
|
||||
pre {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address odd `em`-unit font size rendering in all browsers.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
pre,
|
||||
samp {
|
||||
font-family: monospace, monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
/* Forms
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Known limitation: by default, Chrome and Safari on OS X allow very limited
|
||||
* styling of `select`, unless a `border` property is set.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 1. Correct color not being inherited.
|
||||
* Known issue: affects color of disabled elements.
|
||||
* 2. Correct font properties not being inherited.
|
||||
* 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
color: inherit; /* 1 */
|
||||
font: inherit; /* 2 */
|
||||
margin: 0; /* 3 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Address `overflow` set to `hidden` in IE 8/9/10/11.
|
||||
*/
|
||||
|
||||
button {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address inconsistent `text-transform` inheritance for `button` and `select`.
|
||||
* All other form control elements do not inherit `text-transform` values.
|
||||
* Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
|
||||
* Correct `select` style inheritance in Firefox.
|
||||
*/
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
|
||||
* and `video` controls.
|
||||
* 2. Correct inability to style clickable `input` types in iOS.
|
||||
* 3. Improve usability and consistency of cursor style between image-type
|
||||
* `input` and others.
|
||||
*/
|
||||
|
||||
button,
|
||||
html input[type="button"], /* 1 */
|
||||
input[type="reset"],
|
||||
input[type="submit"] {
|
||||
-webkit-appearance: button; /* 2 */
|
||||
cursor: pointer; /* 3 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-set default cursor for disabled elements.
|
||||
*/
|
||||
|
||||
button[disabled],
|
||||
html input[disabled] {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove inner padding and border in Firefox 4+.
|
||||
*/
|
||||
|
||||
button::-moz-focus-inner,
|
||||
input::-moz-focus-inner {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address Firefox 4+ setting `line-height` on `input` using `!important` in
|
||||
* the UA stylesheet.
|
||||
*/
|
||||
|
||||
input {
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
/**
|
||||
* It's recommended that you don't attempt to style these elements.
|
||||
* Firefox's implementation doesn't respect box-sizing, padding, or width.
|
||||
*
|
||||
* 1. Address box sizing set to `content-box` in IE 8/9/10.
|
||||
* 2. Remove excess padding in IE 8/9/10.
|
||||
*/
|
||||
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
box-sizing: border-box; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix the cursor style for Chrome's increment/decrement buttons. For certain
|
||||
* `font-size` values of the `input`, it causes the cursor style of the
|
||||
* decrement button to change from `default` to `text`.
|
||||
*/
|
||||
|
||||
input[type="number"]::-webkit-inner-spin-button,
|
||||
input[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Address `appearance` set to `searchfield` in Safari and Chrome.
|
||||
* 2. Address `box-sizing` set to `border-box` in Safari and Chrome
|
||||
* (include `-moz` to future-proof).
|
||||
*/
|
||||
|
||||
input[type="search"] {
|
||||
-webkit-appearance: textfield; /* 1 */
|
||||
-moz-box-sizing: content-box;
|
||||
-webkit-box-sizing: content-box; /* 2 */
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove inner padding and search cancel button in Safari and Chrome on OS X.
|
||||
* Safari (but not Chrome) clips the cancel button when the search input has
|
||||
* padding (and `textfield` appearance).
|
||||
*/
|
||||
|
||||
input[type="search"]::-webkit-search-cancel-button,
|
||||
input[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define consistent border, margin, and padding.
|
||||
*/
|
||||
|
||||
fieldset {
|
||||
border: 1px solid #c0c0c0;
|
||||
margin: 0 2px;
|
||||
padding: 0.35em 0.625em 0.75em;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct `color` not being inherited in IE 8/9/10/11.
|
||||
* 2. Remove padding so people aren't caught out if they zero out fieldsets.
|
||||
*/
|
||||
|
||||
legend {
|
||||
border: 0; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove default vertical scrollbar in IE 8/9/10/11.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't inherit the `font-weight` (applied by a rule above).
|
||||
* NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
|
||||
*/
|
||||
|
||||
optgroup {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Tables
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove most spacing between table cells.
|
||||
*/
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
padding: 0;
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
// RTL Styles Variables
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
$default: auto;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// TABLE OF CONTENTS
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#toc>ul>li>a>span {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.toc-wrapper {
|
||||
transition: right 0.3s ease-in-out !important;
|
||||
left: $default !important;
|
||||
#{right}: 0;
|
||||
}
|
||||
|
||||
.toc-h2 {
|
||||
padding-#{right}: $nav-padding + $nav-indent;
|
||||
}
|
||||
|
||||
#nav-button {
|
||||
#{right}: 0;
|
||||
transition: right 0.3s ease-in-out;
|
||||
&.open {
|
||||
right: $nav-width
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// PAGE LAYOUT AND CODE SAMPLE BACKGROUND
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
.page-wrapper {
|
||||
margin-#{left}: $default !important;
|
||||
margin-#{right}: $nav-width;
|
||||
.dark-box {
|
||||
#{right}: $default;
|
||||
#{left}: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.lang-selector {
|
||||
width: $default !important;
|
||||
a {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// CODE SAMPLE STYLES
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
.content {
|
||||
&>h1,
|
||||
&>h2,
|
||||
&>h3,
|
||||
&>h4,
|
||||
&>h5,
|
||||
&>h6,
|
||||
&>p,
|
||||
&>table,
|
||||
&>ul,
|
||||
&>ol,
|
||||
&>aside,
|
||||
&>dl {
|
||||
margin-#{left}: $examples-width;
|
||||
margin-#{right}: $default !important;
|
||||
}
|
||||
&>ul,
|
||||
&>ol {
|
||||
padding-#{right}: $main-padding + 15px;
|
||||
}
|
||||
table {
|
||||
th,
|
||||
td {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
dd {
|
||||
margin-#{right}: 15px;
|
||||
}
|
||||
aside {
|
||||
aside:before {
|
||||
padding-#{left}: 0.5em;
|
||||
}
|
||||
.search-highlight {
|
||||
background: linear-gradient(to top right, #F7E633 0%, #F1D32F 100%);
|
||||
}
|
||||
}
|
||||
pre,
|
||||
blockquote {
|
||||
float: left !important;
|
||||
clear: left !important;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// TYPOGRAPHY
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
p,
|
||||
aside {
|
||||
text-align: right;
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
.toc-wrapper {
|
||||
text-align: right;
|
||||
direction: rtl;
|
||||
font-weight: 100 !important;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// RESPONSIVE DESIGN
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@media (max-width: $tablet-width) {
|
||||
.toc-wrapper {
|
||||
#{right}: -$nav-width;
|
||||
&.open {
|
||||
#{right}: 0;
|
||||
}
|
||||
}
|
||||
.page-wrapper {
|
||||
margin-#{right}: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $phone-width) {
|
||||
%left-col {
|
||||
margin-#{left}: 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
Copyright 2008-2013 Concur Technologies, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
not use this file except in compliance with the License. You may obtain
|
||||
a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
License for the specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// CUSTOMIZE SLATE
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Use these settings to help adjust the appearance of Slate
|
||||
|
||||
|
||||
// BACKGROUND COLORS
|
||||
////////////////////
|
||||
$nav-bg: #2E3336 !default;
|
||||
$examples-bg: #2E3336 !default;
|
||||
$code-bg: #1E2224 !default;
|
||||
$code-annotation-bg: #191D1F !default;
|
||||
$nav-subitem-bg: #1E2224 !default;
|
||||
$nav-active-bg: #0F75D4 !default;
|
||||
$nav-active-parent-bg: #1E2224 !default; // parent links of the current section
|
||||
$lang-select-border: #000 !default;
|
||||
$lang-select-bg: #1E2224 !default;
|
||||
$lang-select-active-bg: $examples-bg !default; // feel free to change this to blue or something
|
||||
$lang-select-pressed-bg: #111 !default; // color of language tab bg when mouse is pressed
|
||||
$main-bg: #F3F7F9 !default;
|
||||
$aside-notice-bg: #8fbcd4 !default;
|
||||
$aside-warning-bg: #c97a7e !default;
|
||||
$aside-success-bg: #6ac174 !default;
|
||||
$search-notice-bg: #c97a7e !default;
|
||||
|
||||
|
||||
// TEXT COLORS
|
||||
////////////////////
|
||||
$main-text: #333 !default; // main content text color
|
||||
$nav-text: #fff !default;
|
||||
$nav-active-text: #fff !default;
|
||||
$nav-active-parent-text: #fff !default; // parent links of the current section
|
||||
$lang-select-text: #fff !default; // color of unselected language tab text
|
||||
$lang-select-active-text: #fff !default; // color of selected language tab text
|
||||
$lang-select-pressed-text: #fff !default; // color of language tab text when mouse is pressed
|
||||
|
||||
|
||||
// SIZES
|
||||
////////////////////
|
||||
$nav-width: 230px !default; // width of the navbar
|
||||
$examples-width: 50% !default; // portion of the screen taken up by code examples
|
||||
$logo-margin: 0px !default; // margin below logo
|
||||
$main-padding: 28px !default; // padding to left and right of content & examples
|
||||
$nav-padding: 15px !default; // padding to left and right of navbar
|
||||
$nav-v-padding: 10px !default; // padding used vertically around search boxes and results
|
||||
$nav-indent: 10px !default; // extra padding for ToC subitems
|
||||
$code-annotation-padding: 13px !default; // padding inside code annotations
|
||||
$h1-margin-bottom: 21px !default; // padding under the largest header tags
|
||||
$tablet-width: 930px !default; // min width before reverting to tablet size
|
||||
$phone-width: $tablet-width - $nav-width !default; // min width before reverting to mobile size
|
||||
|
||||
|
||||
// FONTS
|
||||
////////////////////
|
||||
%default-font {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
%header-font {
|
||||
@extend %default-font;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
%code-font {
|
||||
font-family: Consolas, Menlo, Monaco, "Lucida Console", "Liberation Mono", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Courier New", monospace, serif;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
|
||||
// OTHER
|
||||
////////////////////
|
||||
$nav-footer-border-color: #666 !default;
|
||||
$search-box-border-color: #666 !default;
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// INTERNAL
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// These settings are probably best left alone.
|
||||
|
||||
%break-words {
|
||||
word-break: break-all;
|
||||
hyphens: auto;
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
@charset "utf-8";
|
||||
@import 'normalize';
|
||||
@import 'variables';
|
||||
@import 'icon-font';
|
||||
|
||||
/*
|
||||
Copyright 2008-2013 Concur Technologies, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
not use this file except in compliance with the License. You may obtain
|
||||
a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
License for the specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
$print-color: #999;
|
||||
$print-color-light: #ccc;
|
||||
$print-font-size: 12px;
|
||||
|
||||
body {
|
||||
@extend %default-font;
|
||||
}
|
||||
|
||||
.tocify, .toc-footer, .lang-selector, .search, #nav-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tocify-wrapper>img {
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.content {
|
||||
font-size: 12px;
|
||||
|
||||
pre, code {
|
||||
@extend %code-font;
|
||||
@extend %break-words;
|
||||
border: 1px solid $print-color;
|
||||
border-radius: 5px;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
pre {
|
||||
code {
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 1.3em;
|
||||
}
|
||||
|
||||
code {
|
||||
padding: 0.2em;
|
||||
}
|
||||
|
||||
table {
|
||||
border: 1px solid $print-color;
|
||||
tr {
|
||||
border-bottom: 1px solid $print-color;
|
||||
}
|
||||
td,th {
|
||||
padding: 0.7em;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@extend %header-font;
|
||||
font-size: 2.5em;
|
||||
padding-top: 0.5em;
|
||||
padding-bottom: 0.5em;
|
||||
margin-top: 1em;
|
||||
margin-bottom: $h1-margin-bottom;
|
||||
border: 2px solid $print-color-light;
|
||||
border-width: 2px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@extend %header-font;
|
||||
font-size: 1.8em;
|
||||
margin-top: 2em;
|
||||
border-top: 2px solid $print-color-light;
|
||||
padding-top: 0.8em;
|
||||
}
|
||||
|
||||
h1+h2, h1+div+h2 {
|
||||
border-top: none;
|
||||
padding-top: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
h3, h4 {
|
||||
@extend %header-font;
|
||||
font-size: 0.8em;
|
||||
margin-top: 1.5em;
|
||||
margin-bottom: 0.8em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
h5, h6 {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
aside {
|
||||
padding: 1em;
|
||||
border: 1px solid $print-color-light;
|
||||
border-radius: 5px;
|
||||
margin-top: 1.5em;
|
||||
margin-bottom: 1.5em;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
aside:before {
|
||||
vertical-align: middle;
|
||||
padding-right: 0.5em;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
aside.notice:before {
|
||||
@extend %icon-info-sign;
|
||||
}
|
||||
|
||||
aside.warning:before {
|
||||
@extend %icon-exclamation-sign;
|
||||
}
|
||||
|
||||
aside.success:before {
|
||||
@extend %icon-ok-sign;
|
||||
}
|
||||
}
|
||||
|
||||
.copy-clipboard {
|
||||
@media print {
|
||||
display: none
|
||||
}
|
||||
}
|
|
@ -0,0 +1,633 @@
|
|||
@charset "utf-8";
|
||||
@import 'normalize';
|
||||
@import 'variables';
|
||||
@import 'icon-font';
|
||||
// @import 'rtl'; // uncomment to switch to RTL format
|
||||
|
||||
/*
|
||||
Copyright 2008-2013 Concur Technologies, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
not use this file except in compliance with the License. You may obtain
|
||||
a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
License for the specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// GENERAL STUFF
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
html, body {
|
||||
color: $main-text;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
@extend %default-font;
|
||||
background-color: $main-bg;
|
||||
height: 100%;
|
||||
-webkit-text-size-adjust: none; /* Never autoresize text */
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// TABLE OF CONTENTS
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#toc > ul > li > a > span {
|
||||
float: right;
|
||||
background-color: #2484FF;
|
||||
border-radius: 40px;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.toc-wrapper {
|
||||
transition: left 0.3s ease-in-out;
|
||||
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
position: fixed;
|
||||
z-index: 30;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: $nav-width;
|
||||
background-color: $nav-bg;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
|
||||
// language selector for mobile devices
|
||||
.lang-selector {
|
||||
display: none;
|
||||
a {
|
||||
padding-top: 0.5em;
|
||||
padding-bottom: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
// This is the logo at the top of the ToC
|
||||
.logo {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
margin-bottom: $logo-margin;
|
||||
}
|
||||
|
||||
&>.search {
|
||||
position: relative;
|
||||
|
||||
input {
|
||||
background: $nav-bg;
|
||||
border-width: 0 0 1px 0;
|
||||
border-color: $search-box-border-color;
|
||||
padding: 6px 0 6px 20px;
|
||||
box-sizing: border-box;
|
||||
margin: $nav-v-padding $nav-padding;
|
||||
width: $nav-width - ($nav-padding*2);
|
||||
outline: none;
|
||||
color: $nav-text;
|
||||
border-radius: 0; /* ios has a default border radius */
|
||||
}
|
||||
|
||||
&:before {
|
||||
position: absolute;
|
||||
top: 17px;
|
||||
left: $nav-padding;
|
||||
color: $nav-text;
|
||||
@extend %icon-search;
|
||||
}
|
||||
}
|
||||
|
||||
.search-results {
|
||||
margin-top: 0;
|
||||
box-sizing: border-box;
|
||||
height: 0;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
transition-property: height, margin;
|
||||
transition-duration: 180ms;
|
||||
transition-timing-function: ease-in-out;
|
||||
background: $nav-subitem-bg;
|
||||
&.visible {
|
||||
height: 30%;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
li {
|
||||
margin: 1em $nav-padding;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $nav-text;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// The Table of Contents is composed of multiple nested
|
||||
// unordered lists. These styles remove the default
|
||||
// styling of an unordered list because it is ugly.
|
||||
ul, li {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
li {
|
||||
color: $nav-text;
|
||||
transition-property: background;
|
||||
transition-timing-function: linear;
|
||||
transition-duration: 200ms;
|
||||
}
|
||||
|
||||
// This is the currently selected ToC entry
|
||||
.toc-link.active {
|
||||
background-color: $nav-active-bg;
|
||||
color: $nav-active-text;
|
||||
}
|
||||
|
||||
// this is parent links of the currently selected ToC entry
|
||||
.toc-link.active-parent {
|
||||
background-color: $nav-active-parent-bg;
|
||||
color: $nav-active-parent-text;
|
||||
}
|
||||
|
||||
.toc-list-h2 {
|
||||
display: none;
|
||||
background-color: $nav-subitem-bg;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.toc-h2 {
|
||||
padding-left: $nav-padding + $nav-indent;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.toc-footer {
|
||||
padding: 1em 0;
|
||||
margin-top: 1em;
|
||||
border-top: 1px dashed $nav-footer-border-color;
|
||||
|
||||
li,a {
|
||||
color: $nav-text;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
li {
|
||||
font-size: 0.8em;
|
||||
line-height: 1.7;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.toc-link, .toc-footer li {
|
||||
padding: 0 $nav-padding 0 $nav-padding;
|
||||
display: block;
|
||||
overflow-x: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
text-decoration: none;
|
||||
color: $nav-text;
|
||||
transition-property: background;
|
||||
transition-timing-function: linear;
|
||||
transition-duration: 130ms;
|
||||
}
|
||||
|
||||
// button to show navigation on mobile devices
|
||||
#nav-button {
|
||||
span {
|
||||
display: block;
|
||||
$side-pad: $main-padding / 2 - 8px;
|
||||
padding: $side-pad $side-pad $side-pad;
|
||||
background-color: rgba($main-bg, 0.7);
|
||||
transform-origin: 0 0;
|
||||
transform: rotate(-90deg) translate(-100%, 0);
|
||||
border-radius: 0 0 0 5px;
|
||||
}
|
||||
padding: 0 1.5em 5em 0; // increase touch size area
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 100;
|
||||
color: #000;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
opacity: 0.7;
|
||||
line-height: 16px;
|
||||
img {
|
||||
height: 16px;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
transition: left 0.3s ease-in-out;
|
||||
|
||||
&:hover { opacity: 1; }
|
||||
&.open {left: $nav-width}
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// PAGE LAYOUT AND CODE SAMPLE BACKGROUND
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
.page-wrapper {
|
||||
margin-left: $nav-width;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
background-color: $main-bg;
|
||||
min-height: 100%;
|
||||
|
||||
padding-bottom: 1px; // prevent margin overflow
|
||||
|
||||
// The dark box is what gives the code samples their dark background.
|
||||
// It sits essentially under the actual content block, which has a
|
||||
// transparent background.
|
||||
// I know, it's hackish, but it's the simplist way to make the left
|
||||
// half of the content always this background color.
|
||||
.dark-box {
|
||||
width: $examples-width;
|
||||
background-color: $examples-bg;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.lang-selector {
|
||||
position: fixed;
|
||||
z-index: 50;
|
||||
border-bottom: 5px solid $lang-select-active-bg;
|
||||
}
|
||||
}
|
||||
|
||||
.lang-selector {
|
||||
display: flex;
|
||||
background-color: $lang-select-bg;
|
||||
width: 100%;
|
||||
font-weight: bold;
|
||||
overflow-x: auto;
|
||||
a {
|
||||
display: inline;
|
||||
color: $lang-select-text;
|
||||
text-decoration: none;
|
||||
padding: 0 10px;
|
||||
line-height: 30px;
|
||||
outline: 0;
|
||||
|
||||
&:active, &:focus {
|
||||
background-color: $lang-select-pressed-bg;
|
||||
color: $lang-select-pressed-text;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: $lang-select-active-bg;
|
||||
color: $lang-select-active-text;
|
||||
}
|
||||
}
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
clear: both;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// CONTENT STYLES
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// This is all the stuff with the light background in the left half of the page
|
||||
|
||||
.content {
|
||||
// fixes webkit rendering bug for some: see #538
|
||||
-webkit-transform: translateZ(0);
|
||||
// to place content above the dark box
|
||||
position: relative;
|
||||
z-index: 30;
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
display: block;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
&>h1, &>h2, &>h3, &>h4, &>h5, &>h6, &>p, &>table, &>ul, &>ol, &>aside, &>dl {
|
||||
margin-right: $examples-width;
|
||||
padding: 0 $main-padding;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
|
||||
@extend %left-col;
|
||||
}
|
||||
|
||||
&>ul, &>ol {
|
||||
padding-left: $main-padding + 15px;
|
||||
}
|
||||
|
||||
// the div is the tocify hidden div for placeholding stuff
|
||||
&>h1, &>h2, &>div {
|
||||
clear:both;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@extend %header-font;
|
||||
font-size: 25px;
|
||||
padding-top: 0.5em;
|
||||
padding-bottom: 0.5em;
|
||||
margin-bottom: $h1-margin-bottom;
|
||||
margin-top: 2em;
|
||||
border-top: 1px solid #ccc;
|
||||
border-bottom: 1px solid #ccc;
|
||||
background-color: #fdfdfd;
|
||||
}
|
||||
|
||||
h1:first-child, div:first-child + h1 {
|
||||
border-top-width: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@extend %header-font;
|
||||
font-size: 19px;
|
||||
margin-top: 4em;
|
||||
margin-bottom: 0;
|
||||
border-top: 1px solid #ccc;
|
||||
padding-top: 1.2em;
|
||||
padding-bottom: 1.2em;
|
||||
background-image: linear-gradient(to bottom, rgba(#fff, 0.2), rgba(#fff, 0));
|
||||
}
|
||||
|
||||
// h2s right after h1s should bump right up
|
||||
// against the h1s.
|
||||
h1 + h2, h1 + div + h2 {
|
||||
margin-top: $h1-margin-bottom * -1;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
h3, h4, h5, h6 {
|
||||
@extend %header-font;
|
||||
font-size: 15px;
|
||||
margin-top: 2.5em;
|
||||
margin-bottom: 0.8em;
|
||||
}
|
||||
|
||||
h4, h5, h6 {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 2em 0;
|
||||
border-top: 2px solid $examples-bg;
|
||||
border-bottom: 2px solid $main-bg;
|
||||
}
|
||||
|
||||
table {
|
||||
margin-bottom: 1em;
|
||||
overflow: auto;
|
||||
th,td {
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
line-height: 1.6;
|
||||
code {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
th {
|
||||
padding: 5px 10px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
tr:last-child {
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
tr:nth-child(odd)>td {
|
||||
background-color: lighten($main-bg,4.2%);
|
||||
}
|
||||
|
||||
tr:nth-child(even)>td {
|
||||
background-color: lighten($main-bg,2.4%);
|
||||
}
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
p, li, dt, dd {
|
||||
line-height: 1.6;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: rgba(0,0,0,0.05);
|
||||
padding: 3px;
|
||||
border-radius: 3px;
|
||||
@extend %break-words;
|
||||
@extend %code-font;
|
||||
}
|
||||
|
||||
pre>code {
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
aside {
|
||||
padding-top: 1em;
|
||||
padding-bottom: 1em;
|
||||
margin-top: 1.5em;
|
||||
margin-bottom: 1.5em;
|
||||
background: $aside-notice-bg;
|
||||
line-height: 1.6;
|
||||
|
||||
&.warning {
|
||||
background-color: $aside-warning-bg;
|
||||
}
|
||||
|
||||
&.success {
|
||||
background-color: $aside-success-bg;
|
||||
}
|
||||
}
|
||||
|
||||
aside:before {
|
||||
vertical-align: middle;
|
||||
padding-right: 0.5em;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
aside.notice:before {
|
||||
@extend %icon-info-sign;
|
||||
}
|
||||
|
||||
aside.warning:before {
|
||||
@extend %icon-exclamation-sign;
|
||||
}
|
||||
|
||||
aside.success:before {
|
||||
@extend %icon-ok-sign;
|
||||
}
|
||||
|
||||
.search-highlight {
|
||||
padding: 2px;
|
||||
margin: -3px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #F7E633;
|
||||
background: linear-gradient(to top left, #F7E633 0%, #F1D32F 100%);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// CODE SAMPLE STYLES
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// This is all the stuff that appears in the right half of the page
|
||||
|
||||
.content {
|
||||
&>div.highlight {
|
||||
clear:none;
|
||||
}
|
||||
|
||||
pre, blockquote {
|
||||
background-color: $code-bg;
|
||||
color: #fff;
|
||||
|
||||
margin: 0;
|
||||
width: $examples-width;
|
||||
|
||||
float:right;
|
||||
clear:right;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
@extend %right-col;
|
||||
|
||||
&>p { margin: 0; }
|
||||
|
||||
a {
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
border-bottom: dashed 1px #ccc;
|
||||
}
|
||||
}
|
||||
|
||||
pre {
|
||||
@extend %code-font;
|
||||
padding-top: 2em;
|
||||
padding-bottom: 2em;
|
||||
padding: 2em $main-padding;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
&>p {
|
||||
background-color: $code-annotation-bg;
|
||||
padding: $code-annotation-padding 2em;
|
||||
color: #eee;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// RESPONSIVE DESIGN
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// These are the styles for phones and tablets
|
||||
// There are also a couple styles disperesed
|
||||
|
||||
@media (max-width: $tablet-width) {
|
||||
.toc-wrapper {
|
||||
left: -$nav-width;
|
||||
|
||||
&.open {
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.page-wrapper {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
#nav-button {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.toc-link {
|
||||
padding-top: 0.3em;
|
||||
padding-bottom: 0.3em;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $phone-width) {
|
||||
.dark-box {
|
||||
display: none;
|
||||
}
|
||||
|
||||
%left-col {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.toc-wrapper .lang-selector {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.page-wrapper .lang-selector {
|
||||
display: none;
|
||||
}
|
||||
|
||||
%right-col {
|
||||
width: auto;
|
||||
float: none;
|
||||
}
|
||||
|
||||
%right-col + %left-col {
|
||||
margin-top: $main-padding;
|
||||
}
|
||||
}
|
||||
|
||||
.highlight .c, .highlight .cm, .highlight .c1, .highlight .cs {
|
||||
color: #909090;
|
||||
}
|
||||
|
||||
.highlight, .highlight .w {
|
||||
background-color: $code-bg;
|
||||
}
|
||||
|
||||
.copy-clipboard {
|
||||
float: right;
|
||||
fill: #9DAAB6;
|
||||
cursor: pointer;
|
||||
opacity: 0.4;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
}
|
||||
|
||||
.copy-clipboard:hover {
|
||||
opacity: 0.8;
|
||||
}
|
|
@ -1,11 +1,12 @@
|
|||
# hugo server --minify --themesDir ... --baseURL=http://0.0.0.0:1313/theme/hugo-book/
|
||||
|
||||
baseURL = 'https://rustybever.be/docs/vieter/'
|
||||
title = 'The Rusty Bever - Docs'
|
||||
title = 'Vieter - Docs'
|
||||
theme = 'hugo-book'
|
||||
|
||||
# Book configuration
|
||||
disablePathToLower = true
|
||||
# Doesn't work with docs as subdir
|
||||
enableGitInfo = true
|
||||
|
||||
# Needed for mermaid/katex shortcodes
|
||||
|
@ -27,16 +28,22 @@ enableGitInfo = true
|
|||
weight = 1
|
||||
|
||||
[menu]
|
||||
# [[menu.before]]
|
||||
[[menu.after]]
|
||||
name = "Source"
|
||||
url = "https://git.rustybever.be/Chewing_Bever/docs"
|
||||
name = "HTTP API Docs"
|
||||
url = "https://rustybever.be/docs/vieter/api/"
|
||||
weight = 10
|
||||
|
||||
[[menu.after]]
|
||||
name = "Man Pages"
|
||||
url = "https://rustybever.be/man/vieter/vieter.1.html"
|
||||
weight = 20
|
||||
[[menu.after]]
|
||||
name = "Vieter"
|
||||
url = "https://git.rustybever.be/vieter-v/vieter"
|
||||
weight = 30
|
||||
[[menu.after]]
|
||||
name = "Hugo Theme"
|
||||
url = "https://github.com/alex-shpak/hugo-book"
|
||||
weight = 20
|
||||
weight = 40
|
||||
|
||||
[params]
|
||||
# (Optional, default light) Sets color theme: light, dark or auto.
|
||||
|
@ -63,14 +70,14 @@ enableGitInfo = true
|
|||
|
||||
# Set source repository location.
|
||||
# Used for 'Last Modified' and 'Edit this page' links.
|
||||
BookRepo = 'https://git.rustybever.be/Chewing_Bever/docs'
|
||||
BookRepo = 'https://git.rustybever.be/vieter-v/vieter'
|
||||
|
||||
# (Optional, default 'commit') Specifies commit portion of the link to the page's last modified
|
||||
# commit hash for 'doc' page type.
|
||||
# Requires 'BookRepo' param.
|
||||
# Value used to construct a URL consisting of BookRepo/BookCommitPath/<commit-hash>
|
||||
# Github uses 'commit', Bitbucket uses 'commits'
|
||||
# BookCommitPath = 'commit'
|
||||
BookCommitPath = 'src/commit'
|
||||
|
||||
# Enable "Edit this page" links for 'doc' page type.
|
||||
# Disabled by default. Uncomment to enable. Requires 'BookRepo' param.
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
# Vieter CLI
|
||||
|
||||
I provide a simple CLI tool that currently only allows changing the Git
|
||||
repository API. Its usage is quite simple.
|
||||
|
||||
First, you need to create a file in your home directory called `.vieterrc` with
|
||||
the following content:
|
||||
|
||||
```toml
|
||||
address = "https://example.com"
|
||||
api_key = "your-api-key"
|
||||
```
|
||||
|
||||
You can also use a different file or use environment variables, as described in
|
||||
[Configuration](/configuration).
|
||||
|
||||
Now you're ready to use the CLI tool.
|
||||
|
||||
## Usage
|
||||
|
||||
* `vieter repos list` returns all repositories currently stored in the API.
|
||||
* `vieter repos add url branch repo arch...` adds the repository with the given
|
||||
URL, branch, repo & arch to the API.
|
||||
* `vieter repos remove id` removes the repository with the given ID prefix.
|
||||
|
||||
You can always check `vieter -help` or `vieter repos -help` for more
|
||||
information about the commands.
|
|
@ -9,12 +9,9 @@ documentation might not be relevant anymore for the latest release.
|
|||
|
||||
## Overview
|
||||
|
||||
Vieter has a few main features:
|
||||
|
||||
* It's a simple & lightweight implementation of an Arch repository server
|
||||
* It allows for uploading of built package archives
|
||||
* It supports a basic build system to periodically re-build packages & upload
|
||||
them to the server
|
||||
Vieter consists of two main parts, namely an implementation of an Arch
|
||||
repository server & a scheduling system to periodically build Pacman packages &
|
||||
publish them to a repository.
|
||||
|
||||
{{< hint info >}}
|
||||
**Note**
|
||||
|
@ -26,12 +23,12 @@ well.
|
|||
|
||||
### Why?
|
||||
|
||||
Vieter is my personal solution for a problem I've been facing for months:
|
||||
Vieter is my personal solution to a problem I've been facing for months:
|
||||
extremely long AUR package build times. I run EndeavourOS on both my laptops,
|
||||
one of which being a rather old MacBook Air. I really like being a beta-tester
|
||||
for projects & run development builds for multiple packages (nheko,
|
||||
newsflash...). The issue with this is that I have to regularly re-build these
|
||||
packages in order to stay up to date with development & these builds can take a
|
||||
newsflash...). Because of this, I have to regularly re-build these packages in
|
||||
order to stay up to date with development. However, these builds can take a
|
||||
really long time on the old MacBook. This project is a solution to that
|
||||
problem: instead of building the packages locally, I can build them
|
||||
automatically in the cloud & just download them whenever I update my system!
|
||||
|
|
|
@ -1,84 +0,0 @@
|
|||
# API Reference
|
||||
|
||||
All routes that return JSON use the following shape:
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "some message",
|
||||
"data": {}
|
||||
}
|
||||
```
|
||||
|
||||
Here, data can be any JSON object, so it's not guaranteed to be a struct.
|
||||
|
||||
### `GET /<repo>/<arch>/<filename>`
|
||||
|
||||
This route serves the contents of a specific architecture' repo.
|
||||
|
||||
If `<filename>` is one of `<repo>.db`, `<repo>.files`, `<repo>.db.tar.gz` or
|
||||
`<repo>.files.tar.gz`, it will serve the respective archive file from the
|
||||
repository.
|
||||
|
||||
If `<filename>` contains `.pkg`, it assumes the request to be for a package
|
||||
archive & will serve that file from the specific arch-repo's package directory.
|
||||
|
||||
Finally, if none of the above are true, Vieter assumes it to be request for a
|
||||
package version's desc file & tries to serve this instead. This functionality
|
||||
is very useful for the build system for checking whether a package needs to be
|
||||
rebuilt or not.
|
||||
|
||||
### `HEAD /<repo>/<arch>/<filename>`
|
||||
|
||||
Behaves the same as the above route, but instead of returning actual data, it
|
||||
returns either 200 or 404, depending on whether the file exists. This route is
|
||||
used by the build system to determine whether a package needs to be rebuilt.
|
||||
|
||||
### `POST /<repo>/publish`
|
||||
|
||||
This route is used to upload packages to a repository. It requires the API
|
||||
key to be provided using the `X-Api-Key` HTTP header. Vieter will parse the
|
||||
package's contents & update the repository files accordingely. I find the
|
||||
easiest way to use this route is using cURL:
|
||||
|
||||
```sh
|
||||
curl -XPOST -T "path-to-package.pkg.tar.zst" -H "X-API-KEY: your-api-key" https://example.com/somerepo/publish
|
||||
```
|
||||
|
||||
Packages are automatically added to the correct arch-repo. If a package type is
|
||||
`any`, the package is added to the configured `default_arch`, as well as all
|
||||
already present arch-repos. To prevent unnecessary duplication of package
|
||||
files, these packages are shared between arch-repos' package directories using
|
||||
hard links.
|
||||
|
||||
{{< hint info >}}
|
||||
**Note**
|
||||
Vieter only supports uploading archives compressed using either gzip, zstd or
|
||||
xz at the moment.
|
||||
{{< /hint >}}
|
||||
|
||||
## API
|
||||
|
||||
All API routes require the API key to provided using the `X-Api-Key` header.
|
||||
Otherwise, they'll return a status code 401.
|
||||
|
||||
### `GET /api/repos`
|
||||
|
||||
Returns the current list of Git repositories.
|
||||
|
||||
### `GET /api/repos/<id>`
|
||||
|
||||
Get the information for the Git repo with the given ID.
|
||||
|
||||
### `POST /api/repos?<url>&<branch>&<arch>&<repo>`
|
||||
|
||||
Adds a new Git repository with the provided URL, Git branch & comma-separated
|
||||
list of architectures.
|
||||
|
||||
### `DELETE /api/repos/<id>`
|
||||
|
||||
Deletes the Git repository with the provided ID.
|
||||
|
||||
### `PATCH /api/repos/<id>?<url>&<branch>&<arch>&<repo>`
|
||||
|
||||
Updates the provided parameters for the repo with the given ID. All arguments
|
||||
are optional.
|
|
@ -1,56 +0,0 @@
|
|||
# Builder
|
||||
|
||||
Vieter supports a basic build system that allows you to build the packages
|
||||
defined using the Git repositories API by running `vieter build`. For
|
||||
configuration, see [here](/configuration#builder).
|
||||
|
||||
## How it works
|
||||
|
||||
The build system works in two stages. First it pulls down the
|
||||
`archlinux:latest` image from Docker Hub, runs `pacman -Syu` & configures a
|
||||
non-root build user. It then creates a new Docker image from this container.
|
||||
This is to prevent each build having to fully update the container's
|
||||
repositories. After the image has been created, each repository returned by
|
||||
`/api/repos` is built sequentially by starting up a new container with the
|
||||
previously created image as a base. Each container goes through the following steps:
|
||||
|
||||
1. The repository is cloned
|
||||
2. `makepkg --nobuild --nodeps` is ran to update the `pkgver` variable inside
|
||||
the `PKGBUILD` file
|
||||
3. A HEAD request is sent to the Vieter server to check whether the specific
|
||||
version of the package is already present. If it is, the container exits.
|
||||
4. `makepkg` is ran with `MAKEFLAGS="-j\$(nproc)`
|
||||
5. Each produced package archive is uploaded to the Vieter instance's
|
||||
repository, as defined in the API for that specific Git repo.
|
||||
|
||||
## Cron image
|
||||
|
||||
The Vieter Docker image contains crond & a cron config that runs `vieter build`
|
||||
every night at 3AM. This value is currently hardcoded, but I wish to change
|
||||
that down the line (work is in progress). There's also some other caveats you
|
||||
should be aware of, namely that the image should be run as root & that the
|
||||
healthcheck will always fail, so you might have to disable it. This boils down
|
||||
to the following docker-compose file:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
cron:
|
||||
image: 'chewingbever/vieter:dev'
|
||||
command: crond -f
|
||||
user: root
|
||||
|
||||
healthcheck:
|
||||
disable: true
|
||||
|
||||
environment:
|
||||
- 'VIETER_API_KEY=some-key'
|
||||
- 'VIETER_ADDRESS=https://example.com'
|
||||
volumes:
|
||||
- '/var/run/docker.sock:/var/run/docker.sock'
|
||||
```
|
||||
|
||||
Important to note is that the container also requires the host's Docker socket
|
||||
to be mounted as this is how it spawns the necessary containers, as well as a
|
||||
change to the container's command.
|
|
@ -3,7 +3,7 @@ weight: 20
|
|||
---
|
||||
# Configuration
|
||||
|
||||
All vieter operations by default try to read in the TOML file `~/.vieterrc` for
|
||||
By default, all vieter commands try to read in the TOML file `~/.vieterrc` for
|
||||
configuration. The location of this file can be changed by using the `-f` flag.
|
||||
|
||||
If the above file doesn't exist or you wish to override some of its settings,
|
||||
|
@ -19,53 +19,120 @@ the value in the environment variable is used.
|
|||
{{< hint info >}}
|
||||
**Note**
|
||||
All environment variables can also be provided from a file by appending them
|
||||
with `_FILE`. This for example allows you to provide the API key from a docker
|
||||
with `_FILE`. This for example allows you to provide the API key from a Docker
|
||||
secrets file.
|
||||
{{< /hint >}}
|
||||
|
||||
## Modes
|
||||
## Commands
|
||||
|
||||
The vieter binary can run in several "modes", indicated by the first argument
|
||||
passed to them. Each mode requires a different configuration.
|
||||
The first argument passed to Vieter determines which command you wish to use.
|
||||
Each of these can contain subcommands (e.g. `vieter targets list`), but all
|
||||
subcommands will use the same configuration. Below you can find the
|
||||
configuration variable required for each command.
|
||||
|
||||
### Server
|
||||
### `vieter server`
|
||||
|
||||
* `log_level`: defines how much logs to show. Valid values are one of `FATAL`,
|
||||
`ERROR`, `WARN`, `INFO` or `DEBUG`. Defaults to `WARN`
|
||||
* `log_file`: log file to write logs to. Defaults to `vieter.log` in the
|
||||
current directory.
|
||||
* `port`: HTTP port to run on
|
||||
* Default: `8000`
|
||||
* `log_level`: log verbosity level. Value should be one of `FATAL`, `ERROR`,
|
||||
`WARN`, `INFO` or `DEBUG`.
|
||||
* Default: `WARN`
|
||||
* `pkg_dir`: where Vieter should store the actual package archives.
|
||||
* `data_dir`: where Vieter stores the repositories, log file & database.
|
||||
* `api_key`: the API key to use when authenticating requests.
|
||||
* `default_arch`: architecture to always add packages of arch `any` to.
|
||||
|
||||
### Builder
|
||||
|
||||
* `api_key`: the API key to use when authenticating requests.
|
||||
* `address`: Base your URL of your Vieter instance, e.g. https://example.com
|
||||
* `base_image`: image to use when building a package. It should be an Archlinux
|
||||
image. The default if not configured is `archlinux:base-devel`, but this
|
||||
image only supports arm64. If you require aarch64 support as well, consider
|
||||
using
|
||||
* `default_arch`: this setting serves two main purposes:
|
||||
* Packages with architecture `any` are always added to this architecture.
|
||||
This prevents the server from being confused when an `any` package is
|
||||
published as the very first package for a repository.
|
||||
* Targets added without an `arch` value use this value instead.
|
||||
* `global_schedule`: build schedule for any target that does not have a
|
||||
schedule defined. For information about this syntax, see
|
||||
[here](/usage/builds/schedule).
|
||||
* Default: `0 3` (3AM every night)
|
||||
* `base_image`: Docker image to use when building a package. Any Pacman-based
|
||||
distro image should work, as long as `/etc/pacman.conf` is used &
|
||||
`base-devel` exists in the repositories. Make sure that the image supports
|
||||
the architecture of your cron daemon.
|
||||
* Default: `archlinux:base-devel` (only works on `x86_64`). If you require
|
||||
`aarch64` support, consider using
|
||||
[`menci/archlinuxarm:base-devel`](https://hub.docker.com/r/menci/archlinuxarm)
|
||||
([GH](https://github.com/Menci/docker-archlinuxarm))
|
||||
([GitHub](https://github.com/Menci/docker-archlinuxarm)). This is the
|
||||
image used for the Vieter CI builds.
|
||||
* `max_log_age`: maximum age of logs (in days). Logs older than this will get
|
||||
cleaned by the log removal daemon. If set to zero, no logs are ever removed.
|
||||
The age of logs is determined by the time the build was started.
|
||||
* Default: `0`
|
||||
* `log_removal_schedule`: cron schedule defining when to clean old logs.
|
||||
* Default: `0 0` (every day at midnight)
|
||||
|
||||
### Repos
|
||||
### `vieter cron`
|
||||
|
||||
* `log_level`: log verbosity level. Value should be one of `FATAL`, `ERROR`,
|
||||
`WARN`, `INFO` or `DEBUG`.
|
||||
* Default: `WARN`
|
||||
* `log_file`: log file to write logs to.
|
||||
* Default: `vieter.log` (in `data_dir`)
|
||||
* `address`: *public* URL of the Vieter repository server to build for. From
|
||||
this server the list of Git repositories is retrieved. All built packages are
|
||||
published to this server.
|
||||
* `api_key`: API key of the above server.
|
||||
* `data_dir`: directory to store log file in.
|
||||
* `base_image`: Docker image to use when building a package. Any Pacman-based
|
||||
distro image should work, as long as `/etc/pacman.conf` is used &
|
||||
`base-devel` exists in the repositories. Make sure that the image supports
|
||||
the architecture of your cron daemon.
|
||||
* Default: `archlinux:base-devel` (only works on `x86_64`). If you require
|
||||
`aarch64` support, consider using
|
||||
[`menci/archlinuxarm:base-devel`](https://hub.docker.com/r/menci/archlinuxarm)
|
||||
([GitHub](https://github.com/Menci/docker-archlinuxarm)). This is the image
|
||||
used for the Vieter CI builds.
|
||||
* `max_concurrent_builds`: how many builds to run at the same time.
|
||||
* Default: `1`
|
||||
* `api_update_frequency`: how frequently (in minutes) to poll the Vieter
|
||||
repository server for a new list of Git repositories to build.
|
||||
* Default: `15`
|
||||
* `image_rebuild_frequency`: Vieter periodically builds a builder image using
|
||||
the configured base image. This makes sure build containers do not have to
|
||||
download a lot of packages when updating their system. This setting defines
|
||||
how frequently (in minutes) to rebuild this builder image.
|
||||
* Default: `1440` (every 24 hours)
|
||||
* `global_schedule`: build schedule for any Git repository that does not have a
|
||||
schedule defined. For information about this syntax, see
|
||||
[here](/usage/builds/schedule).
|
||||
* Default: `0 3` (3AM every night)
|
||||
|
||||
### `vieter logs`
|
||||
|
||||
* `api_key`: the API key to use when authenticating requests.
|
||||
* `address`: Base your URL of your Vieter instance, e.g. https://example.com
|
||||
* `address`: Base URL of your Vieter instance, e.g. https://example.com
|
||||
|
||||
### Cron
|
||||
### `vieter targets`
|
||||
|
||||
* `log_level`: defines how much logs to show. Valid values are one of `FATAL`,
|
||||
`ERROR`, `WARN`, `INFO` or `DEBUG`. Defaults to `WARN`
|
||||
* `api_key`: the API key to use when authenticating requests.
|
||||
* `address`: Base your URL of your Vieter instance, e.g. https://example.com.
|
||||
This *must* be the publicly facing URL of your Vieter instance.
|
||||
* `data_dir`: where Vieter stores the log file.
|
||||
* `base_image`: Docker image from which to create the builder images.
|
||||
* `max_concurrent_builds`: amount of builds to run at once.
|
||||
* `api_update_frequency`: how frequenty to check for changes in the repo list.
|
||||
* `image_rebuild+frequency`: how frequently to rebuild the builder image
|
||||
* `global_schedule`: cron schedule to use for any repo without an individual
|
||||
schedule
|
||||
* `address`: Base URL of your Vieter instance, e.g. https://example.com
|
||||
* `base_image`: image to use when building a package using `vieter targets
|
||||
build`.
|
||||
* Default: `archlinux:base-devel`
|
||||
|
||||
### `vieter agent`
|
||||
|
||||
* `log_level`: log verbosity level. Value should be one of `FATAL`, `ERROR`,
|
||||
`WARN`, `INFO` or `DEBUG`.
|
||||
* Default: `WARN`
|
||||
* `address`: *public* URL of the Vieter repository server to build for. From
|
||||
this server jobs are retrieved. All built packages are published to this
|
||||
server.
|
||||
* `api_key`: API key of the above server.
|
||||
* `data_dir`: directory to store log file in.
|
||||
* `max_concurrent_builds`: how many builds to run at the same time.
|
||||
* Default: `1`
|
||||
* `polling_frequency`: how often (in seconds) to poll the server for new
|
||||
builds. Note that the agent might poll more frequently when it's actively
|
||||
processing builds.
|
||||
* `image_rebuild_frequency`: Vieter periodically builds images that are then
|
||||
used as a basis for running build containers. This is to prevent each build
|
||||
from downloading an entire repository worth of dependencies. This setting
|
||||
defines how frequently (in minutes) to rebuild these images.
|
||||
* Default: `1440` (every 24 hours)
|
||||
* `arch`: architecture for which this agent should pull down builds (e.g.
|
||||
`x86_64`)
|
||||
|
|
|
@ -3,76 +3,112 @@ weight: 10
|
|||
---
|
||||
# Installation
|
||||
|
||||
Vieter consists of a single binary, akin to busybox. The binary's behavior is
|
||||
determined by its CLI arguments, e.g. `vieter server` starts the repository
|
||||
server.
|
||||
|
||||
All installation solutions can be configured the same way,
|
||||
as described [here](/configuration).
|
||||
|
||||
## Docker
|
||||
|
||||
Docker is the recommended way to install vieter. The images can be pulled from
|
||||
[`chewingbever/vieter`](https://hub.docker.com/r/chewingbever/vieter). You can
|
||||
either pull a release tag (e.g. `chewingbever/vieter:0.1.0-rc1`), or pull the
|
||||
`chewingbever/vieter:dev` tag. The latter is updated every time a new commit is
|
||||
pushed to the development branch. This branch will be the most up to date, but
|
||||
does not give any guarantees about stability, so beware!
|
||||
Docker images are published to the
|
||||
[`chewingbever/vieter`](https://hub.docker.com/r/chewingbever/vieter) Docker
|
||||
Hub repository. You can either pull a release tag (e.g.
|
||||
`chewingbever/vieter:0.1.0-rc1`), or pull the `chewingbever/vieter:dev` tag.
|
||||
The latter is updated every time a new commit is pushed to the development
|
||||
branch. This branch will be the most up to date, but does not give any
|
||||
guarantees about stability, so beware!
|
||||
|
||||
The simplest way to run the Docker image is using a plain Docker command:
|
||||
Thanks to the single-binary design of Vieter, this image can be used both for
|
||||
the repository server, the cron daemon and the agent.
|
||||
|
||||
```sh
|
||||
docker run \
|
||||
--rm \
|
||||
-d \
|
||||
-v /path/to/data:/data \
|
||||
-e VIETER_API_KEY=changeme \
|
||||
-e VIETER_DEFAULT_ARCH=x86_64 \
|
||||
-p 8000:8000 \
|
||||
chewingbever/vieter:dev
|
||||
Below is a minimal compose file to set up both the repository server & a build
|
||||
agent:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
server:
|
||||
image: 'chewingbever/vieter:0.5.0-rc.1'
|
||||
restart: 'always'
|
||||
|
||||
environment:
|
||||
- 'VIETER_API_KEY=secret'
|
||||
- 'VIETER_DEFAULT_ARCH=x86_64'
|
||||
volumes:
|
||||
- 'data:/data'
|
||||
|
||||
cron:
|
||||
image: 'chewingbever/vieter:0.5.0-rc.1'
|
||||
restart: 'always'
|
||||
# Required to connect to the Docker daemon
|
||||
user: root
|
||||
command: 'vieter agent'
|
||||
|
||||
environment:
|
||||
- 'VIETER_API_KEY=secret'
|
||||
# MUST be public URL of Vieter repository
|
||||
- 'VIETER_ADDRESS=https://example.com'
|
||||
# Architecture for which the agent builds
|
||||
- 'VIETER_ARCH=x86_64'
|
||||
- 'VIETER_MAX_CONCURRENT_BUILDS=2'
|
||||
volumes:
|
||||
- '/var/run/docker.sock:/var/run/docker.sock'
|
||||
|
||||
volumes:
|
||||
data:
|
||||
```
|
||||
|
||||
Here, you should change `/path/to/data` to the path on your host where you want
|
||||
vieter to store its files.
|
||||
If you do not require the build system, the repository server can be used
|
||||
independently as well.
|
||||
|
||||
The default configuration will store everything inside the `/data` directory.
|
||||
Of course, Vieter allows a lot more configuration than this. This compose file
|
||||
is meant as a starting point for setting up your installation.
|
||||
|
||||
Inside the container, the Vieter server runs on port 8000. This port should be
|
||||
exposed to the public accordingely.
|
||||
|
||||
For an overview of how to configure vieter & which environment variables can be
|
||||
used, see the [Configuration](/configuration) page.
|
||||
{{< hint info >}}
|
||||
**Note**
|
||||
Builds are executed on the agent's system using the host's Docker daemon. An
|
||||
agent for a specific `arch` will only build packages for that specific
|
||||
architecture. Therefore, if you wish to build packages for both `x86_64` &
|
||||
`aarch64`, you'll have to deploy two agents, one on each architecture.
|
||||
Afterwards, any Git repositories enabled for those two architectures will build
|
||||
on both.
|
||||
{{< /hint >}}
|
||||
|
||||
## Binary
|
||||
|
||||
On the [releases](https://git.rustybever.be/Chewing_Bever/vieter/releases)
|
||||
page, you can find statically compiled binaries for all released versions. You
|
||||
can download the binary for your host's architecture & run it that way.
|
||||
On the
|
||||
[releases](https://git.rustybever.be/vieter-v/vieter/releases)
|
||||
page, you can find statically compiled binaries for all
|
||||
released versions. This is the same binary as used inside
|
||||
the Docker images.
|
||||
|
||||
For more information about configuring the binary, check out the
|
||||
[Configuration](/configuration) page.
|
||||
## Arch
|
||||
|
||||
I publish both development & release versions of Vieter to my personal
|
||||
repository, https://arch.r8r.be. Packages are available for `x86_64` &
|
||||
`aarch64`. To use the repository, add the following to your `pacman.conf`:
|
||||
|
||||
```
|
||||
[vieter]
|
||||
Server = https://arch.r8r.be/$repo/$arch
|
||||
SigLevel = Optional
|
||||
```
|
||||
|
||||
Afterwards, you can update your system & install the `vieter` package for the
|
||||
latest official release or `vieter-git` for the latest development release.
|
||||
|
||||
### AUR
|
||||
|
||||
If you prefer building the packages locally (or on your own Vieter instance),
|
||||
there's the [`vieter`](https://aur.archlinux.org/packages/vieter) &
|
||||
[`vieter-git`](https://aur.archlinux.org/packages/vieter-git) packages on the
|
||||
AUR. These packages build using the `vlang` compiler package, so I can't
|
||||
guarantee that a compiler update won't temporarily break them.
|
||||
|
||||
## Building from source
|
||||
|
||||
Because the project is still in heavy development, it might be useful to build
|
||||
from source instead. Luckily, this process is very easy. You'll need make,
|
||||
libarchive & openssl; all of which should be present on an every-day Arch
|
||||
install. Then, after cloning the repository, you can use the following commands:
|
||||
|
||||
```sh
|
||||
# Builds the compiler; should usually only be ran once. Vieter compiles using
|
||||
# the default compiler, but I maintain my own mirror to ensure nothing breaks
|
||||
# without me knowing.
|
||||
make v
|
||||
|
||||
# Build vieter
|
||||
# Alternatively, use `make prod` to build the production build.
|
||||
make
|
||||
```
|
||||
{{< hint info >}}
|
||||
**Note**
|
||||
My version of the V compiler is also available on my Vieter instance,
|
||||
https://arch.r8r.be. It's in the `vieter` repository, with the package being
|
||||
named `vieter-v`. The compiler is available for both x86_64 & aarch64.
|
||||
{{< /hint >}}
|
||||
|
||||
## My Vieter instance
|
||||
|
||||
Besides uploading development Docker images, my CI also publishes x86_64 &
|
||||
aarch64 packages to my personal Vieter instance, https://arch.r8r.be. If you'd
|
||||
like, you can use this repository as well by adding it to your Pacman
|
||||
configuration as described [here](/usage#configuring-pacman). Both the
|
||||
repository & the package are called `vieter`.
|
||||
The project [README](https://git.rustybever.be/vieter-v/vieter#building)
|
||||
contains instructions for building Vieter from source.
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
---
|
||||
weight: 30
|
||||
---
|
||||
# Usage
|
||||
|
||||
## Starting the server
|
||||
|
||||
To start a server, either install it using Docker (see
|
||||
[Installation](/installation)) or run it locally by executing `vieter
|
||||
server`. See [Configuration](/configuration) for more information about
|
||||
configuring the binary.
|
||||
|
||||
## Multiple repositories
|
||||
|
||||
Vieter works with multiple repositories. This means that a single Vieter server
|
||||
can serve multiple repositories in Pacman. It also automatically divides files
|
||||
with specific architectures among arch-repos. Arch-repos are the actual
|
||||
repositories you add to your `/etc/pacman.conf` file. See [Configuring
|
||||
Pacman](/usage#configuring-pacman) below for more info.
|
||||
|
||||
## Adding packages
|
||||
|
||||
Using Vieter is currently very simple. If you wish to add a package to Vieter,
|
||||
build it using makepkg & POST that file to the `/<repo>/publish` endpoint of
|
||||
your server. This will add the package to the repository. Authentification
|
||||
requires you to add the API key as the `X-Api-Key` header.
|
||||
|
||||
All of this can be combined into a simple cURL call:
|
||||
|
||||
```
|
||||
curl -XPOST -H "X-API-KEY: your-key" -T some-package.pkg.tar.zst https://example.com/somerepo/publish
|
||||
```
|
||||
|
||||
`somerepo` is automatically created if it doesn't exist yet.
|
||||
|
||||
## Configuring Pacman
|
||||
|
||||
Configuring Pacman to use a Vieter instance is very simple. In your
|
||||
`/etc/pacman.conf` file, add the following lines:
|
||||
|
||||
```
|
||||
[vieter]
|
||||
Server = https://example.com/$repo/$arch
|
||||
SigLevel = Optional
|
||||
```
|
||||
|
||||
Here, you see two important placeholder variables. `$repo` is replaced by the
|
||||
name within the square brackets, which in this case would be `vieter`. `$arch`
|
||||
is replaced by the output of `uname -m`. Because Vieter supports multiple
|
||||
repositories & architectures per repository, using this notation makes sure you
|
||||
always use the correct endpoint for fetching files.
|
||||
|
||||
I recommend placing this below all other repository entries, as the order
|
||||
decides which repository should be used if there's ever a naming conflict.
|
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
weight: 30
|
||||
---
|
|
@ -0,0 +1,51 @@
|
|||
---
|
||||
weight: 20
|
||||
---
|
||||
# Building packages
|
||||
|
||||
The automatic build system is what makes Vieter very useful as a replacement
|
||||
for an AUR helper. It can perodically build packages & publish them to your
|
||||
personal Vieter repository server, removing the need to build the packages
|
||||
locally.
|
||||
|
||||
## Adding builds
|
||||
|
||||
Before the cron system can start building your package, you need to add its
|
||||
info to the system. The Vieter repository server exposes an HTTP API for this
|
||||
(see the [HTTP API Docs](https://rustybever.be/docs/vieter/api/) for more
|
||||
info). For ease of use, the Vieter binary contains a CLI interface for
|
||||
interacting with this API (see [Configuration](/configuration) for
|
||||
configuration details). The [man
|
||||
pages](https://rustybever.be/man/vieter/vieter-targets.1.html) describe this in
|
||||
greater detail, but the basic usage is as follows:
|
||||
|
||||
```
|
||||
vieter targets add some-url some-repository
|
||||
```
|
||||
|
||||
Here, `some-url` is the URL of the Git repository containing the PKGBUILD. This
|
||||
URL is passed to `git clone`, meaning the repository should be public. Vieter
|
||||
expects the same format as an AUR Git repository, so you can directly use AUR
|
||||
URLs here. Alternatively, you can also provide the URL to a PKGBUILD file
|
||||
instead. See
|
||||
[vieter-targets-add(1)](https://rustybever.be/man/vieter/vieter-targets-add.1.html)
|
||||
for more information.
|
||||
|
||||
`some-repo` is the repository to which the built package archives should be
|
||||
published.
|
||||
|
||||
The above command intentionally leaves out a few parameters to make the CLI
|
||||
more useable. For information on how to modify all parameters using the CLI,
|
||||
see
|
||||
[vieter-targets(1)](https://rustybever.be/man/vieter/vieter-targets.1.html).
|
||||
|
||||
## Reading logs
|
||||
|
||||
The logs of each build are uploaded to the Vieter repository server, along with
|
||||
information about the exit code of the build container, when the build
|
||||
started/ended etc. These logs can then be accessed using the [HTTP
|
||||
API](https://rustybever.be/docs/vieter/api/).
|
||||
|
||||
For ease of use, the logs are also available using some CLI commands; see
|
||||
[vieter-logs(1)](https://rustybever.be/man/vieter/vieter-logs.1.html) for more
|
||||
information.
|
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
weight: 20
|
||||
---
|
||||
|
||||
# Cleanup
|
||||
|
||||
Vieter stores the logs of every single package build. While this is great for
|
||||
debugging why builds fail, it also causes an active or long-running Vieter
|
||||
instance to accumulate thousands of logs.
|
||||
|
||||
To combat this, a log removal daemon can be enabled that periodically removes
|
||||
old build logs. By starting your server with the `max_log_age` variable (see
|
||||
[Configuration](/configuration#vieter-server)), a daemon will get enabled that
|
||||
periodically removes logs older than this setting. By default, this will happen
|
||||
every day at midnight, but this behavior can be changed using the
|
||||
`log_removal_schedule` variable.
|
||||
|
||||
{{< hint info >}}
|
||||
**Note**
|
||||
The daemon will always run a removal of logs on startup. Therefore, it's
|
||||
possible the daemon will be *very* active when first enabling this setting.
|
||||
After the initial surge of logs to remove, it'll calm down again.
|
||||
{{< /hint >}}
|
|
@ -0,0 +1,46 @@
|
|||
---
|
||||
weight: 10
|
||||
---
|
||||
|
||||
# Cron schedule syntax
|
||||
|
||||
The Vieter cron daemon uses a subset of the cron expression syntax to schedule
|
||||
builds.
|
||||
|
||||
## Format
|
||||
|
||||
`a b c d`
|
||||
|
||||
* `a`: minutes
|
||||
* `b`: hours
|
||||
* `c`: days
|
||||
* `d`: months
|
||||
|
||||
An expression consists of two to four sections. If less than four sections are
|
||||
provided, the parser will append `*` until there are four sections. This means
|
||||
that `0 3` is the same as `0 3 * *`.
|
||||
|
||||
Each section consists of one or more parts, separated by a comma. Each of these
|
||||
parts, in turn, can be one of the following (any letters are integers):
|
||||
|
||||
* `*`: allow all possible values.
|
||||
* `a`: only this value is allowed.
|
||||
* `*/n`: allow every n-th value.
|
||||
* `a/n`: allow every n-th value, starting at a in the list.
|
||||
* `a-b`: allow every value between a and b, bounds included.
|
||||
* `a-b/n`: allow every n-th value inside the list of values between a and b,
|
||||
bounds included.
|
||||
|
||||
Each section can consist of as many of these parts as necessary.
|
||||
|
||||
## Examples
|
||||
|
||||
* `0 3`: every day at 03:00AM.
|
||||
* `0 0 */7`: every 7th day of the month, at midnight.
|
||||
|
||||
## CLI tool
|
||||
|
||||
The Vieter binary contains a command that shows you the next matching times for
|
||||
a given expression. This can be useful for understanding the syntax. For more
|
||||
information, see
|
||||
[vieter-schedule(1)](https://rustybever.be/man/vieter/vieter-schedule.1.html).
|
|
@ -0,0 +1,41 @@
|
|||
---
|
||||
weight: 10
|
||||
---
|
||||
# Pacman repository
|
||||
|
||||
The part of Vieter that users will interact with the most is the Pacman
|
||||
repository aka `vieter server`.
|
||||
|
||||
## Design overview
|
||||
|
||||
A Vieter repository server has support for multiple repositories, with each
|
||||
repository containing packages for multiple architectures.
|
||||
|
||||
If you wish to use these repositories on your system, add the following to
|
||||
`/etc/pacman.conf` for each repository you wish to use:
|
||||
|
||||
```
|
||||
[repo-name]
|
||||
Server = https://example.com/$repo/$arch
|
||||
SigLevel = Optional
|
||||
```
|
||||
|
||||
Here, `$repo` and `$arch` are not variables you have to fill in yourself.
|
||||
Rather, Pacman will substitute these when reading the config file. `$repo` is
|
||||
replaced by the name between the square brackets (in this case `repo-name`),
|
||||
and `$arch` is replaced by your system's architecture, e.g. `x86_64`. Of
|
||||
course, you can also fill in these values manually yourself, e.g. if you wish
|
||||
to use a different name inside the square brackets.
|
||||
|
||||
Important to note is that, when two repositories contain a package with the
|
||||
same name, Pacman will choose the one from the repository that's highest up in
|
||||
the `pacman.conf` file. Therefore, if you know your repository has packages
|
||||
with the same name as ones from the official repositories, it might be better
|
||||
to place the repository below the official repositories to avoid overwriting
|
||||
official packages.
|
||||
|
||||
## Publishing packages
|
||||
|
||||
Packages can be easily published using a single HTTP POST request. Check out
|
||||
the [HTTP API docs](https://rustybever.be/docs/vieter/api/) for more info on
|
||||
these routes, including example cURL commands.
|
|
@ -0,0 +1,27 @@
|
|||
module agent
|
||||
|
||||
import log
|
||||
import os
|
||||
import util
|
||||
|
||||
const log_file_name = 'vieter.agent.log'
|
||||
|
||||
// agent starts an agent service
|
||||
pub fn agent(conf Config) ! {
|
||||
log_level := log.level_from_tag(conf.log_level) or {
|
||||
return error('Invalid log level. The allowed values are FATAL, ERROR, WARN, INFO & DEBUG.')
|
||||
}
|
||||
|
||||
mut logger := log.Log{
|
||||
level: log_level
|
||||
}
|
||||
|
||||
os.mkdir_all(conf.data_dir) or { util.exit_with_message(1, 'Failed to create data directory.') }
|
||||
|
||||
log_file := os.join_path_single(conf.data_dir, agent.log_file_name)
|
||||
logger.set_full_logpath(log_file)
|
||||
logger.log_to_console_too()
|
||||
|
||||
mut d := agent_init(logger, conf)
|
||||
d.run()
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
module agent
|
||||
|
||||
import cli
|
||||
import conf as vconf
|
||||
|
||||
struct Config {
|
||||
pub:
|
||||
log_level string = 'WARN'
|
||||
// Architecture that the agent represents
|
||||
arch string
|
||||
api_key string
|
||||
address string
|
||||
data_dir string
|
||||
max_concurrent_builds int = 1
|
||||
polling_frequency int = 30
|
||||
image_rebuild_frequency int = 1440
|
||||
}
|
||||
|
||||
// cmd returns the cli module that handles the cron daemon.
|
||||
pub fn cmd() cli.Command {
|
||||
return cli.Command{
|
||||
name: 'agent'
|
||||
description: 'Start an agent daemon.'
|
||||
execute: fn (cmd cli.Command) ! {
|
||||
config_file := cmd.flags.get_string('config-file')!
|
||||
conf_ := vconf.load[Config](prefix: 'VIETER_', default_path: config_file)!
|
||||
|
||||
agent(conf_)!
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,197 @@
|
|||
module agent
|
||||
|
||||
import log
|
||||
import sync.stdatomic
|
||||
import build
|
||||
import models { BuildConfig }
|
||||
import client
|
||||
import time
|
||||
import os
|
||||
|
||||
const (
|
||||
build_empty = 0
|
||||
build_running = 1
|
||||
build_done = 2
|
||||
)
|
||||
|
||||
struct AgentDaemon {
|
||||
logger shared log.Log
|
||||
conf Config
|
||||
client client.Client
|
||||
mut:
|
||||
images ImageManager
|
||||
// Atomic variables used to detect when a build has finished; length is
|
||||
// conf.max_concurrent_builds. This approach is used as the difference
|
||||
// between a recently finished build and an empty build slot is important
|
||||
// for knowing whether the agent is currently "active".
|
||||
atomics []u64
|
||||
// Channel used to send builds to worker threads
|
||||
build_channel chan BuildConfig
|
||||
}
|
||||
|
||||
// agent_init initializes a new agent
|
||||
fn agent_init(logger log.Log, conf Config) AgentDaemon {
|
||||
mut d := AgentDaemon{
|
||||
logger: logger
|
||||
client: client.new(conf.address, conf.api_key)
|
||||
conf: conf
|
||||
images: new_image_manager(conf.image_rebuild_frequency * 60)
|
||||
atomics: []u64{len: conf.max_concurrent_builds}
|
||||
build_channel: chan BuildConfig{cap: conf.max_concurrent_builds}
|
||||
}
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
// run starts the actual agent daemon. This function will run forever.
|
||||
pub fn (mut d AgentDaemon) run() {
|
||||
// Spawn worker threads
|
||||
for builder_index in 0 .. d.conf.max_concurrent_builds {
|
||||
spawn d.builder_thread(d.build_channel, builder_index)
|
||||
}
|
||||
|
||||
// This is just so that the very first time the loop is ran, the jobs are
|
||||
// always polled
|
||||
mut last_poll_time := time.now().add_seconds(-d.conf.polling_frequency)
|
||||
mut sleep_time := 0 * time.second
|
||||
mut finished, mut empty, mut running := 0, 0, 0
|
||||
|
||||
for {
|
||||
if sleep_time > 0 {
|
||||
d.ldebug('Sleeping for ${sleep_time}')
|
||||
time.sleep(sleep_time)
|
||||
}
|
||||
|
||||
finished, empty = d.update_atomics()
|
||||
running = d.conf.max_concurrent_builds - finished - empty
|
||||
|
||||
// No new finished builds and no free slots, so there's nothing to be
|
||||
// done
|
||||
if finished + empty == 0 {
|
||||
sleep_time = 1 * time.second
|
||||
continue
|
||||
}
|
||||
|
||||
// Builds have finished, so old builder images might have freed up.
|
||||
// TODO this might query the docker daemon too frequently.
|
||||
if finished > 0 {
|
||||
d.images.clean_old_images()
|
||||
}
|
||||
|
||||
// The agent will always poll for new jobs after at most
|
||||
// `polling_frequency` seconds. However, when jobs have finished, the
|
||||
// agent will also poll for new jobs. This is because jobs are often
|
||||
// clustered together (especially when mostly using the global cron
|
||||
// schedule), so there's a much higher chance jobs are available.
|
||||
if finished > 0 || time.now() >= last_poll_time.add_seconds(d.conf.polling_frequency) {
|
||||
d.ldebug('Polling for new jobs')
|
||||
|
||||
new_configs := d.client.poll_jobs(d.conf.arch, finished + empty) or {
|
||||
d.lerror('Failed to poll jobs: ${err.msg()}')
|
||||
|
||||
// TODO pick a better delay here
|
||||
sleep_time = 5 * time.second
|
||||
continue
|
||||
}
|
||||
|
||||
d.ldebug('Received ${new_configs.len} jobs')
|
||||
|
||||
last_poll_time = time.now()
|
||||
|
||||
for config in new_configs {
|
||||
// Make sure a recent build base image is available for
|
||||
// building the config
|
||||
if !d.images.up_to_date(config.base_image) {
|
||||
d.linfo('Building builder image from base image ${config.base_image}')
|
||||
|
||||
// TODO handle this better than to just skip the config
|
||||
d.images.refresh_image(config.base_image) or {
|
||||
d.lerror(err.msg())
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// It's technically still possible that the build image is
|
||||
// removed in the very short period between building the
|
||||
// builder image and starting a build container with it. If
|
||||
// this happens, fate really just didn't want you to do this
|
||||
// build.
|
||||
|
||||
d.build_channel <- config
|
||||
running++
|
||||
}
|
||||
}
|
||||
|
||||
// The agent is not doing anything, so we just wait until the next poll
|
||||
// time
|
||||
if running == 0 {
|
||||
sleep_time = last_poll_time.add_seconds(d.conf.polling_frequency) - time.now()
|
||||
} else {
|
||||
sleep_time = 1 * time.second
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update_atomics checks for each build whether it's completed, and sets it to
|
||||
// empty again if so. The return value is a tuple `(finished, empty)` where
|
||||
// `finished` is how many builds were just finished and thus set to empty, and
|
||||
// `empty` is how many build slots were already empty. The amount of running
|
||||
// builds can then be calculated by substracting these two values from the
|
||||
// total allowed concurrent builds.
|
||||
fn (mut d AgentDaemon) update_atomics() (int, int) {
|
||||
mut finished := 0
|
||||
mut empty := 0
|
||||
|
||||
for i in 0 .. d.atomics.len {
|
||||
if stdatomic.load_u64(&d.atomics[i]) == agent.build_done {
|
||||
stdatomic.store_u64(&d.atomics[i], agent.build_empty)
|
||||
finished++
|
||||
} else if stdatomic.load_u64(&d.atomics[i]) == agent.build_empty {
|
||||
empty++
|
||||
}
|
||||
}
|
||||
|
||||
return finished, empty
|
||||
}
|
||||
|
||||
// run_build actually starts the build process for a given target.
|
||||
fn (mut d AgentDaemon) run_build(build_index int, config BuildConfig) {
|
||||
d.linfo('started build: ${config}')
|
||||
|
||||
// 0 means success, 1 means failure
|
||||
mut status := 0
|
||||
|
||||
new_config := BuildConfig{
|
||||
...config
|
||||
base_image: d.images.get(config.base_image)
|
||||
}
|
||||
|
||||
res := build.build_config(d.client.address, d.client.api_key, new_config) or {
|
||||
d.ldebug('build_config error: ${err.msg()}')
|
||||
status = 1
|
||||
|
||||
build.BuildResult{}
|
||||
}
|
||||
|
||||
if status == 0 {
|
||||
d.linfo('Uploading build logs for ${config}')
|
||||
|
||||
// TODO use the arch value here
|
||||
build_arch := os.uname().machine
|
||||
d.client.add_build_log(config.target_id, res.start_time, res.end_time, build_arch,
|
||||
res.exit_code, res.logs) or { d.lerror('Failed to upload logs for ${config}') }
|
||||
} else {
|
||||
d.lwarn('an error occurred during build: ${config}')
|
||||
}
|
||||
|
||||
stdatomic.store_u64(&d.atomics[build_index], agent.build_done)
|
||||
}
|
||||
|
||||
// builder_thread is a thread that constantly listens for builds to process
|
||||
fn (mut d AgentDaemon) builder_thread(ch chan BuildConfig, builder_index int) {
|
||||
for {
|
||||
build_config := <-ch or { break }
|
||||
|
||||
d.run_build(builder_index, build_config)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
module agent
|
||||
|
||||
import time
|
||||
import docker
|
||||
import build
|
||||
|
||||
// An ImageManager is a utility that creates builder images from given base
|
||||
// images, updating these builder images if they've become too old. This
|
||||
// structure can manage images from any number of base images, paving the way
|
||||
// for configurable base images per target/repository.
|
||||
struct ImageManager {
|
||||
max_image_age int [required]
|
||||
mut:
|
||||
// For each base image, one or more builder images can exist at the same
|
||||
// time
|
||||
images map[string][]string [required]
|
||||
// For each base image, we track when its newest image was built
|
||||
timestamps map[string]time.Time [required]
|
||||
}
|
||||
|
||||
// new_image_manager initializes a new image manager.
|
||||
fn new_image_manager(max_image_age int) ImageManager {
|
||||
return ImageManager{
|
||||
max_image_age: max_image_age
|
||||
images: map[string][]string{}
|
||||
timestamps: map[string]time.Time{}
|
||||
}
|
||||
}
|
||||
|
||||
// get returns the name of the newest image for the given base image. Note that
|
||||
// this function should only be called *after* a first call to `refresh_image`.
|
||||
pub fn (m &ImageManager) get(base_image string) string {
|
||||
return m.images[base_image].last()
|
||||
}
|
||||
|
||||
// up_to_date returns true if the last known builder image exists and is up to
|
||||
// date. If this function returns true, the last builder image may be used to
|
||||
// perform a build.
|
||||
pub fn (mut m ImageManager) up_to_date(base_image string) bool {
|
||||
if base_image !in m.timestamps
|
||||
|| m.timestamps[base_image].add_seconds(m.max_image_age) <= time.now() {
|
||||
return false
|
||||
}
|
||||
|
||||
// It's possible the image has been removed by some external event, so we
|
||||
// check whether it actually exists as well.
|
||||
mut dd := docker.new_conn() or { return false }
|
||||
|
||||
defer {
|
||||
dd.close() or {}
|
||||
}
|
||||
|
||||
dd.image_inspect(m.images[base_image].last()) or {
|
||||
// Image doesn't exist, so we stop tracking it
|
||||
if err.code() == 404 {
|
||||
m.images[base_image].delete_last()
|
||||
m.timestamps.delete(base_image)
|
||||
}
|
||||
|
||||
// If the inspect fails, it's either because the image doesn't exist or
|
||||
// because of some other error. Either way, we can't know *for certain*
|
||||
// that the image exists, so we return false.
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// refresh_image builds a new builder image from the given base image. This
|
||||
// function should only be called if `up_to_date` returned false.
|
||||
fn (mut m ImageManager) refresh_image(base_image string) ! {
|
||||
// TODO use better image tags for built images
|
||||
new_image := build.create_build_image(base_image) or {
|
||||
return error('Failed to build builder image from base image ${base_image}')
|
||||
}
|
||||
|
||||
m.images[base_image] << new_image
|
||||
m.timestamps[base_image] = time.now()
|
||||
}
|
||||
|
||||
// clean_old_images removes all older builder images that are no longer in use.
|
||||
// The function will always leave at least one builder image, namely the newest
|
||||
// one.
|
||||
fn (mut m ImageManager) clean_old_images() {
|
||||
mut dd := docker.new_conn() or { return }
|
||||
|
||||
defer {
|
||||
dd.close() or {}
|
||||
}
|
||||
|
||||
mut i := 0
|
||||
|
||||
for image in m.images.keys() {
|
||||
i = 0
|
||||
|
||||
for i < m.images[image].len - 1 {
|
||||
// For each builder image, we try to remove it by calling the Docker
|
||||
// API. If the function returns an error or false, that means the image
|
||||
// wasn't deleted. Therefore, we move the index over. If the function
|
||||
// returns true, the array's length has decreased by one so we don't
|
||||
// move the index.
|
||||
dd.image_remove(m.images[image][i]) or {
|
||||
// The image was removed by an external event
|
||||
if err.code() == 404 {
|
||||
m.images[image].delete(i)
|
||||
}
|
||||
// The image couldn't be removed, so we need to keep track of
|
||||
// it
|
||||
else {
|
||||
i += 1
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
m.images[image].delete(i)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
module agent
|
||||
|
||||
// lfatal create a log message with the fatal level
|
||||
pub fn (mut d AgentDaemon) lfatal(msg string) {
|
||||
lock d.logger {
|
||||
d.logger.fatal(msg)
|
||||
}
|
||||
}
|
||||
|
||||
// lerror create a log message with the error level
|
||||
pub fn (mut d AgentDaemon) lerror(msg string) {
|
||||
lock d.logger {
|
||||
d.logger.error(msg)
|
||||
}
|
||||
}
|
||||
|
||||
// lwarn create a log message with the warn level
|
||||
pub fn (mut d AgentDaemon) lwarn(msg string) {
|
||||
lock d.logger {
|
||||
d.logger.warn(msg)
|
||||
}
|
||||
}
|
||||
|
||||
// linfo create a log message with the info level
|
||||
pub fn (mut d AgentDaemon) linfo(msg string) {
|
||||
lock d.logger {
|
||||
d.logger.info(msg)
|
||||
}
|
||||
}
|
||||
|
||||
// ldebug create a log message with the debug level
|
||||
pub fn (mut d AgentDaemon) ldebug(msg string) {
|
||||
lock d.logger {
|
||||
d.logger.debug(msg)
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
#include "archive.h"
|
||||
|
||||
struct C.archive {}
|
||||
pub struct C.archive {}
|
||||
|
||||
// Create a new archive struct for reading
|
||||
fn C.archive_read_new() &C.archive
|
||||
|
@ -71,7 +71,7 @@ fn C.archive_filter_code(&C.archive, int) int
|
|||
|
||||
#include "archive_entry.h"
|
||||
|
||||
struct C.archive_entry {}
|
||||
pub struct C.archive_entry {}
|
||||
|
||||
// Create a new archive_entry struct
|
||||
fn C.archive_entry_new() &C.archive_entry
|
||||
|
|
|
@ -3,20 +3,31 @@ module build
|
|||
import docker
|
||||
import encoding.base64
|
||||
import time
|
||||
import git
|
||||
import os
|
||||
import db
|
||||
import strings
|
||||
import util
|
||||
import models { BuildConfig, Target }
|
||||
|
||||
const container_build_dir = '/build'
|
||||
|
||||
const build_image_repo = 'vieter-build'
|
||||
const (
|
||||
container_build_dir = '/build'
|
||||
build_image_repo = 'vieter-build'
|
||||
// Contents of PATH variable in build containers
|
||||
path_dirs = ['/sbin', '/bin', '/usr/sbin', '/usr/bin', '/usr/local/sbin',
|
||||
'/usr/local/bin', '/usr/bin/site_perl', '/usr/bin/vendor_perl', '/usr/bin/core_perl']
|
||||
)
|
||||
|
||||
// create_build_image creates a builder image given some base image which can
|
||||
// then be used to build & package Arch images. It mostly just updates the
|
||||
// system, install some necessary packages & creates a non-root user to run
|
||||
// makepkg with. The base image should be some Linux distribution that uses
|
||||
// Pacman as its package manager.
|
||||
pub fn create_build_image(base_image string) ?string {
|
||||
pub fn create_build_image(base_image string) !string {
|
||||
mut dd := docker.new_conn()!
|
||||
|
||||
defer {
|
||||
dd.close() or {}
|
||||
}
|
||||
|
||||
commands := [
|
||||
// Update repos & install required packages
|
||||
'pacman -Syu --needed --noconfirm base-devel git'
|
||||
|
@ -34,7 +45,7 @@ pub fn create_build_image(base_image string) ?string {
|
|||
|
||||
c := docker.NewContainer{
|
||||
image: base_image
|
||||
env: ['BUILD_SCRIPT=$cmds_str']
|
||||
env: ['BUILD_SCRIPT=${cmds_str}']
|
||||
entrypoint: ['/bin/sh', '-c']
|
||||
cmd: ['echo \$BUILD_SCRIPT | base64 -d | /bin/sh -e']
|
||||
}
|
||||
|
@ -46,14 +57,15 @@ pub fn create_build_image(base_image string) ?string {
|
|||
image_tag := if image_parts.len > 1 { image_parts[1] } else { 'latest' }
|
||||
|
||||
// We pull the provided image
|
||||
docker.pull_image(image_name, image_tag) ?
|
||||
dd.image_pull(image_name, image_tag)!
|
||||
|
||||
id := docker.create_container(c) ?
|
||||
docker.start_container(id) ?
|
||||
id := dd.container_create(c)!.id
|
||||
// id := docker.create_container(c)!
|
||||
dd.container_start(id)!
|
||||
|
||||
// This loop waits until the container has stopped, so we can remove it after
|
||||
for {
|
||||
data := docker.inspect_container(id) ?
|
||||
data := dd.container_inspect(id)!
|
||||
|
||||
if !data.state.running {
|
||||
break
|
||||
|
@ -67,83 +79,91 @@ pub fn create_build_image(base_image string) ?string {
|
|||
// TODO also add the base image's name into the image name to prevent
|
||||
// conflicts.
|
||||
tag := time.sys_mono_now().str()
|
||||
image := docker.create_image_from_container(id, 'vieter-build', tag) ?
|
||||
docker.remove_container(id) ?
|
||||
image := dd.image_from_container(id, 'vieter-build', tag)!
|
||||
dd.container_remove(id)!
|
||||
|
||||
return image.id
|
||||
}
|
||||
|
||||
// build_repo builds, packages & publishes a given Arch package based on the
|
||||
// provided GitRepo. The base image ID should be of an image previously created
|
||||
// by create_build_image.
|
||||
pub fn build_repo(address string, api_key string, base_image_id string, repo &db.GitRepo) ? {
|
||||
pub struct BuildResult {
|
||||
pub:
|
||||
start_time time.Time
|
||||
end_time time.Time
|
||||
exit_code int
|
||||
logs string
|
||||
}
|
||||
|
||||
// build_target builds the given target. Internally it calls `build_config`.
|
||||
pub fn build_target(address string, api_key string, base_image_id string, target &Target, force bool, timeout int) !BuildResult {
|
||||
config := target.as_build_config(base_image_id, force, timeout)
|
||||
|
||||
return build_config(address, api_key, config)
|
||||
}
|
||||
|
||||
// build_config builds, packages & publishes a given Arch package based on the
|
||||
// provided target. The base image ID should be of an image previously created
|
||||
// by create_build_image. It returns the logs of the container.
|
||||
pub fn build_config(address string, api_key string, config BuildConfig) !BuildResult {
|
||||
mut dd := docker.new_conn()!
|
||||
|
||||
defer {
|
||||
dd.close() or {}
|
||||
}
|
||||
|
||||
build_arch := os.uname().machine
|
||||
build_script := create_build_script(address, config, build_arch)
|
||||
|
||||
// TODO what to do with PKGBUILDs that build multiple packages?
|
||||
commands := [
|
||||
'git clone --single-branch --depth 1 --branch $repo.branch $repo.url repo',
|
||||
'cd repo',
|
||||
'makepkg --nobuild --nodeps',
|
||||
'source PKGBUILD',
|
||||
// The build container checks whether the package is already
|
||||
// present on the server
|
||||
'curl --head --fail $address/$repo.repo/$build_arch/\$pkgname-\$pkgver-\$pkgrel && exit 0',
|
||||
'MAKEFLAGS="-j\$(nproc)" makepkg -s --noconfirm --needed && for pkg in \$(ls -1 *.pkg*); do curl -XPOST -T "\$pkg" -H "X-API-KEY: \$API_KEY" $address/$repo.repo/publish; done',
|
||||
]
|
||||
|
||||
// We convert the list of commands into a base64 string, which then gets
|
||||
// passed to the container as an env var
|
||||
cmds_str := base64.encode_str(commands.join('\n'))
|
||||
// We convert the build script into a base64 string, which then gets passed
|
||||
// to the container as an env var
|
||||
base64_script := base64.encode_str(build_script)
|
||||
|
||||
c := docker.NewContainer{
|
||||
image: '$base_image_id'
|
||||
env: ['BUILD_SCRIPT=$cmds_str', 'API_KEY=$api_key']
|
||||
image: '${config.base_image}'
|
||||
env: [
|
||||
'BUILD_SCRIPT=${base64_script}',
|
||||
'API_KEY=${api_key}',
|
||||
// `archlinux:base-devel` does not correctly set the path variable,
|
||||
// causing certain builds to fail. This fixes it.
|
||||
'PATH=${build.path_dirs.join(':')}',
|
||||
]
|
||||
entrypoint: ['/bin/sh', '-c']
|
||||
cmd: ['echo \$BUILD_SCRIPT | base64 -d | /bin/bash -e']
|
||||
work_dir: '/build'
|
||||
user: 'builder:builder'
|
||||
user: '0:0'
|
||||
}
|
||||
|
||||
id := docker.create_container(c) ?
|
||||
docker.start_container(id) ?
|
||||
id := dd.container_create(c)!.id
|
||||
dd.container_start(id)!
|
||||
|
||||
mut data := dd.container_inspect(id)!
|
||||
start_time := time.now()
|
||||
|
||||
// This loop waits until the container has stopped, so we can remove it after
|
||||
for {
|
||||
data := docker.inspect_container(id) ?
|
||||
for data.state.running {
|
||||
if time.now() - start_time > config.timeout * time.second {
|
||||
dd.container_kill(id)!
|
||||
dd.container_remove(id)!
|
||||
|
||||
if !data.state.running {
|
||||
break
|
||||
return error('Build killed due to timeout (${config.timeout}s)')
|
||||
}
|
||||
|
||||
time.sleep(1 * time.second)
|
||||
|
||||
data = dd.container_inspect(id)!
|
||||
}
|
||||
|
||||
docker.remove_container(id) ?
|
||||
mut logs_stream := dd.container_get_logs(id)!
|
||||
|
||||
// Read in the entire stream
|
||||
mut logs_builder := strings.new_builder(10 * 1024)
|
||||
util.reader_to_writer(mut logs_stream, mut logs_builder)!
|
||||
|
||||
dd.container_remove(id)!
|
||||
|
||||
return BuildResult{
|
||||
start_time: data.state.start_time
|
||||
end_time: data.state.end_time
|
||||
exit_code: data.state.exit_code
|
||||
logs: logs_builder.str()
|
||||
}
|
||||
|
||||
// build builds every Git repo in the server's list.
|
||||
fn build(conf Config) ? {
|
||||
build_arch := os.uname().machine
|
||||
|
||||
// We get the repos map from the Vieter instance
|
||||
repos := git.get_repos(conf.address, conf.api_key) ?
|
||||
|
||||
// We filter out any repos that aren't allowed to be built on this
|
||||
// architecture
|
||||
filtered_repos := repos.filter(it.arch.map(it.value).contains(build_arch))
|
||||
|
||||
// No point in doing work if there's no repos present
|
||||
if filtered_repos.len == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// First, we create a base image which has updated repos n stuff
|
||||
image_id := create_build_image(conf.base_image) ?
|
||||
|
||||
for repo in filtered_repos {
|
||||
build_repo(conf.address, conf.api_key, image_id, repo) ?
|
||||
}
|
||||
|
||||
// Finally, we remove the builder image
|
||||
docker.remove_image(image_id) ?
|
||||
}
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
module build
|
||||
|
||||
import cli
|
||||
import env
|
||||
|
||||
pub struct Config {
|
||||
pub:
|
||||
api_key string
|
||||
address string
|
||||
base_image string = 'archlinux:base-devel'
|
||||
}
|
||||
|
||||
// cmd returns the cli submodule that handles the build process
|
||||
pub fn cmd() cli.Command {
|
||||
return cli.Command{
|
||||
name: 'build'
|
||||
description: 'Run the build process.'
|
||||
execute: fn (cmd cli.Command) ? {
|
||||
config_file := cmd.flags.get_string('config-file') ?
|
||||
conf := env.load<Config>(config_file) ?
|
||||
|
||||
build(conf) ?
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,219 @@
|
|||
module build
|
||||
|
||||
import models { BuildConfig, Target }
|
||||
import cron
|
||||
import time
|
||||
import datatypes { MinHeap }
|
||||
import util
|
||||
|
||||
struct BuildJob {
|
||||
pub mut:
|
||||
// Time at which this build job was created/queued
|
||||
created time.Time
|
||||
// Next timestamp from which point this job is allowed to be executed
|
||||
timestamp time.Time
|
||||
// Required for calculating next timestamp after having pop'ed a job
|
||||
ce &cron.Expression = unsafe { nil }
|
||||
// Actual build config sent to the agent
|
||||
config BuildConfig
|
||||
// Whether this is a one-time job
|
||||
single bool
|
||||
}
|
||||
|
||||
// Allows BuildJob structs to be sorted according to their timestamp in
|
||||
// MinHeaps
|
||||
fn (r1 BuildJob) < (r2 BuildJob) bool {
|
||||
return r1.timestamp < r2.timestamp
|
||||
}
|
||||
|
||||
// The build job queue is responsible for managing the list of scheduled builds
|
||||
// for each architecture. Agents receive jobs from this queue.
|
||||
pub struct BuildJobQueue {
|
||||
// Schedule to use for targets without explicitely defined cron expression
|
||||
default_schedule &cron.Expression
|
||||
// Base image to use for targets without defined base image
|
||||
default_base_image string
|
||||
// After how many minutes a build should be forcefully cancelled
|
||||
default_build_timeout int
|
||||
mut:
|
||||
mutex shared util.Dummy
|
||||
// For each architecture, a priority queue is tracked
|
||||
queues map[string]MinHeap[BuildJob]
|
||||
// When a target is removed from the server or edited, its previous build
|
||||
// configs will be invalid. This map allows for those to be simply skipped
|
||||
// by ignoring any build configs created before this timestamp.
|
||||
invalidated map[int]time.Time
|
||||
}
|
||||
|
||||
// new_job_queue initializes a new job queue
|
||||
pub fn new_job_queue(default_schedule &cron.Expression, default_base_image string, default_build_timeout int) BuildJobQueue {
|
||||
return BuildJobQueue{
|
||||
default_schedule: unsafe { default_schedule }
|
||||
default_base_image: default_base_image
|
||||
default_build_timeout: default_build_timeout
|
||||
invalidated: map[int]time.Time{}
|
||||
}
|
||||
}
|
||||
|
||||
// insert_all executes insert for each architecture of the given Target.
|
||||
pub fn (mut q BuildJobQueue) insert_all(target Target) ! {
|
||||
for arch in target.arch {
|
||||
q.insert(target: target, arch: arch.value)!
|
||||
}
|
||||
}
|
||||
|
||||
[params]
|
||||
pub struct InsertConfig {
|
||||
target Target [required]
|
||||
arch string [required]
|
||||
single bool
|
||||
force bool
|
||||
now bool
|
||||
}
|
||||
|
||||
// insert a new target's job into the queue for the given architecture. This
|
||||
// job will then be endlessly rescheduled after being pop'ed, unless removed
|
||||
// explicitely.
|
||||
pub fn (mut q BuildJobQueue) insert(input InsertConfig) ! {
|
||||
lock q.mutex {
|
||||
if input.arch !in q.queues {
|
||||
q.queues[input.arch] = MinHeap[BuildJob]{}
|
||||
}
|
||||
|
||||
mut job := BuildJob{
|
||||
created: time.now()
|
||||
single: input.single
|
||||
config: input.target.as_build_config(q.default_base_image, input.force, q.default_build_timeout)
|
||||
}
|
||||
|
||||
if !input.now {
|
||||
ce := if input.target.schedule != '' {
|
||||
cron.parse_expression(input.target.schedule) or {
|
||||
return error("Error while parsing cron expression '${input.target.schedule}' (id ${input.target.id}): ${err.msg()}")
|
||||
}
|
||||
} else {
|
||||
q.default_schedule
|
||||
}
|
||||
|
||||
job.timestamp = ce.next_from_now()
|
||||
job.ce = ce
|
||||
} else {
|
||||
job.timestamp = time.now()
|
||||
}
|
||||
|
||||
q.queues[input.arch].insert(job)
|
||||
}
|
||||
}
|
||||
|
||||
// reschedule the given job by calculating the next timestamp and re-adding it
|
||||
// to its respective queue. This function is called by the pop functions
|
||||
// *after* having pop'ed the job.
|
||||
fn (mut q BuildJobQueue) reschedule(job BuildJob, arch string) {
|
||||
new_timestamp := job.ce.next_from_now()
|
||||
|
||||
new_job := BuildJob{
|
||||
...job
|
||||
created: time.now()
|
||||
timestamp: new_timestamp
|
||||
}
|
||||
|
||||
q.queues[arch].insert(new_job)
|
||||
}
|
||||
|
||||
// pop_invalid pops all invalid jobs.
|
||||
fn (mut q BuildJobQueue) pop_invalid(arch string) {
|
||||
for {
|
||||
job := q.queues[arch].peek() or { return }
|
||||
|
||||
if job.config.target_id in q.invalidated
|
||||
&& job.created < q.invalidated[job.config.target_id] {
|
||||
// This pop *should* never fail according to the source code
|
||||
q.queues[arch].pop() or {}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// peek shows the first job for the given architecture that's ready to be
|
||||
// executed, if present.
|
||||
pub fn (mut q BuildJobQueue) peek(arch string) ?BuildJob {
|
||||
// Even peek requires a write lock, because pop_invalid can modify the data
|
||||
// structure
|
||||
lock q.mutex {
|
||||
if arch !in q.queues {
|
||||
return none
|
||||
}
|
||||
|
||||
q.pop_invalid(arch)
|
||||
job := q.queues[arch].peek() or { return none }
|
||||
|
||||
if job.timestamp < time.now() {
|
||||
return job
|
||||
}
|
||||
}
|
||||
|
||||
return none
|
||||
}
|
||||
|
||||
// pop removes the first job for the given architecture that's ready to be
|
||||
// executed from the queue and returns it, if present.
|
||||
pub fn (mut q BuildJobQueue) pop(arch string) ?BuildJob {
|
||||
lock q.mutex {
|
||||
if arch !in q.queues {
|
||||
return none
|
||||
}
|
||||
|
||||
q.pop_invalid(arch)
|
||||
mut job := q.queues[arch].peek() or { return none }
|
||||
|
||||
if job.timestamp < time.now() {
|
||||
job = q.queues[arch].pop() or { return none }
|
||||
|
||||
if !job.single {
|
||||
q.reschedule(job, arch)
|
||||
}
|
||||
|
||||
return job
|
||||
}
|
||||
}
|
||||
|
||||
return none
|
||||
}
|
||||
|
||||
// pop_n tries to pop at most n available jobs for the given architecture.
|
||||
pub fn (mut q BuildJobQueue) pop_n(arch string, n int) []BuildJob {
|
||||
lock q.mutex {
|
||||
if arch !in q.queues {
|
||||
return []
|
||||
}
|
||||
|
||||
mut out := []BuildJob{}
|
||||
|
||||
for out.len < n {
|
||||
q.pop_invalid(arch)
|
||||
mut job := q.queues[arch].peek() or { break }
|
||||
|
||||
if job.timestamp < time.now() {
|
||||
job = q.queues[arch].pop() or { break }
|
||||
|
||||
if !job.single {
|
||||
q.reschedule(job, arch)
|
||||
}
|
||||
|
||||
out << job
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
// invalidate a target's old build jobs.
|
||||
pub fn (mut q BuildJobQueue) invalidate(target_id int) {
|
||||
q.invalidated[target_id] = time.now()
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
echo -e '+ echo -e '\''[vieter]\\nServer = https://example.com/$repo/$arch\\nSigLevel = Optional'\'' >> /etc/pacman.conf'
|
||||
echo -e '[vieter]\nServer = https://example.com/$repo/$arch\nSigLevel = Optional' >> /etc/pacman.conf
|
||||
echo -e '+ pacman -Syu --needed --noconfirm'
|
||||
pacman -Syu --needed --noconfirm
|
||||
echo -e '+ su builder'
|
||||
su builder
|
||||
echo -e '+ git clone --single-branch --depth 1 '\''https://examplerepo.com'\'' repo'
|
||||
git clone --single-branch --depth 1 'https://examplerepo.com' repo
|
||||
echo -e '+ cd repo'
|
||||
cd repo
|
||||
echo -e '+ makepkg --nobuild --syncdeps --needed --noconfirm'
|
||||
makepkg --nobuild --syncdeps --needed --noconfirm
|
||||
echo -e '+ source PKGBUILD'
|
||||
source PKGBUILD
|
||||
echo -e '+ curl -s --head --fail https://example.com/vieter/x86_64/$pkgname-$pkgver-$pkgrel && exit 0'
|
||||
curl -s --head --fail https://example.com/vieter/x86_64/$pkgname-$pkgver-$pkgrel && exit 0
|
||||
echo -e '+ [ "$(id -u)" == 0 ] && exit 0'
|
||||
[ "$(id -u)" == 0 ] && exit 0
|
||||
echo -e '+ MAKEFLAGS="-j$(nproc)" makepkg -s --noconfirm --needed --noextract && for pkg in $(ls -1 *.pkg*); do curl -XPOST -T "$pkg" -H "X-API-KEY: $API_KEY" https://example.com/vieter/publish; done'
|
||||
MAKEFLAGS="-j$(nproc)" makepkg -s --noconfirm --needed --noextract && for pkg in $(ls -1 *.pkg*); do curl -XPOST -T "$pkg" -H "X-API-KEY: $API_KEY" https://example.com/vieter/publish; done
|
|
@ -0,0 +1,20 @@
|
|||
echo -e '+ echo -e '\''[vieter]\\nServer = https://example.com/$repo/$arch\\nSigLevel = Optional'\'' >> /etc/pacman.conf'
|
||||
echo -e '[vieter]\nServer = https://example.com/$repo/$arch\nSigLevel = Optional' >> /etc/pacman.conf
|
||||
echo -e '+ pacman -Syu --needed --noconfirm'
|
||||
pacman -Syu --needed --noconfirm
|
||||
echo -e '+ su builder'
|
||||
su builder
|
||||
echo -e '+ git clone --single-branch --depth 1 --branch main '\''https://examplerepo.com'\'' repo'
|
||||
git clone --single-branch --depth 1 --branch main 'https://examplerepo.com' repo
|
||||
echo -e '+ cd repo'
|
||||
cd repo
|
||||
echo -e '+ makepkg --nobuild --syncdeps --needed --noconfirm'
|
||||
makepkg --nobuild --syncdeps --needed --noconfirm
|
||||
echo -e '+ source PKGBUILD'
|
||||
source PKGBUILD
|
||||
echo -e '+ curl -s --head --fail https://example.com/vieter/x86_64/$pkgname-$pkgver-$pkgrel && exit 0'
|
||||
curl -s --head --fail https://example.com/vieter/x86_64/$pkgname-$pkgver-$pkgrel && exit 0
|
||||
echo -e '+ [ "$(id -u)" == 0 ] && exit 0'
|
||||
[ "$(id -u)" == 0 ] && exit 0
|
||||
echo -e '+ MAKEFLAGS="-j$(nproc)" makepkg -s --noconfirm --needed --noextract && for pkg in $(ls -1 *.pkg*); do curl -XPOST -T "$pkg" -H "X-API-KEY: $API_KEY" https://example.com/vieter/publish; done'
|
||||
MAKEFLAGS="-j$(nproc)" makepkg -s --noconfirm --needed --noextract && for pkg in $(ls -1 *.pkg*); do curl -XPOST -T "$pkg" -H "X-API-KEY: $API_KEY" https://example.com/vieter/publish; done
|
|
@ -0,0 +1,20 @@
|
|||
echo -e '+ echo -e '\''[vieter]\\nServer = https://example.com/$repo/$arch\\nSigLevel = Optional'\'' >> /etc/pacman.conf'
|
||||
echo -e '[vieter]\nServer = https://example.com/$repo/$arch\nSigLevel = Optional' >> /etc/pacman.conf
|
||||
echo -e '+ pacman -Syu --needed --noconfirm'
|
||||
pacman -Syu --needed --noconfirm
|
||||
echo -e '+ su builder'
|
||||
su builder
|
||||
echo -e '+ git clone --single-branch --depth 1 '\''https://examplerepo.com'\'' repo'
|
||||
git clone --single-branch --depth 1 'https://examplerepo.com' repo
|
||||
echo -e '+ cd '\''repo/example/path'\'''
|
||||
cd 'repo/example/path'
|
||||
echo -e '+ makepkg --nobuild --syncdeps --needed --noconfirm'
|
||||
makepkg --nobuild --syncdeps --needed --noconfirm
|
||||
echo -e '+ source PKGBUILD'
|
||||
source PKGBUILD
|
||||
echo -e '+ curl -s --head --fail https://example.com/vieter/x86_64/$pkgname-$pkgver-$pkgrel && exit 0'
|
||||
curl -s --head --fail https://example.com/vieter/x86_64/$pkgname-$pkgver-$pkgrel && exit 0
|
||||
echo -e '+ [ "$(id -u)" == 0 ] && exit 0'
|
||||
[ "$(id -u)" == 0 ] && exit 0
|
||||
echo -e '+ MAKEFLAGS="-j$(nproc)" makepkg -s --noconfirm --needed --noextract && for pkg in $(ls -1 *.pkg*); do curl -XPOST -T "$pkg" -H "X-API-KEY: $API_KEY" https://example.com/vieter/publish; done'
|
||||
MAKEFLAGS="-j$(nproc)" makepkg -s --noconfirm --needed --noextract && for pkg in $(ls -1 *.pkg*); do curl -XPOST -T "$pkg" -H "X-API-KEY: $API_KEY" https://example.com/vieter/publish; done
|
|
@ -0,0 +1,20 @@
|
|||
echo -e '+ echo -e '\''[vieter]\\nServer = https://example.com/$repo/$arch\\nSigLevel = Optional'\'' >> /etc/pacman.conf'
|
||||
echo -e '[vieter]\nServer = https://example.com/$repo/$arch\nSigLevel = Optional' >> /etc/pacman.conf
|
||||
echo -e '+ pacman -Syu --needed --noconfirm'
|
||||
pacman -Syu --needed --noconfirm
|
||||
echo -e '+ su builder'
|
||||
su builder
|
||||
echo -e '+ git clone --single-branch --depth 1 '\''https://examplerepo.com'\'' repo'
|
||||
git clone --single-branch --depth 1 'https://examplerepo.com' repo
|
||||
echo -e '+ cd '\''repo/example/path with spaces'\'''
|
||||
cd 'repo/example/path with spaces'
|
||||
echo -e '+ makepkg --nobuild --syncdeps --needed --noconfirm'
|
||||
makepkg --nobuild --syncdeps --needed --noconfirm
|
||||
echo -e '+ source PKGBUILD'
|
||||
source PKGBUILD
|
||||
echo -e '+ curl -s --head --fail https://example.com/vieter/x86_64/$pkgname-$pkgver-$pkgrel && exit 0'
|
||||
curl -s --head --fail https://example.com/vieter/x86_64/$pkgname-$pkgver-$pkgrel && exit 0
|
||||
echo -e '+ [ "$(id -u)" == 0 ] && exit 0'
|
||||
[ "$(id -u)" == 0 ] && exit 0
|
||||
echo -e '+ MAKEFLAGS="-j$(nproc)" makepkg -s --noconfirm --needed --noextract && for pkg in $(ls -1 *.pkg*); do curl -XPOST -T "$pkg" -H "X-API-KEY: $API_KEY" https://example.com/vieter/publish; done'
|
||||
MAKEFLAGS="-j$(nproc)" makepkg -s --noconfirm --needed --noextract && for pkg in $(ls -1 *.pkg*); do curl -XPOST -T "$pkg" -H "X-API-KEY: $API_KEY" https://example.com/vieter/publish; done
|
|
@ -0,0 +1,22 @@
|
|||
echo -e '+ echo -e '\''[vieter]\\nServer = https://example.com/$repo/$arch\\nSigLevel = Optional'\'' >> /etc/pacman.conf'
|
||||
echo -e '[vieter]\nServer = https://example.com/$repo/$arch\nSigLevel = Optional' >> /etc/pacman.conf
|
||||
echo -e '+ pacman -Syu --needed --noconfirm'
|
||||
pacman -Syu --needed --noconfirm
|
||||
echo -e '+ su builder'
|
||||
su builder
|
||||
echo -e '+ mkdir repo'
|
||||
mkdir repo
|
||||
echo -e '+ curl -o repo/PKGBUILD -L '\''https://examplerepo.com'\'''
|
||||
curl -o repo/PKGBUILD -L 'https://examplerepo.com'
|
||||
echo -e '+ cd repo'
|
||||
cd repo
|
||||
echo -e '+ makepkg --nobuild --syncdeps --needed --noconfirm'
|
||||
makepkg --nobuild --syncdeps --needed --noconfirm
|
||||
echo -e '+ source PKGBUILD'
|
||||
source PKGBUILD
|
||||
echo -e '+ curl -s --head --fail https://example.com/vieter/x86_64/$pkgname-$pkgver-$pkgrel && exit 0'
|
||||
curl -s --head --fail https://example.com/vieter/x86_64/$pkgname-$pkgver-$pkgrel && exit 0
|
||||
echo -e '+ [ "$(id -u)" == 0 ] && exit 0'
|
||||
[ "$(id -u)" == 0 ] && exit 0
|
||||
echo -e '+ MAKEFLAGS="-j$(nproc)" makepkg -s --noconfirm --needed --noextract && for pkg in $(ls -1 *.pkg*); do curl -XPOST -T "$pkg" -H "X-API-KEY: $API_KEY" https://example.com/vieter/publish; done'
|
||||
MAKEFLAGS="-j$(nproc)" makepkg -s --noconfirm --needed --noextract && for pkg in $(ls -1 *.pkg*); do curl -XPOST -T "$pkg" -H "X-API-KEY: $API_KEY" https://example.com/vieter/publish; done
|
|
@ -0,0 +1,93 @@
|
|||
module build
|
||||
|
||||
import models { BuildConfig }
|
||||
|
||||
// escape_shell_string escapes any characters that could be interpreted
|
||||
// incorrectly by a shell. The resulting value should be safe to use inside an
|
||||
// echo statement.
|
||||
fn escape_shell_string(s string) string {
|
||||
return s.replace(r'\', r'\\').replace("'", r"'\''")
|
||||
}
|
||||
|
||||
// echo_commands takes a list of shell commands & prepends each one with
|
||||
// an echo call displaying said command.
|
||||
pub fn echo_commands(cmds []string) []string {
|
||||
mut out := []string{cap: 2 * cmds.len}
|
||||
|
||||
for cmd in cmds {
|
||||
out << "echo -e '+ ${escape_shell_string(cmd)}'"
|
||||
out << cmd
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// create_build_script generates a shell script that builds a given Target.
|
||||
fn create_build_script(address string, config BuildConfig, build_arch string) string {
|
||||
repo_url := '${address}/${config.repo}'
|
||||
|
||||
mut commands := [
|
||||
// This will later be replaced by a proper setting for changing the
|
||||
// mirrorlist
|
||||
"echo -e '[${config.repo}]\\nServer = ${address}/\$repo/\$arch\\nSigLevel = Optional' >> /etc/pacman.conf"
|
||||
// We need to update the package list of the repo we just added above.
|
||||
// This should however not pull in a lot of packages as long as the
|
||||
// builder image is rebuilt frequently.
|
||||
'pacman -Syu --needed --noconfirm',
|
||||
// makepkg can't run as root
|
||||
'su builder',
|
||||
]
|
||||
|
||||
commands << match config.kind {
|
||||
'git' {
|
||||
if config.branch == '' {
|
||||
[
|
||||
"git clone --single-branch --depth 1 '${config.url}' repo",
|
||||
]
|
||||
} else {
|
||||
[
|
||||
"git clone --single-branch --depth 1 --branch ${config.branch} '${config.url}' repo",
|
||||
]
|
||||
}
|
||||
}
|
||||
'url' {
|
||||
[
|
||||
'mkdir repo',
|
||||
"curl -o repo/PKGBUILD -L '${config.url}'",
|
||||
]
|
||||
}
|
||||
else {
|
||||
panic("Invalid kind. This shouldn't be possible.")
|
||||
}
|
||||
}
|
||||
|
||||
commands << if config.path != '' {
|
||||
"cd 'repo/${config.path}'"
|
||||
} else {
|
||||
'cd repo'
|
||||
}
|
||||
|
||||
commands << [
|
||||
'makepkg --nobuild --syncdeps --needed --noconfirm',
|
||||
'source PKGBUILD',
|
||||
]
|
||||
|
||||
if !config.force {
|
||||
// The build container checks whether the package is already present on
|
||||
// the server.
|
||||
commands << [
|
||||
'curl -s --head --fail ${repo_url}/${build_arch}/\$pkgname-\$pkgver-\$pkgrel && exit 0',
|
||||
// If the above curl command succeeds, we don't need to rebuild the
|
||||
// package. However, because we're in a su shell, the exit command will
|
||||
// drop us back into the root shell. Therefore, we must check whether
|
||||
// we're in root so we don't proceed.
|
||||
'[ "\$(id -u)" == 0 ] && exit 0',
|
||||
]
|
||||
}
|
||||
|
||||
commands << [
|
||||
'MAKEFLAGS="-j\$(nproc)" makepkg -s --noconfirm --needed --noextract && for pkg in \$(ls -1 *.pkg*); do curl -XPOST -T "\$pkg" -H "X-API-KEY: \$API_KEY" ${repo_url}/publish; done',
|
||||
]
|
||||
|
||||
return echo_commands(commands).join('\n')
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
module build
|
||||
|
||||
import models { BuildConfig }
|
||||
|
||||
fn test_create_build_script_git() {
|
||||
config := BuildConfig{
|
||||
target_id: 1
|
||||
kind: 'git'
|
||||
url: 'https://examplerepo.com'
|
||||
repo: 'vieter'
|
||||
base_image: 'not-used:latest'
|
||||
}
|
||||
|
||||
build_script := create_build_script('https://example.com', config, 'x86_64')
|
||||
expected := $embed_file('scripts/git.sh')
|
||||
|
||||
assert build_script == expected.to_string().trim_space()
|
||||
}
|
||||
|
||||
fn test_create_build_script_git_path() {
|
||||
mut config := BuildConfig{
|
||||
target_id: 1
|
||||
kind: 'git'
|
||||
url: 'https://examplerepo.com'
|
||||
repo: 'vieter'
|
||||
path: 'example/path'
|
||||
base_image: 'not-used:latest'
|
||||
}
|
||||
|
||||
mut build_script := create_build_script('https://example.com', config, 'x86_64')
|
||||
mut expected := $embed_file('scripts/git_path.sh')
|
||||
|
||||
assert build_script == expected.to_string().trim_space()
|
||||
|
||||
config = BuildConfig{
|
||||
...config
|
||||
path: 'example/path with spaces'
|
||||
}
|
||||
|
||||
build_script = create_build_script('https://example.com', config, 'x86_64')
|
||||
expected = $embed_file('scripts/git_path_spaces.sh')
|
||||
|
||||
assert build_script == expected.to_string().trim_space()
|
||||
}
|
||||
|
||||
fn test_create_build_script_git_branch() {
|
||||
config := BuildConfig{
|
||||
target_id: 1
|
||||
kind: 'git'
|
||||
url: 'https://examplerepo.com'
|
||||
branch: 'main'
|
||||
repo: 'vieter'
|
||||
base_image: 'not-used:latest'
|
||||
}
|
||||
|
||||
build_script := create_build_script('https://example.com', config, 'x86_64')
|
||||
expected := $embed_file('scripts/git_branch.sh')
|
||||
|
||||
assert build_script == expected.to_string().trim_space()
|
||||
}
|
||||
|
||||
fn test_create_build_script_url() {
|
||||
config := BuildConfig{
|
||||
target_id: 1
|
||||
kind: 'url'
|
||||
url: 'https://examplerepo.com'
|
||||
repo: 'vieter'
|
||||
base_image: 'not-used:latest'
|
||||
}
|
||||
|
||||
build_script := create_build_script('https://example.com', config, 'x86_64')
|
||||
expected := $embed_file('scripts/url.sh')
|
||||
|
||||
assert build_script == expected.to_string().trim_space()
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
module client
|
||||
|
||||
import net.http { Method }
|
||||
import net.urllib
|
||||
import web.response { Response, new_data_response }
|
||||
import json
|
||||
|
||||
pub struct Client {
|
||||
pub:
|
||||
address string
|
||||
api_key string
|
||||
}
|
||||
|
||||
// new creates a new Client instance.
|
||||
pub fn new(address string, api_key string) Client {
|
||||
return Client{
|
||||
address: address
|
||||
api_key: api_key
|
||||
}
|
||||
}
|
||||
|
||||
// send_request_raw sends an HTTP request, returning the http.Response object.
|
||||
// It encodes the params so that they're safe to pass as HTTP query parameters.
|
||||
fn (c &Client) send_request_raw(method Method, url string, params map[string]string, body string) !http.Response {
|
||||
mut full_url := '${c.address}${url}'
|
||||
|
||||
if params.len > 0 {
|
||||
mut params_escaped := map[string]string{}
|
||||
|
||||
// Escape each query param
|
||||
for k, v in params {
|
||||
// An empty parameter should be the same as not providing it at all
|
||||
params_escaped[k] = urllib.query_escape(v)
|
||||
}
|
||||
|
||||
params_str := params_escaped.keys().map('${it}=${params_escaped[it]}').join('&')
|
||||
|
||||
full_url = '${full_url}?${params_str}'
|
||||
}
|
||||
|
||||
// Looking at the source code, this function doesn't actually fail, so I'm
|
||||
// not sure why it returns an optional
|
||||
mut req := http.new_request(method, full_url, body) or { return error('') }
|
||||
req.add_custom_header('X-Api-Key', c.api_key)!
|
||||
|
||||
res := req.do()!
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// send_request<T> just calls send_request_with_body<T> with an empty body.
|
||||
fn (c &Client) send_request[T](method Method, url string, params map[string]string) !Response[T] {
|
||||
return c.send_request_with_body[T](method, url, params, '')
|
||||
}
|
||||
|
||||
// send_request_with_body<T> calls send_request_raw_response & parses its
|
||||
// output as a Response<T> object.
|
||||
fn (c &Client) send_request_with_body[T](method Method, url string, params map[string]string, body string) !Response[T] {
|
||||
res := c.send_request_raw(method, url, params, body)!
|
||||
status := res.status()
|
||||
|
||||
// Non-successful requests are expected to return either an empty body or
|
||||
// Response<string>
|
||||
if status.is_error() {
|
||||
// A non-successful status call will have an empty body
|
||||
if res.body == '' {
|
||||
return error('Error ${res.status_code} (${status.str()}): (empty response)')
|
||||
}
|
||||
|
||||
data := json.decode(Response[string], res.body)!
|
||||
|
||||
return error('Status ${res.status_code} (${status.str()}): ${data.message}')
|
||||
}
|
||||
|
||||
// Just return an empty successful response
|
||||
if res.body == '' {
|
||||
return new_data_response(T{})
|
||||
}
|
||||
|
||||
data := json.decode(Response[T], res.body)!
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// send_request_raw_response returns the raw text response for an HTTP request.
|
||||
fn (c &Client) send_request_raw_response(method Method, url string, params map[string]string, body string) !string {
|
||||
res := c.send_request_raw(method, url, params, body)!
|
||||
|
||||
return res.body
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
module client
|
||||
|
||||
import models { BuildConfig }
|
||||
|
||||
// poll_jobs requests a list of new build jobs from the server.
|
||||
pub fn (c &Client) poll_jobs(arch string, max int) ![]BuildConfig {
|
||||
data := c.send_request[[]BuildConfig](.get, '/api/v1/jobs/poll', {
|
||||
'arch': arch
|
||||
'max': max.str()
|
||||
})!
|
||||
|
||||
return data.data
|
||||
}
|
||||
|
||||
// queue_job adds a new one-time build job for the given target to the job
|
||||
// queue.
|
||||
pub fn (c &Client) queue_job(target_id int, arch string, force bool) ! {
|
||||
c.send_request[string](.post, '/api/v1/jobs/queue', {
|
||||
'target': target_id.str()
|
||||
'arch': arch
|
||||
'force': force.str()
|
||||
})!
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
module client
|
||||
|
||||
import models { BuildLog, BuildLogFilter }
|
||||
import web.response { Response }
|
||||
import time
|
||||
|
||||
// get_build_logs returns all build logs.
|
||||
pub fn (c &Client) get_build_logs(filter BuildLogFilter) ![]BuildLog {
|
||||
params := models.params_from(filter)
|
||||
data := c.send_request[[]BuildLog](.get, '/api/v1/logs', params)!
|
||||
|
||||
return data.data
|
||||
}
|
||||
|
||||
// get_build_log returns a specific build log.
|
||||
pub fn (c &Client) get_build_log(id int) !BuildLog {
|
||||
data := c.send_request[BuildLog](.get, '/api/v1/logs/${id}', {})!
|
||||
|
||||
return data.data
|
||||
}
|
||||
|
||||
// get_build_log_content returns the contents of the build log file.
|
||||
pub fn (c &Client) get_build_log_content(id int) !string {
|
||||
data := c.send_request_raw_response(.get, '/api/v1/logs/${id}/content', {}, '')!
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// add_build_log adds a new build log to the server.
|
||||
pub fn (c &Client) add_build_log(target_id int, start_time time.Time, end_time time.Time, arch string, exit_code int, content string) !Response[int] {
|
||||
params := {
|
||||
'target': target_id.str()
|
||||
'startTime': start_time.unix_time().str()
|
||||
'endTime': end_time.unix_time().str()
|
||||
'arch': arch
|
||||
'exitCode': exit_code.str()
|
||||
}
|
||||
|
||||
data := c.send_request_with_body[int](.post, '/api/v1/logs', params, content)!
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// remove_build_log removes the build log with the given id from the server.
|
||||
pub fn (c &Client) remove_build_log(id int) ! {
|
||||
c.send_request[string](.delete, '/api/v1/logs/${id}', {})!
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
module client
|
||||
|
||||
// remove_repo removes an entire repository.
|
||||
pub fn (c &Client) remove_repo(repo string) ! {
|
||||
c.send_request[string](.delete, '/${repo}', {})!
|
||||
}
|
||||
|
||||
// remove_arch_repo removes an entire arch-repo.
|
||||
pub fn (c &Client) remove_arch_repo(repo string, arch string) ! {
|
||||
c.send_request[string](.delete, '/${repo}/${arch}', {})!
|
||||
}
|
||||
|
||||
// remove_package removes a single package from the given arch-repo.
|
||||
pub fn (c &Client) remove_package(repo string, arch string, pkgname string) ! {
|
||||
c.send_request[string](.delete, '/${repo}/${arch}/${pkgname}', {})!
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
module client
|
||||
|
||||
import models { Target, TargetFilter }
|
||||
|
||||
// get_targets returns a list of targets, given a filter object.
|
||||
pub fn (c &Client) get_targets(filter TargetFilter) ![]Target {
|
||||
params := models.params_from(filter)
|
||||
data := c.send_request[[]Target](.get, '/api/v1/targets', params)!
|
||||
|
||||
return data.data
|
||||
}
|
||||
|
||||
// get_all_targets retrieves *all* targs from the API using the default
|
||||
// limit.
|
||||
pub fn (c &Client) get_all_targets() ![]Target {
|
||||
mut targets := []Target{}
|
||||
mut offset := u64(0)
|
||||
|
||||
for {
|
||||
sub_targets := c.get_targets(offset: offset)!
|
||||
|
||||
if sub_targets.len == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
targets << sub_targets
|
||||
|
||||
offset += u64(sub_targets.len)
|
||||
}
|
||||
|
||||
return targets
|
||||
}
|
||||
|
||||
// get_target returns the target for a specific id.
|
||||
pub fn (c &Client) get_target(id int) !Target {
|
||||
data := c.send_request[Target](.get, '/api/v1/targets/${id}', {})!
|
||||
|
||||
return data.data
|
||||
}
|
||||
|
||||
pub struct NewTarget {
|
||||
kind string
|
||||
url string
|
||||
branch string
|
||||
repo string
|
||||
path string
|
||||
arch []string
|
||||
}
|
||||
|
||||
// add_target adds a new target to the server.
|
||||
pub fn (c &Client) add_target(t NewTarget) !int {
|
||||
params := models.params_from[NewTarget](t)
|
||||
data := c.send_request[int](.post, '/api/v1/targets', params)!
|
||||
|
||||
return data.data
|
||||
}
|
||||
|
||||
// remove_target removes the target with the given id from the server.
|
||||
pub fn (c &Client) remove_target(id int) !string {
|
||||
data := c.send_request[string](.delete, '/api/v1/targets/${id}', {})!
|
||||
|
||||
return data.data
|
||||
}
|
||||
|
||||
// patch_target sends a PATCH request to the given target with the params as
|
||||
// payload.
|
||||
pub fn (c &Client) patch_target(id int, params map[string]string) !string {
|
||||
data := c.send_request[string](.patch, '/api/v1/targets/${id}', params)!
|
||||
|
||||
return data.data
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
module aur
|
||||
|
||||
import cli
|
||||
import console
|
||||
import client
|
||||
import aur
|
||||
import conf as vconf
|
||||
|
||||
struct Config {
|
||||
address string [required]
|
||||
api_key string [required]
|
||||
}
|
||||
|
||||
// cmd returns the cli module for interacting with the AUR API.
|
||||
pub fn cmd() cli.Command {
|
||||
return cli.Command{
|
||||
name: 'aur'
|
||||
description: 'Interact with the AUR.'
|
||||
commands: [
|
||||
cli.Command{
|
||||
name: 'search'
|
||||
description: 'Search for packages.'
|
||||
required_args: 1
|
||||
execute: fn (cmd cli.Command) ! {
|
||||
c := aur.new()
|
||||
pkgs := c.search(cmd.args[0])!
|
||||
data := pkgs.map([it.name, it.description])
|
||||
|
||||
println(console.pretty_table(['name', 'description'], data)!)
|
||||
}
|
||||
},
|
||||
cli.Command{
|
||||
name: 'add'
|
||||
usage: 'repo pkg-name [pkg-name...]'
|
||||
description: 'Add the given AUR package(s) to Vieter. Non-existent packages will be silently ignored.'
|
||||
required_args: 2
|
||||
execute: fn (cmd cli.Command) ! {
|
||||
config_file := cmd.flags.get_string('config-file')!
|
||||
conf_ := vconf.load[Config](prefix: 'VIETER_', default_path: config_file)!
|
||||
|
||||
c := aur.new()
|
||||
pkgs := c.info(cmd.args[1..])!
|
||||
|
||||
vc := client.new(conf_.address, conf_.api_key)
|
||||
|
||||
for pkg in pkgs {
|
||||
vc.add_target(
|
||||
kind: 'git'
|
||||
url: 'https://aur.archlinux.org/${pkg.package_base}' + '.git'
|
||||
repo: cmd.args[0]
|
||||
) or {
|
||||
println('Failed to add ${pkg.name}: ${err.msg()}')
|
||||
continue
|
||||
}
|
||||
|
||||
println('Added ${pkg.name}' + '.')
|
||||
}
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue