Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f23f7d74d | ||
|
|
997a638235 | ||
|
|
cb1bc72335 | ||
|
|
63b386f581 | ||
|
|
be58bc4a11 | ||
|
|
ad1d4bde2a | ||
|
|
4ef0b52df5 | ||
|
|
3bca6aab6e | ||
|
|
13547e0252 | ||
|
|
bb1972e559 | ||
|
|
633ce3f063 | ||
|
|
2c857a6eb1 | ||
|
|
ad64ced2df | ||
|
|
81fb54cff5 | ||
|
|
45bbc93bee | ||
|
|
f9542f9214 | ||
|
|
60052b8ab7 | ||
|
|
7b9a6b4e9c | ||
|
|
09ca2a6740 | ||
|
|
20cd27e68d | ||
|
|
129d061434 | ||
|
|
19f59b207f | ||
|
|
528028d41b | ||
|
|
d77fa67729 | ||
|
|
f33418ec35 | ||
|
|
dd379c41c9 | ||
|
|
609a25119d | ||
|
|
9258acf765 | ||
|
|
d46d90395c | ||
|
|
b4e97ead04 | ||
|
|
ea5574bfa5 | ||
|
|
5b6e8de9a1 | ||
|
|
9980ec62fd | ||
|
|
8abb9b4358 | ||
|
|
40c1c0ec02 | ||
|
|
f2bc95ffcc | ||
|
|
4b1fbb1243 | ||
|
|
e7c2a8b880 | ||
|
|
dbbe9e8b0e | ||
|
|
b26a12074f | ||
|
|
79f99bd4f1 | ||
|
|
38cfe3a23b | ||
|
|
5e27701228 | ||
|
|
0a9fe9bb89 | ||
|
|
d0cd206f51 | ||
|
|
9d5f11aed5 | ||
|
|
eba5e422bf | ||
|
|
0e6f200efe | ||
|
|
e890a603c2 | ||
|
|
97951e7e8e | ||
|
|
3d98b64a42 | ||
|
|
d3dfd54f54 | ||
|
|
2e15732f64 | ||
|
|
45f5c5368a | ||
|
|
012e3b5f3c | ||
|
|
35521a5214 | ||
|
|
ad1e8d03e4 | ||
|
|
1eef1e083d | ||
|
|
bb5215c306 | ||
|
|
2c47b4e585 | ||
|
|
4371eb7c39 | ||
|
|
e16a3d46b3 | ||
|
|
1a3996e73b | ||
|
|
c3006c69a6 | ||
|
|
523bcbb6e3 | ||
|
|
127ad155ba | ||
|
|
14d287afe3 | ||
|
|
6231977e70 | ||
|
|
758b3e804e | ||
|
|
67a6d8194d | ||
|
|
31b5a3df77 | ||
|
|
785cf9c73a | ||
|
|
d9aa319e2c | ||
|
|
141e7233ff | ||
|
|
9ff25e7c3a | ||
|
|
c9fe54ed29 | ||
|
|
bbaef6cf8e | ||
|
|
7df1432744 | ||
|
|
6442d85c9f | ||
|
|
f4ad2ea6c8 | ||
|
|
f7fdf3f987 | ||
|
|
b8833b74eb | ||
|
|
bc29d1e86c | ||
|
|
5ea9b8211e |
No files matched your search
@@ -0,0 +1,69 @@
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- test-github-actions
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
APPCENTER_SECRET: ${{ secrets.APPCENTER_SECRET }}
|
||||
APPCENTER_TOKEN: ${{ secrets.APPCENTER_TOKEN }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
APPLE_P12_CERTIFICATE: ${{ secrets.APPLE_P12_CERTIFICATE }}
|
||||
FEEDBACK_TOKEN: ${{ secrets.FEEDBACK_TOKEN }}
|
||||
NETLIFY_WEBHOOK: ${{ secrets.NETLIFY_WEBHOOK }}
|
||||
SPARKLE_ED_PRIVATE_KEY: ${{ secrets.SPARKLE_ED_PRIVATE_KEY }}
|
||||
GITHUB_EVENT_BEFORE: ${{ github.event.before }}
|
||||
GITHUB_EVENT_AFTER: ${{ github.event.after }}
|
||||
BUILD_DIR: ${{ github.workspace }}
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macos-12
|
||||
environment: production
|
||||
steps:
|
||||
# - name: Dump gitHub context
|
||||
# env:
|
||||
# GITHUB_CONTEXT: ${{ toJSON(github) }}
|
||||
# run: echo "$GITHUB_CONTEXT"
|
||||
- name: Set up node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
- name: Set up xcode
|
||||
run: sudo xcode-select -s /Applications/Xcode_13.4.1.app/Contents/Developer
|
||||
- name: Calculate checkout fetch-depth
|
||||
id: pre-checkout
|
||||
env:
|
||||
COMMITS: ${{ toJson(github.event.commits) }}
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "push" ]]; then
|
||||
COUNT=$(( $(echo "$COMMITS" | jq length) + 1 ))
|
||||
else
|
||||
COUNT=1
|
||||
fi
|
||||
echo "fetch-depth=$COUNT" >> "$GITHUB_OUTPUT"
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: ${{ steps.pre-checkout.outputs.fetch-depth }}
|
||||
- name: Set up environment variables
|
||||
run: |
|
||||
echo "IS_RELEASE=$(if [ \"$GITHUB_REF_NAME\" = \"master\" -a \"$GITHUB_EVENT_NAME\" = \"push\" ]; then echo true; else echo false; fi)" >> $GITHUB_ENV
|
||||
echo "XCODE_BUILD_PATH=DerivedData/Build/Products/Release" >> $GITHUB_ENV
|
||||
echo "VERSION_FILE=VERSION.txt" >> $GITHUB_ENV
|
||||
echo "APP_NAME=$(awk -F ' = ' '/PRODUCT_NAME/ { print $2; }' < config/base.xcconfig)" >> $GITHUB_ENV
|
||||
- name: Run print-env.sh
|
||||
run: |
|
||||
scripts/print_env.sh
|
||||
- name: Run ci_cd.sh
|
||||
run: |
|
||||
scripts/pre_build_checks.sh
|
||||
scripts/ci_cd.sh
|
||||
# - name: Deploy to github releases
|
||||
# uses: softprops/action-gh-release@v1
|
||||
# with:
|
||||
# files: ${{ env.XCODE_BUILD_PATH }}/*.zip
|
||||
# - name: Run after_deploy.sh
|
||||
# run: |
|
||||
# scripts/after_deploy.sh
|
||||
@@ -6,7 +6,8 @@ IDEWorkspaceChecks.plist
|
||||
/DerivedData/
|
||||
/build/
|
||||
.DS_Store
|
||||
docs/_site/
|
||||
/docs/_site/
|
||||
/docs/vendor/
|
||||
codesign.conf
|
||||
codesign.crt
|
||||
codesign.key
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
retain_objc_accessible: true
|
||||
schemes:
|
||||
- Release
|
||||
targets:
|
||||
- alt-tab-macos
|
||||
workspace: alt-tab-macos.xcworkspace
|
||||
report_exclude:
|
||||
- src/api-wrappers/PrivateApis.swift
|
||||
@@ -0,0 +1,6 @@
|
||||
# We can find the rules here.
|
||||
# https://github.com/nicklockwood/SwiftFormat/blob/main/Rules.md
|
||||
|
||||
--swiftversion 5.5
|
||||
|
||||
--maxwidth 120
|
||||
@@ -0,0 +1,11 @@
|
||||
# **/*.swift: Matches all .swift files in the current directory and all its subdirectories.
|
||||
# src/**/*.swift: Matches all .swift files in the src directory and all its subdirectories.
|
||||
# !node_modules/**: Excludes all files in the node_modules directory and all its subdirectories.
|
||||
|
||||
Pods/**
|
||||
Generated/**
|
||||
|
||||
**/PrivateApis.swift
|
||||
|
||||
# We will open a new branch to deal with code format issues at the next release.
|
||||
**/*
|
||||
@@ -1,34 +1,36 @@
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- develop
|
||||
os: osx
|
||||
osx_image: xcode13.4
|
||||
language: node_js
|
||||
node_js:
|
||||
- 10
|
||||
addons:
|
||||
ssh_known_hosts: github.com
|
||||
homebrew:
|
||||
packages:
|
||||
- vitorgalvao/tiny-scripts/cask-repair
|
||||
update: true
|
||||
env:
|
||||
global:
|
||||
- IS_RELEASE=$(if [ "$TRAVIS_BRANCH" = "master" -a "$TRAVIS_PULL_REQUEST" = "false" ]; then echo true; fi)
|
||||
- XCODE_BUILD_PATH="DerivedData/Build/Products/Release"
|
||||
- VERSION_FILE="VERSION.txt"
|
||||
- APP_NAME="$(awk -F ' = ' '/PRODUCT_NAME/ { print $2; }' < config/base.xcconfig)"
|
||||
install: true # disable default install
|
||||
script: scripts/travis.sh
|
||||
before_deploy: if [[ $TRAVIS_TEST_RESULT == 1 ]]; then echo "build failed; exiting to prevent deploy phases"; sleep 2; exit 1; fi
|
||||
deploy:
|
||||
provider: releases
|
||||
token:
|
||||
secure: wZUHfOs+JQ0ikNMJ8BnM+8fuoQdhgOEQpNd01ueZxe760GAAjSbq1icME8qaYJJMk1G6n14jo9ecEhzxF0XV8hSdy4WtikK7l5SXiOx5Efuh/tNscmHggGk235eLwQD9pglePZsP3xJUB1BHEj260rypkjnSx5kVOfLUeB1ASeg4+KdxZd2vXjwel+YQf+dYbNUYtwoWqjiLbqT1ao+0q04MPgD2c9jb+HnsA94LUGb44lPXqfUJ+xG2LxBpcDW/q1JhFXL/AmJb0dOOEVzzrBqfWysPukgPMONexZse4c3BBqkMI52ZuF3OoEVAffofmuGKiR5OoyU/hW17xi9jGeb79r+Xt4RdaJAgwcn7/7GtemYKUJp8jhLGsy9XZJShENX7UHTkP3L4iyOrd2Wz+ctZWAebyNReLt985/xTaplpDiQrPcKAuQNBa4sgWk5rIsf7Z0tcBfZau/fQl9Q4100xTbwC+GeAbL0IyXG3kVzQGsGllXHr/2JM1sj7rmKA7lmDlp7xiOBCPscn2YCT87U+F6VbYoNvnxne0igQOUwrvX4qa4XlYrUXDeHcyKku0m8znY74nOu+m6LXkIt8r29pi6Xac0/ZGMwKZRUvKg/skamWPRoy9DAVd8TBQW1c91oYKmqDQRy0eWnD6X1djcKywJ7WlSQDejebJql0h+o=
|
||||
file_glob: true
|
||||
file: "$XCODE_BUILD_PATH/*.zip"
|
||||
skip_cleanup: true
|
||||
on:
|
||||
repo: lwouis/alt-tab-macos
|
||||
after_deploy: scripts/update_homebrew_cask.sh
|
||||
#branches:
|
||||
# only:
|
||||
# - master
|
||||
# - test-github-actions
|
||||
#os: osx
|
||||
#osx_image: xcode13.4
|
||||
#language: node_js
|
||||
#node_js:
|
||||
# - 16
|
||||
#addons:
|
||||
# ssh_known_hosts: github.com
|
||||
# homebrew:
|
||||
# packages:
|
||||
# - vitorgalvao/tiny-scripts/cask-repair
|
||||
# update: true
|
||||
#env:
|
||||
# global:
|
||||
# - IS_RELEASE=$(if [ "$TRAVIS_BRANCH" = "master" -a "$TRAVIS_PULL_REQUEST" = "false" ]; then echo true; fi)
|
||||
# - XCODE_BUILD_PATH="DerivedData/Build/Products/Release"
|
||||
# - VERSION_FILE="VERSION.txt"
|
||||
# - APP_NAME="$(awk -F ' = ' '/PRODUCT_NAME/ { print $2; }' < config/base.xcconfig)"
|
||||
# - BUILD_DIR="$TRAVIS_BUILD_DIR"
|
||||
#install: true # disable default install
|
||||
#script: scripts/print_env_vars.sh
|
||||
#script: scripts/print_env.sh && scripts/pre_build_checks.sh && scripts/ci_cd.sh
|
||||
#before_deploy: if [[ $TRAVIS_TEST_RESULT == 1 ]]; then echo "build failed; exiting to prevent deploy phases"; sleep 2; exit 1; fi
|
||||
#deploy:
|
||||
# provider: releases
|
||||
# token:
|
||||
# secure: wZUHfOs+JQ0ikNMJ8BnM+8fuoQdhgOEQpNd01ueZxe760GAAjSbq1icME8qaYJJMk1G6n14jo9ecEhzxF0XV8hSdy4WtikK7l5SXiOx5Efuh/tNscmHggGk235eLwQD9pglePZsP3xJUB1BHEj260rypkjnSx5kVOfLUeB1ASeg4+KdxZd2vXjwel+YQf+dYbNUYtwoWqjiLbqT1ao+0q04MPgD2c9jb+HnsA94LUGb44lPXqfUJ+xG2LxBpcDW/q1JhFXL/AmJb0dOOEVzzrBqfWysPukgPMONexZse4c3BBqkMI52ZuF3OoEVAffofmuGKiR5OoyU/hW17xi9jGeb79r+Xt4RdaJAgwcn7/7GtemYKUJp8jhLGsy9XZJShENX7UHTkP3L4iyOrd2Wz+ctZWAebyNReLt985/xTaplpDiQrPcKAuQNBa4sgWk5rIsf7Z0tcBfZau/fQl9Q4100xTbwC+GeAbL0IyXG3kVzQGsGllXHr/2JM1sj7rmKA7lmDlp7xiOBCPscn2YCT87U+F6VbYoNvnxne0igQOUwrvX4qa4XlYrUXDeHcyKku0m8znY74nOu+m6LXkIt8r29pi6Xac0/ZGMwKZRUvKg/skamWPRoy9DAVd8TBQW1c91oYKmqDQRy0eWnD6X1djcKywJ7WlSQDejebJql0h+o=
|
||||
# file_glob: true
|
||||
# file: "$XCODE_BUILD_PATH/*.zip"
|
||||
# skip_cleanup: true
|
||||
# on:
|
||||
# repo: lwouis/alt-tab-macos
|
||||
#after_deploy: scripts/after_deploy.sh
|
||||
@@ -6,6 +6,12 @@ verify_ssl = true
|
||||
[dev-packages]
|
||||
|
||||
[packages]
|
||||
fonttools = "*"
|
||||
fonttools = "==4.8.1"
|
||||
googletrans = "==3.0.0"
|
||||
|
||||
[scripts]
|
||||
[requires]
|
||||
python_version = "3.6"
|
||||
|
||||
[scripts]
|
||||
font2png = "scripts/convert_font_to_png.sh"
|
||||
translate = "python scripts/translate_i18n_tools.py resources/l10n --retry_translate"
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "8f11c81dff5e32871b7cb2314acb76fd1fb4a06fdb403a65bc6749fe3770eda6"
|
||||
"sha256": "00c7c75d00988a027e56c90f3a0bbba05aec3b41b2f7fd9be24f3cb27bf15eaf"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {},
|
||||
@@ -14,13 +14,111 @@
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8",
|
||||
"sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==2024.8.30"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
|
||||
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
|
||||
],
|
||||
"version": "==3.0.4"
|
||||
},
|
||||
"fonttools": {
|
||||
"hashes": [
|
||||
"sha256:596c0e8399c650dbf1048b7ca6fc7b12bd79a7db0981f3b87743de8c2b7512de",
|
||||
"sha256:6d241d319ea9eae4c4ee27cbd1db8656dbd496dd9aa884401debfab3a4c7d82b"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==4.8.1"
|
||||
},
|
||||
"googletrans": {
|
||||
"hashes": [
|
||||
"sha256:44caeea42d91ff6ead5c2d49db2b88de66c45c2fe874c8ec03eb9b4ceb3a533d"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.0.0"
|
||||
},
|
||||
"h11": {
|
||||
"hashes": [
|
||||
"sha256:33d4bca7be0fa039f4e84d50ab00531047e53d6ee8ffbc83501ea602c169cae1",
|
||||
"sha256:4bc6d6a1238b7615b266ada57e0618568066f57dd6fa967d1290ec9309b2f2f1"
|
||||
],
|
||||
"version": "==0.9.0"
|
||||
},
|
||||
"h2": {
|
||||
"hashes": [
|
||||
"sha256:61e0f6601fa709f35cdb730863b4e5ec7ad449792add80d1410d4174ed139af5",
|
||||
"sha256:875f41ebd6f2c44781259005b157faed1a5031df3ae5aa7bcb4628a6c0782f14"
|
||||
],
|
||||
"version": "==3.2.0"
|
||||
},
|
||||
"hpack": {
|
||||
"hashes": [
|
||||
"sha256:0edd79eda27a53ba5be2dfabf3b15780928a0dff6eb0c60a3d6767720e970c89",
|
||||
"sha256:8eec9c1f4bfae3408a3f30500261f7e6a65912dc138526ea054f9ad98892e9d2"
|
||||
],
|
||||
"version": "==3.0.0"
|
||||
},
|
||||
"hstspreload": {
|
||||
"hashes": [
|
||||
"sha256:2ab4518495a132c4ae430c474afffd12938852c6eec68ce1368c8f7858dc3076",
|
||||
"sha256:9c1b2d0313899d3ff9dac03ab39d53fed95c32eef9862e7eabee8dc07dfd589c"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==2024.9.1"
|
||||
},
|
||||
"httpcore": {
|
||||
"hashes": [
|
||||
"sha256:9850fe97a166a794d7e920590d5ec49a05488884c9fc8b5dba8561effab0c2a0",
|
||||
"sha256:ecc5949310d9dae4de64648a4ce529f86df1f232ce23dcfefe737c24d21dfbe9"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==0.9.1"
|
||||
},
|
||||
"httpx": {
|
||||
"hashes": [
|
||||
"sha256:32d930858eab677bc29a742aaa4f096de259f1c78c68a90ad11f5c3c04f08335",
|
||||
"sha256:3642bd13e90b80ba8a243a730275eb10a4c26ec96f5fc16b87e458d4ab21efae"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==0.13.3"
|
||||
},
|
||||
"hyperframe": {
|
||||
"hashes": [
|
||||
"sha256:5187962cb16dcc078f23cb5a4b110098d546c3f41ff2d4038a9896893bbd0b40",
|
||||
"sha256:a9f5c17f2cc3c719b917c4f33ed1c61bd1f8dfac4b1bd23b7c80b3400971b41f"
|
||||
],
|
||||
"version": "==5.2.0"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
|
||||
"sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.10"
|
||||
},
|
||||
"rfc3986": {
|
||||
"hashes": [
|
||||
"sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835",
|
||||
"sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"
|
||||
],
|
||||
"version": "==1.5.0"
|
||||
},
|
||||
"sniffio": {
|
||||
"hashes": [
|
||||
"sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2",
|
||||
"sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==1.3.1"
|
||||
}
|
||||
},
|
||||
"develop": {}
|
||||
|
||||
@@ -7,4 +7,5 @@ target 'alt-tab-macos' do
|
||||
pod 'ShortcutRecorder', :git => 'https://github.com/lwouis/ShortcutRecorder.git', :branch => 'alt-tab-current'
|
||||
pod 'SwiftyMarkdown', '1.1.0'
|
||||
pod 'AppCenter/Crashes', '4.3.0'
|
||||
pod 'SwiftyBeaver', '1.9.0'
|
||||
end
|
||||
@@ -5,6 +5,7 @@ PODS:
|
||||
- LetsMove (1.24)
|
||||
- ShortcutRecorder (3.1)
|
||||
- Sparkle (1.24.0)
|
||||
- SwiftyBeaver (1.9.0)
|
||||
- SwiftyMarkdown (1.1.0)
|
||||
|
||||
DEPENDENCIES:
|
||||
@@ -12,11 +13,13 @@ DEPENDENCIES:
|
||||
- LetsMove (from `https://github.com/lwouis/LetsMove.git`, commit `7abf4daed1a25218f2b52f2dfd190aee5a50071c`)
|
||||
- ShortcutRecorder (from `https://github.com/lwouis/ShortcutRecorder.git`, branch `alt-tab-current`)
|
||||
- Sparkle (from `https://raw.githubusercontent.com/lwouis/Sparkle/fix-iframe-popup-1.24/Sparkle.podspec`)
|
||||
- SwiftyBeaver (= 1.9.0)
|
||||
- SwiftyMarkdown (= 1.1.0)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- AppCenter
|
||||
- SwiftyBeaver
|
||||
- SwiftyMarkdown
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
@@ -42,8 +45,9 @@ SPEC CHECKSUMS:
|
||||
LetsMove: fefe56bc7bc7fb7d37049e28a14f297961229fc5
|
||||
ShortcutRecorder: fdf620aca34101b0cba3b10fca815e0459254189
|
||||
Sparkle: 1fa490bb4ab2a9b37cfb9ea4feec19dd33b81cd2
|
||||
SwiftyBeaver: d39e7c376bf15e9fffca8263b8fd1f42ea56aa2d
|
||||
SwiftyMarkdown: 033abc9dad9a37903f81aec5188cade436f40520
|
||||
|
||||
PODFILE CHECKSUM: db137cd4263235b552493949169a44f7e874a90f
|
||||
PODFILE CHECKSUM: 6e94a2963d935455790ee55be0ce938834d1bba5
|
||||
|
||||
COCOAPODS: 1.9.1
|
||||
@@ -5,6 +5,7 @@ PODS:
|
||||
- LetsMove (1.24)
|
||||
- ShortcutRecorder (3.1)
|
||||
- Sparkle (1.24.0)
|
||||
- SwiftyBeaver (1.9.0)
|
||||
- SwiftyMarkdown (1.1.0)
|
||||
|
||||
DEPENDENCIES:
|
||||
@@ -12,11 +13,13 @@ DEPENDENCIES:
|
||||
- LetsMove (from `https://github.com/lwouis/LetsMove.git`, commit `7abf4daed1a25218f2b52f2dfd190aee5a50071c`)
|
||||
- ShortcutRecorder (from `https://github.com/lwouis/ShortcutRecorder.git`, branch `alt-tab-current`)
|
||||
- Sparkle (from `https://raw.githubusercontent.com/lwouis/Sparkle/fix-iframe-popup-1.24/Sparkle.podspec`)
|
||||
- SwiftyBeaver (= 1.9.0)
|
||||
- SwiftyMarkdown (= 1.1.0)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- AppCenter
|
||||
- SwiftyBeaver
|
||||
- SwiftyMarkdown
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
@@ -42,8 +45,9 @@ SPEC CHECKSUMS:
|
||||
LetsMove: fefe56bc7bc7fb7d37049e28a14f297961229fc5
|
||||
ShortcutRecorder: fdf620aca34101b0cba3b10fca815e0459254189
|
||||
Sparkle: 1fa490bb4ab2a9b37cfb9ea4feec19dd33b81cd2
|
||||
SwiftyBeaver: d39e7c376bf15e9fffca8263b8fd1f42ea56aa2d
|
||||
SwiftyMarkdown: 033abc9dad9a37903f81aec5188cade436f40520
|
||||
|
||||
PODFILE CHECKSUM: db137cd4263235b552493949169a44f7e874a90f
|
||||
PODFILE CHECKSUM: 6e94a2963d935455790ee55be0ce938834d1bba5
|
||||
|
||||
COCOAPODS: 1.9.1
|
||||
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Sebastian Kreutzberger
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,270 @@
|
||||
<p align="center"><a href="https://swiftybeaver.com"><img src="https://cloud.githubusercontent.com/assets/564725/19889302/73b1ee84-a034-11e6-8753-2d060502397c.jpg" style="width: 888px;" alt="SwiftyBeaver"></a><br/><b>Colorful</b>, flexible, <b>lightweight</b> logging for Swift 3, Swift 4 & <b>Swift 5</b>.<br/>Great for <b>development & release</b> with support for Console, File & cloud platforms.<br/>Log <b>during release</b> to the conveniently built-in SwiftyBeaver Platform, the <b>dedicated Mac App</b> & <b>Elasticsearch</b>!<br/><br/><a href="http://docs.swiftybeaver.com">Docs</a> | <a href="https://swiftybeaver.com">Website</a> | <a href="https://twitter.com/SwiftyBeaver">Twitter</a> | <a href="#privacy">Privacy</a> | <a href="https://github.com/SwiftyBeaver/SwiftyBeaver/blob/master/LICENSE">License</a><br/></p>
|
||||
|
||||
<p align="center"><a href="https://swift.org" target="_blank"><img src="https://img.shields.io/badge/Language-Swift%203,%204%20&%205-orange.svg" alt="Language Swift 2, 3, 4 & 5"></a> <a href="https://circleci.com/gh/SwiftyBeaver/SwiftyBeaver" target="_blank"><img src="https://circleci.com/gh/SwiftyBeaver/SwiftyBeaver/tree/master.svg?style=shield" alt="CircleCI"/></a><br/><p>
|
||||
|
||||
----
|
||||
|
||||
<br/>
|
||||
|
||||
### During Development: Colored Logging to Xcode Console
|
||||
|
||||
<img src="https://cloud.githubusercontent.com/assets/564725/18608323/ac065a98-7ce6-11e6-8e1b-2a062d54a1d5.png" width="608">
|
||||
|
||||
[Learn more](http://docs.swiftybeaver.com/article/9-log-to-xcode-console) about colored logging to Xcode 8 Console with Swift 3, 4 & 5. For Swift 2.3 [use this Gist](https://gist.github.com/skreutzberger/7c396573796473ed1be2c6d15cafed34). **No need to hack Xcode 8 anymore** to get color. You can even customize the log level word (ATTENTION instead of ERROR maybe?), the general amount of displayed data and if you want to use the 💜s or replace them with something else 😉
|
||||
|
||||
<br/>
|
||||
|
||||
### During Development: Colored Logging to File
|
||||
|
||||
<img src="https://cloud.githubusercontent.com/assets/564725/18608325/b7ecd4c2-7ce6-11e6-829b-7f8f9fe6ef2f.png" width="738">
|
||||
|
||||
[Learn more](http://docs.swiftybeaver.com/article/10-log-to-file) about logging to file which is great for Terminal.app fans or to store logs on disk.
|
||||
|
||||
<br/>
|
||||
|
||||
|
||||
### On Release: Encrypted Logging to SwiftyBeaver Platform
|
||||
|
||||
<img src="https://cloud.githubusercontent.com/assets/564725/14281408/38d6a6ba-fb39-11e5-9584-34e3679bb1c5.jpg" width="700">
|
||||
|
||||
[Learn more](http://docs.swiftybeaver.com/article/11-log-to-swiftybeaver-platform) about logging to the SwiftyBeaver Platform **during release!**
|
||||
|
||||
<br/>
|
||||
|
||||
|
||||
### Browse, Search & Filter via Mac App
|
||||
|
||||

|
||||
|
||||
Conveniently access your logs during development & release with our [free Mac App](https://swiftybeaver.com).
|
||||
|
||||
<br/>
|
||||
|
||||
|
||||
### On Release: Enterprise-ready Logging to Your Elasticsearch & Kibana (on-premise)
|
||||
|
||||
<img src="https://user-images.githubusercontent.com/564725/34486363-dc501aec-efcf-11e7-92b2-1163cca9e7aa.jpg" width="700">
|
||||
|
||||
[Learn more](http://docs.swiftybeaver.com/article/34-enterprise-quick-start-via-docker) about **legally compliant**, end-to-end encrypted logging to Elasticsearch with **SwiftyBeaver Enterprise**. Install via Docker or manual, fully-featured free trial included!
|
||||
|
||||
<br/>
|
||||
|
||||
### Google Cloud & More
|
||||
|
||||
You can fully customize your log format, turn it into JSON, or create your own destinations. For example our [Google Cloud Destination](https://github.com/SwiftyBeaver/SwiftyBeaver/blob/master/Sources/GoogleCloudDestination.swift) is just another customized logging format which adds the powerful functionality of automatic server-side Swift logging when hosted on Google Cloud Platform.
|
||||
|
||||
<br/>
|
||||
|
||||
----
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
## Installation
|
||||
|
||||
- For **Swift 4 & 5** install the latest SwiftyBeaver version
|
||||
- For **Swift 3** install SwiftyBeaver 1.8.4
|
||||
- For **Swift 2** install SwiftyBeaver 0.7.0
|
||||
|
||||
<br/>
|
||||
|
||||
### Carthage
|
||||
|
||||
You can use [Carthage](https://github.com/Carthage/Carthage) to install SwiftyBeaver by adding that to your Cartfile:
|
||||
|
||||
Swift 4 & 5:
|
||||
``` Swift
|
||||
github "SwiftyBeaver/SwiftyBeaver"
|
||||
```
|
||||
|
||||
|
||||
Swift 3:
|
||||
``` Swift
|
||||
github "SwiftyBeaver/SwiftyBeaver" ~> 1.8.4
|
||||
```
|
||||
|
||||
Swift 2:
|
||||
``` Swift
|
||||
github "SwiftyBeaver/SwiftyBeaver" ~> 0.7
|
||||
```
|
||||
|
||||
<br/>
|
||||
|
||||
### Swift Package Manager
|
||||
|
||||
For [Swift Package Manager](https://swift.org/package-manager/) add the following package to your Package.swift file. Just Swift 4 & 5 are supported:
|
||||
|
||||
``` Swift
|
||||
.package(url: "https://github.com/SwiftyBeaver/SwiftyBeaver.git", .upToNextMajor(from: "1.9.0")),
|
||||
```
|
||||
|
||||
<br/>
|
||||
|
||||
### CocoaPods
|
||||
|
||||
To use [CocoaPods](https://cocoapods.org) just add this to your Podfile:
|
||||
|
||||
Swift 4 & 5:
|
||||
``` Swift
|
||||
pod 'SwiftyBeaver'
|
||||
```
|
||||
|
||||
Swift 3:
|
||||
``` Ruby
|
||||
target 'MyProject' do
|
||||
use_frameworks!
|
||||
|
||||
# Pods for MyProject
|
||||
pod 'SwiftyBeaver', '~> 1.8.4'
|
||||
end
|
||||
```
|
||||
|
||||
Swift 2:
|
||||
``` Ruby
|
||||
target 'MyProject' do
|
||||
use_frameworks!
|
||||
|
||||
# Pods for MyProject
|
||||
pod 'SwiftyBeaver', '~> 0.7'
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
installer.pods_project.build_configurations.each do |config|
|
||||
# Configure Pod targets for Xcode 8 with Swift 2.3
|
||||
config.build_settings['SWIFT_VERSION'] = '2.3'
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
## Usage
|
||||
|
||||
Add that near the top of your `AppDelegate.swift` to be able to use SwiftyBeaver in your whole project.
|
||||
|
||||
``` Swift
|
||||
import SwiftyBeaver
|
||||
let log = SwiftyBeaver.self
|
||||
|
||||
```
|
||||
|
||||
At the the beginning of your `AppDelegate:didFinishLaunchingWithOptions()` add the SwiftyBeaver log destinations (console, file, etc.), optionally adjust the [log format](http://docs.swiftybeaver.com//article/20-custom-format) and then you can already do the following log level calls globally:
|
||||
|
||||
``` Swift
|
||||
// add log destinations. at least one is needed!
|
||||
let console = ConsoleDestination() // log to Xcode Console
|
||||
let file = FileDestination() // log to default swiftybeaver.log file
|
||||
let cloud = SBPlatformDestination(appID: "foo", appSecret: "bar", encryptionKey: "123") // to cloud
|
||||
|
||||
// use custom format and set console output to short time, log level & message
|
||||
console.format = "$DHH:mm:ss$d $L $M"
|
||||
// or use this for JSON output: console.format = "$J"
|
||||
|
||||
// add the destinations to SwiftyBeaver
|
||||
log.addDestination(console)
|
||||
log.addDestination(file)
|
||||
log.addDestination(cloud)
|
||||
|
||||
// Now let’s log!
|
||||
log.verbose("not so important") // prio 1, VERBOSE in silver
|
||||
log.debug("something to debug") // prio 2, DEBUG in green
|
||||
log.info("a nice information") // prio 3, INFO in blue
|
||||
log.warning("oh no, that won’t be good") // prio 4, WARNING in yellow
|
||||
log.error("ouch, an error did occur!") // prio 5, ERROR in red
|
||||
|
||||
// log anything!
|
||||
log.verbose(123)
|
||||
log.info(-123.45678)
|
||||
log.warning(Date())
|
||||
log.error(["I", "like", "logs!"])
|
||||
log.error(["name": "Mr Beaver", "address": "7 Beaver Lodge"])
|
||||
|
||||
// optionally add context to a log message
|
||||
console.format = "$L: $M $X"
|
||||
log.debug("age", 123) // "DEBUG: age 123"
|
||||
log.info("my data", context: [1, "a", 2]) // "INFO: my data [1, \"a\", 2]"
|
||||
|
||||
```
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
## Server-side Swift
|
||||
|
||||
We ❤️ server-side Swift 4 & 5 and SwiftyBeaver supports it **out-of-the-box**! Try for yourself and run SwiftyBeaver inside a Ubuntu Docker container. Just install Docker and then go to your the project folder on macOS or Ubuntu and type:
|
||||
|
||||
```shell
|
||||
# create docker image, build SwiftyBeaver and run unit tests
|
||||
swift build --clean && docker build --rm -t swiftybeaver .
|
||||
|
||||
# optionally log into container to run Swift CLI and do more stuff
|
||||
docker run --rm -it --privileged=true -v $PWD:/app swiftybeaver
|
||||
```
|
||||
|
||||
Best: for the popular server-side Swift web framework [Vapor](https://github.com/vapor/vapor) you can use **[our Vapor logging provider](https://github.com/SwiftyBeaver/SwiftyBeaver-Vapor)** which makes server logging awesome again 🙌
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
## Documentation
|
||||
|
||||
**Getting Started:**
|
||||
|
||||
- [Features](http://docs.swiftybeaver.com/article/7-introduction)
|
||||
- [Installation](http://docs.swiftybeaver.com/article/5-installation)
|
||||
- [Basic Setup](http://docs.swiftybeaver.com/article/6-basic-setup)
|
||||
|
||||
**Logging Destinations:**
|
||||
|
||||
- [Colored Logging to Xcode Console](http://docs.swiftybeaver.com/article/9-log-to-xcode-console)
|
||||
- [Colored Logging to File](http://docs.swiftybeaver.com/article/10-log-to-file)
|
||||
- [Encrypted Logging & Analytics to SwiftyBeaver Platform](http://docs.swiftybeaver.com/article/11-log-to-swiftybeaver-platform)
|
||||
- [Encrypted Logging & Analytics to Elasticsearch & Kibana](http://docs.swiftybeaver.com/article/34-enterprise-quick-start-via-docker)
|
||||
|
||||
|
||||
**Advanced Topics:**
|
||||
|
||||
- [Custom Format & Context](http://docs.swiftybeaver.com/article/20-custom-format)
|
||||
- [Filters](http://docs.swiftybeaver.com/article/21-filters)
|
||||
|
||||
**Stay Informed:**
|
||||
|
||||
- [Official Website](https://swiftybeaver.com))
|
||||
- [On Twitter](https://twitter.com/SwiftyBeaver)
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
## Privacy
|
||||
|
||||
**SwiftyBeaver is not collecting any data without you as a developer knowing about it**. That's why it is **open-source** and developed in a simple way to be easy to inspect and check what it is actually doing under the hood.
|
||||
|
||||
The only sending to servers is done if you use the `SBPlatformDestination`. That destination is meant for production logging and on default it sends your logs plus additional device information **end-to-end encrypted** to our cloud service. Our cloud service **can not decrypt the data**.
|
||||
|
||||
Instead, you install our Mac App and that Mac App downloads the encrypted logs from the cloud and decrypts and shows them to you. Additionally, the Mac App stores all data that it downloads in a local SQLite database file on your computer so that you actually "physically" own your data.
|
||||
|
||||
The business model of the SwiftyBeaver cloud service is to provide the most secure logging solution in the market. On purpose we do not provide a web UI for you because it would require us to store your encryption key on our servers.
|
||||
|
||||
**Only you can see the logging and device data** which is sent from your users' devices. Our servers just see encrypted data and do not know your decryption key.
|
||||
|
||||
SwiftyBeaver is **fully GDPR compliant** due to its focus on encryption and transparency in what data is collected and also meets **Apple’s latest requirements** on the privacy of 3rd party frameworks.
|
||||
|
||||
Our Enterprise offering is an even more secure solution where you are not using anymore our cloud service and Mac App but you send your end-to-end encrypted logs directly to your own servers and you store them in your Elasticsearch cluster. The **Enterprise offering is used by health tech** and governmental institutions which require the highest level of privacy and security.
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
## End-to-End Encryption
|
||||
|
||||
SwiftyBeaver is using symmetric AES256CBC encryption in the `SBPlatformDestination` destination. No other officially supported destination uses encryption.
|
||||
|
||||
The encryption used in the `SBPlatformDestination` destination is end-to-end. The open-source SwiftyBeaver logging framework symmetrically encrypts all logging data on your client's device inside your app (iPhone, iPad, ...) before it is sent to the SwiftyBeaver Crypto Cloud. The decryption is done on your Mac which has the SwiftyBeaver Mac App installed. All logging data stays encrypted in the SwiftyBeaver Crypto Cloud due to the lack of the password.
|
||||
|
||||
You are using the encryption at your own risk. SwiftyBeaver’s authors and contributors do not take over any guarantee about the absence of potential security or cryptopgraphy issues, weaknesses, etc.; please also read the LICENSE file for details. Also if you are interested in cryptography in general, please have a look at the file AES256CBC.swift to learn more about the cryptographical implementation.
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
## License
|
||||
|
||||
SwiftyBeaver Framework is released under the [MIT License](https://github.com/SwiftyBeaver/SwiftyBeaver/blob/master/LICENSE).
|
||||
@@ -0,0 +1,788 @@
|
||||
//
|
||||
// AES256CBC.swift
|
||||
// AES256CBC https://github.com/SwiftyBeaver/AES256CBC
|
||||
//
|
||||
// Created by Sebastian Kreutzberger on 2/9/16.
|
||||
// Copyright © 2016 SwiftyBeaver. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
final class AES256CBC {
|
||||
|
||||
/// returns optional encrypted string via AES-256CBC
|
||||
/// automatically generates and puts a random IV at first 16 chars
|
||||
/// the password must be exactly 32 chars long for AES-256
|
||||
class func encryptString(_ str: String, password: String) -> String? {
|
||||
if !str.isEmpty && Data(password.utf8).count == 32 {
|
||||
let iv = randomText(16)
|
||||
let key = password
|
||||
|
||||
guard let encryptedString = try? aesEncrypt(str, key: key, iv: iv) else {
|
||||
print("an error occured while encrypting")
|
||||
return nil
|
||||
}
|
||||
return iv + encryptedString
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/// returns optional decrypted string via AES-256CBC
|
||||
/// IV need to be at first 16 chars, password must be 32 chars long
|
||||
class func decryptString(_ str: String, password: String) -> String? {
|
||||
if Data(str.utf8).count > 16 && Data(password.utf8).count == 32 {
|
||||
// get AES initialization vector from first 16 chars
|
||||
let iv = String(str.prefix(16))
|
||||
let encryptedString = str.replacingOccurrences(of: iv, with: "",
|
||||
options: String.CompareOptions.literal,
|
||||
range: nil) // remove IV
|
||||
|
||||
guard let decryptedString = try? aesDecrypt(encryptedString, key: password, iv: iv) else {
|
||||
print("an error occured while decrypting")
|
||||
return nil
|
||||
}
|
||||
return decryptedString
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/// returns random string (uppercase & lowercase, no spaces) of 32 characters length
|
||||
/// which can be used as SHA-256 compatbile password
|
||||
class func generatePassword() -> String {
|
||||
return randomText(32)
|
||||
}
|
||||
|
||||
/// returns random text of a defined length
|
||||
public class func randomText(_ length: Int) -> String {
|
||||
let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
return String((0..<length).map { _ in letters.randomElement()!})
|
||||
}
|
||||
|
||||
/// returns encrypted string, IV must be 16 chars long
|
||||
fileprivate class func aesEncrypt(_ str: String, key: String, iv: String) throws -> String {
|
||||
let keyData = key.data(using: String.Encoding.utf8)!
|
||||
let ivData = iv.data(using: String.Encoding.utf8)!
|
||||
let data = str.data(using: String.Encoding.utf8)!
|
||||
#if swift(>=5)
|
||||
let enc = try Data(AESCipher(key: keyData.bytes,
|
||||
iv: ivData.bytes).encrypt(bytes: data.bytes))
|
||||
#else
|
||||
let enc = try Data(bytes: AESCipher(key: keyData.bytes,
|
||||
iv: ivData.bytes).encrypt(bytes: data.bytes))
|
||||
#endif
|
||||
return enc.base64EncodedString(options: [])
|
||||
}
|
||||
|
||||
/// returns decrypted string, IV must be 16 chars long
|
||||
fileprivate class func aesDecrypt(_ str: String, key: String, iv: String) throws -> String {
|
||||
let keyData = key.data(using: String.Encoding.utf8)!
|
||||
let ivData = iv.data(using: String.Encoding.utf8)!
|
||||
if let data = Data(base64Encoded: str) {
|
||||
#if swift(>=5)
|
||||
let dec = try Data(AESCipher(key: keyData.bytes,
|
||||
iv: ivData.bytes).decrypt(bytes: data.bytes))
|
||||
#else
|
||||
let dec = try Data(bytes: AESCipher(key: keyData.bytes,
|
||||
iv: ivData.bytes).decrypt(bytes: data.bytes))
|
||||
#endif
|
||||
guard let decryptStr = String(data: dec, encoding: String.Encoding.utf8) else {
|
||||
throw NSError(domain: "Invalid utf8 data", code: 0, userInfo: nil)
|
||||
}
|
||||
return decryptStr
|
||||
} else {
|
||||
return "error"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// swiftlint:disable line_length
|
||||
// The following is an altered source version that only includes AES-CBC. The original software can be found at:
|
||||
// https://github.com/krzyzanowskim/CryptoSwift
|
||||
//
|
||||
// This is the original copyright notice:
|
||||
//
|
||||
// Copyright (C) 2014 Marcin Krzyżanowski <marcin.krzyzanowski@gmail.com>
|
||||
// This software is provided 'as-is', without any express or implied warranty.
|
||||
//
|
||||
// In no event will the authors be held liable for any damages arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
|
||||
//
|
||||
// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required.
|
||||
// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
|
||||
// - This notice may not be removed or altered from any source or binary distribution.
|
||||
//
|
||||
// swiftlint:enable line_length
|
||||
|
||||
// MARK: - AESCipher
|
||||
|
||||
private typealias Key = Array<UInt8>
|
||||
|
||||
final private class AESCipher {
|
||||
|
||||
enum Error: Swift.Error {
|
||||
case blockSizeExceeded
|
||||
case dataPaddingRequired
|
||||
case invalidKeyOrInitializationVector
|
||||
case invalidInitializationVector
|
||||
}
|
||||
|
||||
enum Variant: Int {
|
||||
case aes128 = 1, aes192, aes256
|
||||
|
||||
var Nk: Int { // Nk words
|
||||
return [4, 6, 8][self.rawValue - 1]
|
||||
}
|
||||
|
||||
var Nb: Int { // Nb words
|
||||
return 4
|
||||
}
|
||||
|
||||
var Nr: Int { // Nr
|
||||
return Nk + 6
|
||||
}
|
||||
}
|
||||
|
||||
static let blockSize: Int = 16 // 128 /8
|
||||
|
||||
var variant: Variant {
|
||||
switch self.key.count * 8 {
|
||||
case 128:
|
||||
return .aes128
|
||||
case 192:
|
||||
return .aes192
|
||||
case 256:
|
||||
return .aes256
|
||||
default:
|
||||
preconditionFailure("Unknown AES variant for given key.")
|
||||
}
|
||||
}
|
||||
private let key: [UInt8]
|
||||
private let iv: [UInt8]?
|
||||
private let blockMode = CBCBlockMode()
|
||||
private lazy var expandedKey: Array<Array<UInt32>> = self.expandKey(self.key, variant: self.variant)
|
||||
private lazy var expandedKeyInv: Array<Array<UInt32>> = self.expandKeyInv(self.key, variant: self.variant)
|
||||
|
||||
private lazy var sBoxes:(sBox: [UInt32], invSBox: [UInt32]) = self.calculateSBox()
|
||||
private lazy var sBox: [UInt32] = self.sBoxes.sBox
|
||||
private lazy var sBoxInv: [UInt32] = self.sBoxes.invSBox
|
||||
|
||||
// Parameters for Linear Congruence Generators
|
||||
private let Rcon: [UInt8] = [
|
||||
0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a,
|
||||
0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39,
|
||||
0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a,
|
||||
0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8,
|
||||
0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef,
|
||||
0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc,
|
||||
0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b,
|
||||
0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3,
|
||||
0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94,
|
||||
0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20,
|
||||
0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35,
|
||||
0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f,
|
||||
0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04,
|
||||
0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63,
|
||||
0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd,
|
||||
0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d]
|
||||
|
||||
// swiftlint:disable line_length
|
||||
fileprivate let T0: Array<UInt32> = [0xa56363c6, 0x847c7cf8, 0x997777ee, 0x8d7b7bf6, 0xdf2f2ff, 0xbd6b6bd6, 0xb16f6fde, 0x54c5c591, 0x50303060, 0x3010102, 0xa96767ce, 0x7d2b2b56, 0x19fefee7, 0x62d7d7b5, 0xe6abab4d, 0x9a7676ec, 0x45caca8f, 0x9d82821f, 0x40c9c989, 0x877d7dfa, 0x15fafaef, 0xeb5959b2, 0xc947478e, 0xbf0f0fb, 0xecadad41, 0x67d4d4b3, 0xfda2a25f, 0xeaafaf45, 0xbf9c9c23, 0xf7a4a453, 0x967272e4, 0x5bc0c09b, 0xc2b7b775, 0x1cfdfde1, 0xae93933d, 0x6a26264c, 0x5a36366c, 0x413f3f7e, 0x2f7f7f5, 0x4fcccc83, 0x5c343468, 0xf4a5a551, 0x34e5e5d1, 0x8f1f1f9, 0x937171e2, 0x73d8d8ab, 0x53313162, 0x3f15152a, 0xc040408, 0x52c7c795, 0x65232346, 0x5ec3c39d, 0x28181830, 0xa1969637, 0xf05050a, 0xb59a9a2f, 0x907070e, 0x36121224, 0x9b80801b, 0x3de2e2df, 0x26ebebcd, 0x6927274e, 0xcdb2b27f, 0x9f7575ea, 0x1b090912, 0x9e83831d, 0x742c2c58, 0x2e1a1a34, 0x2d1b1b36, 0xb26e6edc, 0xee5a5ab4, 0xfba0a05b, 0xf65252a4, 0x4d3b3b76, 0x61d6d6b7, 0xceb3b37d, 0x7b292952, 0x3ee3e3dd, 0x712f2f5e, 0x97848413, 0xf55353a6, 0x68d1d1b9, 0x0, 0x2cededc1, 0x60202040, 0x1ffcfce3, 0xc8b1b179, 0xed5b5bb6, 0xbe6a6ad4, 0x46cbcb8d, 0xd9bebe67, 0x4b393972, 0xde4a4a94, 0xd44c4c98, 0xe85858b0, 0x4acfcf85, 0x6bd0d0bb, 0x2aefefc5, 0xe5aaaa4f, 0x16fbfbed, 0xc5434386, 0xd74d4d9a, 0x55333366, 0x94858511, 0xcf45458a, 0x10f9f9e9, 0x6020204, 0x817f7ffe, 0xf05050a0, 0x443c3c78, 0xba9f9f25, 0xe3a8a84b, 0xf35151a2, 0xfea3a35d, 0xc0404080, 0x8a8f8f05, 0xad92923f, 0xbc9d9d21, 0x48383870, 0x4f5f5f1, 0xdfbcbc63, 0xc1b6b677, 0x75dadaaf, 0x63212142, 0x30101020, 0x1affffe5, 0xef3f3fd, 0x6dd2d2bf, 0x4ccdcd81, 0x140c0c18, 0x35131326, 0x2fececc3, 0xe15f5fbe, 0xa2979735, 0xcc444488, 0x3917172e, 0x57c4c493, 0xf2a7a755, 0x827e7efc, 0x473d3d7a, 0xac6464c8, 0xe75d5dba, 0x2b191932, 0x957373e6, 0xa06060c0, 0x98818119, 0xd14f4f9e, 0x7fdcdca3, 0x66222244, 0x7e2a2a54, 0xab90903b, 0x8388880b, 0xca46468c, 0x29eeeec7, 0xd3b8b86b, 0x3c141428, 0x79dedea7, 0xe25e5ebc, 0x1d0b0b16, 0x76dbdbad, 0x3be0e0db, 0x56323264, 0x4e3a3a74, 0x1e0a0a14, 0xdb494992, 0xa06060c, 0x6c242448, 0xe45c5cb8, 0x5dc2c29f, 0x6ed3d3bd, 0xefacac43, 0xa66262c4, 0xa8919139, 0xa4959531, 0x37e4e4d3, 0x8b7979f2, 0x32e7e7d5, 0x43c8c88b, 0x5937376e, 0xb76d6dda, 0x8c8d8d01, 0x64d5d5b1, 0xd24e4e9c, 0xe0a9a949, 0xb46c6cd8, 0xfa5656ac, 0x7f4f4f3, 0x25eaeacf, 0xaf6565ca, 0x8e7a7af4, 0xe9aeae47, 0x18080810, 0xd5baba6f, 0x887878f0, 0x6f25254a, 0x722e2e5c, 0x241c1c38, 0xf1a6a657, 0xc7b4b473, 0x51c6c697, 0x23e8e8cb, 0x7cdddda1, 0x9c7474e8, 0x211f1f3e, 0xdd4b4b96, 0xdcbdbd61, 0x868b8b0d, 0x858a8a0f, 0x907070e0, 0x423e3e7c, 0xc4b5b571, 0xaa6666cc, 0xd8484890, 0x5030306, 0x1f6f6f7, 0x120e0e1c, 0xa36161c2, 0x5f35356a, 0xf95757ae, 0xd0b9b969, 0x91868617, 0x58c1c199, 0x271d1d3a, 0xb99e9e27, 0x38e1e1d9, 0x13f8f8eb, 0xb398982b, 0x33111122, 0xbb6969d2, 0x70d9d9a9, 0x898e8e07, 0xa7949433, 0xb69b9b2d, 0x221e1e3c, 0x92878715, 0x20e9e9c9, 0x49cece87, 0xff5555aa, 0x78282850, 0x7adfdfa5, 0x8f8c8c03, 0xf8a1a159, 0x80898909, 0x170d0d1a, 0xdabfbf65, 0x31e6e6d7, 0xc6424284, 0xb86868d0, 0xc3414182, 0xb0999929, 0x772d2d5a, 0x110f0f1e, 0xcbb0b07b, 0xfc5454a8, 0xd6bbbb6d, 0x3a16162c]
|
||||
fileprivate let T0_INV: Array<UInt32> = [0x50a7f451, 0x5365417e, 0xc3a4171a, 0x965e273a, 0xcb6bab3b, 0xf1459d1f, 0xab58faac, 0x9303e34b, 0x55fa3020, 0xf66d76ad, 0x9176cc88, 0x254c02f5, 0xfcd7e54f, 0xd7cb2ac5, 0x80443526, 0x8fa362b5, 0x495ab1de, 0x671bba25, 0x980eea45, 0xe1c0fe5d, 0x2752fc3, 0x12f04c81, 0xa397468d, 0xc6f9d36b, 0xe75f8f03, 0x959c9215, 0xeb7a6dbf, 0xda595295, 0x2d83bed4, 0xd3217458, 0x2969e049, 0x44c8c98e, 0x6a89c275, 0x78798ef4, 0x6b3e5899, 0xdd71b927, 0xb64fe1be, 0x17ad88f0, 0x66ac20c9, 0xb43ace7d, 0x184adf63, 0x82311ae5, 0x60335197, 0x457f5362, 0xe07764b1, 0x84ae6bbb, 0x1ca081fe, 0x942b08f9, 0x58684870, 0x19fd458f, 0x876cde94, 0xb7f87b52, 0x23d373ab, 0xe2024b72, 0x578f1fe3, 0x2aab5566, 0x728ebb2, 0x3c2b52f, 0x9a7bc586, 0xa50837d3, 0xf2872830, 0xb2a5bf23, 0xba6a0302, 0x5c8216ed, 0x2b1ccf8a, 0x92b479a7, 0xf0f207f3, 0xa1e2694e, 0xcdf4da65, 0xd5be0506, 0x1f6234d1, 0x8afea6c4, 0x9d532e34, 0xa055f3a2, 0x32e18a05, 0x75ebf6a4, 0x39ec830b, 0xaaef6040, 0x69f715e, 0x51106ebd, 0xf98a213e, 0x3d06dd96, 0xae053edd, 0x46bde64d, 0xb58d5491, 0x55dc471, 0x6fd40604, 0xff155060, 0x24fb9819, 0x97e9bdd6, 0xcc434089, 0x779ed967, 0xbd42e8b0, 0x888b8907, 0x385b19e7, 0xdbeec879, 0x470a7ca1, 0xe90f427c, 0xc91e84f8, 0x0, 0x83868009, 0x48ed2b32, 0xac70111e, 0x4e725a6c, 0xfbff0efd, 0x5638850f, 0x1ed5ae3d, 0x27392d36, 0x64d90f0a, 0x21a65c68, 0xd1545b9b, 0x3a2e3624, 0xb1670a0c, 0xfe75793, 0xd296eeb4, 0x9e919b1b, 0x4fc5c080, 0xa220dc61, 0x694b775a, 0x161a121c, 0xaba93e2, 0xe52aa0c0, 0x43e0223c, 0x1d171b12, 0xb0d090e, 0xadc78bf2, 0xb9a8b62d, 0xc8a91e14, 0x8519f157, 0x4c0775af, 0xbbdd99ee, 0xfd607fa3, 0x9f2601f7, 0xbcf5725c, 0xc53b6644, 0x347efb5b, 0x7629438b, 0xdcc623cb, 0x68fcedb6, 0x63f1e4b8, 0xcadc31d7, 0x10856342, 0x40229713, 0x2011c684, 0x7d244a85, 0xf83dbbd2, 0x1132f9ae, 0x6da129c7, 0x4b2f9e1d, 0xf330b2dc, 0xec52860d, 0xd0e3c177, 0x6c16b32b, 0x99b970a9, 0xfa489411, 0x2264e947, 0xc48cfca8, 0x1a3ff0a0, 0xd82c7d56, 0xef903322, 0xc74e4987, 0xc1d138d9, 0xfea2ca8c, 0x360bd498, 0xcf81f5a6, 0x28de7aa5, 0x268eb7da, 0xa4bfad3f, 0xe49d3a2c, 0xd927850, 0x9bcc5f6a, 0x62467e54, 0xc2138df6, 0xe8b8d890, 0x5ef7392e, 0xf5afc382, 0xbe805d9f, 0x7c93d069, 0xa92dd56f, 0xb31225cf, 0x3b99acc8, 0xa77d1810, 0x6e639ce8, 0x7bbb3bdb, 0x97826cd, 0xf418596e, 0x1b79aec, 0xa89a4f83, 0x656e95e6, 0x7ee6ffaa, 0x8cfbc21, 0xe6e815ef, 0xd99be7ba, 0xce366f4a, 0xd4099fea, 0xd67cb029, 0xafb2a431, 0x31233f2a, 0x3094a5c6, 0xc066a235, 0x37bc4e74, 0xa6ca82fc, 0xb0d090e0, 0x15d8a733, 0x4a9804f1, 0xf7daec41, 0xe50cd7f, 0x2ff69117, 0x8dd64d76, 0x4db0ef43, 0x544daacc, 0xdf0496e4, 0xe3b5d19e, 0x1b886a4c, 0xb81f2cc1, 0x7f516546, 0x4ea5e9d, 0x5d358c01, 0x737487fa, 0x2e410bfb, 0x5a1d67b3, 0x52d2db92, 0x335610e9, 0x1347d66d, 0x8c61d79a, 0x7a0ca137, 0x8e14f859, 0x893c13eb, 0xee27a9ce, 0x35c961b7, 0xede51ce1, 0x3cb1477a, 0x59dfd29c, 0x3f73f255, 0x79ce1418, 0xbf37c773, 0xeacdf753, 0x5baafd5f, 0x146f3ddf, 0x86db4478, 0x81f3afca, 0x3ec468b9, 0x2c342438, 0x5f40a3c2, 0x72c31d16, 0xc25e2bc, 0x8b493c28, 0x41950dff, 0x7101a839, 0xdeb30c08, 0x9ce4b4d8, 0x90c15664, 0x6184cb7b, 0x70b632d5, 0x745c6c48, 0x4257b8d0]
|
||||
fileprivate let T1: Array<UInt32> = [0x6363c6a5, 0x7c7cf884, 0x7777ee99, 0x7b7bf68d, 0xf2f2ff0d, 0x6b6bd6bd, 0x6f6fdeb1, 0xc5c59154, 0x30306050, 0x1010203, 0x6767cea9, 0x2b2b567d, 0xfefee719, 0xd7d7b562, 0xabab4de6, 0x7676ec9a, 0xcaca8f45, 0x82821f9d, 0xc9c98940, 0x7d7dfa87, 0xfafaef15, 0x5959b2eb, 0x47478ec9, 0xf0f0fb0b, 0xadad41ec, 0xd4d4b367, 0xa2a25ffd, 0xafaf45ea, 0x9c9c23bf, 0xa4a453f7, 0x7272e496, 0xc0c09b5b, 0xb7b775c2, 0xfdfde11c, 0x93933dae, 0x26264c6a, 0x36366c5a, 0x3f3f7e41, 0xf7f7f502, 0xcccc834f, 0x3434685c, 0xa5a551f4, 0xe5e5d134, 0xf1f1f908, 0x7171e293, 0xd8d8ab73, 0x31316253, 0x15152a3f, 0x404080c, 0xc7c79552, 0x23234665, 0xc3c39d5e, 0x18183028, 0x969637a1, 0x5050a0f, 0x9a9a2fb5, 0x7070e09, 0x12122436, 0x80801b9b, 0xe2e2df3d, 0xebebcd26, 0x27274e69, 0xb2b27fcd, 0x7575ea9f, 0x909121b, 0x83831d9e, 0x2c2c5874, 0x1a1a342e, 0x1b1b362d, 0x6e6edcb2, 0x5a5ab4ee, 0xa0a05bfb, 0x5252a4f6, 0x3b3b764d, 0xd6d6b761, 0xb3b37dce, 0x2929527b, 0xe3e3dd3e, 0x2f2f5e71, 0x84841397, 0x5353a6f5, 0xd1d1b968, 0x0, 0xededc12c, 0x20204060, 0xfcfce31f, 0xb1b179c8, 0x5b5bb6ed, 0x6a6ad4be, 0xcbcb8d46, 0xbebe67d9, 0x3939724b, 0x4a4a94de, 0x4c4c98d4, 0x5858b0e8, 0xcfcf854a, 0xd0d0bb6b, 0xefefc52a, 0xaaaa4fe5, 0xfbfbed16, 0x434386c5, 0x4d4d9ad7, 0x33336655, 0x85851194, 0x45458acf, 0xf9f9e910, 0x2020406, 0x7f7ffe81, 0x5050a0f0, 0x3c3c7844, 0x9f9f25ba, 0xa8a84be3, 0x5151a2f3, 0xa3a35dfe, 0x404080c0, 0x8f8f058a, 0x92923fad, 0x9d9d21bc, 0x38387048, 0xf5f5f104, 0xbcbc63df, 0xb6b677c1, 0xdadaaf75, 0x21214263, 0x10102030, 0xffffe51a, 0xf3f3fd0e, 0xd2d2bf6d, 0xcdcd814c, 0xc0c1814, 0x13132635, 0xececc32f, 0x5f5fbee1, 0x979735a2, 0x444488cc, 0x17172e39, 0xc4c49357, 0xa7a755f2, 0x7e7efc82, 0x3d3d7a47, 0x6464c8ac, 0x5d5dbae7, 0x1919322b, 0x7373e695, 0x6060c0a0, 0x81811998, 0x4f4f9ed1, 0xdcdca37f, 0x22224466, 0x2a2a547e, 0x90903bab, 0x88880b83, 0x46468cca, 0xeeeec729, 0xb8b86bd3, 0x1414283c, 0xdedea779, 0x5e5ebce2, 0xb0b161d, 0xdbdbad76, 0xe0e0db3b, 0x32326456, 0x3a3a744e, 0xa0a141e, 0x494992db, 0x6060c0a, 0x2424486c, 0x5c5cb8e4, 0xc2c29f5d, 0xd3d3bd6e, 0xacac43ef, 0x6262c4a6, 0x919139a8, 0x959531a4, 0xe4e4d337, 0x7979f28b, 0xe7e7d532, 0xc8c88b43, 0x37376e59, 0x6d6ddab7, 0x8d8d018c, 0xd5d5b164, 0x4e4e9cd2, 0xa9a949e0, 0x6c6cd8b4, 0x5656acfa, 0xf4f4f307, 0xeaeacf25, 0x6565caaf, 0x7a7af48e, 0xaeae47e9, 0x8081018, 0xbaba6fd5, 0x7878f088, 0x25254a6f, 0x2e2e5c72, 0x1c1c3824, 0xa6a657f1, 0xb4b473c7, 0xc6c69751, 0xe8e8cb23, 0xdddda17c, 0x7474e89c, 0x1f1f3e21, 0x4b4b96dd, 0xbdbd61dc, 0x8b8b0d86, 0x8a8a0f85, 0x7070e090, 0x3e3e7c42, 0xb5b571c4, 0x6666ccaa, 0x484890d8, 0x3030605, 0xf6f6f701, 0xe0e1c12, 0x6161c2a3, 0x35356a5f, 0x5757aef9, 0xb9b969d0, 0x86861791, 0xc1c19958, 0x1d1d3a27, 0x9e9e27b9, 0xe1e1d938, 0xf8f8eb13, 0x98982bb3, 0x11112233, 0x6969d2bb, 0xd9d9a970, 0x8e8e0789, 0x949433a7, 0x9b9b2db6, 0x1e1e3c22, 0x87871592, 0xe9e9c920, 0xcece8749, 0x5555aaff, 0x28285078, 0xdfdfa57a, 0x8c8c038f, 0xa1a159f8, 0x89890980, 0xd0d1a17, 0xbfbf65da, 0xe6e6d731, 0x424284c6, 0x6868d0b8, 0x414182c3, 0x999929b0, 0x2d2d5a77, 0xf0f1e11, 0xb0b07bcb, 0x5454a8fc, 0xbbbb6dd6, 0x16162c3a]
|
||||
fileprivate let T1_INV: Array<UInt32> = [0xa7f45150, 0x65417e53, 0xa4171ac3, 0x5e273a96, 0x6bab3bcb, 0x459d1ff1, 0x58faacab, 0x3e34b93, 0xfa302055, 0x6d76adf6, 0x76cc8891, 0x4c02f525, 0xd7e54ffc, 0xcb2ac5d7, 0x44352680, 0xa362b58f, 0x5ab1de49, 0x1bba2567, 0xeea4598, 0xc0fe5de1, 0x752fc302, 0xf04c8112, 0x97468da3, 0xf9d36bc6, 0x5f8f03e7, 0x9c921595, 0x7a6dbfeb, 0x595295da, 0x83bed42d, 0x217458d3, 0x69e04929, 0xc8c98e44, 0x89c2756a, 0x798ef478, 0x3e58996b, 0x71b927dd, 0x4fe1beb6, 0xad88f017, 0xac20c966, 0x3ace7db4, 0x4adf6318, 0x311ae582, 0x33519760, 0x7f536245, 0x7764b1e0, 0xae6bbb84, 0xa081fe1c, 0x2b08f994, 0x68487058, 0xfd458f19, 0x6cde9487, 0xf87b52b7, 0xd373ab23, 0x24b72e2, 0x8f1fe357, 0xab55662a, 0x28ebb207, 0xc2b52f03, 0x7bc5869a, 0x837d3a5, 0x872830f2, 0xa5bf23b2, 0x6a0302ba, 0x8216ed5c, 0x1ccf8a2b, 0xb479a792, 0xf207f3f0, 0xe2694ea1, 0xf4da65cd, 0xbe0506d5, 0x6234d11f, 0xfea6c48a, 0x532e349d, 0x55f3a2a0, 0xe18a0532, 0xebf6a475, 0xec830b39, 0xef6040aa, 0x9f715e06, 0x106ebd51, 0x8a213ef9, 0x6dd963d, 0x53eddae, 0xbde64d46, 0x8d5491b5, 0x5dc47105, 0xd406046f, 0x155060ff, 0xfb981924, 0xe9bdd697, 0x434089cc, 0x9ed96777, 0x42e8b0bd, 0x8b890788, 0x5b19e738, 0xeec879db, 0xa7ca147, 0xf427ce9, 0x1e84f8c9, 0x0, 0x86800983, 0xed2b3248, 0x70111eac, 0x725a6c4e, 0xff0efdfb, 0x38850f56, 0xd5ae3d1e, 0x392d3627, 0xd90f0a64, 0xa65c6821, 0x545b9bd1, 0x2e36243a, 0x670a0cb1, 0xe757930f, 0x96eeb4d2, 0x919b1b9e, 0xc5c0804f, 0x20dc61a2, 0x4b775a69, 0x1a121c16, 0xba93e20a, 0x2aa0c0e5, 0xe0223c43, 0x171b121d, 0xd090e0b, 0xc78bf2ad, 0xa8b62db9, 0xa91e14c8, 0x19f15785, 0x775af4c, 0xdd99eebb, 0x607fa3fd, 0x2601f79f, 0xf5725cbc, 0x3b6644c5, 0x7efb5b34, 0x29438b76, 0xc623cbdc, 0xfcedb668, 0xf1e4b863, 0xdc31d7ca, 0x85634210, 0x22971340, 0x11c68420, 0x244a857d, 0x3dbbd2f8, 0x32f9ae11, 0xa129c76d, 0x2f9e1d4b, 0x30b2dcf3, 0x52860dec, 0xe3c177d0, 0x16b32b6c, 0xb970a999, 0x489411fa, 0x64e94722, 0x8cfca8c4, 0x3ff0a01a, 0x2c7d56d8, 0x903322ef, 0x4e4987c7, 0xd138d9c1, 0xa2ca8cfe, 0xbd49836, 0x81f5a6cf, 0xde7aa528, 0x8eb7da26, 0xbfad3fa4, 0x9d3a2ce4, 0x9278500d, 0xcc5f6a9b, 0x467e5462, 0x138df6c2, 0xb8d890e8, 0xf7392e5e, 0xafc382f5, 0x805d9fbe, 0x93d0697c, 0x2dd56fa9, 0x1225cfb3, 0x99acc83b, 0x7d1810a7, 0x639ce86e, 0xbb3bdb7b, 0x7826cd09, 0x18596ef4, 0xb79aec01, 0x9a4f83a8, 0x6e95e665, 0xe6ffaa7e, 0xcfbc2108, 0xe815efe6, 0x9be7bad9, 0x366f4ace, 0x99fead4, 0x7cb029d6, 0xb2a431af, 0x233f2a31, 0x94a5c630, 0x66a235c0, 0xbc4e7437, 0xca82fca6, 0xd090e0b0, 0xd8a73315, 0x9804f14a, 0xdaec41f7, 0x50cd7f0e, 0xf691172f, 0xd64d768d, 0xb0ef434d, 0x4daacc54, 0x496e4df, 0xb5d19ee3, 0x886a4c1b, 0x1f2cc1b8, 0x5165467f, 0xea5e9d04, 0x358c015d, 0x7487fa73, 0x410bfb2e, 0x1d67b35a, 0xd2db9252, 0x5610e933, 0x47d66d13, 0x61d79a8c, 0xca1377a, 0x14f8598e, 0x3c13eb89, 0x27a9ceee, 0xc961b735, 0xe51ce1ed, 0xb1477a3c, 0xdfd29c59, 0x73f2553f, 0xce141879, 0x37c773bf, 0xcdf753ea, 0xaafd5f5b, 0x6f3ddf14, 0xdb447886, 0xf3afca81, 0xc468b93e, 0x3424382c, 0x40a3c25f, 0xc31d1672, 0x25e2bc0c, 0x493c288b, 0x950dff41, 0x1a83971, 0xb30c08de, 0xe4b4d89c, 0xc1566490, 0x84cb7b61, 0xb632d570, 0x5c6c4874, 0x57b8d042]
|
||||
fileprivate let T2: Array<UInt32> = [0x63c6a563, 0x7cf8847c, 0x77ee9977, 0x7bf68d7b, 0xf2ff0df2, 0x6bd6bd6b, 0x6fdeb16f, 0xc59154c5, 0x30605030, 0x1020301, 0x67cea967, 0x2b567d2b, 0xfee719fe, 0xd7b562d7, 0xab4de6ab, 0x76ec9a76, 0xca8f45ca, 0x821f9d82, 0xc98940c9, 0x7dfa877d, 0xfaef15fa, 0x59b2eb59, 0x478ec947, 0xf0fb0bf0, 0xad41ecad, 0xd4b367d4, 0xa25ffda2, 0xaf45eaaf, 0x9c23bf9c, 0xa453f7a4, 0x72e49672, 0xc09b5bc0, 0xb775c2b7, 0xfde11cfd, 0x933dae93, 0x264c6a26, 0x366c5a36, 0x3f7e413f, 0xf7f502f7, 0xcc834fcc, 0x34685c34, 0xa551f4a5, 0xe5d134e5, 0xf1f908f1, 0x71e29371, 0xd8ab73d8, 0x31625331, 0x152a3f15, 0x4080c04, 0xc79552c7, 0x23466523, 0xc39d5ec3, 0x18302818, 0x9637a196, 0x50a0f05, 0x9a2fb59a, 0x70e0907, 0x12243612, 0x801b9b80, 0xe2df3de2, 0xebcd26eb, 0x274e6927, 0xb27fcdb2, 0x75ea9f75, 0x9121b09, 0x831d9e83, 0x2c58742c, 0x1a342e1a, 0x1b362d1b, 0x6edcb26e, 0x5ab4ee5a, 0xa05bfba0, 0x52a4f652, 0x3b764d3b, 0xd6b761d6, 0xb37dceb3, 0x29527b29, 0xe3dd3ee3, 0x2f5e712f, 0x84139784, 0x53a6f553, 0xd1b968d1, 0x0, 0xedc12ced, 0x20406020, 0xfce31ffc, 0xb179c8b1, 0x5bb6ed5b, 0x6ad4be6a, 0xcb8d46cb, 0xbe67d9be, 0x39724b39, 0x4a94de4a, 0x4c98d44c, 0x58b0e858, 0xcf854acf, 0xd0bb6bd0, 0xefc52aef, 0xaa4fe5aa, 0xfbed16fb, 0x4386c543, 0x4d9ad74d, 0x33665533, 0x85119485, 0x458acf45, 0xf9e910f9, 0x2040602, 0x7ffe817f, 0x50a0f050, 0x3c78443c, 0x9f25ba9f, 0xa84be3a8, 0x51a2f351, 0xa35dfea3, 0x4080c040, 0x8f058a8f, 0x923fad92, 0x9d21bc9d, 0x38704838, 0xf5f104f5, 0xbc63dfbc, 0xb677c1b6, 0xdaaf75da, 0x21426321, 0x10203010, 0xffe51aff, 0xf3fd0ef3, 0xd2bf6dd2, 0xcd814ccd, 0xc18140c, 0x13263513, 0xecc32fec, 0x5fbee15f, 0x9735a297, 0x4488cc44, 0x172e3917, 0xc49357c4, 0xa755f2a7, 0x7efc827e, 0x3d7a473d, 0x64c8ac64, 0x5dbae75d, 0x19322b19, 0x73e69573, 0x60c0a060, 0x81199881, 0x4f9ed14f, 0xdca37fdc, 0x22446622, 0x2a547e2a, 0x903bab90, 0x880b8388, 0x468cca46, 0xeec729ee, 0xb86bd3b8, 0x14283c14, 0xdea779de, 0x5ebce25e, 0xb161d0b, 0xdbad76db, 0xe0db3be0, 0x32645632, 0x3a744e3a, 0xa141e0a, 0x4992db49, 0x60c0a06, 0x24486c24, 0x5cb8e45c, 0xc29f5dc2, 0xd3bd6ed3, 0xac43efac, 0x62c4a662, 0x9139a891, 0x9531a495, 0xe4d337e4, 0x79f28b79, 0xe7d532e7, 0xc88b43c8, 0x376e5937, 0x6ddab76d, 0x8d018c8d, 0xd5b164d5, 0x4e9cd24e, 0xa949e0a9, 0x6cd8b46c, 0x56acfa56, 0xf4f307f4, 0xeacf25ea, 0x65caaf65, 0x7af48e7a, 0xae47e9ae, 0x8101808, 0xba6fd5ba, 0x78f08878, 0x254a6f25, 0x2e5c722e, 0x1c38241c, 0xa657f1a6, 0xb473c7b4, 0xc69751c6, 0xe8cb23e8, 0xdda17cdd, 0x74e89c74, 0x1f3e211f, 0x4b96dd4b, 0xbd61dcbd, 0x8b0d868b, 0x8a0f858a, 0x70e09070, 0x3e7c423e, 0xb571c4b5, 0x66ccaa66, 0x4890d848, 0x3060503, 0xf6f701f6, 0xe1c120e, 0x61c2a361, 0x356a5f35, 0x57aef957, 0xb969d0b9, 0x86179186, 0xc19958c1, 0x1d3a271d, 0x9e27b99e, 0xe1d938e1, 0xf8eb13f8, 0x982bb398, 0x11223311, 0x69d2bb69, 0xd9a970d9, 0x8e07898e, 0x9433a794, 0x9b2db69b, 0x1e3c221e, 0x87159287, 0xe9c920e9, 0xce8749ce, 0x55aaff55, 0x28507828, 0xdfa57adf, 0x8c038f8c, 0xa159f8a1, 0x89098089, 0xd1a170d, 0xbf65dabf, 0xe6d731e6, 0x4284c642, 0x68d0b868, 0x4182c341, 0x9929b099, 0x2d5a772d, 0xf1e110f, 0xb07bcbb0, 0x54a8fc54, 0xbb6dd6bb, 0x162c3a16]
|
||||
fileprivate let T2_INV: Array<UInt32> = [0xf45150a7, 0x417e5365, 0x171ac3a4, 0x273a965e, 0xab3bcb6b, 0x9d1ff145, 0xfaacab58, 0xe34b9303, 0x302055fa, 0x76adf66d, 0xcc889176, 0x2f5254c, 0xe54ffcd7, 0x2ac5d7cb, 0x35268044, 0x62b58fa3, 0xb1de495a, 0xba25671b, 0xea45980e, 0xfe5de1c0, 0x2fc30275, 0x4c8112f0, 0x468da397, 0xd36bc6f9, 0x8f03e75f, 0x9215959c, 0x6dbfeb7a, 0x5295da59, 0xbed42d83, 0x7458d321, 0xe0492969, 0xc98e44c8, 0xc2756a89, 0x8ef47879, 0x58996b3e, 0xb927dd71, 0xe1beb64f, 0x88f017ad, 0x20c966ac, 0xce7db43a, 0xdf63184a, 0x1ae58231, 0x51976033, 0x5362457f, 0x64b1e077, 0x6bbb84ae, 0x81fe1ca0, 0x8f9942b, 0x48705868, 0x458f19fd, 0xde94876c, 0x7b52b7f8, 0x73ab23d3, 0x4b72e202, 0x1fe3578f, 0x55662aab, 0xebb20728, 0xb52f03c2, 0xc5869a7b, 0x37d3a508, 0x2830f287, 0xbf23b2a5, 0x302ba6a, 0x16ed5c82, 0xcf8a2b1c, 0x79a792b4, 0x7f3f0f2, 0x694ea1e2, 0xda65cdf4, 0x506d5be, 0x34d11f62, 0xa6c48afe, 0x2e349d53, 0xf3a2a055, 0x8a0532e1, 0xf6a475eb, 0x830b39ec, 0x6040aaef, 0x715e069f, 0x6ebd5110, 0x213ef98a, 0xdd963d06, 0x3eddae05, 0xe64d46bd, 0x5491b58d, 0xc471055d, 0x6046fd4, 0x5060ff15, 0x981924fb, 0xbdd697e9, 0x4089cc43, 0xd967779e, 0xe8b0bd42, 0x8907888b, 0x19e7385b, 0xc879dbee, 0x7ca1470a, 0x427ce90f, 0x84f8c91e, 0x0, 0x80098386, 0x2b3248ed, 0x111eac70, 0x5a6c4e72, 0xefdfbff, 0x850f5638, 0xae3d1ed5, 0x2d362739, 0xf0a64d9, 0x5c6821a6, 0x5b9bd154, 0x36243a2e, 0xa0cb167, 0x57930fe7, 0xeeb4d296, 0x9b1b9e91, 0xc0804fc5, 0xdc61a220, 0x775a694b, 0x121c161a, 0x93e20aba, 0xa0c0e52a, 0x223c43e0, 0x1b121d17, 0x90e0b0d, 0x8bf2adc7, 0xb62db9a8, 0x1e14c8a9, 0xf1578519, 0x75af4c07, 0x99eebbdd, 0x7fa3fd60, 0x1f79f26, 0x725cbcf5, 0x6644c53b, 0xfb5b347e, 0x438b7629, 0x23cbdcc6, 0xedb668fc, 0xe4b863f1, 0x31d7cadc, 0x63421085, 0x97134022, 0xc6842011, 0x4a857d24, 0xbbd2f83d, 0xf9ae1132, 0x29c76da1, 0x9e1d4b2f, 0xb2dcf330, 0x860dec52, 0xc177d0e3, 0xb32b6c16, 0x70a999b9, 0x9411fa48, 0xe9472264, 0xfca8c48c, 0xf0a01a3f, 0x7d56d82c, 0x3322ef90, 0x4987c74e, 0x38d9c1d1, 0xca8cfea2, 0xd498360b, 0xf5a6cf81, 0x7aa528de, 0xb7da268e, 0xad3fa4bf, 0x3a2ce49d, 0x78500d92, 0x5f6a9bcc, 0x7e546246, 0x8df6c213, 0xd890e8b8, 0x392e5ef7, 0xc382f5af, 0x5d9fbe80, 0xd0697c93, 0xd56fa92d, 0x25cfb312, 0xacc83b99, 0x1810a77d, 0x9ce86e63, 0x3bdb7bbb, 0x26cd0978, 0x596ef418, 0x9aec01b7, 0x4f83a89a, 0x95e6656e, 0xffaa7ee6, 0xbc2108cf, 0x15efe6e8, 0xe7bad99b, 0x6f4ace36, 0x9fead409, 0xb029d67c, 0xa431afb2, 0x3f2a3123, 0xa5c63094, 0xa235c066, 0x4e7437bc, 0x82fca6ca, 0x90e0b0d0, 0xa73315d8, 0x4f14a98, 0xec41f7da, 0xcd7f0e50, 0x91172ff6, 0x4d768dd6, 0xef434db0, 0xaacc544d, 0x96e4df04, 0xd19ee3b5, 0x6a4c1b88, 0x2cc1b81f, 0x65467f51, 0x5e9d04ea, 0x8c015d35, 0x87fa7374, 0xbfb2e41, 0x67b35a1d, 0xdb9252d2, 0x10e93356, 0xd66d1347, 0xd79a8c61, 0xa1377a0c, 0xf8598e14, 0x13eb893c, 0xa9ceee27, 0x61b735c9, 0x1ce1ede5, 0x477a3cb1, 0xd29c59df, 0xf2553f73, 0x141879ce, 0xc773bf37, 0xf753eacd, 0xfd5f5baa, 0x3ddf146f, 0x447886db, 0xafca81f3, 0x68b93ec4, 0x24382c34, 0xa3c25f40, 0x1d1672c3, 0xe2bc0c25, 0x3c288b49, 0xdff4195, 0xa8397101, 0xc08deb3, 0xb4d89ce4, 0x566490c1, 0xcb7b6184, 0x32d570b6, 0x6c48745c, 0xb8d04257]
|
||||
fileprivate let T3: Array<UInt32> = [0xc6a56363, 0xf8847c7c, 0xee997777, 0xf68d7b7b, 0xff0df2f2, 0xd6bd6b6b, 0xdeb16f6f, 0x9154c5c5, 0x60503030, 0x2030101, 0xcea96767, 0x567d2b2b, 0xe719fefe, 0xb562d7d7, 0x4de6abab, 0xec9a7676, 0x8f45caca, 0x1f9d8282, 0x8940c9c9, 0xfa877d7d, 0xef15fafa, 0xb2eb5959, 0x8ec94747, 0xfb0bf0f0, 0x41ecadad, 0xb367d4d4, 0x5ffda2a2, 0x45eaafaf, 0x23bf9c9c, 0x53f7a4a4, 0xe4967272, 0x9b5bc0c0, 0x75c2b7b7, 0xe11cfdfd, 0x3dae9393, 0x4c6a2626, 0x6c5a3636, 0x7e413f3f, 0xf502f7f7, 0x834fcccc, 0x685c3434, 0x51f4a5a5, 0xd134e5e5, 0xf908f1f1, 0xe2937171, 0xab73d8d8, 0x62533131, 0x2a3f1515, 0x80c0404, 0x9552c7c7, 0x46652323, 0x9d5ec3c3, 0x30281818, 0x37a19696, 0xa0f0505, 0x2fb59a9a, 0xe090707, 0x24361212, 0x1b9b8080, 0xdf3de2e2, 0xcd26ebeb, 0x4e692727, 0x7fcdb2b2, 0xea9f7575, 0x121b0909, 0x1d9e8383, 0x58742c2c, 0x342e1a1a, 0x362d1b1b, 0xdcb26e6e, 0xb4ee5a5a, 0x5bfba0a0, 0xa4f65252, 0x764d3b3b, 0xb761d6d6, 0x7dceb3b3, 0x527b2929, 0xdd3ee3e3, 0x5e712f2f, 0x13978484, 0xa6f55353, 0xb968d1d1, 0x0, 0xc12ceded, 0x40602020, 0xe31ffcfc, 0x79c8b1b1, 0xb6ed5b5b, 0xd4be6a6a, 0x8d46cbcb, 0x67d9bebe, 0x724b3939, 0x94de4a4a, 0x98d44c4c, 0xb0e85858, 0x854acfcf, 0xbb6bd0d0, 0xc52aefef, 0x4fe5aaaa, 0xed16fbfb, 0x86c54343, 0x9ad74d4d, 0x66553333, 0x11948585, 0x8acf4545, 0xe910f9f9, 0x4060202, 0xfe817f7f, 0xa0f05050, 0x78443c3c, 0x25ba9f9f, 0x4be3a8a8, 0xa2f35151, 0x5dfea3a3, 0x80c04040, 0x58a8f8f, 0x3fad9292, 0x21bc9d9d, 0x70483838, 0xf104f5f5, 0x63dfbcbc, 0x77c1b6b6, 0xaf75dada, 0x42632121, 0x20301010, 0xe51affff, 0xfd0ef3f3, 0xbf6dd2d2, 0x814ccdcd, 0x18140c0c, 0x26351313, 0xc32fecec, 0xbee15f5f, 0x35a29797, 0x88cc4444, 0x2e391717, 0x9357c4c4, 0x55f2a7a7, 0xfc827e7e, 0x7a473d3d, 0xc8ac6464, 0xbae75d5d, 0x322b1919, 0xe6957373, 0xc0a06060, 0x19988181, 0x9ed14f4f, 0xa37fdcdc, 0x44662222, 0x547e2a2a, 0x3bab9090, 0xb838888, 0x8cca4646, 0xc729eeee, 0x6bd3b8b8, 0x283c1414, 0xa779dede, 0xbce25e5e, 0x161d0b0b, 0xad76dbdb, 0xdb3be0e0, 0x64563232, 0x744e3a3a, 0x141e0a0a, 0x92db4949, 0xc0a0606, 0x486c2424, 0xb8e45c5c, 0x9f5dc2c2, 0xbd6ed3d3, 0x43efacac, 0xc4a66262, 0x39a89191, 0x31a49595, 0xd337e4e4, 0xf28b7979, 0xd532e7e7, 0x8b43c8c8, 0x6e593737, 0xdab76d6d, 0x18c8d8d, 0xb164d5d5, 0x9cd24e4e, 0x49e0a9a9, 0xd8b46c6c, 0xacfa5656, 0xf307f4f4, 0xcf25eaea, 0xcaaf6565, 0xf48e7a7a, 0x47e9aeae, 0x10180808, 0x6fd5baba, 0xf0887878, 0x4a6f2525, 0x5c722e2e, 0x38241c1c, 0x57f1a6a6, 0x73c7b4b4, 0x9751c6c6, 0xcb23e8e8, 0xa17cdddd, 0xe89c7474, 0x3e211f1f, 0x96dd4b4b, 0x61dcbdbd, 0xd868b8b, 0xf858a8a, 0xe0907070, 0x7c423e3e, 0x71c4b5b5, 0xccaa6666, 0x90d84848, 0x6050303, 0xf701f6f6, 0x1c120e0e, 0xc2a36161, 0x6a5f3535, 0xaef95757, 0x69d0b9b9, 0x17918686, 0x9958c1c1, 0x3a271d1d, 0x27b99e9e, 0xd938e1e1, 0xeb13f8f8, 0x2bb39898, 0x22331111, 0xd2bb6969, 0xa970d9d9, 0x7898e8e, 0x33a79494, 0x2db69b9b, 0x3c221e1e, 0x15928787, 0xc920e9e9, 0x8749cece, 0xaaff5555, 0x50782828, 0xa57adfdf, 0x38f8c8c, 0x59f8a1a1, 0x9808989, 0x1a170d0d, 0x65dabfbf, 0xd731e6e6, 0x84c64242, 0xd0b86868, 0x82c34141, 0x29b09999, 0x5a772d2d, 0x1e110f0f, 0x7bcbb0b0, 0xa8fc5454, 0x6dd6bbbb, 0x2c3a1616]
|
||||
fileprivate let T3_INV: Array<UInt32> = [0x5150a7f4, 0x7e536541, 0x1ac3a417, 0x3a965e27, 0x3bcb6bab, 0x1ff1459d, 0xacab58fa, 0x4b9303e3, 0x2055fa30, 0xadf66d76, 0x889176cc, 0xf5254c02, 0x4ffcd7e5, 0xc5d7cb2a, 0x26804435, 0xb58fa362, 0xde495ab1, 0x25671bba, 0x45980eea, 0x5de1c0fe, 0xc302752f, 0x8112f04c, 0x8da39746, 0x6bc6f9d3, 0x3e75f8f, 0x15959c92, 0xbfeb7a6d, 0x95da5952, 0xd42d83be, 0x58d32174, 0x492969e0, 0x8e44c8c9, 0x756a89c2, 0xf478798e, 0x996b3e58, 0x27dd71b9, 0xbeb64fe1, 0xf017ad88, 0xc966ac20, 0x7db43ace, 0x63184adf, 0xe582311a, 0x97603351, 0x62457f53, 0xb1e07764, 0xbb84ae6b, 0xfe1ca081, 0xf9942b08, 0x70586848, 0x8f19fd45, 0x94876cde, 0x52b7f87b, 0xab23d373, 0x72e2024b, 0xe3578f1f, 0x662aab55, 0xb20728eb, 0x2f03c2b5, 0x869a7bc5, 0xd3a50837, 0x30f28728, 0x23b2a5bf, 0x2ba6a03, 0xed5c8216, 0x8a2b1ccf, 0xa792b479, 0xf3f0f207, 0x4ea1e269, 0x65cdf4da, 0x6d5be05, 0xd11f6234, 0xc48afea6, 0x349d532e, 0xa2a055f3, 0x532e18a, 0xa475ebf6, 0xb39ec83, 0x40aaef60, 0x5e069f71, 0xbd51106e, 0x3ef98a21, 0x963d06dd, 0xddae053e, 0x4d46bde6, 0x91b58d54, 0x71055dc4, 0x46fd406, 0x60ff1550, 0x1924fb98, 0xd697e9bd, 0x89cc4340, 0x67779ed9, 0xb0bd42e8, 0x7888b89, 0xe7385b19, 0x79dbeec8, 0xa1470a7c, 0x7ce90f42, 0xf8c91e84, 0x0, 0x9838680, 0x3248ed2b, 0x1eac7011, 0x6c4e725a, 0xfdfbff0e, 0xf563885, 0x3d1ed5ae, 0x3627392d, 0xa64d90f, 0x6821a65c, 0x9bd1545b, 0x243a2e36, 0xcb1670a, 0x930fe757, 0xb4d296ee, 0x1b9e919b, 0x804fc5c0, 0x61a220dc, 0x5a694b77, 0x1c161a12, 0xe20aba93, 0xc0e52aa0, 0x3c43e022, 0x121d171b, 0xe0b0d09, 0xf2adc78b, 0x2db9a8b6, 0x14c8a91e, 0x578519f1, 0xaf4c0775, 0xeebbdd99, 0xa3fd607f, 0xf79f2601, 0x5cbcf572, 0x44c53b66, 0x5b347efb, 0x8b762943, 0xcbdcc623, 0xb668fced, 0xb863f1e4, 0xd7cadc31, 0x42108563, 0x13402297, 0x842011c6, 0x857d244a, 0xd2f83dbb, 0xae1132f9, 0xc76da129, 0x1d4b2f9e, 0xdcf330b2, 0xdec5286, 0x77d0e3c1, 0x2b6c16b3, 0xa999b970, 0x11fa4894, 0x472264e9, 0xa8c48cfc, 0xa01a3ff0, 0x56d82c7d, 0x22ef9033, 0x87c74e49, 0xd9c1d138, 0x8cfea2ca, 0x98360bd4, 0xa6cf81f5, 0xa528de7a, 0xda268eb7, 0x3fa4bfad, 0x2ce49d3a, 0x500d9278, 0x6a9bcc5f, 0x5462467e, 0xf6c2138d, 0x90e8b8d8, 0x2e5ef739, 0x82f5afc3, 0x9fbe805d, 0x697c93d0, 0x6fa92dd5, 0xcfb31225, 0xc83b99ac, 0x10a77d18, 0xe86e639c, 0xdb7bbb3b, 0xcd097826, 0x6ef41859, 0xec01b79a, 0x83a89a4f, 0xe6656e95, 0xaa7ee6ff, 0x2108cfbc, 0xefe6e815, 0xbad99be7, 0x4ace366f, 0xead4099f, 0x29d67cb0, 0x31afb2a4, 0x2a31233f, 0xc63094a5, 0x35c066a2, 0x7437bc4e, 0xfca6ca82, 0xe0b0d090, 0x3315d8a7, 0xf14a9804, 0x41f7daec, 0x7f0e50cd, 0x172ff691, 0x768dd64d, 0x434db0ef, 0xcc544daa, 0xe4df0496, 0x9ee3b5d1, 0x4c1b886a, 0xc1b81f2c, 0x467f5165, 0x9d04ea5e, 0x15d358c, 0xfa737487, 0xfb2e410b, 0xb35a1d67, 0x9252d2db, 0xe9335610, 0x6d1347d6, 0x9a8c61d7, 0x377a0ca1, 0x598e14f8, 0xeb893c13, 0xceee27a9, 0xb735c961, 0xe1ede51c, 0x7a3cb147, 0x9c59dfd2, 0x553f73f2, 0x1879ce14, 0x73bf37c7, 0x53eacdf7, 0x5f5baafd, 0xdf146f3d, 0x7886db44, 0xca81f3af, 0xb93ec468, 0x382c3424, 0xc25f40a3, 0x1672c31d, 0xbc0c25e2, 0x288b493c, 0xff41950d, 0x397101a8, 0x8deb30c, 0xd89ce4b4, 0x6490c156, 0x7b6184cb, 0xd570b632, 0x48745c6c, 0xd04257b8]
|
||||
fileprivate var U1: Array<UInt32> = [0x0, 0xb0d090e, 0x161a121c, 0x1d171b12, 0x2c342438, 0x27392d36, 0x3a2e3624, 0x31233f2a, 0x58684870, 0x5365417e, 0x4e725a6c, 0x457f5362, 0x745c6c48, 0x7f516546, 0x62467e54, 0x694b775a, 0xb0d090e0, 0xbbdd99ee, 0xa6ca82fc, 0xadc78bf2, 0x9ce4b4d8, 0x97e9bdd6, 0x8afea6c4, 0x81f3afca, 0xe8b8d890, 0xe3b5d19e, 0xfea2ca8c, 0xf5afc382, 0xc48cfca8, 0xcf81f5a6, 0xd296eeb4, 0xd99be7ba, 0x7bbb3bdb, 0x70b632d5, 0x6da129c7, 0x66ac20c9, 0x578f1fe3, 0x5c8216ed, 0x41950dff, 0x4a9804f1, 0x23d373ab, 0x28de7aa5, 0x35c961b7, 0x3ec468b9, 0xfe75793, 0x4ea5e9d, 0x19fd458f, 0x12f04c81, 0xcb6bab3b, 0xc066a235, 0xdd71b927, 0xd67cb029, 0xe75f8f03, 0xec52860d, 0xf1459d1f, 0xfa489411, 0x9303e34b, 0x980eea45, 0x8519f157, 0x8e14f859, 0xbf37c773, 0xb43ace7d, 0xa92dd56f, 0xa220dc61, 0xf66d76ad, 0xfd607fa3, 0xe07764b1, 0xeb7a6dbf, 0xda595295, 0xd1545b9b, 0xcc434089, 0xc74e4987, 0xae053edd, 0xa50837d3, 0xb81f2cc1, 0xb31225cf, 0x82311ae5, 0x893c13eb, 0x942b08f9, 0x9f2601f7, 0x46bde64d, 0x4db0ef43, 0x50a7f451, 0x5baafd5f, 0x6a89c275, 0x6184cb7b, 0x7c93d069, 0x779ed967, 0x1ed5ae3d, 0x15d8a733, 0x8cfbc21, 0x3c2b52f, 0x32e18a05, 0x39ec830b, 0x24fb9819, 0x2ff69117, 0x8dd64d76, 0x86db4478, 0x9bcc5f6a, 0x90c15664, 0xa1e2694e, 0xaaef6040, 0xb7f87b52, 0xbcf5725c, 0xd5be0506, 0xdeb30c08, 0xc3a4171a, 0xc8a91e14, 0xf98a213e, 0xf2872830, 0xef903322, 0xe49d3a2c, 0x3d06dd96, 0x360bd498, 0x2b1ccf8a, 0x2011c684, 0x1132f9ae, 0x1a3ff0a0, 0x728ebb2, 0xc25e2bc, 0x656e95e6, 0x6e639ce8, 0x737487fa, 0x78798ef4, 0x495ab1de, 0x4257b8d0, 0x5f40a3c2, 0x544daacc, 0xf7daec41, 0xfcd7e54f, 0xe1c0fe5d, 0xeacdf753, 0xdbeec879, 0xd0e3c177, 0xcdf4da65, 0xc6f9d36b, 0xafb2a431, 0xa4bfad3f, 0xb9a8b62d, 0xb2a5bf23, 0x83868009, 0x888b8907, 0x959c9215, 0x9e919b1b, 0x470a7ca1, 0x4c0775af, 0x51106ebd, 0x5a1d67b3, 0x6b3e5899, 0x60335197, 0x7d244a85, 0x7629438b, 0x1f6234d1, 0x146f3ddf, 0x97826cd, 0x2752fc3, 0x335610e9, 0x385b19e7, 0x254c02f5, 0x2e410bfb, 0x8c61d79a, 0x876cde94, 0x9a7bc586, 0x9176cc88, 0xa055f3a2, 0xab58faac, 0xb64fe1be, 0xbd42e8b0, 0xd4099fea, 0xdf0496e4, 0xc2138df6, 0xc91e84f8, 0xf83dbbd2, 0xf330b2dc, 0xee27a9ce, 0xe52aa0c0, 0x3cb1477a, 0x37bc4e74, 0x2aab5566, 0x21a65c68, 0x10856342, 0x1b886a4c, 0x69f715e, 0xd927850, 0x64d90f0a, 0x6fd40604, 0x72c31d16, 0x79ce1418, 0x48ed2b32, 0x43e0223c, 0x5ef7392e, 0x55fa3020, 0x1b79aec, 0xaba93e2, 0x17ad88f0, 0x1ca081fe, 0x2d83bed4, 0x268eb7da, 0x3b99acc8, 0x3094a5c6, 0x59dfd29c, 0x52d2db92, 0x4fc5c080, 0x44c8c98e, 0x75ebf6a4, 0x7ee6ffaa, 0x63f1e4b8, 0x68fcedb6, 0xb1670a0c, 0xba6a0302, 0xa77d1810, 0xac70111e, 0x9d532e34, 0x965e273a, 0x8b493c28, 0x80443526, 0xe90f427c, 0xe2024b72, 0xff155060, 0xf418596e, 0xc53b6644, 0xce366f4a, 0xd3217458, 0xd82c7d56, 0x7a0ca137, 0x7101a839, 0x6c16b32b, 0x671bba25, 0x5638850f, 0x5d358c01, 0x40229713, 0x4b2f9e1d, 0x2264e947, 0x2969e049, 0x347efb5b, 0x3f73f255, 0xe50cd7f, 0x55dc471, 0x184adf63, 0x1347d66d, 0xcadc31d7, 0xc1d138d9, 0xdcc623cb, 0xd7cb2ac5, 0xe6e815ef, 0xede51ce1, 0xf0f207f3, 0xfbff0efd, 0x92b479a7, 0x99b970a9, 0x84ae6bbb, 0x8fa362b5, 0xbe805d9f, 0xb58d5491, 0xa89a4f83, 0xa397468d]
|
||||
fileprivate var U2: Array<UInt32> = [0x0, 0xd090e0b, 0x1a121c16, 0x171b121d, 0x3424382c, 0x392d3627, 0x2e36243a, 0x233f2a31, 0x68487058, 0x65417e53, 0x725a6c4e, 0x7f536245, 0x5c6c4874, 0x5165467f, 0x467e5462, 0x4b775a69, 0xd090e0b0, 0xdd99eebb, 0xca82fca6, 0xc78bf2ad, 0xe4b4d89c, 0xe9bdd697, 0xfea6c48a, 0xf3afca81, 0xb8d890e8, 0xb5d19ee3, 0xa2ca8cfe, 0xafc382f5, 0x8cfca8c4, 0x81f5a6cf, 0x96eeb4d2, 0x9be7bad9, 0xbb3bdb7b, 0xb632d570, 0xa129c76d, 0xac20c966, 0x8f1fe357, 0x8216ed5c, 0x950dff41, 0x9804f14a, 0xd373ab23, 0xde7aa528, 0xc961b735, 0xc468b93e, 0xe757930f, 0xea5e9d04, 0xfd458f19, 0xf04c8112, 0x6bab3bcb, 0x66a235c0, 0x71b927dd, 0x7cb029d6, 0x5f8f03e7, 0x52860dec, 0x459d1ff1, 0x489411fa, 0x3e34b93, 0xeea4598, 0x19f15785, 0x14f8598e, 0x37c773bf, 0x3ace7db4, 0x2dd56fa9, 0x20dc61a2, 0x6d76adf6, 0x607fa3fd, 0x7764b1e0, 0x7a6dbfeb, 0x595295da, 0x545b9bd1, 0x434089cc, 0x4e4987c7, 0x53eddae, 0x837d3a5, 0x1f2cc1b8, 0x1225cfb3, 0x311ae582, 0x3c13eb89, 0x2b08f994, 0x2601f79f, 0xbde64d46, 0xb0ef434d, 0xa7f45150, 0xaafd5f5b, 0x89c2756a, 0x84cb7b61, 0x93d0697c, 0x9ed96777, 0xd5ae3d1e, 0xd8a73315, 0xcfbc2108, 0xc2b52f03, 0xe18a0532, 0xec830b39, 0xfb981924, 0xf691172f, 0xd64d768d, 0xdb447886, 0xcc5f6a9b, 0xc1566490, 0xe2694ea1, 0xef6040aa, 0xf87b52b7, 0xf5725cbc, 0xbe0506d5, 0xb30c08de, 0xa4171ac3, 0xa91e14c8, 0x8a213ef9, 0x872830f2, 0x903322ef, 0x9d3a2ce4, 0x6dd963d, 0xbd49836, 0x1ccf8a2b, 0x11c68420, 0x32f9ae11, 0x3ff0a01a, 0x28ebb207, 0x25e2bc0c, 0x6e95e665, 0x639ce86e, 0x7487fa73, 0x798ef478, 0x5ab1de49, 0x57b8d042, 0x40a3c25f, 0x4daacc54, 0xdaec41f7, 0xd7e54ffc, 0xc0fe5de1, 0xcdf753ea, 0xeec879db, 0xe3c177d0, 0xf4da65cd, 0xf9d36bc6, 0xb2a431af, 0xbfad3fa4, 0xa8b62db9, 0xa5bf23b2, 0x86800983, 0x8b890788, 0x9c921595, 0x919b1b9e, 0xa7ca147, 0x775af4c, 0x106ebd51, 0x1d67b35a, 0x3e58996b, 0x33519760, 0x244a857d, 0x29438b76, 0x6234d11f, 0x6f3ddf14, 0x7826cd09, 0x752fc302, 0x5610e933, 0x5b19e738, 0x4c02f525, 0x410bfb2e, 0x61d79a8c, 0x6cde9487, 0x7bc5869a, 0x76cc8891, 0x55f3a2a0, 0x58faacab, 0x4fe1beb6, 0x42e8b0bd, 0x99fead4, 0x496e4df, 0x138df6c2, 0x1e84f8c9, 0x3dbbd2f8, 0x30b2dcf3, 0x27a9ceee, 0x2aa0c0e5, 0xb1477a3c, 0xbc4e7437, 0xab55662a, 0xa65c6821, 0x85634210, 0x886a4c1b, 0x9f715e06, 0x9278500d, 0xd90f0a64, 0xd406046f, 0xc31d1672, 0xce141879, 0xed2b3248, 0xe0223c43, 0xf7392e5e, 0xfa302055, 0xb79aec01, 0xba93e20a, 0xad88f017, 0xa081fe1c, 0x83bed42d, 0x8eb7da26, 0x99acc83b, 0x94a5c630, 0xdfd29c59, 0xd2db9252, 0xc5c0804f, 0xc8c98e44, 0xebf6a475, 0xe6ffaa7e, 0xf1e4b863, 0xfcedb668, 0x670a0cb1, 0x6a0302ba, 0x7d1810a7, 0x70111eac, 0x532e349d, 0x5e273a96, 0x493c288b, 0x44352680, 0xf427ce9, 0x24b72e2, 0x155060ff, 0x18596ef4, 0x3b6644c5, 0x366f4ace, 0x217458d3, 0x2c7d56d8, 0xca1377a, 0x1a83971, 0x16b32b6c, 0x1bba2567, 0x38850f56, 0x358c015d, 0x22971340, 0x2f9e1d4b, 0x64e94722, 0x69e04929, 0x7efb5b34, 0x73f2553f, 0x50cd7f0e, 0x5dc47105, 0x4adf6318, 0x47d66d13, 0xdc31d7ca, 0xd138d9c1, 0xc623cbdc, 0xcb2ac5d7, 0xe815efe6, 0xe51ce1ed, 0xf207f3f0, 0xff0efdfb, 0xb479a792, 0xb970a999, 0xae6bbb84, 0xa362b58f, 0x805d9fbe, 0x8d5491b5, 0x9a4f83a8, 0x97468da3]
|
||||
fileprivate var U3: Array<UInt32> = [0x0, 0x90e0b0d, 0x121c161a, 0x1b121d17, 0x24382c34, 0x2d362739, 0x36243a2e, 0x3f2a3123, 0x48705868, 0x417e5365, 0x5a6c4e72, 0x5362457f, 0x6c48745c, 0x65467f51, 0x7e546246, 0x775a694b, 0x90e0b0d0, 0x99eebbdd, 0x82fca6ca, 0x8bf2adc7, 0xb4d89ce4, 0xbdd697e9, 0xa6c48afe, 0xafca81f3, 0xd890e8b8, 0xd19ee3b5, 0xca8cfea2, 0xc382f5af, 0xfca8c48c, 0xf5a6cf81, 0xeeb4d296, 0xe7bad99b, 0x3bdb7bbb, 0x32d570b6, 0x29c76da1, 0x20c966ac, 0x1fe3578f, 0x16ed5c82, 0xdff4195, 0x4f14a98, 0x73ab23d3, 0x7aa528de, 0x61b735c9, 0x68b93ec4, 0x57930fe7, 0x5e9d04ea, 0x458f19fd, 0x4c8112f0, 0xab3bcb6b, 0xa235c066, 0xb927dd71, 0xb029d67c, 0x8f03e75f, 0x860dec52, 0x9d1ff145, 0x9411fa48, 0xe34b9303, 0xea45980e, 0xf1578519, 0xf8598e14, 0xc773bf37, 0xce7db43a, 0xd56fa92d, 0xdc61a220, 0x76adf66d, 0x7fa3fd60, 0x64b1e077, 0x6dbfeb7a, 0x5295da59, 0x5b9bd154, 0x4089cc43, 0x4987c74e, 0x3eddae05, 0x37d3a508, 0x2cc1b81f, 0x25cfb312, 0x1ae58231, 0x13eb893c, 0x8f9942b, 0x1f79f26, 0xe64d46bd, 0xef434db0, 0xf45150a7, 0xfd5f5baa, 0xc2756a89, 0xcb7b6184, 0xd0697c93, 0xd967779e, 0xae3d1ed5, 0xa73315d8, 0xbc2108cf, 0xb52f03c2, 0x8a0532e1, 0x830b39ec, 0x981924fb, 0x91172ff6, 0x4d768dd6, 0x447886db, 0x5f6a9bcc, 0x566490c1, 0x694ea1e2, 0x6040aaef, 0x7b52b7f8, 0x725cbcf5, 0x506d5be, 0xc08deb3, 0x171ac3a4, 0x1e14c8a9, 0x213ef98a, 0x2830f287, 0x3322ef90, 0x3a2ce49d, 0xdd963d06, 0xd498360b, 0xcf8a2b1c, 0xc6842011, 0xf9ae1132, 0xf0a01a3f, 0xebb20728, 0xe2bc0c25, 0x95e6656e, 0x9ce86e63, 0x87fa7374, 0x8ef47879, 0xb1de495a, 0xb8d04257, 0xa3c25f40, 0xaacc544d, 0xec41f7da, 0xe54ffcd7, 0xfe5de1c0, 0xf753eacd, 0xc879dbee, 0xc177d0e3, 0xda65cdf4, 0xd36bc6f9, 0xa431afb2, 0xad3fa4bf, 0xb62db9a8, 0xbf23b2a5, 0x80098386, 0x8907888b, 0x9215959c, 0x9b1b9e91, 0x7ca1470a, 0x75af4c07, 0x6ebd5110, 0x67b35a1d, 0x58996b3e, 0x51976033, 0x4a857d24, 0x438b7629, 0x34d11f62, 0x3ddf146f, 0x26cd0978, 0x2fc30275, 0x10e93356, 0x19e7385b, 0x2f5254c, 0xbfb2e41, 0xd79a8c61, 0xde94876c, 0xc5869a7b, 0xcc889176, 0xf3a2a055, 0xfaacab58, 0xe1beb64f, 0xe8b0bd42, 0x9fead409, 0x96e4df04, 0x8df6c213, 0x84f8c91e, 0xbbd2f83d, 0xb2dcf330, 0xa9ceee27, 0xa0c0e52a, 0x477a3cb1, 0x4e7437bc, 0x55662aab, 0x5c6821a6, 0x63421085, 0x6a4c1b88, 0x715e069f, 0x78500d92, 0xf0a64d9, 0x6046fd4, 0x1d1672c3, 0x141879ce, 0x2b3248ed, 0x223c43e0, 0x392e5ef7, 0x302055fa, 0x9aec01b7, 0x93e20aba, 0x88f017ad, 0x81fe1ca0, 0xbed42d83, 0xb7da268e, 0xacc83b99, 0xa5c63094, 0xd29c59df, 0xdb9252d2, 0xc0804fc5, 0xc98e44c8, 0xf6a475eb, 0xffaa7ee6, 0xe4b863f1, 0xedb668fc, 0xa0cb167, 0x302ba6a, 0x1810a77d, 0x111eac70, 0x2e349d53, 0x273a965e, 0x3c288b49, 0x35268044, 0x427ce90f, 0x4b72e202, 0x5060ff15, 0x596ef418, 0x6644c53b, 0x6f4ace36, 0x7458d321, 0x7d56d82c, 0xa1377a0c, 0xa8397101, 0xb32b6c16, 0xba25671b, 0x850f5638, 0x8c015d35, 0x97134022, 0x9e1d4b2f, 0xe9472264, 0xe0492969, 0xfb5b347e, 0xf2553f73, 0xcd7f0e50, 0xc471055d, 0xdf63184a, 0xd66d1347, 0x31d7cadc, 0x38d9c1d1, 0x23cbdcc6, 0x2ac5d7cb, 0x15efe6e8, 0x1ce1ede5, 0x7f3f0f2, 0xefdfbff, 0x79a792b4, 0x70a999b9, 0x6bbb84ae, 0x62b58fa3, 0x5d9fbe80, 0x5491b58d, 0x4f83a89a, 0x468da397]
|
||||
fileprivate var U4: Array<UInt32> = [0x0, 0xe0b0d09, 0x1c161a12, 0x121d171b, 0x382c3424, 0x3627392d, 0x243a2e36, 0x2a31233f, 0x70586848, 0x7e536541, 0x6c4e725a, 0x62457f53, 0x48745c6c, 0x467f5165, 0x5462467e, 0x5a694b77, 0xe0b0d090, 0xeebbdd99, 0xfca6ca82, 0xf2adc78b, 0xd89ce4b4, 0xd697e9bd, 0xc48afea6, 0xca81f3af, 0x90e8b8d8, 0x9ee3b5d1, 0x8cfea2ca, 0x82f5afc3, 0xa8c48cfc, 0xa6cf81f5, 0xb4d296ee, 0xbad99be7, 0xdb7bbb3b, 0xd570b632, 0xc76da129, 0xc966ac20, 0xe3578f1f, 0xed5c8216, 0xff41950d, 0xf14a9804, 0xab23d373, 0xa528de7a, 0xb735c961, 0xb93ec468, 0x930fe757, 0x9d04ea5e, 0x8f19fd45, 0x8112f04c, 0x3bcb6bab, 0x35c066a2, 0x27dd71b9, 0x29d67cb0, 0x3e75f8f, 0xdec5286, 0x1ff1459d, 0x11fa4894, 0x4b9303e3, 0x45980eea, 0x578519f1, 0x598e14f8, 0x73bf37c7, 0x7db43ace, 0x6fa92dd5, 0x61a220dc, 0xadf66d76, 0xa3fd607f, 0xb1e07764, 0xbfeb7a6d, 0x95da5952, 0x9bd1545b, 0x89cc4340, 0x87c74e49, 0xddae053e, 0xd3a50837, 0xc1b81f2c, 0xcfb31225, 0xe582311a, 0xeb893c13, 0xf9942b08, 0xf79f2601, 0x4d46bde6, 0x434db0ef, 0x5150a7f4, 0x5f5baafd, 0x756a89c2, 0x7b6184cb, 0x697c93d0, 0x67779ed9, 0x3d1ed5ae, 0x3315d8a7, 0x2108cfbc, 0x2f03c2b5, 0x532e18a, 0xb39ec83, 0x1924fb98, 0x172ff691, 0x768dd64d, 0x7886db44, 0x6a9bcc5f, 0x6490c156, 0x4ea1e269, 0x40aaef60, 0x52b7f87b, 0x5cbcf572, 0x6d5be05, 0x8deb30c, 0x1ac3a417, 0x14c8a91e, 0x3ef98a21, 0x30f28728, 0x22ef9033, 0x2ce49d3a, 0x963d06dd, 0x98360bd4, 0x8a2b1ccf, 0x842011c6, 0xae1132f9, 0xa01a3ff0, 0xb20728eb, 0xbc0c25e2, 0xe6656e95, 0xe86e639c, 0xfa737487, 0xf478798e, 0xde495ab1, 0xd04257b8, 0xc25f40a3, 0xcc544daa, 0x41f7daec, 0x4ffcd7e5, 0x5de1c0fe, 0x53eacdf7, 0x79dbeec8, 0x77d0e3c1, 0x65cdf4da, 0x6bc6f9d3, 0x31afb2a4, 0x3fa4bfad, 0x2db9a8b6, 0x23b2a5bf, 0x9838680, 0x7888b89, 0x15959c92, 0x1b9e919b, 0xa1470a7c, 0xaf4c0775, 0xbd51106e, 0xb35a1d67, 0x996b3e58, 0x97603351, 0x857d244a, 0x8b762943, 0xd11f6234, 0xdf146f3d, 0xcd097826, 0xc302752f, 0xe9335610, 0xe7385b19, 0xf5254c02, 0xfb2e410b, 0x9a8c61d7, 0x94876cde, 0x869a7bc5, 0x889176cc, 0xa2a055f3, 0xacab58fa, 0xbeb64fe1, 0xb0bd42e8, 0xead4099f, 0xe4df0496, 0xf6c2138d, 0xf8c91e84, 0xd2f83dbb, 0xdcf330b2, 0xceee27a9, 0xc0e52aa0, 0x7a3cb147, 0x7437bc4e, 0x662aab55, 0x6821a65c, 0x42108563, 0x4c1b886a, 0x5e069f71, 0x500d9278, 0xa64d90f, 0x46fd406, 0x1672c31d, 0x1879ce14, 0x3248ed2b, 0x3c43e022, 0x2e5ef739, 0x2055fa30, 0xec01b79a, 0xe20aba93, 0xf017ad88, 0xfe1ca081, 0xd42d83be, 0xda268eb7, 0xc83b99ac, 0xc63094a5, 0x9c59dfd2, 0x9252d2db, 0x804fc5c0, 0x8e44c8c9, 0xa475ebf6, 0xaa7ee6ff, 0xb863f1e4, 0xb668fced, 0xcb1670a, 0x2ba6a03, 0x10a77d18, 0x1eac7011, 0x349d532e, 0x3a965e27, 0x288b493c, 0x26804435, 0x7ce90f42, 0x72e2024b, 0x60ff1550, 0x6ef41859, 0x44c53b66, 0x4ace366f, 0x58d32174, 0x56d82c7d, 0x377a0ca1, 0x397101a8, 0x2b6c16b3, 0x25671bba, 0xf563885, 0x15d358c, 0x13402297, 0x1d4b2f9e, 0x472264e9, 0x492969e0, 0x5b347efb, 0x553f73f2, 0x7f0e50cd, 0x71055dc4, 0x63184adf, 0x6d1347d6, 0xd7cadc31, 0xd9c1d138, 0xcbdcc623, 0xc5d7cb2a, 0xefe6e815, 0xe1ede51c, 0xf3f0f207, 0xfdfbff0e, 0xa792b479, 0xa999b970, 0xbb84ae6b, 0xb58fa362, 0x9fbe805d, 0x91b58d54, 0x83a89a4f, 0x8da39746]
|
||||
// swiftlint:enable line_length
|
||||
|
||||
init(key: [UInt8], iv: [UInt8]) throws {
|
||||
self.key = key
|
||||
self.iv = iv
|
||||
}
|
||||
|
||||
/**
|
||||
Encrypt message. If padding is necessary, then PKCS7 padding is added and needs to be removed after decryption.
|
||||
|
||||
- parameter message: Plaintext data
|
||||
- parameter padding: Optional padding
|
||||
|
||||
- returns: Encrypted data
|
||||
*/
|
||||
|
||||
func encrypt(bytes: [UInt8]) throws -> [UInt8] {
|
||||
let finalBytes = PKCS7().add(bytes: bytes, blockSize: AESCipher.blockSize)
|
||||
let blocks = finalBytes.chunks(size: AESCipher.blockSize)
|
||||
return try blockMode.encrypt(blocks: blocks, iv: self.iv, cipherOperation: encrypt)
|
||||
}
|
||||
|
||||
private func encrypt(block: [UInt8]) -> [UInt8]? {
|
||||
let rounds = self.variant.Nr
|
||||
let rk = self.expandedKey
|
||||
var b = toUInt32Array(slice: block[block.startIndex..<block.endIndex])
|
||||
|
||||
var t = [UInt32](repeating: 0, count: 4)
|
||||
|
||||
for r in 0..<rounds - 1 {
|
||||
t[0] = b[0] ^ rk[r][0]
|
||||
t[1] = b[1] ^ rk[r][1]
|
||||
t[2] = b[2] ^ rk[r][2]
|
||||
t[3] = b[3] ^ rk[r][3]
|
||||
|
||||
let lb00 = T0[Int(t[0] & 0xFF)]
|
||||
let lb01 = T1[Int((t[1] >> 8) & 0xFF)]
|
||||
let lb02 = T2[Int((t[2] >> 16) & 0xFF)]
|
||||
let lb03 = T3[Int(t[3] >> 24)]
|
||||
b[0] = lb00 ^ lb01 ^ lb02 ^ lb03
|
||||
|
||||
let lb10 = T0[Int(t[1] & 0xFF)]
|
||||
let lb11 = T1[Int((t[2] >> 8) & 0xFF)]
|
||||
let lb12 = T2[Int((t[3] >> 16) & 0xFF)]
|
||||
let lb13 = T3[Int(t[0] >> 24)]
|
||||
b[1] = lb10 ^ lb11 ^ lb12 ^ lb13
|
||||
|
||||
let lb20 = T0[Int(t[2] & 0xFF)]
|
||||
let lb21 = T1[Int((t[3] >> 8) & 0xFF)]
|
||||
let lb22 = T2[Int((t[0] >> 16) & 0xFF)]
|
||||
let lb23 = T3[Int(t[1] >> 24)]
|
||||
b[2] = lb20 ^ lb21 ^ lb22 ^ lb23
|
||||
|
||||
let lb30 = T0[Int(t[3] & 0xFF)]
|
||||
let lb31 = T1[Int((t[0] >> 8) & 0xFF)]
|
||||
let lb32 = T2[Int((t[1] >> 16) & 0xFF)]
|
||||
let lb33 = T3[Int(t[2] >> 24)]
|
||||
b[3] = lb30 ^ lb31 ^ lb32 ^ lb33
|
||||
}
|
||||
|
||||
// last round
|
||||
let r = rounds - 1
|
||||
|
||||
t[0] = b[0] ^ rk[r][0]
|
||||
t[1] = b[1] ^ rk[r][1]
|
||||
t[2] = b[2] ^ rk[r][2]
|
||||
t[3] = b[3] ^ rk[r][3]
|
||||
|
||||
// rounds
|
||||
b[0] = F1(t[0], t[1], t[2], t[3]) ^ rk[rounds][0]
|
||||
b[1] = F1(t[1], t[2], t[3], t[0]) ^ rk[rounds][1]
|
||||
b[2] = F1(t[2], t[3], t[0], t[1]) ^ rk[rounds][2]
|
||||
b[3] = F1(t[3], t[0], t[1], t[2]) ^ rk[rounds][3]
|
||||
|
||||
var out = [UInt8]()
|
||||
out.reserveCapacity(b.count * 4)
|
||||
for num in b {
|
||||
out.append(UInt8(num & 0xFF))
|
||||
out.append(UInt8((num >> 8) & 0xFF))
|
||||
out.append(UInt8((num >> 16) & 0xFF))
|
||||
out.append(UInt8((num >> 24) & 0xFF))
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func decrypt(bytes: [UInt8]) throws -> [UInt8] {
|
||||
if (bytes.count % AESCipher.blockSize) != 0 {
|
||||
throw Error.blockSizeExceeded
|
||||
}
|
||||
|
||||
let blocks = bytes.chunks(size: AESCipher.blockSize)
|
||||
return try PKCS7().remove(bytes: blockMode.decrypt(blocks: blocks, iv: self.iv, cipherOperation: decrypt), blockSize: AESCipher.blockSize)
|
||||
}
|
||||
|
||||
private func decrypt(block: [UInt8]) -> [UInt8]? {
|
||||
let rounds = self.variant.Nr
|
||||
let rk = expandedKeyInv
|
||||
var b = toUInt32Array(slice: block[block.startIndex..<block.endIndex])
|
||||
|
||||
var t = [UInt32](repeating: 0, count: 4)
|
||||
|
||||
for r in (2...rounds).reversed() {
|
||||
t[0] = b[0] ^ rk[r][0]
|
||||
t[1] = b[1] ^ rk[r][1]
|
||||
t[2] = b[2] ^ rk[r][2]
|
||||
t[3] = b[3] ^ rk[r][3]
|
||||
|
||||
let b00 = T0_INV[Int(t[0] & 0xFF)]
|
||||
let b01 = T1_INV[Int((t[3] >> 8) & 0xFF)]
|
||||
let b02 = T2_INV[Int((t[2] >> 16) & 0xFF)]
|
||||
let b03 = T3_INV[Int(t[1] >> 24)]
|
||||
b[0] = b00 ^ b01 ^ b02 ^ b03
|
||||
|
||||
let b10 = T0_INV[Int(t[1] & 0xFF)]
|
||||
let b11 = T1_INV[Int((t[0] >> 8) & 0xFF)]
|
||||
let b12 = T2_INV[Int((t[3] >> 16) & 0xFF)]
|
||||
let b13 = T3_INV[Int(t[2] >> 24)]
|
||||
b[1] = b10 ^ b11 ^ b12 ^ b13
|
||||
|
||||
let b20 = T0_INV[Int(t[2] & 0xFF)]
|
||||
let b21 = T1_INV[Int((t[1] >> 8) & 0xFF)]
|
||||
let b22 = T2_INV[Int((t[0] >> 16) & 0xFF)]
|
||||
let b23 = T3_INV[Int(t[3] >> 24)]
|
||||
b[2] = b20 ^ b21 ^ b22 ^ b23
|
||||
|
||||
let b30 = T0_INV[Int(t[3] & 0xFF)]
|
||||
let b31 = T1_INV[Int((t[2] >> 8) & 0xFF)]
|
||||
let b32 = T2_INV[Int((t[1] >> 16) & 0xFF)]
|
||||
let b33 = T3_INV[Int(t[0] >> 24)]
|
||||
b[3] = b30 ^ b31 ^ b32 ^ b33
|
||||
}
|
||||
|
||||
// last round
|
||||
t[0] = b[0] ^ rk[1][0]
|
||||
t[1] = b[1] ^ rk[1][1]
|
||||
t[2] = b[2] ^ rk[1][2]
|
||||
t[3] = b[3] ^ rk[1][3]
|
||||
|
||||
// rounds
|
||||
|
||||
let lb00 = sBoxInv[Int(B0(t[0]))]
|
||||
let lb01 = (sBoxInv[Int(B1(t[3]))] << 8)
|
||||
let lb02 = (sBoxInv[Int(B2(t[2]))] << 16)
|
||||
let lb03 = (sBoxInv[Int(B3(t[1]))] << 24)
|
||||
b[0] = lb00 | lb01 | lb02 | lb03 ^ rk[0][0]
|
||||
|
||||
let lb10 = sBoxInv[Int(B0(t[1]))]
|
||||
let lb11 = (sBoxInv[Int(B1(t[0]))] << 8)
|
||||
let lb12 = (sBoxInv[Int(B2(t[3]))] << 16)
|
||||
let lb13 = (sBoxInv[Int(B3(t[2]))] << 24)
|
||||
b[1] = lb10 | lb11 | lb12 | lb13 ^ rk[0][1]
|
||||
|
||||
let lb20 = sBoxInv[Int(B0(t[2]))]
|
||||
let lb21 = (sBoxInv[Int(B1(t[1]))] << 8)
|
||||
let lb22 = (sBoxInv[Int(B2(t[0]))] << 16)
|
||||
let lb23 = (sBoxInv[Int(B3(t[3]))] << 24)
|
||||
b[2] = lb20 | lb21 | lb22 | lb23 ^ rk[0][2]
|
||||
|
||||
let lb30 = sBoxInv[Int(B0(t[3]))]
|
||||
let lb31 = (sBoxInv[Int(B1(t[2]))] << 8)
|
||||
let lb32 = (sBoxInv[Int(B2(t[1]))] << 16)
|
||||
let lb33 = (sBoxInv[Int(B3(t[0]))] << 24)
|
||||
b[3] = lb30 | lb31 | lb32 | lb33 ^ rk[0][3]
|
||||
|
||||
var out = [UInt8]()
|
||||
out.reserveCapacity(b.count * 4)
|
||||
for num in b {
|
||||
out.append(UInt8(num & 0xFF))
|
||||
out.append(UInt8((num >> 8) & 0xFF))
|
||||
out.append(UInt8((num >> 16) & 0xFF))
|
||||
out.append(UInt8((num >> 24) & 0xFF))
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
fileprivate func expandKeyInv(_ key: Key, variant: Variant) -> Array<Array<UInt32>> {
|
||||
let rounds = variant.Nr
|
||||
var rk2: Array<Array<UInt32>> = expandKey(key, variant: variant)
|
||||
|
||||
for r in 1..<rounds {
|
||||
var w: UInt32
|
||||
|
||||
w = rk2[r][0]
|
||||
let u1 = U1[Int(B0(w))] ^ U2[Int(B1(w))]
|
||||
let u2 = U3[Int(B2(w))] ^ U4[Int(B3(w))]
|
||||
rk2[r][0] = u1 ^ u2
|
||||
|
||||
w = rk2[r][1]
|
||||
let u11 = U1[Int(B0(w))] ^ U2[Int(B1(w))]
|
||||
let u12 = U3[Int(B2(w))] ^ U4[Int(B3(w))]
|
||||
rk2[r][1] = u11 ^ u12
|
||||
|
||||
w = rk2[r][2]
|
||||
let u22 = U1[Int(B0(w))] ^ U2[Int(B1(w))]
|
||||
let u23 = U3[Int(B2(w))] ^ U4[Int(B3(w))]
|
||||
rk2[r][2] = u22 ^ u23
|
||||
|
||||
w = rk2[r][3]
|
||||
let u33 = U1[Int(B0(w))] ^ U2[Int(B1(w))]
|
||||
let u34 = U3[Int(B2(w))] ^ U4[Int(B3(w))]
|
||||
rk2[r][3] = u33 ^ u34
|
||||
}
|
||||
|
||||
return rk2
|
||||
}
|
||||
|
||||
fileprivate func expandKey(_ key: Key, variant: Variant) -> Array<Array<UInt32>> {
|
||||
|
||||
func convertExpandedKey(_ expanded: Array<UInt8>) -> Array<Array<UInt32>> {
|
||||
var arr = Array<UInt32>()
|
||||
for idx in stride(from: expanded.startIndex, to: expanded.endIndex, by: 4) {
|
||||
let four = Array(expanded[idx..<idx.advanced(by: 4)].reversed())
|
||||
let num = UInt32(bytes: four)
|
||||
arr.append(num)
|
||||
}
|
||||
|
||||
var allarr = Array<Array<UInt32>>()
|
||||
for idx in stride(from: arr.startIndex, to: arr.endIndex, by: 4) {
|
||||
allarr.append(Array(arr[idx..<idx.advanced(by: 4)]))
|
||||
}
|
||||
return allarr
|
||||
}
|
||||
|
||||
/*
|
||||
* Function used in the Key Expansion routine that takes a four-byte
|
||||
* input word and applies an S-box to each of the four bytes to
|
||||
* produce an output word.
|
||||
*/
|
||||
func subWord(_ word: Array<UInt8>) -> Array<UInt8> {
|
||||
var result = word
|
||||
for i in 0..<4 {
|
||||
result[i] = UInt8(sBox[Int(word[i])])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
var w = Array<UInt8>(repeating: 0, count: variant.Nb * (variant.Nr + 1) * 4)
|
||||
for i in 0..<variant.Nk {
|
||||
for wordIdx in 0..<4 {
|
||||
w[(4*i)+wordIdx] = key[(4*i)+wordIdx]
|
||||
}
|
||||
}
|
||||
|
||||
var tmp: Array<UInt8>
|
||||
|
||||
for i in variant.Nk..<variant.Nb * (variant.Nr + 1) {
|
||||
tmp = Array<UInt8>(repeating: 0, count: 4)
|
||||
|
||||
for wordIdx in 0..<4 {
|
||||
tmp[wordIdx] = w[4*(i-1)+wordIdx]
|
||||
}
|
||||
if (i % variant.Nk) == 0 {
|
||||
tmp = subWord(rotateLeft(UInt32(bytes: tmp), by: 8).bytes(totalBytes: MemoryLayout<UInt32>.size))
|
||||
tmp[0] = tmp.first! ^ Rcon[i/variant.Nk]
|
||||
} else if variant.Nk > 6 && (i % variant.Nk) == 4 {
|
||||
tmp = subWord(tmp)
|
||||
}
|
||||
|
||||
// xor array of bytes
|
||||
for wordIdx in 0..<4 {
|
||||
w[4*i+wordIdx] = w[4*(i-variant.Nk)+wordIdx]^tmp[wordIdx]
|
||||
}
|
||||
}
|
||||
return convertExpandedKey(w)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension AESCipher {
|
||||
|
||||
fileprivate func B0(_ x: UInt32) -> UInt32 {
|
||||
return x & 0xFF
|
||||
}
|
||||
|
||||
fileprivate func B1(_ x: UInt32) -> UInt32 {
|
||||
return (x >> 8) & 0xFF
|
||||
}
|
||||
|
||||
fileprivate func B2(_ x: UInt32) -> UInt32 {
|
||||
return (x >> 16) & 0xFF
|
||||
}
|
||||
|
||||
fileprivate func B3(_ x: UInt32) -> UInt32 {
|
||||
return (x >> 24) & 0xFF
|
||||
}
|
||||
|
||||
fileprivate func F1(_ x0: UInt32, _ x1: UInt32, _ x2: UInt32, _ x3: UInt32) -> UInt32 {
|
||||
var result: UInt32 = 0
|
||||
result |= UInt32(B1(T0[Int(x0 & 255)]))
|
||||
result |= UInt32(B1(T0[Int((x1 >> 8) & 255)])) << 8
|
||||
result |= UInt32(B1(T0[Int((x2 >> 16) & 255)])) << 16
|
||||
result |= UInt32(B1(T0[Int(x3 >> 24)])) << 24
|
||||
return result
|
||||
}
|
||||
|
||||
fileprivate func calculateSBox() -> (sBox: Array<UInt32>, invSBox: Array<UInt32>) {
|
||||
var sbox = Array<UInt32>(repeating: 0, count: 256)
|
||||
var invsbox = sbox
|
||||
sbox[0] = 0x63
|
||||
|
||||
var p: UInt8 = 1, q: UInt8 = 1
|
||||
|
||||
repeat {
|
||||
#if swift(>=4.0)
|
||||
p = p ^ (UInt8(truncatingIfNeeded: Int(p) << 1) ^ ((p & 0x80) == 0x80 ? 0x1B : 0))
|
||||
#else
|
||||
p = p ^ (UInt8(truncatingBitPattern: Int(p) << 1) ^ ((p & 0x80) == 0x80 ? 0x1B : 0))
|
||||
|
||||
#endif
|
||||
q ^= q << 1
|
||||
q ^= q << 2
|
||||
q ^= q << 4
|
||||
q ^= (q & 0x80) == 0x80 ? 0x09 : 0
|
||||
|
||||
let s = 0x63 ^ q ^ rotateLeft(q, by: 1) ^ rotateLeft(q, by: 2) ^ rotateLeft(q, by: 3) ^ rotateLeft(q, by: 4)
|
||||
|
||||
sbox[Int(p)] = UInt32(s)
|
||||
invsbox[Int(s)] = UInt32(p)
|
||||
} while (p != 1)
|
||||
|
||||
return (sBox: sbox, invSBox: invsbox)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Foundation
|
||||
|
||||
extension AESCipher {
|
||||
|
||||
convenience init(key: String, iv: String) throws {
|
||||
guard let kkey = key.data(using: String.Encoding.utf8, allowLossyConversion: false)?.bytes,
|
||||
// swiftlint:disable conditional_binding_cascade
|
||||
let iiv = iv.data(using: String.Encoding.utf8, allowLossyConversion: false)?.bytes else {
|
||||
// swiftlint:disable conditional_binding_cascade
|
||||
throw Error.invalidKeyOrInitializationVector
|
||||
}
|
||||
|
||||
try self.init(key: kkey, iv: iiv)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - CBC
|
||||
|
||||
// I have no better name for that
|
||||
private typealias CipherOperationOnBlock = ([UInt8]) -> [UInt8]?
|
||||
|
||||
private struct CBCBlockMode {
|
||||
enum BlockError: Swift.Error {
|
||||
case MissingInitializationVector
|
||||
}
|
||||
|
||||
func encrypt(blocks: [[UInt8]], iv: [UInt8]?, cipherOperation: CipherOperationOnBlock) throws -> [UInt8] {
|
||||
precondition(!blocks.isEmpty)
|
||||
guard let iv = iv else {
|
||||
throw BlockError.MissingInitializationVector
|
||||
}
|
||||
|
||||
var out: [UInt8] = [UInt8]()
|
||||
out.reserveCapacity(blocks.count * blocks[blocks.startIndex].count)
|
||||
var prevCiphertext = iv // for the first time prevCiphertext = iv
|
||||
for plaintext in blocks {
|
||||
if let encrypted = cipherOperation(xor(prevCiphertext, plaintext)) {
|
||||
out.append(contentsOf: encrypted)
|
||||
prevCiphertext = encrypted
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func decrypt(blocks: [[UInt8]], iv: [UInt8]?, cipherOperation: CipherOperationOnBlock) throws -> [UInt8] {
|
||||
precondition(!blocks.isEmpty)
|
||||
guard let iv = iv else {
|
||||
throw BlockError.MissingInitializationVector
|
||||
}
|
||||
|
||||
var out: [UInt8] = [UInt8]()
|
||||
out.reserveCapacity(blocks.count * blocks[blocks.startIndex].count)
|
||||
var prevCiphertext = iv // for the first time prevCiphertext = iv
|
||||
for ciphertext in blocks {
|
||||
if let decrypted = cipherOperation(ciphertext) { // decrypt
|
||||
out.append(contentsOf: xor(prevCiphertext, decrypted))
|
||||
}
|
||||
prevCiphertext = ciphertext
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Padding
|
||||
|
||||
private struct PKCS7 {
|
||||
|
||||
init() {
|
||||
}
|
||||
|
||||
func add(bytes: [UInt8], blockSize: Int) -> [UInt8] {
|
||||
let padding = UInt8(blockSize - (bytes.count % blockSize))
|
||||
var withPadding = bytes
|
||||
if padding == 0 {
|
||||
// If the original data is a multiple of N bytes, then an extra block of bytes with value N is added.
|
||||
for _ in 0..<blockSize {
|
||||
withPadding.append(contentsOf: [UInt8(blockSize)])
|
||||
}
|
||||
} else {
|
||||
// The value of each added byte is the number of bytes that are added
|
||||
for _ in 0..<padding {
|
||||
withPadding.append(contentsOf: [UInt8(padding)])
|
||||
}
|
||||
}
|
||||
return withPadding
|
||||
}
|
||||
|
||||
func remove(bytes: [UInt8], blockSize: Int?) -> [UInt8] {
|
||||
let lastByte = bytes.last!
|
||||
let padding = Int(lastByte) // last byte
|
||||
let finalLength = bytes.count - padding
|
||||
|
||||
if finalLength < 0 {
|
||||
return bytes
|
||||
}
|
||||
|
||||
if padding >= 1 {
|
||||
return Array(bytes[0..<finalLength])
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Utils
|
||||
|
||||
private func xor(_ a: Array<UInt8>, _ b: Array<UInt8>) -> Array<UInt8> {
|
||||
var xored = Array<UInt8>(repeating: 0, count: min(a.count, b.count))
|
||||
for i in 0..<xored.count {
|
||||
xored[i] = a[i] ^ b[i]
|
||||
}
|
||||
return xored
|
||||
}
|
||||
|
||||
private func rotateLeft(_ value: UInt8, by: UInt8) -> UInt8 {
|
||||
return ((value << by) & 0xFF) | (value >> (8 - by))
|
||||
}
|
||||
|
||||
private func rotateLeft(_ value: UInt32, by: UInt32) -> UInt32 {
|
||||
return ((value << by) & 0xFFFFFFFF) | (value >> (32 - by))
|
||||
}
|
||||
|
||||
private protocol BitshiftOperationsType {
|
||||
static func << (lhs: Self, rhs: Self) -> Self
|
||||
}
|
||||
|
||||
private protocol ByteConvertible {
|
||||
init(_ value: UInt8)
|
||||
init(truncatingBitPattern: UInt64)
|
||||
}
|
||||
|
||||
#if swift(>=4.0)
|
||||
#else
|
||||
extension UInt32: BitshiftOperationsType, ByteConvertible { }
|
||||
#endif
|
||||
|
||||
fileprivate extension UInt32 {
|
||||
init<T: Collection>(bytes: T) where T.Iterator.Element == UInt8, T.Index == Int {
|
||||
self = bytes.toInteger()
|
||||
}
|
||||
|
||||
func bytes(totalBytes: Int = MemoryLayout<UInt32>.size) -> Array<UInt8> {
|
||||
return arrayOfBytes(value: self, length: totalBytes)
|
||||
}
|
||||
}
|
||||
|
||||
private func toUInt32Array(slice: ArraySlice<UInt8>) -> Array<UInt32> {
|
||||
var result = Array<UInt32>()
|
||||
result.reserveCapacity(16)
|
||||
for idx in stride(from: slice.startIndex, to: slice.endIndex, by: MemoryLayout<UInt32>.size) {
|
||||
var val: UInt32 = 0
|
||||
val |= slice.count > 3 ? UInt32(slice[idx.advanced(by: 3)]) << 24 : 0
|
||||
val |= slice.count > 2 ? UInt32(slice[idx.advanced(by: 2)]) << 16 : 0
|
||||
val |= slice.count > 1 ? UInt32(slice[idx.advanced(by: 1)]) << 8 : 0
|
||||
val |= !slice.isEmpty ? UInt32(slice[idx]) : 0
|
||||
result.append(val)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/// Array of bytes, little-endian representation. Don't use if not necessary.
|
||||
/// I found this method slow
|
||||
private func arrayOfBytes<T>(value: T, length: Int? = nil) -> Array<UInt8> {
|
||||
let totalBytes = length ?? MemoryLayout<T>.size
|
||||
|
||||
let valuePointer = UnsafeMutablePointer<T>.allocate(capacity: 1)
|
||||
valuePointer.pointee = value
|
||||
|
||||
let bytesPointer = UnsafeMutablePointer<UInt8>(OpaquePointer(valuePointer))
|
||||
var bytes = Array<UInt8>(repeating: 0, count: totalBytes)
|
||||
for j in 0..<min(MemoryLayout<T>.size, totalBytes) {
|
||||
bytes[totalBytes - 1 - j] = (bytesPointer + j).pointee
|
||||
}
|
||||
|
||||
#if swift(>=4.1)
|
||||
valuePointer.deinitialize(count: 1)
|
||||
valuePointer.deallocate()
|
||||
#else
|
||||
valuePointer.deinitialize()
|
||||
valuePointer.deallocate(capacity: 1)
|
||||
#endif
|
||||
|
||||
return bytes
|
||||
}
|
||||
|
||||
fileprivate extension Collection where Self.Iterator.Element == UInt8, Self.Index == Int {
|
||||
#if swift(>=4.0)
|
||||
func toInteger<T>() -> T where T: FixedWidthInteger {
|
||||
if self.isEmpty {
|
||||
return 0
|
||||
}
|
||||
|
||||
let size = MemoryLayout<T>.size
|
||||
var bytes = self.reversed()
|
||||
if bytes.count < MemoryLayout<T>.size {
|
||||
let paddingCount = MemoryLayout<T>.size - bytes.count
|
||||
if paddingCount > 0 {
|
||||
bytes += Array<UInt8>(repeating: 0, count: paddingCount)
|
||||
}
|
||||
}
|
||||
|
||||
if size == 1 {
|
||||
return T(truncatingIfNeeded: UInt64(bytes[0]))
|
||||
}
|
||||
|
||||
var result: T = 0
|
||||
for byte in bytes.reversed() {
|
||||
result = result << 8 | T(byte)
|
||||
}
|
||||
return result
|
||||
}
|
||||
#else
|
||||
func toInteger<T: Integer>() -> T where T: ByteConvertible, T: BitshiftOperationsType {
|
||||
if self.isEmpty {
|
||||
return 0
|
||||
}
|
||||
|
||||
var bytes = self.reversed()
|
||||
if bytes.count < MemoryLayout<T>.size {
|
||||
let paddingCount = MemoryLayout<T>.size - bytes.count
|
||||
if paddingCount > 0 {
|
||||
bytes += Array<UInt8>(repeating: 0, count: paddingCount)
|
||||
}
|
||||
}
|
||||
|
||||
if MemoryLayout<T>.size == 1 {
|
||||
return T(truncatingBitPattern: UInt64(bytes[0]))
|
||||
}
|
||||
|
||||
var result: T = 0
|
||||
for byte in bytes.reversed() {
|
||||
result = result << 8 | T(byte)
|
||||
}
|
||||
return result
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
fileprivate extension Array {
|
||||
/** split in chunks with given chunk size */
|
||||
func chunks(size chunksize: Int) -> Array<Array<Element>> {
|
||||
var words = Array<Array<Element>>()
|
||||
words.reserveCapacity(self.count / chunksize)
|
||||
for idx in stride(from: chunksize, through: self.count, by: chunksize) {
|
||||
words.append(Array(self[idx - chunksize..<idx])) // slow for large table
|
||||
}
|
||||
let reminder = self.suffix(self.count % chunksize)
|
||||
if !reminder.isEmpty {
|
||||
words.append(Array(reminder))
|
||||
}
|
||||
return words
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate extension Data {
|
||||
var bytes: Array<UInt8> {
|
||||
return Array(self)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
//
|
||||
// Base64.swift
|
||||
// SwiftyBeaver (macOS)
|
||||
//
|
||||
// Copyright © 2017 Sebastian Kreutzberger. All rights reserved.
|
||||
//
|
||||
#if os(Linux)
|
||||
import Foundation
|
||||
|
||||
struct InvalidBase64: Error {}
|
||||
|
||||
struct Base64 {
|
||||
static func decode(_ string: String) throws -> [UInt8] {
|
||||
return try decode([UInt8](string.utf8))
|
||||
}
|
||||
|
||||
/// Decodes a Base64 encoded String into Data
|
||||
///
|
||||
/// - throws: If the string isn't base64 encoded
|
||||
static func decode(_ string: [UInt8]) throws -> [UInt8] {
|
||||
let lookupTable: [UInt8] = [
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 62, 64, 63,
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64,
|
||||
64, 00, 01, 02, 03, 04, 05, 06, 07, 08, 09, 10, 11, 12, 13, 14,
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 63,
|
||||
64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
||||
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64
|
||||
]
|
||||
|
||||
let remainder = string.count % 4
|
||||
let length = (string.count - remainder) / 4
|
||||
|
||||
var decoded = [UInt8]()
|
||||
decoded.reserveCapacity(length)
|
||||
|
||||
var index = 0
|
||||
var i0: UInt8 = 0
|
||||
var i1: UInt8 = 0
|
||||
var i2: UInt8 = 0
|
||||
var i3: UInt8 = 0
|
||||
|
||||
while index &+ 4 < string.count {
|
||||
i0 = lookupTable[numericCast(string[index])]
|
||||
i1 = lookupTable[numericCast(string[index &+ 1])]
|
||||
i2 = lookupTable[numericCast(string[index &+ 2])]
|
||||
i3 = lookupTable[numericCast(string[index &+ 3])]
|
||||
|
||||
if i0 > 63 || i1 > 63 || i2 > 63 || i3 > 63 {
|
||||
throw InvalidBase64()
|
||||
}
|
||||
|
||||
decoded.append(i0 << 2 | i1 >> 4)
|
||||
decoded.append(i1 << 4 | i2 >> 2)
|
||||
decoded.append(i2 << 6 | i3)
|
||||
index += 4
|
||||
}
|
||||
|
||||
if string.count &- index > 1 {
|
||||
i0 = lookupTable[numericCast(string[index])]
|
||||
i1 = lookupTable[numericCast(string[index &+ 1])]
|
||||
|
||||
if i1 > 63 {
|
||||
guard string[index] == 61 else {
|
||||
throw InvalidBase64()
|
||||
}
|
||||
|
||||
return decoded
|
||||
}
|
||||
|
||||
if i2 > 63 {
|
||||
guard string[index &+ 2] == 61 else {
|
||||
throw InvalidBase64()
|
||||
}
|
||||
|
||||
return decoded
|
||||
}
|
||||
|
||||
decoded.append(i0 << 2 | i1 >> 4)
|
||||
|
||||
if string.count &- index > 2 {
|
||||
i2 = lookupTable[numericCast(string[index &+ 2])]
|
||||
|
||||
if i2 > 63 {
|
||||
guard string[index &+ 2] == 61 else {
|
||||
throw InvalidBase64()
|
||||
}
|
||||
|
||||
return decoded
|
||||
}
|
||||
|
||||
decoded.append(i1 << 4 | i2 >> 2)
|
||||
|
||||
if string.count &- index > 3 {
|
||||
i3 = lookupTable[numericCast(string[index &+ 3])]
|
||||
|
||||
if i3 > 63 {
|
||||
guard string[index &+ 3] == 61 else {
|
||||
throw InvalidBase64()
|
||||
}
|
||||
|
||||
return decoded
|
||||
}
|
||||
|
||||
decoded.append(i2 << 6 | i3)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return decoded
|
||||
}
|
||||
|
||||
static func encode(_ data: [UInt8]) -> String {
|
||||
let base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||
var encoded: String = ""
|
||||
|
||||
func appendCharacterFromBase(_ character: Int) {
|
||||
encoded.append(base64[base64.index(base64.startIndex, offsetBy: character)])
|
||||
}
|
||||
|
||||
func byte(_ index: Int) -> Int {
|
||||
return Int(data[index])
|
||||
}
|
||||
|
||||
let decodedBytes = data.map { Int($0) }
|
||||
|
||||
var i = 0
|
||||
|
||||
while i < decodedBytes.count - 2 {
|
||||
appendCharacterFromBase((byte(i) >> 2) & 0x3F)
|
||||
appendCharacterFromBase(((byte(i) & 0x3) << 4) | ((byte(i + 1) & 0xF0) >> 4))
|
||||
appendCharacterFromBase(((byte(i + 1) & 0xF) << 2) | ((byte(i + 2) & 0xC0) >> 6))
|
||||
appendCharacterFromBase(byte(i + 2) & 0x3F)
|
||||
i += 3
|
||||
}
|
||||
|
||||
if i < decodedBytes.count {
|
||||
appendCharacterFromBase((byte(i) >> 2) & 0x3F)
|
||||
|
||||
if i == decodedBytes.count - 1 {
|
||||
appendCharacterFromBase(((byte(i) & 0x3) << 4))
|
||||
encoded.append("=")
|
||||
} else {
|
||||
appendCharacterFromBase(((byte(i) & 0x3) << 4) | ((byte(i + 1) & 0xF0) >> 4))
|
||||
appendCharacterFromBase(((byte(i + 1) & 0xF) << 2))
|
||||
}
|
||||
|
||||
encoded.append("=")
|
||||
}
|
||||
|
||||
return encoded
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,510 @@
|
||||
//
|
||||
// BaseDestination.swift
|
||||
// SwiftyBeaver
|
||||
//
|
||||
// Created by Sebastian Kreutzberger (Twitter @skreutzb) on 05.12.15.
|
||||
// Copyright © 2015 Sebastian Kreutzberger
|
||||
// Some rights reserved: http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Dispatch
|
||||
|
||||
// store operating system / platform
|
||||
#if os(iOS)
|
||||
let OS = "iOS"
|
||||
#elseif os(OSX)
|
||||
let OS = "OSX"
|
||||
#elseif os(watchOS)
|
||||
let OS = "watchOS"
|
||||
#elseif os(tvOS)
|
||||
let OS = "tvOS"
|
||||
#elseif os(Linux)
|
||||
let OS = "Linux"
|
||||
#elseif os(FreeBSD)
|
||||
let OS = "FreeBSD"
|
||||
#elseif os(Windows)
|
||||
let OS = "Windows"
|
||||
#elseif os(Android)
|
||||
let OS = "Android"
|
||||
#else
|
||||
let OS = "Unknown"
|
||||
#endif
|
||||
|
||||
/// destination which all others inherit from. do not directly use
|
||||
open class BaseDestination: Hashable, Equatable {
|
||||
|
||||
/// output format pattern, see documentation for syntax
|
||||
open var format = "$DHH:mm:ss.SSS$d $C$L$c $N.$F:$l - $M"
|
||||
|
||||
/// runs in own serial background thread for better performance
|
||||
open var asynchronously = true
|
||||
|
||||
/// do not log any message which has a lower level than this one
|
||||
open var minLevel = SwiftyBeaver.Level.verbose
|
||||
|
||||
/// set custom log level words for each level
|
||||
open var levelString = LevelString()
|
||||
|
||||
/// set custom log level colors for each level
|
||||
open var levelColor = LevelColor()
|
||||
|
||||
public struct LevelString {
|
||||
public var verbose = "VERBOSE"
|
||||
public var debug = "DEBUG"
|
||||
public var info = "INFO"
|
||||
public var warning = "WARNING"
|
||||
public var error = "ERROR"
|
||||
}
|
||||
|
||||
// For a colored log level word in a logged line
|
||||
// empty on default
|
||||
public struct LevelColor {
|
||||
public var verbose = "" // silver
|
||||
public var debug = "" // green
|
||||
public var info = "" // blue
|
||||
public var warning = "" // yellow
|
||||
public var error = "" // red
|
||||
}
|
||||
|
||||
var reset = ""
|
||||
var escape = ""
|
||||
|
||||
var filters = [FilterType]()
|
||||
let formatter = DateFormatter()
|
||||
let startDate = Date()
|
||||
|
||||
// each destination class must have an own hashValue Int
|
||||
#if swift(>=4.2)
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(defaultHashValue)
|
||||
}
|
||||
#else
|
||||
lazy public var hashValue: Int = self.defaultHashValue
|
||||
#endif
|
||||
|
||||
open var defaultHashValue: Int {return 0}
|
||||
|
||||
// each destination instance must have an own serial queue to ensure serial output
|
||||
// GCD gives it a prioritization between User Initiated and Utility
|
||||
var queue: DispatchQueue? //dispatch_queue_t?
|
||||
var debugPrint = false // set to true to debug the internal filter logic of the class
|
||||
|
||||
public init() {
|
||||
let uuid = NSUUID().uuidString
|
||||
let queueLabel = "swiftybeaver-queue-" + uuid
|
||||
queue = DispatchQueue(label: queueLabel, target: queue)
|
||||
}
|
||||
|
||||
/// send / store the formatted log message to the destination
|
||||
/// returns the formatted log message for processing by inheriting method
|
||||
/// and for unit tests (nil if error)
|
||||
open func send(_ level: SwiftyBeaver.Level, msg: String, thread: String, file: String,
|
||||
function: String, line: Int, context: Any? = nil) -> String? {
|
||||
|
||||
if format.hasPrefix("$J") {
|
||||
return messageToJSON(level, msg: msg, thread: thread,
|
||||
file: file, function: function, line: line, context: context)
|
||||
|
||||
} else {
|
||||
return formatMessage(format, level: level, msg: msg, thread: thread,
|
||||
file: file, function: function, line: line, context: context)
|
||||
}
|
||||
}
|
||||
|
||||
public func execute(synchronously: Bool, block: @escaping () -> Void) {
|
||||
guard let queue = queue else {
|
||||
fatalError("Queue not set")
|
||||
}
|
||||
if synchronously {
|
||||
queue.sync(execute: block)
|
||||
} else {
|
||||
queue.async(execute: block)
|
||||
}
|
||||
}
|
||||
|
||||
public func executeSynchronously<T>(block: @escaping () throws -> T) rethrows -> T {
|
||||
guard let queue = queue else {
|
||||
fatalError("Queue not set")
|
||||
}
|
||||
return try queue.sync(execute: block)
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// MARK: Format
|
||||
////////////////////////////////
|
||||
|
||||
/// returns (padding length value, offset in string after padding info)
|
||||
private func parsePadding(_ text: String) -> (Int, Int) {
|
||||
// look for digits followed by a alpha character
|
||||
var s: String!
|
||||
var sign: Int = 1
|
||||
if text.firstChar == "-" {
|
||||
sign = -1
|
||||
s = String(text.suffix(from: text.index(text.startIndex, offsetBy: 1)))
|
||||
} else {
|
||||
s = text
|
||||
}
|
||||
let numStr = s.prefix { $0 >= "0" && $0 <= "9" }
|
||||
if let num = Int(String(numStr)) {
|
||||
return (sign * num, (sign == -1 ? 1 : 0) + numStr.count)
|
||||
} else {
|
||||
return (0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
private func paddedString(_ text: String, _ toLength: Int, truncating: Bool = false) -> String {
|
||||
if toLength > 0 {
|
||||
// Pad to the left of the string
|
||||
if text.count > toLength {
|
||||
// Hm... better to use suffix or prefix?
|
||||
return truncating ? String(text.suffix(toLength)) : text
|
||||
} else {
|
||||
return "".padding(toLength: toLength - text.count, withPad: " ", startingAt: 0) + text
|
||||
}
|
||||
} else if toLength < 0 {
|
||||
// Pad to the right of the string
|
||||
let maxLength = truncating ? -toLength : max(-toLength, text.count)
|
||||
return text.padding(toLength: maxLength, withPad: " ", startingAt: 0)
|
||||
} else {
|
||||
return text
|
||||
}
|
||||
}
|
||||
|
||||
/// returns the log message based on the format pattern
|
||||
func formatMessage(_ format: String, level: SwiftyBeaver.Level, msg: String, thread: String,
|
||||
file: String, function: String, line: Int, context: Any? = nil) -> String {
|
||||
|
||||
var text = ""
|
||||
// Prepend a $I for 'ignore' or else the first character is interpreted as a format character
|
||||
// even if the format string did not start with a $.
|
||||
let phrases: [String] = ("$I" + format).components(separatedBy: "$")
|
||||
|
||||
for phrase in phrases where !phrase.isEmpty {
|
||||
let (padding, offset) = parsePadding(phrase)
|
||||
let formatCharIndex = phrase.index(phrase.startIndex, offsetBy: offset)
|
||||
let formatChar = phrase[formatCharIndex]
|
||||
let rangeAfterFormatChar = phrase.index(formatCharIndex, offsetBy: 1)..<phrase.endIndex
|
||||
let remainingPhrase = phrase[rangeAfterFormatChar]
|
||||
|
||||
switch formatChar {
|
||||
case "I": // ignore
|
||||
text += remainingPhrase
|
||||
case "L":
|
||||
text += paddedString(levelWord(level), padding) + remainingPhrase
|
||||
case "M":
|
||||
text += paddedString(msg, padding) + remainingPhrase
|
||||
case "T":
|
||||
text += paddedString(thread, padding) + remainingPhrase
|
||||
case "N":
|
||||
// name of file without suffix
|
||||
text += paddedString(fileNameWithoutSuffix(file), padding) + remainingPhrase
|
||||
case "n":
|
||||
// name of file with suffix
|
||||
text += paddedString(fileNameOfFile(file), padding) + remainingPhrase
|
||||
case "F":
|
||||
text += paddedString(function, padding) + remainingPhrase
|
||||
case "l":
|
||||
text += paddedString(String(line), padding) + remainingPhrase
|
||||
case "D":
|
||||
// start of datetime format
|
||||
#if swift(>=3.2)
|
||||
text += paddedString(formatDate(String(remainingPhrase)), padding)
|
||||
#else
|
||||
text += paddedString(formatDate(remainingPhrase), padding)
|
||||
#endif
|
||||
case "d":
|
||||
text += remainingPhrase
|
||||
case "U":
|
||||
text += paddedString(uptime(), padding) + remainingPhrase
|
||||
case "Z":
|
||||
// start of datetime format in UTC timezone
|
||||
#if swift(>=3.2)
|
||||
text += paddedString(formatDate(String(remainingPhrase), timeZone: "UTC"), padding)
|
||||
#else
|
||||
text += paddedString(formatDate(remainingPhrase, timeZone: "UTC"), padding)
|
||||
#endif
|
||||
case "z":
|
||||
text += remainingPhrase
|
||||
case "C":
|
||||
// color code ("" on default)
|
||||
text += escape + colorForLevel(level) + remainingPhrase
|
||||
case "c":
|
||||
text += reset + remainingPhrase
|
||||
case "X":
|
||||
// add the context
|
||||
if let cx = context {
|
||||
text += paddedString(String(describing: cx).trimmingCharacters(in: .whitespacesAndNewlines), padding) + remainingPhrase
|
||||
} else {
|
||||
text += paddedString("", padding) + remainingPhrase
|
||||
}
|
||||
default:
|
||||
text += phrase
|
||||
}
|
||||
}
|
||||
// right trim only
|
||||
return text.replacingOccurrences(of: "\\s+$", with: "", options: .regularExpression)
|
||||
}
|
||||
|
||||
/// returns the log payload as optional JSON string
|
||||
func messageToJSON(_ level: SwiftyBeaver.Level, msg: String,
|
||||
thread: String, file: String, function: String, line: Int, context: Any? = nil) -> String? {
|
||||
var dict: [String: Any] = [
|
||||
"timestamp": Date().timeIntervalSince1970,
|
||||
"level": level.rawValue,
|
||||
"message": msg,
|
||||
"thread": thread,
|
||||
"file": file,
|
||||
"function": function,
|
||||
"line": line
|
||||
]
|
||||
if let cx = context {
|
||||
dict["context"] = cx
|
||||
}
|
||||
return jsonStringFromDict(dict)
|
||||
}
|
||||
|
||||
/// returns the string of a level
|
||||
func levelWord(_ level: SwiftyBeaver.Level) -> String {
|
||||
|
||||
var str = ""
|
||||
|
||||
switch level {
|
||||
case .debug:
|
||||
str = levelString.debug
|
||||
|
||||
case .info:
|
||||
str = levelString.info
|
||||
|
||||
case .warning:
|
||||
str = levelString.warning
|
||||
|
||||
case .error:
|
||||
str = levelString.error
|
||||
|
||||
default:
|
||||
// Verbose is default
|
||||
str = levelString.verbose
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
/// returns color string for level
|
||||
func colorForLevel(_ level: SwiftyBeaver.Level) -> String {
|
||||
var color = ""
|
||||
|
||||
switch level {
|
||||
case .debug:
|
||||
color = levelColor.debug
|
||||
|
||||
case .info:
|
||||
color = levelColor.info
|
||||
|
||||
case .warning:
|
||||
color = levelColor.warning
|
||||
|
||||
case .error:
|
||||
color = levelColor.error
|
||||
|
||||
default:
|
||||
color = levelColor.verbose
|
||||
}
|
||||
return color
|
||||
}
|
||||
|
||||
/// returns the filename of a path
|
||||
func fileNameOfFile(_ file: String) -> String {
|
||||
let fileParts = file.components(separatedBy: "/")
|
||||
if let lastPart = fileParts.last {
|
||||
return lastPart
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
/// returns the filename without suffix (= file ending) of a path
|
||||
func fileNameWithoutSuffix(_ file: String) -> String {
|
||||
let fileName = fileNameOfFile(file)
|
||||
|
||||
if !fileName.isEmpty {
|
||||
let fileNameParts = fileName.components(separatedBy: ".")
|
||||
if let firstPart = fileNameParts.first {
|
||||
return firstPart
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
/// returns a formatted date string
|
||||
/// optionally in a given abbreviated timezone like "UTC"
|
||||
func formatDate(_ dateFormat: String, timeZone: String = "") -> String {
|
||||
if !timeZone.isEmpty {
|
||||
formatter.timeZone = TimeZone(abbreviation: timeZone)
|
||||
}
|
||||
formatter.dateFormat = dateFormat
|
||||
//let dateStr = formatter.string(from: NSDate() as Date)
|
||||
let dateStr = formatter.string(from: Date())
|
||||
return dateStr
|
||||
}
|
||||
|
||||
/// returns a uptime string
|
||||
func uptime() -> String {
|
||||
let interval = Date().timeIntervalSince(startDate)
|
||||
|
||||
let hours = Int(interval) / 3600
|
||||
let minutes = Int(interval / 60) - Int(hours * 60)
|
||||
let seconds = Int(interval) - (Int(interval / 60) * 60)
|
||||
let milliseconds = Int(interval.truncatingRemainder(dividingBy: 1) * 1000)
|
||||
|
||||
return String(format: "%0.2d:%0.2d:%0.2d.%03d", arguments: [hours, minutes, seconds, milliseconds])
|
||||
}
|
||||
|
||||
/// returns the json-encoded string value
|
||||
/// after it was encoded by jsonStringFromDict
|
||||
func jsonStringValue(_ jsonString: String?, key: String) -> String {
|
||||
guard let str = jsonString else {
|
||||
return ""
|
||||
}
|
||||
|
||||
// remove the leading {"key":" from the json string and the final }
|
||||
let offset = key.length + 5
|
||||
let endIndex = str.index(str.startIndex,
|
||||
offsetBy: str.length - 2)
|
||||
let range = str.index(str.startIndex, offsetBy: offset)..<endIndex
|
||||
#if swift(>=3.2)
|
||||
return String(str[range])
|
||||
#else
|
||||
return str[range]
|
||||
#endif
|
||||
}
|
||||
|
||||
/// turns dict into JSON-encoded string
|
||||
func jsonStringFromDict(_ dict: [String: Any]) -> String? {
|
||||
var jsonString: String?
|
||||
|
||||
// try to create JSON string
|
||||
do {
|
||||
let jsonData = try JSONSerialization.data(withJSONObject: dict, options: [])
|
||||
jsonString = String(data: jsonData, encoding: .utf8)
|
||||
} catch {
|
||||
print("SwiftyBeaver could not create JSON from dict.")
|
||||
}
|
||||
return jsonString
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// MARK: Filters
|
||||
////////////////////////////////
|
||||
|
||||
/// Add a filter that determines whether or not a particular message will be logged to this destination
|
||||
public func addFilter(_ filter: FilterType) {
|
||||
filters.append(filter)
|
||||
}
|
||||
|
||||
/// Remove a filter from the list of filters
|
||||
public func removeFilter(_ filter: FilterType) {
|
||||
#if swift(>=5)
|
||||
let index = filters.firstIndex {
|
||||
return ObjectIdentifier($0) == ObjectIdentifier(filter)
|
||||
}
|
||||
#else
|
||||
let index = filters.index {
|
||||
return ObjectIdentifier($0) == ObjectIdentifier(filter)
|
||||
}
|
||||
#endif
|
||||
|
||||
guard let filterIndex = index else {
|
||||
return
|
||||
}
|
||||
|
||||
filters.remove(at: filterIndex)
|
||||
}
|
||||
|
||||
/// Answer whether the destination has any message filters
|
||||
/// returns boolean and is used to decide whether to resolve
|
||||
/// the message before invoking shouldLevelBeLogged
|
||||
func hasMessageFilters() -> Bool {
|
||||
return !getFiltersTargeting(Filter.TargetType.Message(.Equals([], true)),
|
||||
fromFilters: self.filters).isEmpty
|
||||
}
|
||||
|
||||
/// checks if level is at least minLevel or if a minLevel filter for that path does exist
|
||||
/// returns boolean and can be used to decide if a message should be logged or not
|
||||
func shouldLevelBeLogged(_ level: SwiftyBeaver.Level, path: String,
|
||||
function: String, message: String? = nil) -> Bool {
|
||||
|
||||
if filters.isEmpty {
|
||||
if level.rawValue >= minLevel.rawValue {
|
||||
if debugPrint {
|
||||
print("filters are empty and level >= minLevel")
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
if debugPrint {
|
||||
print("filters are empty and level < minLevel")
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
let filterCheckResult = FilterValidator.validate(input: .init(filters: self.filters, level: level, path: path, function: function, message: message))
|
||||
|
||||
// Exclusion filters match if they do NOT meet the filter condition (see Filter.apply(_:) method)
|
||||
switch filterCheckResult[.excluded] {
|
||||
case .some(.someFiltersMatch):
|
||||
// Exclusion filters are present and at least one of them matches the log entry
|
||||
if debugPrint {
|
||||
print("filters are not empty and message was excluded")
|
||||
}
|
||||
return false
|
||||
case .some(.allFiltersMatch), .some(.noFiltersMatchingType), .none: break
|
||||
}
|
||||
|
||||
// If required filters exist, we should validate or invalidate the log if all of them pass or not
|
||||
switch filterCheckResult[.required] {
|
||||
case .some(.allFiltersMatch): return true
|
||||
case .some(.someFiltersMatch): return false
|
||||
case .some(.noFiltersMatchingType), .none: break
|
||||
}
|
||||
|
||||
let checkLogLevel: () -> Bool = {
|
||||
// Check if the log message's level matches or exceeds the minLevel of the destination
|
||||
return level.rawValue >= self.minLevel.rawValue
|
||||
}
|
||||
|
||||
// Non-required filters should only be applied if the log entry matches the filter condition (e.g. path)
|
||||
switch filterCheckResult[.nonRequired] {
|
||||
case .some(.allFiltersMatch): return true
|
||||
case .some(.noFiltersMatchingType), .none: return checkLogLevel()
|
||||
case .some(.someFiltersMatch(let partialMatchData)):
|
||||
if partialMatchData.fullMatchCount > 0 {
|
||||
// The log entry matches at least one filter condition and the destination's log level
|
||||
return true
|
||||
} else if partialMatchData.conditionMatchCount > 0 {
|
||||
// The log entry matches at least one filter condition, but does not match or exceed the destination's log level
|
||||
return false
|
||||
} else {
|
||||
// There is no filter with a matching filter condition. Check the destination's log level
|
||||
return checkLogLevel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getFiltersTargeting(_ target: Filter.TargetType, fromFilters: [FilterType]) -> [FilterType] {
|
||||
return fromFilters.filter { filter in
|
||||
return filter.getTarget() == target
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Triggered by main flush() method on each destination. Runs in background thread.
|
||||
Use for destinations that buffer log items, implement this function to flush those
|
||||
buffers to their final destination (web server...)
|
||||
*/
|
||||
func flush() {
|
||||
// no implementation in base destination needed
|
||||
}
|
||||
}
|
||||
|
||||
public func == (lhs: BaseDestination, rhs: BaseDestination) -> Bool {
|
||||
return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
//
|
||||
// ConsoleDestination.swift
|
||||
// SwiftyBeaver
|
||||
//
|
||||
// Created by Sebastian Kreutzberger on 05.12.15.
|
||||
// Copyright © 2015 Sebastian Kreutzberger
|
||||
// Some rights reserved: http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public class ConsoleDestination: BaseDestination {
|
||||
|
||||
/// use NSLog instead of print, default is false
|
||||
public var useNSLog = false
|
||||
/// uses colors compatible to Terminal instead of Xcode, default is false
|
||||
public var useTerminalColors: Bool = false {
|
||||
didSet {
|
||||
if useTerminalColors {
|
||||
// use Terminal colors
|
||||
reset = "\u{001b}[0m"
|
||||
escape = "\u{001b}[38;5;"
|
||||
levelColor.verbose = "251m" // silver
|
||||
levelColor.debug = "35m" // green
|
||||
levelColor.info = "38m" // blue
|
||||
levelColor.warning = "178m" // yellow
|
||||
levelColor.error = "197m" // red
|
||||
|
||||
} else {
|
||||
// use colored Emojis for better visual distinction
|
||||
// of log level for Xcode 8
|
||||
levelColor.verbose = "💜 " // silver
|
||||
levelColor.debug = "💚 " // green
|
||||
levelColor.info = "💙 " // blue
|
||||
levelColor.warning = "💛 " // yellow
|
||||
levelColor.error = "❤️ " // red
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override public var defaultHashValue: Int { return 1 }
|
||||
|
||||
public override init() {
|
||||
super.init()
|
||||
levelColor.verbose = "💜 " // silver
|
||||
levelColor.debug = "💚 " // green
|
||||
levelColor.info = "💙 " // blue
|
||||
levelColor.warning = "💛 " // yellow
|
||||
levelColor.error = "❤️ " // red
|
||||
}
|
||||
|
||||
// print to Xcode Console. uses full base class functionality
|
||||
override public func send(_ level: SwiftyBeaver.Level, msg: String, thread: String,
|
||||
file: String, function: String, line: Int, context: Any? = nil) -> String? {
|
||||
let formattedString = super.send(level, msg: msg, thread: thread, file: file, function: function, line: line, context: context)
|
||||
|
||||
if let str = formattedString {
|
||||
if useNSLog {
|
||||
#if os(Linux)
|
||||
print(str)
|
||||
#else
|
||||
NSLog("%@", str)
|
||||
#endif
|
||||
} else {
|
||||
print(str)
|
||||
}
|
||||
}
|
||||
return formattedString
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// Extensions.swift
|
||||
// SwiftyBeaver
|
||||
//
|
||||
// Created by Sebastian Kreutzberger on 13.12.17.
|
||||
// Copyright © 2017 Sebastian Kreutzberger. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension String {
|
||||
/// cross-Swift compatible characters count
|
||||
var length: Int {
|
||||
return self.count
|
||||
}
|
||||
|
||||
/// cross-Swift-compatible first character
|
||||
var firstChar: Character? {
|
||||
return self.first
|
||||
}
|
||||
|
||||
/// cross-Swift-compatible last character
|
||||
var lastChar: Character? {
|
||||
return self.last
|
||||
}
|
||||
|
||||
/// cross-Swift-compatible index
|
||||
func find(_ char: Character) -> Index? {
|
||||
#if swift(>=5)
|
||||
return self.firstIndex(of: char)
|
||||
#else
|
||||
return self.index(of: char)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
//
|
||||
// FileDestination.swift
|
||||
// SwiftyBeaver
|
||||
//
|
||||
// Created by Sebastian Kreutzberger on 05.12.15.
|
||||
// Copyright © 2015 Sebastian Kreutzberger
|
||||
// Some rights reserved: http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public class FileDestination: BaseDestination {
|
||||
|
||||
public var logFileURL: URL?
|
||||
public var syncAfterEachWrite: Bool = false
|
||||
public var colored: Bool = false {
|
||||
didSet {
|
||||
if colored {
|
||||
// bash font color, first value is intensity, second is color
|
||||
// see http://bit.ly/1Otu3Zr & for syntax http://bit.ly/1Tp6Fw9
|
||||
// uses the 256-color table from http://bit.ly/1W1qJuH
|
||||
reset = "\u{001b}[0m"
|
||||
escape = "\u{001b}[38;5;"
|
||||
levelColor.verbose = "251m" // silver
|
||||
levelColor.debug = "35m" // green
|
||||
levelColor.info = "38m" // blue
|
||||
levelColor.warning = "178m" // yellow
|
||||
levelColor.error = "197m" // red
|
||||
} else {
|
||||
reset = ""
|
||||
escape = ""
|
||||
levelColor.verbose = ""
|
||||
levelColor.debug = ""
|
||||
levelColor.info = ""
|
||||
levelColor.warning = ""
|
||||
levelColor.error = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override public var defaultHashValue: Int {return 2}
|
||||
let fileManager = FileManager.default
|
||||
|
||||
public init(logFileURL: URL? = nil) {
|
||||
if let logFileURL = logFileURL {
|
||||
self.logFileURL = logFileURL
|
||||
super.init()
|
||||
return
|
||||
}
|
||||
|
||||
// platform-dependent logfile directory default
|
||||
var baseURL: URL?
|
||||
#if os(OSX)
|
||||
if let url = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first {
|
||||
baseURL = url
|
||||
// try to use ~/Library/Caches/APP NAME instead of ~/Library/Caches
|
||||
if let appName = Bundle.main.object(forInfoDictionaryKey: "CFBundleExecutable") as? String {
|
||||
do {
|
||||
if let appURL = baseURL?.appendingPathComponent(appName, isDirectory: true) {
|
||||
try fileManager.createDirectory(at: appURL,
|
||||
withIntermediateDirectories: true, attributes: nil)
|
||||
baseURL = appURL
|
||||
}
|
||||
} catch {
|
||||
print("Warning! Could not create folder /Library/Caches/\(appName)")
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
#if os(Linux)
|
||||
baseURL = URL(fileURLWithPath: "/var/cache")
|
||||
#else
|
||||
// iOS, watchOS, etc. are using the caches directory
|
||||
if let url = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first {
|
||||
baseURL = url
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
if let baseURL = baseURL {
|
||||
self.logFileURL = baseURL.appendingPathComponent("swiftybeaver.log", isDirectory: false)
|
||||
}
|
||||
super.init()
|
||||
}
|
||||
|
||||
// append to file. uses full base class functionality
|
||||
override public func send(_ level: SwiftyBeaver.Level, msg: String, thread: String,
|
||||
file: String, function: String, line: Int, context: Any? = nil) -> String? {
|
||||
let formattedString = super.send(level, msg: msg, thread: thread, file: file, function: function, line: line, context: context)
|
||||
|
||||
if let str = formattedString {
|
||||
_ = saveToFile(str: str)
|
||||
}
|
||||
return formattedString
|
||||
}
|
||||
|
||||
/// appends a string as line to a file.
|
||||
/// returns boolean about success
|
||||
func saveToFile(str: String) -> Bool {
|
||||
guard let url = logFileURL else { return false }
|
||||
|
||||
let line = str + "\n"
|
||||
guard let data = line.data(using: String.Encoding.utf8) else { return false }
|
||||
|
||||
do {
|
||||
if fileManager.fileExists(atPath: url.path) == false {
|
||||
|
||||
let directoryURL = url.deletingLastPathComponent()
|
||||
if fileManager.fileExists(atPath: directoryURL.path) == false {
|
||||
try fileManager.createDirectory(
|
||||
at: directoryURL,
|
||||
withIntermediateDirectories: true
|
||||
)
|
||||
}
|
||||
fileManager.createFile(atPath: url.path, contents: nil)
|
||||
|
||||
#if os(iOS) || os(watchOS)
|
||||
if #available(iOS 10.0, watchOS 3.0, *) {
|
||||
var attributes = try fileManager.attributesOfItem(atPath: url.path)
|
||||
attributes[FileAttributeKey.protectionKey] = FileProtectionType.none
|
||||
try fileManager.setAttributes(attributes, ofItemAtPath: url.path)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
write(data: data, to: url)
|
||||
|
||||
return true
|
||||
} catch {
|
||||
print("SwiftyBeaver File Destination could not write to file \(url).")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private func write(data: Data, to url: URL) {
|
||||
let coordinator = NSFileCoordinator(filePresenter: nil)
|
||||
var error: NSError?
|
||||
coordinator.coordinate(writingItemAt: url, error: &error) { url in
|
||||
do {
|
||||
let fileHandle = try FileHandle(forWritingTo: url)
|
||||
fileHandle.seekToEndOfFile()
|
||||
fileHandle.write(data)
|
||||
if syncAfterEachWrite {
|
||||
fileHandle.synchronizeFile()
|
||||
}
|
||||
fileHandle.closeFile()
|
||||
} catch {
|
||||
print("SwiftyBeaver File Destination could not write to file \(url).")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if let error = error {
|
||||
print("Failed writing file with error: \(String(describing: error))")
|
||||
}
|
||||
}
|
||||
|
||||
/// deletes log file.
|
||||
/// returns true if file was removed or does not exist, false otherwise
|
||||
public func deleteLogFile() -> Bool {
|
||||
guard let url = logFileURL, fileManager.fileExists(atPath: url.path) == true else { return true }
|
||||
do {
|
||||
try fileManager.removeItem(at: url)
|
||||
return true
|
||||
} catch {
|
||||
print("SwiftyBeaver File Destination could not remove file \(url).")
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
//
|
||||
// Filter.swift
|
||||
// SwiftyBeaver
|
||||
//
|
||||
// Created by Jeff Roberts on 5/31/16.
|
||||
// Copyright © 2015 Sebastian Kreutzberger
|
||||
// Some rights reserved: http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// FilterType is a protocol that describes something that determines
|
||||
/// whether or not a message gets logged. A filter answers a Bool when it
|
||||
/// is applied to a value. If the filter passes, it shall return true,
|
||||
/// false otherwise.
|
||||
///
|
||||
/// A filter must contain a target, which identifies what it filters against
|
||||
/// A filter can be required meaning that all required filters against a specific
|
||||
/// target must pass in order for the message to be logged.
|
||||
public protocol FilterType : class {
|
||||
func apply(_ value: Any) -> Bool
|
||||
func getTarget() -> Filter.TargetType
|
||||
func isRequired() -> Bool
|
||||
func isExcluded() -> Bool
|
||||
func reachedMinLevel(_ level: SwiftyBeaver.Level) -> Bool
|
||||
}
|
||||
|
||||
/// Filters is syntactic sugar used to easily construct filters
|
||||
public class Filters {
|
||||
public static let Path = PathFilterFactory.self
|
||||
public static let Function = FunctionFilterFactory.self
|
||||
public static let Message = MessageFilterFactory.self
|
||||
}
|
||||
|
||||
/// Filter is an abstract base class for other filters
|
||||
public class Filter {
|
||||
public enum TargetType {
|
||||
case Path(Filter.ComparisonType)
|
||||
case Function(Filter.ComparisonType)
|
||||
case Message(Filter.ComparisonType)
|
||||
}
|
||||
|
||||
public enum ComparisonType {
|
||||
case StartsWith([String], Bool)
|
||||
case Contains([String], Bool)
|
||||
case Excludes([String], Bool)
|
||||
case EndsWith([String], Bool)
|
||||
case Equals([String], Bool)
|
||||
case Custom((String) -> Bool)
|
||||
}
|
||||
|
||||
let targetType: Filter.TargetType
|
||||
let required: Bool
|
||||
let minLevel: SwiftyBeaver.Level
|
||||
|
||||
public init(_ target: Filter.TargetType, required: Bool, minLevel: SwiftyBeaver.Level) {
|
||||
self.targetType = target
|
||||
self.required = required
|
||||
self.minLevel = minLevel
|
||||
}
|
||||
|
||||
public func getTarget() -> Filter.TargetType {
|
||||
return self.targetType
|
||||
}
|
||||
|
||||
public func isRequired() -> Bool {
|
||||
return self.required
|
||||
}
|
||||
|
||||
public func isExcluded() -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
/// returns true of set minLevel is >= as given level
|
||||
public func reachedMinLevel(_ level: SwiftyBeaver.Level) -> Bool {
|
||||
//print("checking if given level \(level) >= \(minLevel)")
|
||||
return level.rawValue >= minLevel.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
/// CompareFilter is a FilterType that can filter based upon whether a target
|
||||
/// starts with, contains or ends with a specific string. CompareFilters can be
|
||||
/// case sensitive.
|
||||
public class CompareFilter: Filter, FilterType {
|
||||
|
||||
private var filterComparisonType: Filter.ComparisonType?
|
||||
|
||||
override public init(_ target: Filter.TargetType, required: Bool, minLevel: SwiftyBeaver.Level) {
|
||||
super.init(target, required: required, minLevel: minLevel)
|
||||
|
||||
let comparisonType: Filter.ComparisonType?
|
||||
switch self.getTarget() {
|
||||
case let .Function(comparison):
|
||||
comparisonType = comparison
|
||||
|
||||
case let .Path(comparison):
|
||||
comparisonType = comparison
|
||||
|
||||
case let .Message(comparison):
|
||||
comparisonType = comparison
|
||||
|
||||
/*default:
|
||||
comparisonType = nil*/
|
||||
}
|
||||
self.filterComparisonType = comparisonType
|
||||
}
|
||||
|
||||
public func apply(_ value: Any) -> Bool {
|
||||
guard let value = value as? String else {
|
||||
return false
|
||||
}
|
||||
|
||||
guard let filterComparisonType = self.filterComparisonType else {
|
||||
return false
|
||||
}
|
||||
|
||||
let matches: Bool
|
||||
switch filterComparisonType {
|
||||
case let .Contains(strings, caseSensitive):
|
||||
matches = !strings.filter { string in
|
||||
return caseSensitive ? value.contains(string) :
|
||||
value.lowercased().contains(string.lowercased())
|
||||
}.isEmpty
|
||||
|
||||
case let .Excludes(strings, caseSensitive):
|
||||
matches = !strings.filter { string in
|
||||
return caseSensitive ? !value.contains(string) :
|
||||
!value.lowercased().contains(string.lowercased())
|
||||
}.isEmpty
|
||||
|
||||
case let .StartsWith(strings, caseSensitive):
|
||||
matches = !strings.filter { string in
|
||||
return caseSensitive ? value.hasPrefix(string) :
|
||||
value.lowercased().hasPrefix(string.lowercased())
|
||||
}.isEmpty
|
||||
|
||||
case let .EndsWith(strings, caseSensitive):
|
||||
matches = !strings.filter { string in
|
||||
return caseSensitive ? value.hasSuffix(string) :
|
||||
value.lowercased().hasSuffix(string.lowercased())
|
||||
}.isEmpty
|
||||
|
||||
case let .Equals(strings, caseSensitive):
|
||||
matches = !strings.filter { string in
|
||||
return caseSensitive ? value == string :
|
||||
value.lowercased() == string.lowercased()
|
||||
}.isEmpty
|
||||
case let .Custom(predicate):
|
||||
matches = predicate(value)
|
||||
}
|
||||
|
||||
return matches
|
||||
}
|
||||
|
||||
override public func isExcluded() -> Bool {
|
||||
guard let filterComparisonType = self.filterComparisonType else { return false }
|
||||
|
||||
switch filterComparisonType {
|
||||
case .Excludes:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Syntactic sugar for creating a function comparison filter
|
||||
public class FunctionFilterFactory {
|
||||
public static func startsWith(_ prefixes: String..., caseSensitive: Bool = false,
|
||||
required: Bool = false, minLevel: SwiftyBeaver.Level = .verbose) -> FilterType {
|
||||
return CompareFilter(.Function(.StartsWith(prefixes, caseSensitive)), required: required, minLevel: minLevel)
|
||||
}
|
||||
|
||||
public static func contains(_ strings: String..., caseSensitive: Bool = false,
|
||||
required: Bool = false, minLevel: SwiftyBeaver.Level = .verbose) -> FilterType {
|
||||
return CompareFilter(.Function(.Contains(strings, caseSensitive)), required: required, minLevel: minLevel)
|
||||
}
|
||||
|
||||
public static func excludes(_ strings: String..., caseSensitive: Bool = false,
|
||||
required: Bool = false, minLevel: SwiftyBeaver.Level = .verbose) -> FilterType {
|
||||
return CompareFilter(.Function(.Excludes(strings, caseSensitive)), required: required, minLevel: minLevel)
|
||||
}
|
||||
|
||||
public static func endsWith(_ suffixes: String..., caseSensitive: Bool = false,
|
||||
required: Bool = false, minLevel: SwiftyBeaver.Level = .verbose) -> FilterType {
|
||||
return CompareFilter(.Function(.EndsWith(suffixes, caseSensitive)), required: required, minLevel: minLevel)
|
||||
}
|
||||
|
||||
public static func equals(_ strings: String..., caseSensitive: Bool = false,
|
||||
required: Bool = false, minLevel: SwiftyBeaver.Level = .verbose) -> FilterType {
|
||||
return CompareFilter(.Function(.Equals(strings, caseSensitive)), required: required, minLevel: minLevel)
|
||||
}
|
||||
|
||||
public static func custom(required: Bool = false, minLevel: SwiftyBeaver.Level = .verbose, filterPredicate: @escaping (String) -> Bool) -> FilterType {
|
||||
return CompareFilter(.Function(.Custom(filterPredicate)), required: required, minLevel: minLevel)
|
||||
}
|
||||
}
|
||||
|
||||
// Syntactic sugar for creating a message comparison filter
|
||||
public class MessageFilterFactory {
|
||||
public static func startsWith(_ prefixes: String..., caseSensitive: Bool = false,
|
||||
required: Bool = false, minLevel: SwiftyBeaver.Level = .verbose) -> FilterType {
|
||||
return CompareFilter(.Message(.StartsWith(prefixes, caseSensitive)), required: required, minLevel: minLevel)
|
||||
}
|
||||
|
||||
public static func contains(_ strings: String..., caseSensitive: Bool = false,
|
||||
required: Bool = false, minLevel: SwiftyBeaver.Level = .verbose) -> FilterType {
|
||||
return CompareFilter(.Message(.Contains(strings, caseSensitive)), required: required, minLevel: minLevel)
|
||||
}
|
||||
|
||||
public static func excludes(_ strings: String..., caseSensitive: Bool = false,
|
||||
required: Bool = false, minLevel: SwiftyBeaver.Level = .verbose) -> FilterType {
|
||||
return CompareFilter(.Message(.Excludes(strings, caseSensitive)), required: required, minLevel: minLevel)
|
||||
}
|
||||
|
||||
public static func endsWith(_ suffixes: String..., caseSensitive: Bool = false,
|
||||
required: Bool = false, minLevel: SwiftyBeaver.Level = .verbose) -> FilterType {
|
||||
return CompareFilter(.Message(.EndsWith(suffixes, caseSensitive)), required: required, minLevel: minLevel)
|
||||
}
|
||||
|
||||
public static func equals(_ strings: String..., caseSensitive: Bool = false,
|
||||
required: Bool = false, minLevel: SwiftyBeaver.Level = .verbose) -> FilterType {
|
||||
return CompareFilter(.Message(.Equals(strings, caseSensitive)), required: required, minLevel: minLevel)
|
||||
}
|
||||
|
||||
public static func custom(required: Bool = false, minLevel: SwiftyBeaver.Level = .verbose, filterPredicate: @escaping (String) -> Bool) -> FilterType {
|
||||
return CompareFilter(.Message(.Custom(filterPredicate)), required: required, minLevel: minLevel)
|
||||
}
|
||||
}
|
||||
|
||||
// Syntactic sugar for creating a path comparison filter
|
||||
public class PathFilterFactory {
|
||||
public static func startsWith(_ prefixes: String..., caseSensitive: Bool = false,
|
||||
required: Bool = false, minLevel: SwiftyBeaver.Level = .verbose) -> FilterType {
|
||||
return CompareFilter(.Path(.StartsWith(prefixes, caseSensitive)), required: required, minLevel: minLevel)
|
||||
}
|
||||
|
||||
public static func contains(_ strings: String..., caseSensitive: Bool = false,
|
||||
required: Bool = false, minLevel: SwiftyBeaver.Level = .verbose) -> FilterType {
|
||||
return CompareFilter(.Path(.Contains(strings, caseSensitive)), required: required, minLevel: minLevel)
|
||||
}
|
||||
|
||||
public static func excludes(_ strings: String..., caseSensitive: Bool = false,
|
||||
required: Bool = false, minLevel: SwiftyBeaver.Level = .verbose) -> FilterType {
|
||||
return CompareFilter(.Path(.Excludes(strings, caseSensitive)), required: required, minLevel: minLevel)
|
||||
}
|
||||
|
||||
public static func endsWith(_ suffixes: String..., caseSensitive: Bool = false,
|
||||
required: Bool = false, minLevel: SwiftyBeaver.Level = .verbose) -> FilterType {
|
||||
return CompareFilter(.Path(.EndsWith(suffixes, caseSensitive)), required: required, minLevel: minLevel)
|
||||
}
|
||||
|
||||
public static func equals(_ strings: String..., caseSensitive: Bool = false,
|
||||
required: Bool = false, minLevel: SwiftyBeaver.Level = .verbose) -> FilterType {
|
||||
return CompareFilter(.Path(.Equals(strings, caseSensitive)), required: required, minLevel: minLevel)
|
||||
}
|
||||
|
||||
public static func custom(required: Bool = false, minLevel: SwiftyBeaver.Level = .verbose, filterPredicate: @escaping (String) -> Bool) -> FilterType {
|
||||
return CompareFilter(.Path(.Custom(filterPredicate)), required: required, minLevel: minLevel)
|
||||
}
|
||||
}
|
||||
|
||||
extension Filter.TargetType: Equatable {
|
||||
}
|
||||
|
||||
// The == does not compare associated values for each enum. Instead == evaluates to true
|
||||
// if both enums are the same "types", ignoring the associated values of each enum
|
||||
public func == (lhs: Filter.TargetType, rhs: Filter.TargetType) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
|
||||
case (.Path, .Path):
|
||||
return true
|
||||
|
||||
case (.Function, .Function):
|
||||
return true
|
||||
|
||||
case (.Message, .Message):
|
||||
return true
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
//
|
||||
// FilterValidator.swift
|
||||
// SwiftyBeaver (iOS)
|
||||
//
|
||||
// Created by Felix Lisczyk on 07.07.19.
|
||||
// Copyright © 2019 Sebastian Kreutzberger. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// FilterValidator is a utility class used by BaseDestination.
|
||||
/// It encapsulates the filtering logic for excluded, required
|
||||
/// and non-required filters.
|
||||
///
|
||||
/// FilterValidator evaluates a set of filters for a single log
|
||||
/// entry. It determines if these filters apply to the log entry
|
||||
/// based on their condition (path, function, message) and their
|
||||
/// minimum log level.
|
||||
|
||||
struct FilterValidator {
|
||||
|
||||
// These are the different filter types that the user can set
|
||||
enum ValidationType: CaseIterable {
|
||||
case excluded
|
||||
case required
|
||||
case nonRequired
|
||||
|
||||
func apply(to filters: [FilterType]) -> [FilterType] {
|
||||
switch self {
|
||||
case .excluded:
|
||||
return filters.filter { $0.isExcluded() }
|
||||
case .required:
|
||||
return filters.filter { $0.isRequired() && !$0.isExcluded() }
|
||||
case .nonRequired:
|
||||
return filters.filter { !$0.isRequired() && !$0.isExcluded() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapper object for input parameters
|
||||
struct Input {
|
||||
let filters: [FilterType]
|
||||
let level: SwiftyBeaver.Level
|
||||
let path: String
|
||||
let function: String
|
||||
let message: String?
|
||||
}
|
||||
|
||||
// Result wrapper object
|
||||
enum Result {
|
||||
case allFiltersMatch // All filters fully match the log entry (condition + minimum log level)
|
||||
case someFiltersMatch(PartialMatchData) // Only some filters fully match the log entry (condition + minimum log level)
|
||||
case noFiltersMatchingType // There are no filters set for a particular type (excluded, required, nonRequired)
|
||||
|
||||
struct PartialMatchData {
|
||||
let fullMatchCount: Int // Number of filters that match both the condition and the minimum log level of the log entry
|
||||
let conditionMatchCount: Int // Number of filters that match ONLY the condition of the log entry (path, function, message)
|
||||
let logLevelMatchCount: Int // Number of filters that match ONLY the minimum log level of the log entry
|
||||
}
|
||||
}
|
||||
|
||||
static func validate(input: Input, for types: [ValidationType] = ValidationType.allCases) -> [ValidationType: Result] {
|
||||
var results = [ValidationType: Result]()
|
||||
for type in types {
|
||||
let filtersToValidate = type.apply(to: input.filters)
|
||||
|
||||
if filtersToValidate.isEmpty {
|
||||
// There are no filters set for this particular type
|
||||
results[type] = .noFiltersMatchingType
|
||||
} else {
|
||||
var fullMatchCount: Int = 0
|
||||
var conditionMatchCount: Int = 0
|
||||
var logLevelMatchCount: Int = 0
|
||||
|
||||
for filter in filtersToValidate {
|
||||
let filterMatchesCondition = self.filterMatchesCondition(filter, level: input.level, path: input.path, function: input.function, message: input.message)
|
||||
let filterMatchesMinLogLevel = self.filterMatchesMinLogLevel(filter, level: input.level)
|
||||
|
||||
switch (filterMatchesCondition, filterMatchesMinLogLevel) {
|
||||
// Filter matches both the condition and the minimum log level
|
||||
case (true, true): fullMatchCount += 1
|
||||
// Filter matches only the condition (path, function, message)
|
||||
case (true, false): conditionMatchCount += 1
|
||||
// Filter matches only the minimum log level
|
||||
case (false, true): logLevelMatchCount += 1
|
||||
// Filter does not match the condition nor the minimum log level
|
||||
case (false, false): break
|
||||
}
|
||||
}
|
||||
|
||||
if filtersToValidate.count == fullMatchCount {
|
||||
// All filters fully match the log entry
|
||||
results[type] = .allFiltersMatch
|
||||
} else {
|
||||
// Only some filters match the log entry
|
||||
results[type] = .someFiltersMatch(.init(fullMatchCount: fullMatchCount, conditionMatchCount: conditionMatchCount, logLevelMatchCount: logLevelMatchCount))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
private static func filterMatchesCondition(_ filter: FilterType, level: SwiftyBeaver.Level,
|
||||
path: String, function: String, message: String?) -> Bool {
|
||||
let passes: Bool
|
||||
|
||||
switch filter.getTarget() {
|
||||
case .Path(_):
|
||||
passes = filter.apply(path)
|
||||
|
||||
case .Function(_):
|
||||
passes = filter.apply(function)
|
||||
|
||||
case .Message(_):
|
||||
guard let message = message else {
|
||||
return false
|
||||
}
|
||||
|
||||
passes = filter.apply(message)
|
||||
}
|
||||
|
||||
return passes
|
||||
}
|
||||
|
||||
private static func filterMatchesMinLogLevel(_ filter: FilterType, level: SwiftyBeaver.Level) -> Bool {
|
||||
return filter.reachedMinLevel(level)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
//
|
||||
// GoogleCloudDestination.swift
|
||||
// SwiftyBeaver
|
||||
//
|
||||
// Copyright © 2017 Sebastian Kreutzberger. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public final class GoogleCloudDestination: BaseDestination {
|
||||
|
||||
private let serviceName: String
|
||||
|
||||
public init(serviceName: String) {
|
||||
self.serviceName = serviceName
|
||||
super.init()
|
||||
}
|
||||
|
||||
override public var asynchronously: Bool {
|
||||
get {
|
||||
return false
|
||||
}
|
||||
set {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
override public func send(_ level: SwiftyBeaver.Level, msg: String, thread: String,
|
||||
file: String, function: String, line: Int, context: Any? = nil) -> String? {
|
||||
|
||||
let reportLocation: [String: Any] = ["filePath": file, "lineNumber": line, "functionName": function]
|
||||
var gcpContext: [String: Any] = ["reportLocation": reportLocation]
|
||||
if let context = context as? [String: Any] {
|
||||
if let httpRequestContext = context["httpRequest"] as? [String: Any] {
|
||||
gcpContext["httpRequest"] = httpRequestContext
|
||||
}
|
||||
|
||||
if let user = context["user"] as? String {
|
||||
gcpContext["user"] = user
|
||||
}
|
||||
}
|
||||
|
||||
let gcpJSON: [String: Any] = [
|
||||
"serviceContext": [
|
||||
"service": serviceName
|
||||
],
|
||||
"message": msg,
|
||||
"severity": level.severity,
|
||||
"context": gcpContext
|
||||
]
|
||||
|
||||
let finalLogString: String
|
||||
|
||||
do {
|
||||
finalLogString = try jsonString(obj: gcpJSON)
|
||||
} catch {
|
||||
let uncrashableLogString = "{\"context\":{\"reportLocation\":{\"filePath\": \"\(file)\"" +
|
||||
",\"functionName\":\"\(function)\"" +
|
||||
",\"lineNumber\":\(line)},\"severity\"" +
|
||||
":\"CRITICAL\",\"message\":\"Error encoding " +
|
||||
"JSON log entry. You may be losing log messages!\"}"
|
||||
finalLogString = uncrashableLogString.description
|
||||
}
|
||||
print(finalLogString)
|
||||
return finalLogString
|
||||
}
|
||||
|
||||
private func jsonString(obj: Dictionary<String, Any>) throws -> String {
|
||||
let json = try JSONSerialization.data(withJSONObject: obj, options: [])
|
||||
guard let string = String(data: json, encoding: .utf8) else {
|
||||
throw GCPError.serialization
|
||||
}
|
||||
return string
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#LogSeverity
|
||||
extension SwiftyBeaver.Level {
|
||||
|
||||
/// Verbose is reported as Debug to GCP.
|
||||
/// Recommend you don't bother using it.
|
||||
var severity: String {
|
||||
switch self {
|
||||
// There is only one level below "Debug": "Default", which becomes "Any" and is considered as a potential error as well
|
||||
case .verbose: return "DEBUG"
|
||||
case .debug: return "DEBUG"
|
||||
case .info: return "INFO"
|
||||
case .warning: return "WARNING"
|
||||
case .error: return "ERROR"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum GCPError: Error {
|
||||
case serialization
|
||||
}
|
||||
@@ -0,0 +1,625 @@
|
||||
//
|
||||
// SBPlatformDestination
|
||||
// SwiftyBeaver
|
||||
//
|
||||
// Created by Sebastian Kreutzberger on 22.01.16.
|
||||
// Copyright © 2016 Sebastian Kreutzberger
|
||||
// Some rights reserved: http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
#if canImport(FoundationNetworking)
|
||||
import FoundationNetworking
|
||||
#endif
|
||||
|
||||
// platform-dependent import frameworks to get device details
|
||||
// valid values for os(): OSX, iOS, watchOS, tvOS, Linux
|
||||
// in Swift 3 the following were added: FreeBSD, Windows, Android
|
||||
#if os(iOS) || os(tvOS) || os(watchOS)
|
||||
import UIKit
|
||||
var DEVICE_MODEL: String {
|
||||
get {
|
||||
var systemInfo = utsname()
|
||||
uname(&systemInfo)
|
||||
let machineMirror = Mirror(reflecting: systemInfo.machine)
|
||||
let identifier = machineMirror.children.reduce("") { identifier, element in
|
||||
guard let value = element.value as? Int8, value != 0 else { return identifier }
|
||||
return identifier + String(UnicodeScalar(UInt8(value)))
|
||||
}
|
||||
return identifier
|
||||
}
|
||||
}
|
||||
#else
|
||||
let DEVICE_MODEL = ""
|
||||
#endif
|
||||
|
||||
#if os(iOS) || os(tvOS)
|
||||
var DEVICE_NAME = UIDevice.current.name
|
||||
#else
|
||||
// under watchOS UIDevice is not existing, http://apple.co/26ch5J1
|
||||
let DEVICE_NAME = ""
|
||||
#endif
|
||||
|
||||
public class SBPlatformDestination: BaseDestination {
|
||||
|
||||
public var appID = ""
|
||||
public var appSecret = ""
|
||||
public var encryptionKey = ""
|
||||
public var analyticsUserName = "" // user email, ID, name, etc.
|
||||
public var analyticsUUID: String { return uuid }
|
||||
|
||||
// when to send to server
|
||||
public struct SendingPoints {
|
||||
public var verbose = 0
|
||||
public var debug = 1
|
||||
public var info = 5
|
||||
public var warning = 8
|
||||
public var error = 10
|
||||
public var threshold = 10 // send to server if points reach that value
|
||||
}
|
||||
public var sendingPoints = SendingPoints()
|
||||
public var showNSLog = false // executes toNSLog statements to debug the class
|
||||
var points = 0
|
||||
|
||||
public var serverURL = URL(string: "https://api.swiftybeaver.com/api/entries/") // optional
|
||||
public var entriesFileURL = URL(fileURLWithPath: "") // not optional
|
||||
public var sendingFileURL = URL(fileURLWithPath: "")
|
||||
public var analyticsFileURL = URL(fileURLWithPath: "")
|
||||
|
||||
private let minAllowedThreshold = 1 // over-rules SendingPoints.Threshold
|
||||
private let maxAllowedThreshold = 1000 // over-rules SendingPoints.Threshold
|
||||
private var sendingInProgress = false
|
||||
private var initialSending = true
|
||||
|
||||
// analytics
|
||||
var uuid = ""
|
||||
|
||||
// destination
|
||||
override public var defaultHashValue: Int {return 3}
|
||||
let fileManager = FileManager.default
|
||||
let isoDateFormatter = DateFormatter()
|
||||
|
||||
/// init platform with default internal filenames
|
||||
public init(appID: String, appSecret: String, encryptionKey: String,
|
||||
serverURL: URL? = URL(string: "https://api.swiftybeaver.com/api/entries/"),
|
||||
entriesFileName: String = "sbplatform_entries.json",
|
||||
sendingfileName: String = "sbplatform_entries_sending.json",
|
||||
analyticsFileName: String = "sbplatform_analytics.json") {
|
||||
super.init()
|
||||
self.serverURL = serverURL
|
||||
self.appID = appID
|
||||
self.appSecret = appSecret
|
||||
self.encryptionKey = encryptionKey
|
||||
|
||||
// setup where to write the json files
|
||||
var baseURL: URL?
|
||||
#if os(OSX)
|
||||
if let url = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first {
|
||||
baseURL = url
|
||||
// try to use ~/Library/Application Support/APP NAME instead of ~/Library/Application Support
|
||||
if let appName = Bundle.main.object(forInfoDictionaryKey: "CFBundleExecutable") as? String {
|
||||
do {
|
||||
if let appURL = baseURL?.appendingPathComponent(appName, isDirectory: true) {
|
||||
try fileManager.createDirectory(at: appURL,
|
||||
withIntermediateDirectories: true, attributes: nil)
|
||||
baseURL = appURL
|
||||
}
|
||||
} catch {
|
||||
// it is too early in the class lifetime to be able to use toNSLog()
|
||||
print("Warning! Could not create folder ~/Library/Application Support/\(appName).")
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
#if os(tvOS)
|
||||
// tvOS can just use the caches directory
|
||||
if let url = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first {
|
||||
baseURL = url
|
||||
}
|
||||
#elseif os(Linux)
|
||||
// Linux is using /var/cache
|
||||
let baseDir = "/var/cache/"
|
||||
entriesFileURL = URL(fileURLWithPath: baseDir + entriesFileName)
|
||||
sendingFileURL = URL(fileURLWithPath: baseDir + sendingfileName)
|
||||
analyticsFileURL = URL(fileURLWithPath: baseDir + analyticsFileName)
|
||||
#else
|
||||
// iOS and watchOS are using the app’s document directory
|
||||
if let url = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first {
|
||||
baseURL = url
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if os(Linux)
|
||||
// get, update loaded and save analytics data to file on start
|
||||
let dict = analytics(analyticsFileURL, update: true)
|
||||
_ = saveDictToFile(dict, url: analyticsFileURL)
|
||||
#else
|
||||
if let baseURL = baseURL {
|
||||
// is just set for everything but not Linux
|
||||
entriesFileURL = baseURL.appendingPathComponent(entriesFileName,
|
||||
isDirectory: false)
|
||||
sendingFileURL = baseURL.appendingPathComponent(sendingfileName,
|
||||
isDirectory: false)
|
||||
analyticsFileURL = baseURL.appendingPathComponent(analyticsFileName,
|
||||
isDirectory: false)
|
||||
|
||||
// get, update loaded and save analytics data to file on start
|
||||
let dict = analytics(analyticsFileURL, update: true)
|
||||
_ = saveDictToFile(dict, url: analyticsFileURL)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// append to file, each line is a JSON dict
|
||||
override public func send(_ level: SwiftyBeaver.Level, msg: String, thread: String,
|
||||
file: String, function: String, line: Int, context: Any? = nil) -> String? {
|
||||
|
||||
var jsonString: String?
|
||||
|
||||
let dict: [String: Any] = [
|
||||
"timestamp": Date().timeIntervalSince1970,
|
||||
"level": level.rawValue,
|
||||
"message": msg,
|
||||
"thread": thread,
|
||||
"fileName": file.components(separatedBy: "/").last!,
|
||||
"function": function,
|
||||
"line": line]
|
||||
|
||||
jsonString = jsonStringFromDict(dict)
|
||||
|
||||
if let str = jsonString {
|
||||
toNSLog("saving '\(msg)' to \(entriesFileURL)")
|
||||
_ = saveToFile(str, url: entriesFileURL)
|
||||
//toNSLog(entriesFileURL.path!)
|
||||
|
||||
// now decide if the stored log entries should be sent to the server
|
||||
// add level points to current points amount and send to server if threshold is hit
|
||||
let newPoints = sendingPointsForLevel(level)
|
||||
points += newPoints
|
||||
toNSLog("current sending points: \(points)")
|
||||
|
||||
if (points >= sendingPoints.threshold && points >= minAllowedThreshold) || points > maxAllowedThreshold {
|
||||
toNSLog("\(points) points is >= threshold")
|
||||
// above threshold, send to server
|
||||
sendNow()
|
||||
|
||||
} else if initialSending {
|
||||
initialSending = false
|
||||
// first logging at this session
|
||||
// send if json file still contains old log entries
|
||||
if let logEntries = logsFromFile(entriesFileURL) {
|
||||
let lines = logEntries.count
|
||||
if lines > 1 {
|
||||
var msg = "initialSending: \(points) points is below threshold "
|
||||
msg += "but json file already has \(lines) lines."
|
||||
toNSLog(msg)
|
||||
sendNow()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return jsonString
|
||||
}
|
||||
|
||||
// MARK: Send-to-Server Logic
|
||||
|
||||
/// does a (manual) sending attempt of all unsent log entries to SwiftyBeaver Platform
|
||||
public func sendNow() {
|
||||
|
||||
if sendFileExists() {
|
||||
toNSLog("reset points to 0")
|
||||
points = 0
|
||||
} else {
|
||||
if !renameJsonToSendFile() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if !sendingInProgress {
|
||||
sendingInProgress = true
|
||||
//let (jsonString, lines) = logsFromFile(sendingFileURL)
|
||||
var lines = 0
|
||||
|
||||
guard let logEntries = logsFromFile(sendingFileURL) else {
|
||||
sendingInProgress = false
|
||||
return
|
||||
}
|
||||
|
||||
lines = logEntries.count
|
||||
|
||||
if lines > 0 {
|
||||
var payload = [String: Any]()
|
||||
// merge device and analytics dictionaries
|
||||
let deviceDetailsDict = deviceDetails()
|
||||
|
||||
var analyticsDict = analytics(analyticsFileURL)
|
||||
|
||||
for key in deviceDetailsDict.keys {
|
||||
analyticsDict[key] = deviceDetailsDict[key]
|
||||
}
|
||||
payload["device"] = analyticsDict
|
||||
payload["entries"] = logEntries
|
||||
|
||||
if let str = jsonStringFromDict(payload) {
|
||||
//toNSLog(str) // uncomment to see full payload
|
||||
toNSLog("Encrypting \(lines) log entries ...")
|
||||
if let encryptedStr = encrypt(str) {
|
||||
var msg = "Sending \(lines) encrypted log entries "
|
||||
msg += "(\(encryptedStr.length) chars) to server ..."
|
||||
toNSLog(msg)
|
||||
sendToServerAsync(encryptedStr) { ok, _ in
|
||||
|
||||
self.toNSLog("Sent \(lines) encrypted log entries to server, received ok: \(ok)")
|
||||
if ok {
|
||||
_ = self.deleteFile(self.sendingFileURL)
|
||||
}
|
||||
self.sendingInProgress = false
|
||||
self.points = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sendingInProgress = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// sends a string to the SwiftyBeaver Platform server, returns ok if status 200 and HTTP status
|
||||
func sendToServerAsync(_ str: String?, complete: @escaping (_ ok: Bool, _ status: Int) -> Void) {
|
||||
|
||||
let timeout = 10.0
|
||||
|
||||
if let payload = str, let queue = self.queue, let serverURL = serverURL {
|
||||
|
||||
// create operation queue which uses current serial queue of destination
|
||||
let operationQueue = OperationQueue()
|
||||
operationQueue.underlyingQueue = queue
|
||||
|
||||
let session = URLSession(configuration:
|
||||
URLSessionConfiguration.default,
|
||||
delegate: nil, delegateQueue: operationQueue)
|
||||
|
||||
toNSLog("assembling request ...")
|
||||
|
||||
// assemble request
|
||||
var request = URLRequest(url: serverURL,
|
||||
cachePolicy: .reloadIgnoringLocalAndRemoteCacheData,
|
||||
timeoutInterval: timeout)
|
||||
request.httpMethod = "POST"
|
||||
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
request.addValue("application/json", forHTTPHeaderField: "Accept")
|
||||
|
||||
// basic auth header (just works on Linux for Swift 3.1+, macOS is fine)
|
||||
guard let credentials = "\(appID):\(appSecret)".data(using: String.Encoding.utf8) else {
|
||||
toNSLog("Error! Could not set basic auth header")
|
||||
return complete(false, 0)
|
||||
}
|
||||
|
||||
#if os(Linux)
|
||||
let base64Credentials = Base64.encode([UInt8](credentials))
|
||||
#else
|
||||
let base64Credentials = credentials.base64EncodedString(options: [])
|
||||
#endif
|
||||
request.setValue("Basic \(base64Credentials)", forHTTPHeaderField: "Authorization")
|
||||
//toNSLog("\nrequest:")
|
||||
//print(request)
|
||||
|
||||
// POST parameters
|
||||
let params = ["payload": payload]
|
||||
do {
|
||||
request.httpBody = try JSONSerialization.data(withJSONObject: params, options: [])
|
||||
} catch {
|
||||
toNSLog("Error! Could not create JSON for server payload.")
|
||||
return complete(false, 0)
|
||||
}
|
||||
toNSLog("sending params: \(params)")
|
||||
toNSLog("sending ...")
|
||||
|
||||
sendingInProgress = true
|
||||
|
||||
// send request async to server on destination queue
|
||||
let task = session.dataTask(with: request) { _, response, error in
|
||||
var ok = false
|
||||
var status = 0
|
||||
self.toNSLog("received response from server")
|
||||
|
||||
if let error = error {
|
||||
// an error did occur
|
||||
self.toNSLog("Error! Could not send entries to server. \(error)")
|
||||
} else {
|
||||
if let response = response as? HTTPURLResponse {
|
||||
status = response.statusCode
|
||||
if status == 200 {
|
||||
// all went well, entries were uploaded to server
|
||||
ok = true
|
||||
} else {
|
||||
// status code was not 200
|
||||
var msg = "Error! Sending entries to server failed "
|
||||
msg += "with status code \(status)"
|
||||
self.toNSLog(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
return complete(ok, status)
|
||||
}
|
||||
task.resume()
|
||||
session.finishTasksAndInvalidate()
|
||||
//while true {} // commenting this line causes a crash on Linux unit tests?!?
|
||||
}
|
||||
}
|
||||
|
||||
/// returns sending points based on level
|
||||
func sendingPointsForLevel(_ level: SwiftyBeaver.Level) -> Int {
|
||||
|
||||
switch level {
|
||||
case .debug:
|
||||
return sendingPoints.debug
|
||||
case .info:
|
||||
return sendingPoints.info
|
||||
case .warning:
|
||||
return sendingPoints.warning
|
||||
case .error:
|
||||
return sendingPoints.error
|
||||
default:
|
||||
return sendingPoints.verbose
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: File Handling
|
||||
|
||||
/// appends a string as line to a file.
|
||||
/// returns boolean about success
|
||||
func saveToFile(_ str: String, url: URL, overwrite: Bool = false) -> Bool {
|
||||
do {
|
||||
if fileManager.fileExists(atPath: url.path) == false || overwrite {
|
||||
// create file if not existing
|
||||
let line = str + "\n"
|
||||
try line.write(to: url, atomically: true, encoding: String.Encoding.utf8)
|
||||
} else {
|
||||
// append to end of file
|
||||
let fileHandle = try FileHandle(forWritingTo: url)
|
||||
_ = fileHandle.seekToEndOfFile()
|
||||
let line = str + "\n"
|
||||
if let data = line.data(using: String.Encoding.utf8) {
|
||||
fileHandle.write(data)
|
||||
fileHandle.closeFile()
|
||||
}
|
||||
}
|
||||
return true
|
||||
} catch {
|
||||
toNSLog("Error! Could not write to file \(url).")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func sendFileExists() -> Bool {
|
||||
return fileManager.fileExists(atPath: sendingFileURL.path)
|
||||
}
|
||||
|
||||
func renameJsonToSendFile() -> Bool {
|
||||
do {
|
||||
try fileManager.moveItem(at: entriesFileURL, to: sendingFileURL)
|
||||
return true
|
||||
} catch {
|
||||
toNSLog("SwiftyBeaver Platform Destination could not rename json file.")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/// returns optional array of log dicts from a file which has 1 json string per line
|
||||
func logsFromFile(_ url: URL) -> [[String: Any]]? {
|
||||
var lines = 0
|
||||
do {
|
||||
// try to read file, decode every JSON line and put dict from each line in array
|
||||
let fileContent = try String(contentsOfFile: url.path, encoding: .utf8)
|
||||
let linesArray = fileContent.components(separatedBy: "\n")
|
||||
var dicts = [[String: Any]()] // array of dictionaries
|
||||
for lineJSON in linesArray {
|
||||
lines += 1
|
||||
if lineJSON.firstChar == "{" && lineJSON.lastChar == "}" {
|
||||
// try to parse json string into dict
|
||||
if let data = lineJSON.data(using: .utf8) {
|
||||
do {
|
||||
if let dict = try JSONSerialization.jsonObject(with: data,
|
||||
options: .mutableContainers) as? [String: Any] {
|
||||
if !dict.isEmpty {
|
||||
dicts.append(dict)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
var msg = "Error! Could not parse "
|
||||
msg += "line \(lines) in file \(url)."
|
||||
toNSLog(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
dicts.removeFirst()
|
||||
return dicts
|
||||
} catch {
|
||||
toNSLog("Error! Could not read file \(url).")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/// returns AES-256 CBC encrypted optional string
|
||||
func encrypt(_ str: String) -> String? {
|
||||
return AES256CBC.encryptString(str, password: encryptionKey)
|
||||
}
|
||||
|
||||
/// Delete file to get started again
|
||||
func deleteFile(_ url: URL) -> Bool {
|
||||
do {
|
||||
try FileManager.default.removeItem(at: url)
|
||||
return true
|
||||
} catch {
|
||||
toNSLog("Warning! Could not delete file \(url).")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MARK: Device & Analytics
|
||||
|
||||
// returns dict with device details. Amount depends on platform
|
||||
func deviceDetails() -> [String: String] {
|
||||
var details = [String: String]()
|
||||
|
||||
details["os"] = OS
|
||||
let osVersion = ProcessInfo.processInfo.operatingSystemVersion
|
||||
// becomes for example 10.11.2 for El Capitan
|
||||
var osVersionStr = String(osVersion.majorVersion)
|
||||
osVersionStr += "." + String(osVersion.minorVersion)
|
||||
osVersionStr += "." + String(osVersion.patchVersion)
|
||||
details["osVersion"] = osVersionStr
|
||||
details["hostName"] = ProcessInfo.processInfo.hostName
|
||||
details["deviceName"] = ""
|
||||
details["deviceModel"] = ""
|
||||
|
||||
if DEVICE_NAME != "" {
|
||||
details["deviceName"] = DEVICE_NAME
|
||||
}
|
||||
if DEVICE_MODEL != "" {
|
||||
details["deviceModel"] = DEVICE_MODEL
|
||||
}
|
||||
return details
|
||||
}
|
||||
|
||||
/// returns (updated) analytics dict, optionally loaded from file.
|
||||
func analytics(_ url: URL, update: Bool = false) -> [String: Any] {
|
||||
|
||||
var dict = [String: Any]()
|
||||
let now = NSDate().timeIntervalSince1970
|
||||
|
||||
uuid = NSUUID().uuidString
|
||||
dict["uuid"] = uuid
|
||||
dict["firstStart"] = now
|
||||
dict["lastStart"] = now
|
||||
dict["starts"] = 1
|
||||
dict["userName"] = analyticsUserName
|
||||
dict["firstAppVersion"] = appVersion()
|
||||
dict["appVersion"] = appVersion()
|
||||
dict["firstAppBuild"] = appBuild()
|
||||
dict["appBuild"] = appBuild()
|
||||
|
||||
if let loadedDict = dictFromFile(analyticsFileURL) {
|
||||
if let val = loadedDict["firstStart"] as? Double {
|
||||
dict["firstStart"] = val
|
||||
}
|
||||
if let val = loadedDict["lastStart"] as? Double {
|
||||
if update {
|
||||
dict["lastStart"] = now
|
||||
} else {
|
||||
dict["lastStart"] = val
|
||||
}
|
||||
}
|
||||
if let val = loadedDict["starts"] as? Int {
|
||||
if update {
|
||||
dict["starts"] = val + 1
|
||||
} else {
|
||||
dict["starts"] = val
|
||||
}
|
||||
}
|
||||
if let val = loadedDict["uuid"] as? String {
|
||||
dict["uuid"] = val
|
||||
uuid = val
|
||||
}
|
||||
if let val = loadedDict["userName"] as? String {
|
||||
if update && !analyticsUserName.isEmpty {
|
||||
dict["userName"] = analyticsUserName
|
||||
} else {
|
||||
if !val.isEmpty {
|
||||
dict["userName"] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
if let val = loadedDict["firstAppVersion"] as? String {
|
||||
dict["firstAppVersion"] = val
|
||||
}
|
||||
if let val = loadedDict["firstAppBuild"] as? Int {
|
||||
dict["firstAppBuild"] = val
|
||||
}
|
||||
}
|
||||
return dict
|
||||
}
|
||||
|
||||
/// Returns the current app version string (like 1.2.5) or empty string on error
|
||||
func appVersion() -> String {
|
||||
if let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String {
|
||||
return version
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
/// Returns the current app build as integer (like 563, always incrementing) or 0 on error
|
||||
func appBuild() -> Int {
|
||||
if let version = Bundle.main.infoDictionary?["CFBundleVersion"] as? String {
|
||||
if let intVersion = Int(version) {
|
||||
return intVersion
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
/// returns optional dict from a json encoded file
|
||||
func dictFromFile(_ url: URL) -> [String: Any]? {
|
||||
do {
|
||||
let fileContent = try String(contentsOfFile: url.path, encoding: .utf8)
|
||||
if let data = fileContent.data(using: .utf8) {
|
||||
return try JSONSerialization.jsonObject(with: data,
|
||||
options: .mutableContainers) as? [String: Any]
|
||||
}
|
||||
} catch {
|
||||
toNSLog("SwiftyBeaver Platform Destination could not read file \(url)")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// turns dict into JSON and saves it to file
|
||||
func saveDictToFile(_ dict: [String: Any], url: URL) -> Bool {
|
||||
let jsonString = jsonStringFromDict(dict)
|
||||
|
||||
if let str = jsonString {
|
||||
toNSLog("saving '\(str)' to \(url)")
|
||||
return saveToFile(str, url: url, overwrite: true)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MARK: Debug Helpers
|
||||
|
||||
/// log String to toNSLog. Used to debug the class logic
|
||||
func toNSLog(_ str: String) {
|
||||
if showNSLog {
|
||||
#if os(Linux)
|
||||
print("SBPlatform: \(str)")
|
||||
#else
|
||||
NSLog("SBPlatform: \(str)")
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// returns the current thread name
|
||||
class func threadName() -> String {
|
||||
|
||||
#if os(Linux)
|
||||
// on 9/30/2016 not yet implemented in server-side Swift:
|
||||
// > import Foundation
|
||||
// > Thread.isMainThread
|
||||
return ""
|
||||
#else
|
||||
if Thread.isMainThread {
|
||||
return ""
|
||||
} else {
|
||||
let threadName = Thread.current.name
|
||||
if let threadName = threadName, !threadName.isEmpty {
|
||||
return threadName
|
||||
} else {
|
||||
return String(format: "%p", Thread.current)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
//
|
||||
// SwiftyBeaver.swift
|
||||
// SwiftyBeaver
|
||||
//
|
||||
// Created by Sebastian Kreutzberger (Twitter @skreutzb) on 28.11.15.
|
||||
// Copyright © 2015 Sebastian Kreutzberger
|
||||
// Some rights reserved: http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
open class SwiftyBeaver {
|
||||
|
||||
/// version string of framework
|
||||
public static let version = "1.9.0" // UPDATE ON RELEASE!
|
||||
/// build number of framework
|
||||
public static let build = 1900 // version 1.6.2 -> 1620, UPDATE ON RELEASE!
|
||||
|
||||
public enum Level: Int {
|
||||
case verbose = 0
|
||||
case debug = 1
|
||||
case info = 2
|
||||
case warning = 3
|
||||
case error = 4
|
||||
}
|
||||
|
||||
// a set of active destinations
|
||||
public private(set) static var destinations = Set<BaseDestination>()
|
||||
|
||||
// MARK: Destination Handling
|
||||
|
||||
/// returns boolean about success
|
||||
@discardableResult
|
||||
open class func addDestination(_ destination: BaseDestination) -> Bool {
|
||||
if destinations.contains(destination) {
|
||||
return false
|
||||
}
|
||||
destinations.insert(destination)
|
||||
return true
|
||||
}
|
||||
|
||||
/// returns boolean about success
|
||||
@discardableResult
|
||||
open class func removeDestination(_ destination: BaseDestination) -> Bool {
|
||||
if destinations.contains(destination) == false {
|
||||
return false
|
||||
}
|
||||
destinations.remove(destination)
|
||||
return true
|
||||
}
|
||||
|
||||
/// if you need to start fresh
|
||||
open class func removeAllDestinations() {
|
||||
destinations.removeAll()
|
||||
}
|
||||
|
||||
/// returns the amount of destinations
|
||||
open class func countDestinations() -> Int {
|
||||
return destinations.count
|
||||
}
|
||||
|
||||
/// returns the current thread name
|
||||
class func threadName() -> String {
|
||||
|
||||
#if os(Linux)
|
||||
// on 9/30/2016 not yet implemented in server-side Swift:
|
||||
// > import Foundation
|
||||
// > Thread.isMainThread
|
||||
return ""
|
||||
#else
|
||||
if Thread.isMainThread {
|
||||
return ""
|
||||
} else {
|
||||
let name = __dispatch_queue_get_label(nil)
|
||||
return String(cString: name, encoding: .utf8) ?? Thread.current.description
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// MARK: Levels
|
||||
|
||||
/// log something generally unimportant (lowest priority)
|
||||
open class func verbose(_ message: @autoclosure () -> Any, _
|
||||
file: String = #file, _ function: String = #function, line: Int = #line, context: Any? = nil) {
|
||||
#if swift(>=5)
|
||||
custom(level: .verbose, message: message(), file: file, function: function, line: line, context: context)
|
||||
#else
|
||||
custom(level: .verbose, message: message, file: file, function: function, line: line, context: context)
|
||||
#endif
|
||||
}
|
||||
|
||||
/// log something which help during debugging (low priority)
|
||||
open class func debug(_ message: @autoclosure () -> Any, _
|
||||
file: String = #file, _ function: String = #function, line: Int = #line, context: Any? = nil) {
|
||||
#if swift(>=5)
|
||||
custom(level: .debug, message: message(), file: file, function: function, line: line, context: context)
|
||||
#else
|
||||
custom(level: .debug, message: message, file: file, function: function, line: line, context: context)
|
||||
#endif
|
||||
}
|
||||
|
||||
/// log something which you are really interested but which is not an issue or error (normal priority)
|
||||
open class func info(_ message: @autoclosure () -> Any, _
|
||||
file: String = #file, _ function: String = #function, line: Int = #line, context: Any? = nil) {
|
||||
#if swift(>=5)
|
||||
custom(level: .info, message: message(), file: file, function: function, line: line, context: context)
|
||||
#else
|
||||
custom(level: .info, message: message, file: file, function: function, line: line, context: context)
|
||||
#endif
|
||||
}
|
||||
|
||||
/// log something which may cause big trouble soon (high priority)
|
||||
open class func warning(_ message: @autoclosure () -> Any, _
|
||||
file: String = #file, _ function: String = #function, line: Int = #line, context: Any? = nil) {
|
||||
#if swift(>=5)
|
||||
custom(level: .warning, message: message(), file: file, function: function, line: line, context: context)
|
||||
#else
|
||||
custom(level: .warning, message: message, file: file, function: function, line: line, context: context)
|
||||
#endif
|
||||
}
|
||||
|
||||
/// log something which will keep you awake at night (highest priority)
|
||||
open class func error(_ message: @autoclosure () -> Any, _
|
||||
file: String = #file, _ function: String = #function, line: Int = #line, context: Any? = nil) {
|
||||
#if swift(>=5)
|
||||
custom(level: .error, message: message(), file: file, function: function, line: line, context: context)
|
||||
#else
|
||||
custom(level: .error, message: message, file: file, function: function, line: line, context: context)
|
||||
#endif
|
||||
}
|
||||
|
||||
/// custom logging to manually adjust values, should just be used by other frameworks
|
||||
public class func custom(level: SwiftyBeaver.Level, message: @autoclosure () -> Any,
|
||||
file: String = #file, function: String = #function, line: Int = #line, context: Any? = nil) {
|
||||
#if swift(>=5)
|
||||
dispatch_send(level: level, message: message(), thread: threadName(),
|
||||
file: file, function: function, line: line, context: context)
|
||||
#else
|
||||
dispatch_send(level: level, message: message, thread: threadName(),
|
||||
file: file, function: function, line: line, context: context)
|
||||
#endif
|
||||
}
|
||||
|
||||
/// internal helper which dispatches send to dedicated queue if minLevel is ok
|
||||
class func dispatch_send(level: SwiftyBeaver.Level, message: @autoclosure () -> Any,
|
||||
thread: String, file: String, function: String, line: Int, context: Any?) {
|
||||
var resolvedMessage: String?
|
||||
for dest in destinations {
|
||||
|
||||
guard let queue = dest.queue else {
|
||||
continue
|
||||
}
|
||||
|
||||
resolvedMessage = resolvedMessage == nil && dest.hasMessageFilters() ? "\(message())" : resolvedMessage
|
||||
if dest.shouldLevelBeLogged(level, path: file, function: function, message: resolvedMessage) {
|
||||
// try to convert msg object to String and put it on queue
|
||||
let msgStr = resolvedMessage == nil ? "\(message())" : resolvedMessage!
|
||||
let f = stripParams(function: function)
|
||||
|
||||
if dest.asynchronously {
|
||||
queue.async {
|
||||
_ = dest.send(level, msg: msgStr, thread: thread, file: file, function: f, line: line, context: context)
|
||||
}
|
||||
} else {
|
||||
queue.sync {
|
||||
_ = dest.send(level, msg: msgStr, thread: thread, file: file, function: f, line: line, context: context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
DEPRECATED & NEEDS COMPLETE REWRITE DUE TO SWIFT 3 AND GENERAL INCORRECT LOGIC
|
||||
Flush all destinations to make sure all logging messages have been written out
|
||||
Returns after all messages flushed or timeout seconds
|
||||
|
||||
- returns: true if all messages flushed, false if timeout or error occurred
|
||||
*/
|
||||
public class func flush(secondTimeout: Int64) -> Bool {
|
||||
|
||||
/*
|
||||
guard let grp = dispatch_group_create() else { return false }
|
||||
for dest in destinations {
|
||||
if let queue = dest.queue {
|
||||
dispatch_group_enter(grp)
|
||||
queue.asynchronously(execute: {
|
||||
dest.flush()
|
||||
grp.leave()
|
||||
})
|
||||
}
|
||||
}
|
||||
let waitUntil = DispatchTime.now(dispatch_time_t(DISPATCH_TIME_NOW), secondTimeout * 1000000000)
|
||||
return dispatch_group_wait(grp, waitUntil) == 0
|
||||
*/
|
||||
return true
|
||||
}
|
||||
|
||||
/// removes the parameters from a function because it looks weird with a single param
|
||||
class func stripParams(function: String) -> String {
|
||||
var f = function
|
||||
if let indexOfBrace = f.find("(") {
|
||||
#if swift(>=4.0)
|
||||
f = String(f[..<indexOfBrace])
|
||||
#else
|
||||
f = f.substring(to: indexOfBrace)
|
||||
#endif
|
||||
}
|
||||
f += "()"
|
||||
return f
|
||||
}
|
||||
}
|
||||
@@ -435,6 +435,31 @@ IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
## SwiftyBeaver
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Sebastian Kreutzberger
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
## SwiftyMarkdown
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
@@ -468,6 +468,37 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
<key>FooterText</key>
|
||||
<string>The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Sebastian Kreutzberger
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
</string>
|
||||
<key>License</key>
|
||||
<string>MIT</string>
|
||||
<key>Title</key>
|
||||
<string>SwiftyBeaver</string>
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>FooterText</key>
|
||||
<string>The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Simon Fairbairn
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
|
||||
@@ -201,6 +201,7 @@ if [[ "$CONFIGURATION" == "Debug" ]]; then
|
||||
install_framework "${BUILT_PRODUCTS_DIR}/ShortcutRecorder/ShortcutRecorder.framework"
|
||||
install_framework "${PODS_ROOT}/Sparkle/Sparkle.framework"
|
||||
install_dsym "${PODS_ROOT}/Sparkle/Sparkle.framework.dSYM"
|
||||
install_framework "${BUILT_PRODUCTS_DIR}/SwiftyBeaver/SwiftyBeaver.framework"
|
||||
install_framework "${BUILT_PRODUCTS_DIR}/SwiftyMarkdown/SwiftyMarkdown.framework"
|
||||
fi
|
||||
if [[ "$CONFIGURATION" == "Release" ]]; then
|
||||
@@ -208,6 +209,7 @@ if [[ "$CONFIGURATION" == "Release" ]]; then
|
||||
install_framework "${BUILT_PRODUCTS_DIR}/ShortcutRecorder/ShortcutRecorder.framework"
|
||||
install_framework "${PODS_ROOT}/Sparkle/Sparkle.framework"
|
||||
install_dsym "${PODS_ROOT}/Sparkle/Sparkle.framework.dSYM"
|
||||
install_framework "${BUILT_PRODUCTS_DIR}/SwiftyBeaver/SwiftyBeaver.framework"
|
||||
install_framework "${BUILT_PRODUCTS_DIR}/SwiftyMarkdown/SwiftyMarkdown.framework"
|
||||
fi
|
||||
if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/LetsMove" "${PODS_CONFIGURATION_BUILD_DIR}/ShortcutRecorder" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyMarkdown" "${PODS_ROOT}/AppCenter/AppCenter-SDK-Apple" "${PODS_ROOT}/AppCenter/AppCenter-SDK-Apple/AppCenter.xcframework/macos-arm64_x86_64" "${PODS_ROOT}/AppCenter/AppCenter-SDK-Apple/AppCenterCrashes.xcframework/macos-arm64_x86_64" "${PODS_ROOT}/Sparkle"
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/LetsMove" "${PODS_CONFIGURATION_BUILD_DIR}/ShortcutRecorder" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyMarkdown" "${PODS_ROOT}/AppCenter/AppCenter-SDK-Apple" "${PODS_ROOT}/AppCenter/AppCenter-SDK-Apple/AppCenter.xcframework/macos-arm64_x86_64" "${PODS_ROOT}/AppCenter/AppCenter-SDK-Apple/AppCenterCrashes.xcframework/macos-arm64_x86_64" "${PODS_ROOT}/Sparkle"
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/LetsMove/LetsMove.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/ShortcutRecorder/ShortcutRecorder.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyMarkdown/SwiftyMarkdown.framework/Headers"
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/LetsMove/LetsMove.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/ShortcutRecorder/ShortcutRecorder.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver/SwiftyBeaver.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyMarkdown/SwiftyMarkdown.framework/Headers"
|
||||
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/../Frameworks' '@loader_path/Frameworks' @loader_path/../Frameworks
|
||||
OTHER_LDFLAGS = $(inherited) -l"c++" -l"sqlite3" -l"z" -framework "AppCenter" -framework "AppCenterCrashes" -framework "AppKit" -framework "Carbon" -framework "Cocoa" -framework "Foundation" -framework "LetsMove" -framework "ShortcutRecorder" -framework "Sparkle" -framework "SwiftyMarkdown" -framework "SystemConfiguration"
|
||||
OTHER_LDFLAGS = $(inherited) -l"c++" -l"sqlite3" -l"z" -framework "AppCenter" -framework "AppCenterCrashes" -framework "AppKit" -framework "Carbon" -framework "Cocoa" -framework "Foundation" -framework "LetsMove" -framework "ShortcutRecorder" -framework "Sparkle" -framework "SwiftyBeaver" -framework "SwiftyMarkdown" -framework "SystemConfiguration"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/LetsMove" "${PODS_CONFIGURATION_BUILD_DIR}/ShortcutRecorder" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyMarkdown" "${PODS_ROOT}/AppCenter/AppCenter-SDK-Apple" "${PODS_ROOT}/AppCenter/AppCenter-SDK-Apple/AppCenter.xcframework/macos-arm64_x86_64" "${PODS_ROOT}/AppCenter/AppCenter-SDK-Apple/AppCenterCrashes.xcframework/macos-arm64_x86_64" "${PODS_ROOT}/Sparkle"
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/LetsMove" "${PODS_CONFIGURATION_BUILD_DIR}/ShortcutRecorder" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyMarkdown" "${PODS_ROOT}/AppCenter/AppCenter-SDK-Apple" "${PODS_ROOT}/AppCenter/AppCenter-SDK-Apple/AppCenter.xcframework/macos-arm64_x86_64" "${PODS_ROOT}/AppCenter/AppCenter-SDK-Apple/AppCenterCrashes.xcframework/macos-arm64_x86_64" "${PODS_ROOT}/Sparkle"
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/LetsMove/LetsMove.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/ShortcutRecorder/ShortcutRecorder.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyMarkdown/SwiftyMarkdown.framework/Headers"
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/LetsMove/LetsMove.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/ShortcutRecorder/ShortcutRecorder.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver/SwiftyBeaver.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyMarkdown/SwiftyMarkdown.framework/Headers"
|
||||
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/../Frameworks' '@loader_path/Frameworks' @loader_path/../Frameworks
|
||||
OTHER_LDFLAGS = $(inherited) -l"c++" -l"sqlite3" -l"z" -framework "AppCenter" -framework "AppCenterCrashes" -framework "AppKit" -framework "Carbon" -framework "Cocoa" -framework "Foundation" -framework "LetsMove" -framework "ShortcutRecorder" -framework "Sparkle" -framework "SwiftyMarkdown" -framework "SystemConfiguration"
|
||||
OTHER_LDFLAGS = $(inherited) -l"c++" -l"sqlite3" -l"z" -framework "AppCenter" -framework "AppCenterCrashes" -framework "AppKit" -framework "Carbon" -framework "Cocoa" -framework "Foundation" -framework "LetsMove" -framework "ShortcutRecorder" -framework "Sparkle" -framework "SwiftyBeaver" -framework "SwiftyMarkdown" -framework "SystemConfiguration"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>${PRODUCT_NAME}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.9.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${CURRENT_PROJECT_VERSION}</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,5 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
@interface PodsDummy_SwiftyBeaver : NSObject
|
||||
@end
|
||||
@implementation PodsDummy_SwiftyBeaver
|
||||
@end
|
||||
@@ -0,0 +1,12 @@
|
||||
#ifdef __OBJC__
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#else
|
||||
#ifndef FOUNDATION_EXPORT
|
||||
#if defined(__cplusplus)
|
||||
#define FOUNDATION_EXPORT extern "C"
|
||||
#else
|
||||
#define FOUNDATION_EXPORT extern
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
#ifdef __OBJC__
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#else
|
||||
#ifndef FOUNDATION_EXPORT
|
||||
#if defined(__cplusplus)
|
||||
#define FOUNDATION_EXPORT extern "C"
|
||||
#else
|
||||
#define FOUNDATION_EXPORT extern
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
FOUNDATION_EXPORT double SwiftyBeaverVersionNumber;
|
||||
FOUNDATION_EXPORT const unsigned char SwiftyBeaverVersionString[];
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
CODE_SIGN_IDENTITY =
|
||||
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_ROOT = ${SRCROOT}
|
||||
PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftyBeaver
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
|
||||
SKIP_INSTALL = YES
|
||||
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
|
||||
@@ -0,0 +1,6 @@
|
||||
framework module SwiftyBeaver {
|
||||
umbrella header "SwiftyBeaver-umbrella.h"
|
||||
|
||||
export *
|
||||
module * { export * }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
CODE_SIGN_IDENTITY =
|
||||
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_ROOT = ${SRCROOT}
|
||||
PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftyBeaver
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
|
||||
SKIP_INSTALL = YES
|
||||
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
|
||||
@@ -39,6 +39,7 @@
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
enableGPUValidationMode = "1"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
|
||||
@@ -4,6 +4,174 @@
|
||||
<title>alt-tab-macos</title>
|
||||
<language>en</language>
|
||||
|
||||
<item>
|
||||
<title>Version 7.0.2</title>
|
||||
<pubDate>Sat, 12 Oct 2024 11:09:58 +0000</pubDate>
|
||||
<sparkle:minimumSystemVersion>10.12</sparkle:minimumSystemVersion>
|
||||
<sparkle:releaseNotesLink>https://alt-tab-macos.netlify.app/changelog-bare</sparkle:releaseNotesLink>
|
||||
<enclosure
|
||||
url="https://github.com/lwouis/alt-tab-macos/releases/download/v7.0.2/AltTab-7.0.2.zip"
|
||||
sparkle:version="7.0.2"
|
||||
sparkle:shortVersionString="7.0.2"
|
||||
sparkle:edSignature="VVXljkCyzO/m5wy0lYnSsKS1oG0tU2gl4l+ECGvYwP094uaCg5v/G3jmgqEUZAD9uYRfyEqwBgX9XbO93Nn/Dg==" length="12231229"
|
||||
type="application/octet-stream"/>
|
||||
</item>
|
||||
|
||||
|
||||
<item>
|
||||
<title>Version 7.0.1</title>
|
||||
<pubDate>Fri, 11 Oct 2024 10:14:05 +0000</pubDate>
|
||||
<sparkle:minimumSystemVersion>10.12</sparkle:minimumSystemVersion>
|
||||
<sparkle:releaseNotesLink>https://alt-tab-macos.netlify.app/changelog-bare</sparkle:releaseNotesLink>
|
||||
<enclosure
|
||||
url="https://github.com/lwouis/alt-tab-macos/releases/download/v7.0.1/AltTab-7.0.1.zip"
|
||||
sparkle:version="7.0.1"
|
||||
sparkle:shortVersionString="7.0.1"
|
||||
sparkle:edSignature="CIz9O/SprxfNFIlGfSVFqYtOtVin+HX4RKjrQGGxEEMOcEa4oAJfpRgdBK9JwLt6dE3gYGxXkIgdLi9yFuDWBQ==" length="12230956"
|
||||
type="application/octet-stream"/>
|
||||
</item>
|
||||
|
||||
|
||||
<item>
|
||||
<title>Version 7.0.0</title>
|
||||
<pubDate>Wed, 09 Oct 2024 21:13:05 +0000</pubDate>
|
||||
<sparkle:minimumSystemVersion>10.12</sparkle:minimumSystemVersion>
|
||||
<sparkle:releaseNotesLink>https://alt-tab-macos.netlify.app/changelog-bare</sparkle:releaseNotesLink>
|
||||
<enclosure
|
||||
url="https://github.com/lwouis/alt-tab-macos/releases/download/v7.0.0/AltTab-7.0.0.zip"
|
||||
sparkle:version="7.0.0"
|
||||
sparkle:shortVersionString="7.0.0"
|
||||
sparkle:edSignature="PQtCdRCEZzor+RmfDYJBHbo01KufDdjerj8qTqvBXEAYoZiO1kbNefIl5a0K9rCw0mR78W1Q238dly1Ntu3WDA==" length="12230839"
|
||||
type="application/octet-stream"/>
|
||||
</item>
|
||||
|
||||
|
||||
<item>
|
||||
<title>Version 6.73.0</title>
|
||||
<pubDate>Tue, 08 Oct 2024 20:34:05 +0000</pubDate>
|
||||
<sparkle:minimumSystemVersion>10.12</sparkle:minimumSystemVersion>
|
||||
<sparkle:releaseNotesLink>https://alt-tab-macos.netlify.app/changelog-bare</sparkle:releaseNotesLink>
|
||||
<enclosure
|
||||
url="https://github.com/lwouis/alt-tab-macos/releases/download/v6.73.0/AltTab-6.73.0.zip"
|
||||
sparkle:version="6.73.0"
|
||||
sparkle:shortVersionString="6.73.0"
|
||||
sparkle:edSignature="NmSwmx/aU3AtbLRZkWg6/C+4Tp789XmOXpoytTfVRZZmIcxV225L/nkMzAfbd+tMfTQdJDfgSTvZPU64xc6yCg==" length="8880124"
|
||||
type="application/octet-stream"/>
|
||||
</item>
|
||||
|
||||
|
||||
<item>
|
||||
<title>Version 6.72.0</title>
|
||||
<pubDate>Sun, 14 Jul 2024 17:02:37 +0000</pubDate>
|
||||
<sparkle:minimumSystemVersion>10.12</sparkle:minimumSystemVersion>
|
||||
<sparkle:releaseNotesLink>https://alt-tab-macos.netlify.app/changelog-bare</sparkle:releaseNotesLink>
|
||||
<enclosure
|
||||
url="https://github.com/lwouis/alt-tab-macos/releases/download/v6.72.0/AltTab-6.72.0.zip"
|
||||
sparkle:version="6.72.0"
|
||||
sparkle:shortVersionString="6.72.0"
|
||||
sparkle:edSignature="L+ys+mrftNTjcFQpuPKvyvxX/PO6ztISIDfROGmU87v3TYUtGpDGC4QQ86lV1RenqLWsy6AKtepJMWMhPA3DBg==" length="8868472"
|
||||
type="application/octet-stream"/>
|
||||
</item>
|
||||
|
||||
|
||||
<item>
|
||||
<title>Version 6.71.0</title>
|
||||
<pubDate>Sat, 06 Jul 2024 10:30:07 +0000</pubDate>
|
||||
<sparkle:minimumSystemVersion>10.12</sparkle:minimumSystemVersion>
|
||||
<sparkle:releaseNotesLink>https://alt-tab-macos.netlify.app/changelog-bare</sparkle:releaseNotesLink>
|
||||
<enclosure
|
||||
url="https://github.com/lwouis/alt-tab-macos/releases/download/v6.71.0/AltTab-6.71.0.zip"
|
||||
sparkle:version="6.71.0"
|
||||
sparkle:shortVersionString="6.71.0"
|
||||
sparkle:edSignature="fCfTaZ5RxDDU6MFZuKfg6Oen+qcDd1SC5w9i3Ljc1EspNcJ/wNMOB5u/uNW8q++eFKoS9n/CZuSknR/Jjy4kBA==" length="8866807"
|
||||
type="application/octet-stream"/>
|
||||
</item>
|
||||
|
||||
|
||||
<item>
|
||||
<title>Version 6.70.1</title>
|
||||
<pubDate>Sat, 01 Jun 2024 11:38:14 +0000</pubDate>
|
||||
<sparkle:minimumSystemVersion>10.12</sparkle:minimumSystemVersion>
|
||||
<sparkle:releaseNotesLink>https://alt-tab-macos.netlify.app/changelog-bare</sparkle:releaseNotesLink>
|
||||
<enclosure
|
||||
url="https://github.com/lwouis/alt-tab-macos/releases/download/v6.70.1/AltTab-6.70.1.zip"
|
||||
sparkle:version="6.70.1"
|
||||
sparkle:shortVersionString="6.70.1"
|
||||
sparkle:edSignature="4D0dquvbg4oxdPo8iuOa9a3E7zMfDmPOlpTvjWolJtTphkGbMeIwK5ILJT1A8U12tYkxKI86P9Vjg6HjDu9CCA==" length="8863665"
|
||||
type="application/octet-stream"/>
|
||||
</item>
|
||||
|
||||
|
||||
<item>
|
||||
<title>Version 6.70.0</title>
|
||||
<pubDate>Wed, 29 May 2024 22:15:40 +0000</pubDate>
|
||||
<sparkle:minimumSystemVersion>10.12</sparkle:minimumSystemVersion>
|
||||
<sparkle:releaseNotesLink>https://alt-tab-macos.netlify.app/changelog-bare</sparkle:releaseNotesLink>
|
||||
<enclosure
|
||||
url="https://github.com/lwouis/alt-tab-macos/releases/download/v6.70.0/AltTab-6.70.0.zip"
|
||||
sparkle:version="6.70.0"
|
||||
sparkle:shortVersionString="6.70.0"
|
||||
sparkle:edSignature="qCFGllhtXeM8zhJp2fh1rPjVmWMNfFBYMejoA7/P1XfB5Emlom2iPXdBa/VJXboHRxGUwkXgpLWfXl6s4JeJAA==" length="8863597"
|
||||
type="application/octet-stream"/>
|
||||
</item>
|
||||
|
||||
|
||||
<item>
|
||||
<title>Version 6.69.0</title>
|
||||
<pubDate>Sun, 12 May 2024 20:18:45 +0000</pubDate>
|
||||
<sparkle:minimumSystemVersion>10.12</sparkle:minimumSystemVersion>
|
||||
<sparkle:releaseNotesLink>https://alt-tab-macos.netlify.app/changelog-bare</sparkle:releaseNotesLink>
|
||||
<enclosure
|
||||
url="https://github.com/lwouis/alt-tab-macos/releases/download/v6.69.0/AltTab-6.69.0.zip"
|
||||
sparkle:version="6.69.0"
|
||||
sparkle:shortVersionString="6.69.0"
|
||||
sparkle:edSignature="m6NG6masZWjemss8ewXxPR7ckOMtb6M99Ndi99eEuwhEQIJI1Ln+9IPC4wlc2Bk5S9BIeecoQC/SCOmi3mKmCg==" length="8865881"
|
||||
type="application/octet-stream"/>
|
||||
</item>
|
||||
|
||||
|
||||
<item>
|
||||
<title>Version 6.68.0</title>
|
||||
<pubDate>Wed, 03 Apr 2024 19:30:49 +0000</pubDate>
|
||||
<sparkle:minimumSystemVersion>10.12</sparkle:minimumSystemVersion>
|
||||
<sparkle:releaseNotesLink>https://alt-tab-macos.netlify.app/changelog-bare</sparkle:releaseNotesLink>
|
||||
<enclosure
|
||||
url="https://github.com/lwouis/alt-tab-macos/releases/download/v6.68.0/AltTab-6.68.0.zip"
|
||||
sparkle:version="6.68.0"
|
||||
sparkle:shortVersionString="6.68.0"
|
||||
sparkle:edSignature="sqN882pVmxWHNC8RYz5obDAzB3Fj1UwdgJTHueE11weUL1ipEm4D82Y0SX1mpDSSGQLjcUyMl2uhmuGEDqGvDg==" length="8864924"
|
||||
type="application/octet-stream"/>
|
||||
</item>
|
||||
|
||||
|
||||
<item>
|
||||
<title>Version 6.67.0</title>
|
||||
<pubDate>Fri, 15 Mar 2024 17:33:19 +0000</pubDate>
|
||||
<sparkle:minimumSystemVersion>10.12</sparkle:minimumSystemVersion>
|
||||
<sparkle:releaseNotesLink>https://alt-tab-macos.netlify.app/changelog-bare</sparkle:releaseNotesLink>
|
||||
<enclosure
|
||||
url="https://github.com/lwouis/alt-tab-macos/releases/download/v6.67.0/AltTab-6.67.0.zip"
|
||||
sparkle:version="6.67.0"
|
||||
sparkle:shortVersionString="6.67.0"
|
||||
sparkle:edSignature="KboOkWGh9f+cFLEL1SroK3hfshHHaPuI7+V52okCLrnzsfUITh+FMlr+gVaq6jgRziCUuIxkSjuzA6CYqsj8Bw==" length="8856370"
|
||||
type="application/octet-stream"/>
|
||||
</item>
|
||||
|
||||
|
||||
<item>
|
||||
<title>Version 6.66.0</title>
|
||||
<pubDate>Sun, 03 Mar 2024 12:03:26 +0000</pubDate>
|
||||
<sparkle:minimumSystemVersion>10.12</sparkle:minimumSystemVersion>
|
||||
<sparkle:releaseNotesLink>https://alt-tab-macos.netlify.app/changelog-bare</sparkle:releaseNotesLink>
|
||||
<enclosure
|
||||
url="https://github.com/lwouis/alt-tab-macos/releases/download/v6.66.0/AltTab-6.66.0.zip"
|
||||
sparkle:version="6.66.0"
|
||||
sparkle:shortVersionString="6.66.0"
|
||||
sparkle:edSignature="ah92OaNaHEs/B4JZS2zGfVBgDfMIjjdrTot/dYuJ/H0+Ac8N1boaS02b2YdgspYz/16+4C/6oRI7ASniMTMRCw==" length="8851556"
|
||||
type="application/octet-stream"/>
|
||||
</item>
|
||||
|
||||
|
||||
<item>
|
||||
<title>Version 6.65.0</title>
|
||||
<pubDate>Mon, 29 Jan 2024 17:14:02 +0000</pubDate>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
PRODUCT_NAME = AltTab
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lwouis.alt-tab-macos
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.12
|
||||
SWIFT_VERSION = 5.0
|
||||
SWIFT_VERSION = 5.8
|
||||
INFOPLIST_FILE = Info.plist
|
||||
CODE_SIGN_ENTITLEMENTS = alt_tab_macos.entitlements
|
||||
FRAMEWORK_SEARCH_PATHS[config=*] = $(inherited) /System/Library/PrivateFrameworks // for SkyLight.framework
|
||||
@@ -13,4 +13,4 @@ INFOPLIST_OUTPUT_FORMAT = binary
|
||||
PLIST_FILE_OUTPUT_FORMAT = binary
|
||||
COCOAPODS_PARALLEL_CODE_SIGN = YES // codesign pods faster
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES
|
||||
SWIFT_OBJC_BRIDGING_HEADER = alt-tab-macos-Bridging-Header.h
|
||||
SWIFT_OBJC_BRIDGING_HEADER = alt-tab-macos-Bridging-Header.h
|
||||
@@ -3,7 +3,7 @@
|
||||
These third-party libraries are used:
|
||||
|
||||
**ShortcutRecorder**
|
||||
[Website](https://github.com/Kentzo/ShortcutRecorder) - [CC 4.0 license](https://github.com/Kentzo/ShortcutRecorder/blob/master/LICENSE.txt)
|
||||
[Website](https://github.com/Kentzo/ShortcutRecorder) - [CC 4.0 License](https://github.com/Kentzo/ShortcutRecorder/blob/master/LICENSE.txt)
|
||||
|
||||
**Sparkle**
|
||||
[Website](https://github.com/sparkle-project/Sparkle) - [License](https://github.com/sparkle-project/Sparkle/blob/master/LICENSE)
|
||||
@@ -16,3 +16,6 @@ These third-party libraries are used:
|
||||
|
||||
**AppCenter**
|
||||
[Website](https://github.com/microsoft/appcenter-sdk-apple) - [Custom](https://github.com/microsoft/appcenter-sdk-apple/blob/develop/LICENSE)
|
||||
|
||||
**SwiftyBeaver**
|
||||
[Website](https://github.com/SwiftyBeaver/SwiftyBeaver) - [MIT License](https://github.com/SwiftyBeaver/SwiftyBeaver/blob/master/LICENSE)
|
||||
@@ -1,5 +1,147 @@
|
||||
# Release notes
|
||||
|
||||
## [7.0.2](https://github.com/lwouis/alt-tab-macos/compare/v7.0.1...v7.0.2) (2024-10-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* avoid crashing in some cases ([d46d903](https://github.com/lwouis/alt-tab-macos/commit/d46d903))
|
||||
* block titles in preferences in rtl languages ([ea5574b](https://github.com/lwouis/alt-tab-macos/commit/ea5574b))
|
||||
* preferences panel icons wouldnt show on old macos versions ([9258acf](https://github.com/lwouis/alt-tab-macos/commit/9258acf))
|
||||
* show titles > application name, in thumbnails style (closes [#3667](https://github.com/lwouis/alt-tab-macos/issues/3667)) ([b4e97ea](https://github.com/lwouis/alt-tab-macos/commit/b4e97ea))
|
||||
|
||||
## [7.0.1](https://github.com/lwouis/alt-tab-macos/compare/v7.0.0...v7.0.1) (2024-10-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* avoid crashing in rare cases ([9980ec6](https://github.com/lwouis/alt-tab-macos/commit/9980ec6))
|
||||
|
||||
# [7.0.0](https://github.com/lwouis/alt-tab-macos/compare/v6.73.0...v7.0.0) (2024-10-09)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* major rehaul of the preferences! ([6d97cbd](https://github.com/lwouis/alt-tab-macos/commit/6d97cbd)), closes [#351](https://github.com/lwouis/alt-tab-macos/issues/351)
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* The old preferences panel has been replaced with a brand new one
|
||||
|
||||
Preferences are now much simpler, full of visual illustrations, and should provide a much better experience. Pick between 3 styles (thumbnails, app-icons, titles), sizes, dark/light themes, high visibility options, and more!
|
||||
|
||||
# [6.73.0](https://github.com/lwouis/alt-tab-macos/compare/v6.72.0...v6.73.0) (2024-10-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* closing alttab windows gives focus to previous app (closes [#3577](https://github.com/lwouis/alt-tab-macos/issues/3577)) ([0e6f200](https://github.com/lwouis/alt-tab-macos/commit/0e6f200))
|
||||
* detect passwords app (closes [#3545](https://github.com/lwouis/alt-tab-macos/issues/3545)) ([d0cd206](https://github.com/lwouis/alt-tab-macos/commit/d0cd206))
|
||||
* dragging files onto windowless apps was inconsistent ([0a9fe9b](https://github.com/lwouis/alt-tab-macos/commit/0a9fe9b))
|
||||
* finder would sometimes not be listed (closes [#3350](https://github.com/lwouis/alt-tab-macos/issues/3350)) ([eba5e42](https://github.com/lwouis/alt-tab-macos/commit/eba5e42))
|
||||
* focusing an app could open another version of it ([9d5f11a](https://github.com/lwouis/alt-tab-macos/commit/9d5f11a))
|
||||
* preview window could remain after focusing an app (closes [#3505](https://github.com/lwouis/alt-tab-macos/issues/3505)) ([e890a60](https://github.com/lwouis/alt-tab-macos/commit/e890a60))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add kannada and malayalam and localizations ([5e27701](https://github.com/lwouis/alt-tab-macos/commit/5e27701))
|
||||
|
||||
# [6.72.0](https://github.com/lwouis/alt-tab-macos/compare/v6.71.0...v6.72.0) (2024-07-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* preferences window quit button supports right-to-left languages ([d3dfd54](https://github.com/lwouis/alt-tab-macos/commit/d3dfd54)), closes [#3487](https://github.com/lwouis/alt-tab-macos/issues/3487)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* don't show caps-lock indicator as a window (closes [#3171](https://github.com/lwouis/alt-tab-macos/issues/3171)) ([2e15732](https://github.com/lwouis/alt-tab-macos/commit/2e15732))
|
||||
* improve da/he localizations ([3d98b64](https://github.com/lwouis/alt-tab-macos/commit/3d98b64))
|
||||
|
||||
# [6.71.0](https://github.com/lwouis/alt-tab-macos/compare/v6.70.1...v6.71.0) (2024-07-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* restore default cmd+tab shortcut when alt-tab crashes ([2c47b4e](https://github.com/lwouis/alt-tab-macos/commit/2c47b4e))
|
||||
* showing permissions window would crash on macos < 10.15 ([bb5215c](https://github.com/lwouis/alt-tab-macos/commit/bb5215c)), closes [#3437](https://github.com/lwouis/alt-tab-macos/issues/3437)
|
||||
* traffic light icons could appear half-transparent (closes [#2892](https://github.com/lwouis/alt-tab-macos/issues/2892)) ([1eef1e0](https://github.com/lwouis/alt-tab-macos/commit/1eef1e0))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* improve fr/he/it/ja/pt/sv localizations ([35521a5](https://github.com/lwouis/alt-tab-macos/commit/35521a5))
|
||||
* show de-fullscreen button on fullscreen windows ([ad1e8d0](https://github.com/lwouis/alt-tab-macos/commit/ad1e8d0))
|
||||
|
||||
## [6.70.1](https://github.com/lwouis/alt-tab-macos/compare/v6.70.0...v6.70.1) (2024-06-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* prevent crash introduced in v6.70.0 (closes [#3392](https://github.com/lwouis/alt-tab-macos/issues/3392)) ([e16a3d4](https://github.com/lwouis/alt-tab-macos/commit/e16a3d4))
|
||||
|
||||
# [6.70.0](https://github.com/lwouis/alt-tab-macos/compare/v6.69.0...v6.70.0) (2024-05-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* detect safari fullscreen windows better (closes [#3384](https://github.com/lwouis/alt-tab-macos/issues/3384)) ([c3006c6](https://github.com/lwouis/alt-tab-macos/commit/c3006c6))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add utm-app to default blacklist ([523bcbb](https://github.com/lwouis/alt-tab-macos/commit/523bcbb))
|
||||
|
||||
# [6.69.0](https://github.com/lwouis/alt-tab-macos/compare/v6.68.0...v6.69.0) (2024-05-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* only show standard windows of preview.app (closes [#3351](https://github.com/lwouis/alt-tab-macos/issues/3351)) ([6231977](https://github.com/lwouis/alt-tab-macos/commit/6231977))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* update fi, ja, ko, pt-br, th localizations ([14d287a](https://github.com/lwouis/alt-tab-macos/commit/14d287a))
|
||||
|
||||
# [6.68.0](https://github.com/lwouis/alt-tab-macos/compare/v6.67.0...v6.68.0) (2024-04-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* preview.app window may not show when opening many docs ([31b5a3d](https://github.com/lwouis/alt-tab-macos/commit/31b5a3d)), closes [#3276](https://github.com/lwouis/alt-tab-macos/issues/3276)
|
||||
* preview.app windows would sometimes not show (closes [#3275](https://github.com/lwouis/alt-tab-macos/issues/3275)) ([785cf9c](https://github.com/lwouis/alt-tab-macos/commit/785cf9c))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* improve ar/nb/nn/pl/tr/uk localizations ([67a6d81](https://github.com/lwouis/alt-tab-macos/commit/67a6d81))
|
||||
* new thai and norwegian localizations (closes [#3260](https://github.com/lwouis/alt-tab-macos/issues/3260)) ([d9aa319](https://github.com/lwouis/alt-tab-macos/commit/d9aa319))
|
||||
|
||||
# [6.67.0](https://github.com/lwouis/alt-tab-macos/compare/v6.66.0...v6.67.0) (2024-03-15)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add icelandic localization ([7df1432](https://github.com/lwouis/alt-tab-macos/commit/7df1432))
|
||||
* improve ru/cn localizations ([c9fe54e](https://github.com/lwouis/alt-tab-macos/commit/c9fe54e))
|
||||
* support autocad non-native windows ([#3219](https://github.com/lwouis/alt-tab-macos/issues/3219)) ([bbaef6c](https://github.com/lwouis/alt-tab-macos/commit/bbaef6c))
|
||||
|
||||
# [6.66.0](https://github.com/lwouis/alt-tab-macos/compare/v6.65.0...v6.66.0) (2024-03-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* alttab would crash after menubar icon was actioned with voiceover ([f7fdf3f](https://github.com/lwouis/alt-tab-macos/commit/f7fdf3f)), closes [#3211](https://github.com/lwouis/alt-tab-macos/issues/3211)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* improved ca/de/es/ko/nl/sv/vi/zh localizations ([f4ad2ea](https://github.com/lwouis/alt-tab-macos/commit/f4ad2ea))
|
||||
|
||||
# [6.65.0](https://github.com/lwouis/alt-tab-macos/compare/v6.64.0...v6.65.0) (2024-01-29)
|
||||
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ They helped [develop the app](https://github.com/lwouis/alt-tab-macos/graphs/con
|
||||
* [adamnemecek](https://github.com/adamnemecek)
|
||||
* [AfzalivE](https://github.com/AfzalivE)
|
||||
* [akx](https://github.com/akx)
|
||||
* [Allsochen](https://github.com/Allsochen)
|
||||
* [ayroblu](https://github.com/ayroblu)
|
||||
* [Calinou](https://github.com/Calinou)
|
||||
* [damonpam](https://github.com/damonpam)
|
||||
@@ -13,20 +14,19 @@ They helped [develop the app](https://github.com/lwouis/alt-tab-macos/graphs/con
|
||||
* [gingerr](https://github.com/gingerr)
|
||||
* [GrzegorzKazana](https://github.com/GrzegorzKazana)
|
||||
* [hughlilly](https://github.com/hughlilly)
|
||||
* [i0ntempest](https://github.com/i0ntempest)
|
||||
* [karbassi](https://github.com/karbassi)
|
||||
* [L1cardo](https://github.com/L1cardo)
|
||||
* [lwouis](https://github.com/lwouis)
|
||||
* [metacodes](https://github.com/metacodes)
|
||||
* [mnin](https://github.com/mnin)
|
||||
* [MrPaschenko](https://github.com/MrPaschenko)
|
||||
* [nella17](https://github.com/nella17)
|
||||
* [notlmn](https://github.com/notlmn)
|
||||
* [phungtuanhoang1996](https://github.com/phungtuanhoang1996)
|
||||
* [rbnis](https://github.com/rbnis)
|
||||
* [Rjevski](https://github.com/Rjevski)
|
||||
* [ryenus](https://github.com/ryenus)
|
||||
* [s4na](https://github.com/s4na)
|
||||
* [samdenty](https://github.com/samdenty)
|
||||
* [ShlomoCode](https://github.com/ShlomoCode)
|
||||
* [skolj](https://github.com/skolj)
|
||||
* [xanathar](https://github.com/xanathar)
|
||||
* [xconverge](https://github.com/xconverge)
|
||||
@@ -38,16 +38,25 @@ They helped [localize the app](https://poeditor.com/join/project/8AOEZ0eAZE):
|
||||
* Aamirsuleman
|
||||
* Aarni Koskela
|
||||
* Abdulelah
|
||||
* Abdur-Rahman Bilal
|
||||
* Aden Aziz
|
||||
* Admin
|
||||
* Aindriú Mac Giolla Eoin
|
||||
* Ajaykumarmkaju
|
||||
* Aku
|
||||
* Albert Abdilim
|
||||
* Alex
|
||||
* Alexander Kilian
|
||||
* Alexey Eremeev
|
||||
* Ali Gokmen
|
||||
* Ali. tas103
|
||||
* Allen Guan
|
||||
* Alonso Chen
|
||||
* Ameng
|
||||
* Anders
|
||||
* Andreas Wagner
|
||||
* Andrew Vader
|
||||
* antoon334
|
||||
* Anurag Roy
|
||||
* anushree b
|
||||
* Arman
|
||||
@@ -57,42 +66,49 @@ They helped [localize the app](https://poeditor.com/join/project/8AOEZ0eAZE):
|
||||
* blanorama
|
||||
* bluefirex
|
||||
* bovirus
|
||||
* BV Joshi
|
||||
* caduellery
|
||||
* Caner İlhan
|
||||
* catcore
|
||||
* Christian Keilmann
|
||||
* Chun Fei Lung
|
||||
* codecantata
|
||||
* Cong Tri
|
||||
* Dan
|
||||
* Dan84
|
||||
* Darius Rosendahl
|
||||
* Darko
|
||||
* David R
|
||||
* David Süle
|
||||
* Didier Deschrijver
|
||||
* Dzhuneyt
|
||||
* EDUARDO
|
||||
* edu_sombra
|
||||
* Eliezer Shpigelman
|
||||
* Emil Pedersen
|
||||
* Eric WANTZ
|
||||
* Ersagun Kuruca
|
||||
* EUKA
|
||||
* Eukarya
|
||||
* Eyelly wu
|
||||
* fabifabulousity
|
||||
* fil
|
||||
* Filipe
|
||||
* Frangarciasalomon
|
||||
* Frank
|
||||
* Friendship1
|
||||
* Fukuda
|
||||
* Gezimos
|
||||
* Giacomo Volpi
|
||||
* Giang
|
||||
* Github
|
||||
* Gkostov
|
||||
* Grzegorz Kazana
|
||||
* Guillaume
|
||||
* Gumpanath Puttarassu
|
||||
* hann-solo
|
||||
* Haoshuai Xu
|
||||
* Hjörtur Hjartarson
|
||||
* Hokuto Kato
|
||||
* Huandngoc
|
||||
* Huynh Sang
|
||||
* Ialiendeg
|
||||
* Ida Bomholt Dyrholm Jacobsen
|
||||
* Igor Aradski
|
||||
@@ -114,17 +130,25 @@ They helped [localize the app](https://poeditor.com/join/project/8AOEZ0eAZE):
|
||||
* Kagurazaka Tsuki
|
||||
* kal
|
||||
* kant
|
||||
* Kawarimidoll+git
|
||||
* kazuki sakamoto
|
||||
* Kevinsevinche
|
||||
* Kinyagulovrr
|
||||
* Klara
|
||||
* Kristófer Fannar Björnsson
|
||||
* Kushnee5
|
||||
* Kuuchi
|
||||
* L
|
||||
* Lakshman Kolappan
|
||||
* Lasse Mattila
|
||||
* Lcwhhh
|
||||
* Lester
|
||||
* Loïc
|
||||
* Loic. peron
|
||||
* LostInCompilation
|
||||
* Lumaxis
|
||||
* lwouis
|
||||
* Malo
|
||||
* Maplevantablack
|
||||
* Marc Pla
|
||||
* Marcus Billman
|
||||
@@ -132,11 +156,14 @@ They helped [localize the app](https://poeditor.com/join/project/8AOEZ0eAZE):
|
||||
* Marko McLion
|
||||
* Martin Mitka
|
||||
* Martin. mitka
|
||||
* Martinjnilsen
|
||||
* Masayoshi Motojima
|
||||
* Masih
|
||||
* Matheus
|
||||
* Max
|
||||
* Maximilian Falk
|
||||
* MaximilianFreitag
|
||||
* Mehmet Ali Ertorer
|
||||
* Meriç Bağlayan
|
||||
* Michael
|
||||
* Milos M
|
||||
* Mohammad Al Zouabi
|
||||
@@ -149,12 +176,13 @@ They helped [localize the app](https://poeditor.com/join/project/8AOEZ0eAZE):
|
||||
* Nils Fahldieck
|
||||
* Nilton Souza
|
||||
* Nmolham
|
||||
* Nuriddin Islamov
|
||||
* Ori
|
||||
* Paulius
|
||||
* Pehovorka
|
||||
* Peterkim0620
|
||||
* Petr Kolář
|
||||
* Piotr Budny
|
||||
* ponchik
|
||||
* Quentin. douarre
|
||||
* Raphaël
|
||||
* Rasmus
|
||||
@@ -162,8 +190,11 @@ They helped [localize the app](https://poeditor.com/join/project/8AOEZ0eAZE):
|
||||
* raz
|
||||
* Razvan
|
||||
* rbnis
|
||||
* rei fujise
|
||||
* Roccobot
|
||||
* Rodrigo Schneider
|
||||
* Ron Nuss
|
||||
* sa-ha
|
||||
* sawtooth
|
||||
* Selcuk Dursun
|
||||
* Sergey
|
||||
@@ -172,16 +203,18 @@ They helped [localize the app](https://poeditor.com/join/project/8AOEZ0eAZE):
|
||||
* SheNeVmerla
|
||||
* Shivam Bansal
|
||||
* shlomo
|
||||
* Sippawit Thammawiset
|
||||
* Sjur Moshagen
|
||||
* Sjur.n. moshagen
|
||||
* Stan Smits
|
||||
* stas
|
||||
* Stefan
|
||||
* Stefan Lange
|
||||
* Svetoslav Stefanov
|
||||
* Thomas Wölk
|
||||
* Thorsten
|
||||
* Tomoa Nozawa
|
||||
* Umutakkaya1996
|
||||
* Vadym
|
||||
* Vegard
|
||||
* Vijayant Saini
|
||||
* Vincent Orback
|
||||
* Vlad
|
||||
@@ -190,6 +223,9 @@ They helped [localize the app](https://poeditor.com/join/project/8AOEZ0eAZE):
|
||||
* Whatsmine-HaoshuaiXu
|
||||
* Wilhelm Wolfgang Gärtner
|
||||
* Wowpapa3232
|
||||
* wtwt0119!!
|
||||
* Yongsung Yoon
|
||||
* Yorben
|
||||
* Yossi Zahn
|
||||
* ysaito
|
||||
* Yukai
|
||||
|
||||
@@ -1,126 +1,133 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
activesupport (6.0.2.2)
|
||||
activesupport (6.1.7.10)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
tzinfo (~> 1.1)
|
||||
zeitwerk (~> 2.2)
|
||||
addressable (2.7.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
tzinfo (~> 2.0)
|
||||
zeitwerk (~> 2.3)
|
||||
addressable (2.8.7)
|
||||
public_suffix (>= 2.0.2, < 7.0)
|
||||
base64 (0.2.0)
|
||||
coffee-script (2.4.1)
|
||||
coffee-script-source
|
||||
execjs
|
||||
coffee-script-source (1.11.1)
|
||||
coffee-script-source (1.12.2)
|
||||
colorator (1.1.0)
|
||||
commonmarker (0.17.13)
|
||||
ruby-enum (~> 0.5)
|
||||
concurrent-ruby (1.1.6)
|
||||
dnsruby (1.61.3)
|
||||
addressable (~> 2.5)
|
||||
em-websocket (0.5.1)
|
||||
commonmarker (0.23.10)
|
||||
concurrent-ruby (1.3.4)
|
||||
dnsruby (1.72.2)
|
||||
simpleidn (~> 0.2.1)
|
||||
em-websocket (0.5.3)
|
||||
eventmachine (>= 0.12.9)
|
||||
http_parser.rb (~> 0.6.0)
|
||||
ethon (0.12.0)
|
||||
ffi (>= 1.3.0)
|
||||
http_parser.rb (~> 0)
|
||||
ethon (0.16.0)
|
||||
ffi (>= 1.15.0)
|
||||
eventmachine (1.2.7)
|
||||
execjs (2.7.0)
|
||||
faraday (1.0.1)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
ffi (1.12.2)
|
||||
execjs (2.10.0)
|
||||
faraday (2.8.1)
|
||||
base64
|
||||
faraday-net_http (>= 2.0, < 3.1)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday-net_http (3.0.2)
|
||||
ffi (1.17.0)
|
||||
forwardable-extended (2.6.0)
|
||||
gemoji (3.0.1)
|
||||
github-pages (204)
|
||||
github-pages-health-check (= 1.16.1)
|
||||
jekyll (= 3.8.5)
|
||||
jekyll-avatar (= 0.7.0)
|
||||
jekyll-coffeescript (= 1.1.1)
|
||||
jekyll-commonmark-ghpages (= 0.1.6)
|
||||
jekyll-default-layout (= 0.1.4)
|
||||
jekyll-feed (= 0.13.0)
|
||||
gemoji (4.1.0)
|
||||
github-pages (231)
|
||||
github-pages-health-check (= 1.18.2)
|
||||
jekyll (= 3.9.5)
|
||||
jekyll-avatar (= 0.8.0)
|
||||
jekyll-coffeescript (= 1.2.2)
|
||||
jekyll-commonmark-ghpages (= 0.4.0)
|
||||
jekyll-default-layout (= 0.1.5)
|
||||
jekyll-feed (= 0.17.0)
|
||||
jekyll-gist (= 1.5.0)
|
||||
jekyll-github-metadata (= 2.13.0)
|
||||
jekyll-mentions (= 1.5.1)
|
||||
jekyll-github-metadata (= 2.16.1)
|
||||
jekyll-include-cache (= 0.2.1)
|
||||
jekyll-mentions (= 1.6.0)
|
||||
jekyll-optional-front-matter (= 0.3.2)
|
||||
jekyll-paginate (= 1.1.0)
|
||||
jekyll-readme-index (= 0.3.0)
|
||||
jekyll-redirect-from (= 0.15.0)
|
||||
jekyll-redirect-from (= 0.16.0)
|
||||
jekyll-relative-links (= 0.6.1)
|
||||
jekyll-remote-theme (= 0.4.1)
|
||||
jekyll-remote-theme (= 0.4.3)
|
||||
jekyll-sass-converter (= 1.5.2)
|
||||
jekyll-seo-tag (= 2.6.1)
|
||||
jekyll-seo-tag (= 2.8.0)
|
||||
jekyll-sitemap (= 1.4.0)
|
||||
jekyll-swiss (= 1.0.0)
|
||||
jekyll-theme-architect (= 0.1.1)
|
||||
jekyll-theme-cayman (= 0.1.1)
|
||||
jekyll-theme-dinky (= 0.1.1)
|
||||
jekyll-theme-hacker (= 0.1.1)
|
||||
jekyll-theme-leap-day (= 0.1.1)
|
||||
jekyll-theme-merlot (= 0.1.1)
|
||||
jekyll-theme-midnight (= 0.1.1)
|
||||
jekyll-theme-minimal (= 0.1.1)
|
||||
jekyll-theme-modernist (= 0.1.1)
|
||||
jekyll-theme-primer (= 0.5.4)
|
||||
jekyll-theme-slate (= 0.1.1)
|
||||
jekyll-theme-tactile (= 0.1.1)
|
||||
jekyll-theme-time-machine (= 0.1.1)
|
||||
jekyll-theme-architect (= 0.2.0)
|
||||
jekyll-theme-cayman (= 0.2.0)
|
||||
jekyll-theme-dinky (= 0.2.0)
|
||||
jekyll-theme-hacker (= 0.2.0)
|
||||
jekyll-theme-leap-day (= 0.2.0)
|
||||
jekyll-theme-merlot (= 0.2.0)
|
||||
jekyll-theme-midnight (= 0.2.0)
|
||||
jekyll-theme-minimal (= 0.2.0)
|
||||
jekyll-theme-modernist (= 0.2.0)
|
||||
jekyll-theme-primer (= 0.6.0)
|
||||
jekyll-theme-slate (= 0.2.0)
|
||||
jekyll-theme-tactile (= 0.2.0)
|
||||
jekyll-theme-time-machine (= 0.2.0)
|
||||
jekyll-titles-from-headings (= 0.5.3)
|
||||
jemoji (= 0.11.1)
|
||||
kramdown (= 1.17.0)
|
||||
liquid (= 4.0.3)
|
||||
jemoji (= 0.13.0)
|
||||
kramdown (= 2.4.0)
|
||||
kramdown-parser-gfm (= 1.1.0)
|
||||
liquid (= 4.0.4)
|
||||
mercenary (~> 0.3)
|
||||
minima (= 2.5.1)
|
||||
nokogiri (>= 1.10.4, < 2.0)
|
||||
rouge (= 3.13.0)
|
||||
nokogiri (>= 1.13.6, < 2.0)
|
||||
rouge (= 3.30.0)
|
||||
terminal-table (~> 1.4)
|
||||
github-pages-health-check (1.16.1)
|
||||
github-pages-health-check (1.18.2)
|
||||
addressable (~> 2.3)
|
||||
dnsruby (~> 1.60)
|
||||
octokit (~> 4.0)
|
||||
public_suffix (~> 3.0)
|
||||
octokit (>= 4, < 8)
|
||||
public_suffix (>= 3.0, < 6.0)
|
||||
typhoeus (~> 1.3)
|
||||
html-pipeline (2.12.3)
|
||||
html-pipeline (2.14.3)
|
||||
activesupport (>= 2)
|
||||
nokogiri (>= 1.4)
|
||||
http_parser.rb (0.6.0)
|
||||
i18n (0.9.5)
|
||||
http_parser.rb (0.8.0)
|
||||
i18n (1.14.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
jekyll (3.8.5)
|
||||
jekyll (3.9.5)
|
||||
addressable (~> 2.4)
|
||||
colorator (~> 1.0)
|
||||
em-websocket (~> 0.5)
|
||||
i18n (~> 0.7)
|
||||
i18n (>= 0.7, < 2)
|
||||
jekyll-sass-converter (~> 1.0)
|
||||
jekyll-watch (~> 2.0)
|
||||
kramdown (~> 1.14)
|
||||
kramdown (>= 1.17, < 3)
|
||||
liquid (~> 4.0)
|
||||
mercenary (~> 0.3.3)
|
||||
pathutil (~> 0.9)
|
||||
rouge (>= 1.7, < 4)
|
||||
safe_yaml (~> 1.0)
|
||||
jekyll-avatar (0.7.0)
|
||||
jekyll-avatar (0.8.0)
|
||||
jekyll (>= 3.0, < 5.0)
|
||||
jekyll-coffeescript (1.1.1)
|
||||
jekyll-coffeescript (1.2.2)
|
||||
coffee-script (~> 2.2)
|
||||
coffee-script-source (~> 1.11.1)
|
||||
jekyll-commonmark (1.3.1)
|
||||
commonmarker (~> 0.14)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-commonmark-ghpages (0.1.6)
|
||||
commonmarker (~> 0.17.6)
|
||||
jekyll-commonmark (~> 1.2)
|
||||
rouge (>= 2.0, < 4.0)
|
||||
jekyll-default-layout (0.1.4)
|
||||
jekyll (~> 3.0)
|
||||
jekyll-feed (0.13.0)
|
||||
coffee-script-source (~> 1.12)
|
||||
jekyll-commonmark (1.4.0)
|
||||
commonmarker (~> 0.22)
|
||||
jekyll-commonmark-ghpages (0.4.0)
|
||||
commonmarker (~> 0.23.7)
|
||||
jekyll (~> 3.9.0)
|
||||
jekyll-commonmark (~> 1.4.0)
|
||||
rouge (>= 2.0, < 5.0)
|
||||
jekyll-default-layout (0.1.5)
|
||||
jekyll (>= 3.0, < 5.0)
|
||||
jekyll-feed (0.17.0)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-gist (1.5.0)
|
||||
octokit (~> 4.2)
|
||||
jekyll-github-metadata (2.13.0)
|
||||
jekyll-github-metadata (2.16.1)
|
||||
jekyll (>= 3.4, < 5.0)
|
||||
octokit (~> 4.0, != 4.4.0)
|
||||
jekyll-mentions (1.5.1)
|
||||
octokit (>= 4, < 7, != 4.4.0)
|
||||
jekyll-include-cache (0.2.1)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-mentions (1.6.0)
|
||||
html-pipeline (~> 2.3)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-optional-front-matter (0.3.2)
|
||||
@@ -128,115 +135,120 @@ GEM
|
||||
jekyll-paginate (1.1.0)
|
||||
jekyll-readme-index (0.3.0)
|
||||
jekyll (>= 3.0, < 5.0)
|
||||
jekyll-redirect-from (0.15.0)
|
||||
jekyll-redirect-from (0.16.0)
|
||||
jekyll (>= 3.3, < 5.0)
|
||||
jekyll-relative-links (0.6.1)
|
||||
jekyll (>= 3.3, < 5.0)
|
||||
jekyll-remote-theme (0.4.1)
|
||||
jekyll-remote-theme (0.4.3)
|
||||
addressable (~> 2.0)
|
||||
jekyll (>= 3.5, < 5.0)
|
||||
rubyzip (>= 1.3.0)
|
||||
jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0)
|
||||
rubyzip (>= 1.3.0, < 3.0)
|
||||
jekyll-sass-converter (1.5.2)
|
||||
sass (~> 3.4)
|
||||
jekyll-seo-tag (2.6.1)
|
||||
jekyll (>= 3.3, < 5.0)
|
||||
jekyll-seo-tag (2.8.0)
|
||||
jekyll (>= 3.8, < 5.0)
|
||||
jekyll-sitemap (1.4.0)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-swiss (1.0.0)
|
||||
jekyll-theme-architect (0.1.1)
|
||||
jekyll (~> 3.5)
|
||||
jekyll-theme-architect (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-cayman (0.1.1)
|
||||
jekyll (~> 3.5)
|
||||
jekyll-theme-cayman (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-dinky (0.1.1)
|
||||
jekyll (~> 3.5)
|
||||
jekyll-theme-dinky (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-hacker (0.1.1)
|
||||
jekyll (~> 3.5)
|
||||
jekyll-theme-hacker (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-leap-day (0.1.1)
|
||||
jekyll (~> 3.5)
|
||||
jekyll-theme-leap-day (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-merlot (0.1.1)
|
||||
jekyll (~> 3.5)
|
||||
jekyll-theme-merlot (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-midnight (0.1.1)
|
||||
jekyll (~> 3.5)
|
||||
jekyll-theme-midnight (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-minimal (0.1.1)
|
||||
jekyll (~> 3.5)
|
||||
jekyll-theme-minimal (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-modernist (0.1.1)
|
||||
jekyll (~> 3.5)
|
||||
jekyll-theme-modernist (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-primer (0.5.4)
|
||||
jekyll-theme-primer (0.6.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-github-metadata (~> 2.9)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-slate (0.1.1)
|
||||
jekyll (~> 3.5)
|
||||
jekyll-theme-slate (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-tactile (0.1.1)
|
||||
jekyll (~> 3.5)
|
||||
jekyll-theme-tactile (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-time-machine (0.1.1)
|
||||
jekyll (~> 3.5)
|
||||
jekyll-theme-time-machine (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-titles-from-headings (0.5.3)
|
||||
jekyll (>= 3.3, < 5.0)
|
||||
jekyll-watch (2.2.1)
|
||||
listen (~> 3.0)
|
||||
jemoji (0.11.1)
|
||||
gemoji (~> 3.0)
|
||||
jemoji (0.13.0)
|
||||
gemoji (>= 3, < 5)
|
||||
html-pipeline (~> 2.2)
|
||||
jekyll (>= 3.0, < 5.0)
|
||||
kramdown (1.17.0)
|
||||
liquid (4.0.3)
|
||||
listen (3.2.1)
|
||||
kramdown (2.4.0)
|
||||
rexml
|
||||
kramdown-parser-gfm (1.1.0)
|
||||
kramdown (~> 2.0)
|
||||
liquid (4.0.4)
|
||||
listen (3.9.0)
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
mercenary (0.3.6)
|
||||
mini_portile2 (2.4.0)
|
||||
mini_portile2 (2.8.7)
|
||||
minima (2.5.1)
|
||||
jekyll (>= 3.5, < 5.0)
|
||||
jekyll-feed (~> 0.9)
|
||||
jekyll-seo-tag (~> 2.1)
|
||||
minitest (5.14.0)
|
||||
multipart-post (2.1.1)
|
||||
nokogiri (1.10.9)
|
||||
mini_portile2 (~> 2.4.0)
|
||||
octokit (4.18.0)
|
||||
faraday (>= 0.9)
|
||||
sawyer (~> 0.8.0, >= 0.5.3)
|
||||
minitest (5.25.1)
|
||||
nokogiri (1.13.10)
|
||||
mini_portile2 (~> 2.8.0)
|
||||
racc (~> 1.4)
|
||||
octokit (4.25.1)
|
||||
faraday (>= 1, < 3)
|
||||
sawyer (~> 0.9)
|
||||
pathutil (0.16.2)
|
||||
forwardable-extended (~> 2.6)
|
||||
public_suffix (3.1.1)
|
||||
rb-fsevent (0.10.3)
|
||||
rb-inotify (0.10.1)
|
||||
public_suffix (5.1.1)
|
||||
racc (1.8.1)
|
||||
rb-fsevent (0.11.2)
|
||||
rb-inotify (0.11.1)
|
||||
ffi (~> 1.0)
|
||||
rouge (3.13.0)
|
||||
ruby-enum (0.8.0)
|
||||
i18n
|
||||
rubyzip (2.3.0)
|
||||
rexml (3.3.9)
|
||||
rouge (3.30.0)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.3.2)
|
||||
safe_yaml (1.0.5)
|
||||
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)
|
||||
sawyer (0.8.2)
|
||||
sawyer (0.9.2)
|
||||
addressable (>= 2.3.5)
|
||||
faraday (> 0.8, < 2.0)
|
||||
faraday (>= 0.17.3, < 3)
|
||||
simpleidn (0.2.3)
|
||||
terminal-table (1.8.0)
|
||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||
thread_safe (0.3.6)
|
||||
typhoeus (1.3.1)
|
||||
typhoeus (1.4.1)
|
||||
ethon (>= 0.9.0)
|
||||
tzinfo (1.2.7)
|
||||
thread_safe (~> 0.1)
|
||||
unicode-display_width (1.7.0)
|
||||
zeitwerk (2.3.0)
|
||||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
unicode-display_width (1.8.0)
|
||||
zeitwerk (2.6.18)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
@@ -4,33 +4,28 @@ permalink: /
|
||||
|
||||
# Introduction
|
||||
|
||||
[](public/demo/frontpage.jpg)
|
||||
[<img src="public/demo/frontpage.jpg" alt="Screenshot showing AltTab" width="1920" height="1080" style="width:100%; height:auto;" />](public/demo/frontpage.jpg)
|
||||
|
||||
**AltTab** brings the power of Windows’s "alt-tab" window switcher to macOS.
|
||||
Alternative Styles
|
||||
|
||||
| [<img loading="lazy" src="public/demo/app-icons-style.jpg" alt="Screenshot: App Icons Style" width="513" height="241" style="width:100%; height:auto;" />](public/demo/app-icons-style.jpg) | [<img loading="lazy" src="public/demo/titles-style.jpg" alt="Screenshot: Titles Style" width="766" height="248" style="width:100%; height:auto;" />](public/demo/titles-style.jpg) |
|
||||
|
||||
## Installation
|
||||
|
||||
* [**Download the latest release**]({{ site.github.latest_release.assets[0].browser_download_url }})
|
||||
* Alternatively, you can use [homebrew](https://brew.sh/): `brew install --cask alt-tab`
|
||||
|
||||
## Features
|
||||
|
||||
* Switch focus to any window
|
||||
* Minimize, close, fullscreen any window
|
||||
* Hide, quit any app
|
||||
* Customize AltTab appearance (e.g. show app badges, Space numbers, increase icon, thumbnail, title size, etc)
|
||||
* Custom trigger shortcuts with almost any key
|
||||
* Minimize, close, fullscreen any window; Hide, quit any app
|
||||
* Customize AltTab appearance (pick one of 3 styles, then customize further)
|
||||
* Custom trigger shortcuts with almost any keys
|
||||
* Blacklist apps you don’t want to list or trigger AltTab from
|
||||
* Dark Mode
|
||||
* Light and Dark Appearance
|
||||
* Drag-and-drop things on top of window thumbnails
|
||||
* Right-to-left languages and UI
|
||||
* Accessibility: VoiceOver, sticky keys, reduced transparency, etc
|
||||
|
||||
## Installation
|
||||
|
||||
[**Download the latest release**]({{ site.github.latest_release.assets[0].browser_download_url }})
|
||||
|
||||
Alternatively, you can use [homebrew](https://brew.sh/):
|
||||
|
||||
| Homebrew 2.7 and above | Homebrew 2.5 and 2.6 | Homebrew 2.4 and below |
|
||||
|------------------------|----------------------|------------------------------------|
|
||||
| `brew install --cask alt-tab` | `brew install alt-tab` | `brew cask install alt-tab` |
|
||||
|
||||
## Compatibility
|
||||
|
||||
* __macOS version:__ from 10.12 to latest
|
||||
@@ -38,7 +33,7 @@ Alternatively, you can use [homebrew](https://brew.sh/):
|
||||
|
||||
## Localization
|
||||
|
||||
AltTab is available in: Bahasa Indonesia, Català, Dansk, Deutsch, Eesti keel, English, Español, Français, Galego, Hrvatski, Italiano, Kurdî, Lëtzebuergesch, Limba română, Magyar, Nederlands, Norsk, Oʻzbekcha, Polski, Português, Português (Brasil), Shqip, Slovenčina, Slovenščina, Suomi, Svenska, Tiếng Việt, Türkçe, Čeština, Ελληνικά, Български, Русский язык, Српски / Srpski, українська мова, עִבְרִית ,العربية ,فارسی, हिन्दी, বাংলা, தமிழ், 日本語, 简体中文, 繁體中文, 한국어
|
||||
AltTab is available in many languages: Bahasa Indonesia, Català, Dansk, Deutsch, Eesti keel, English, Español, Français, Gaeilge, Galego, Hrvatski, Italiano, Kurdî, Limba română, Lëtzebuergesch, Magyar, Nederlands, Norsk Bokmål, Norsk Nynorsk, Oʻzbekcha, Polski, Português, Português (Brasil), Shqip, Slovenčina, Slovenščina, Suomi, Svenska, Tiếng Việt, Türkçe, Íslenska, Čeština, Ελληνικά, Български, Русский, Српски / Srpski, Українська, עִבְרִית, العربية, فارسی, हिन्दी, বাংলা, தமிழ், ಕನ್ನಡ, മലയാളം, ภาษาไทย, 日本語, 简体中文, 繁體中文, 한국어
|
||||
|
||||
[Contribute your own language easily!](https://poeditor.com/join/project/8AOEZ0eAZE)
|
||||
|
||||
@@ -51,9 +46,9 @@ AltTab is available in: Bahasa Indonesia, Català, Dansk, Deutsch, Eesti keel, E
|
||||
|
||||
Change the shortcut keys, switch to a Windows theme and more, using the Preferences window:
|
||||
|
||||
| [](public/demo/preferences-appearance.jpg) | [](public/demo/preferences-controls.jpg) |
|
||||
| [](public/demo/preferences-blacklist.jpg) | [](public/demo/preferences-policies.jpg) |
|
||||
| [](public/demo/preferences-general.jpg) | |
|
||||
| [<img loading="lazy" src="public/demo/preferences-general.jpg" alt="Screenshot: General preferences" width="672" height="354" style="width:100%; height:auto;" />](public/demo/preferences-general.jpg) | [<img loading="lazy" src="public/demo/preferences-controls.jpg" alt="Screenshot: Controls preferences" width="677" height="688" style="width:100%; height:auto;" />](public/demo/preferences-controls.jpg) |
|
||||
| [<img loading="lazy" src="public/demo/preferences-appearance.jpg" alt="Screenshot: Appearance preferences" width="672" height="735" style="width:100%; height:auto;" />](public/demo/preferences-appearance.jpg) | [<img loading="lazy" src="public/demo/preferences-appearance-details.jpg" alt="Screenshot: Appearance preferences (details)" width="672" height="934" style="width:100%; height:auto;" />](public/demo/preferences-appearance-details.jpg) |
|
||||
| [<img loading="lazy" src="public/demo/preferences-blacklist.jpg" alt="Screenshot: Blacklist preferences" width="672" height="655" style="width:100%; height:auto;" />](public/demo/preferences-blacklist.jpg) | [<img loading="lazy" src="public/demo/preferences-policies.jpg" alt="Screenshot: Policies preferences" width="672" height="353" style="width:100%; height:auto;" />](public/demo/preferences-policies.jpg) |
|
||||
|
||||
## Alternatives
|
||||
|
||||
@@ -72,12 +67,6 @@ Before building my own app, I looked around at similar apps. However, none was c
|
||||
|
||||
There are also related apps which don’t really overlap in functionality, but target similar needs: [Swish](https://highlyopinionated.co/swish/), [Hookshot](https://hookshot.app/), [Magnet](https://magnet.crowdcafe.com/), [Spectacle](https://www.spectacleapp.com/), [Rectangle](https://github.com/rxhanson/Rectangle), [yabai](https://github.com/koekeishiya/yabai), [LayAuto](https://layautoapp.com/), [OptimalLayout](http://most-advantageous.com/optimal-layout/), [BetterTouchTool](https://folivora.ai/), [BetterSnapTool](https://folivora.ai/bettersnaptool), [Moom](https://manytricks.com/moom/), [uBar](https://brawersoftware.com/products/ubar).
|
||||
|
||||
## More screenshots
|
||||
|
||||
| 1 row | 2 rows | Windows theme |
|
||||
|-------|---------|-------|
|
||||
| [](public/demo/1-row.jpg) | [](public/demo/2-rows.jpg) | [](public/demo/windows-theme.jpg) |
|
||||
|
||||
## License
|
||||
|
||||
AltTab is under the [GPL-3.0 license](https://github.com/lwouis/alt-tab-macos/blob/master/LICENCE.md).
|
||||
@@ -139,7 +139,8 @@ nav {
|
||||
word-break: break-word;
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
code, pre, tt {
|
||||
|
||||
@@ -37,8 +37,7 @@
|
||||
|
||||
<header>
|
||||
<div>
|
||||
<a href="{{ site.baseurl }}"><img src="{{ site.baseurl }}public/android-chrome-256x256.png" width="100"
|
||||
height="100"/></a>
|
||||
<a href="{{ site.baseurl }}"><img src="{{ site.baseurl }}public/android-chrome-256x256.png" width="100" height="100"/></a>
|
||||
<div>
|
||||
<h1><a href="{{ site.baseurl }}">{{ site.title }}</a></h1>
|
||||
<p>{{ site.github.project_tagline }}</p>
|
||||
|
||||
|
Before Width: | Height: | Size: 178 KiB |
|
Before Width: | Height: | Size: 187 KiB |
|
After Width: | Height: | Size: 501 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 345 KiB After Width: | Height: | Size: 507 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 173 KiB After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 491 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 191 KiB |
@@ -6,9 +6,19 @@
|
||||
"license": "MIT",
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
|
||||
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS",
|
||||
"pre-commit": "lint-staged"
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.swift": [
|
||||
"node scripts/swiftformat.js --lint"
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"format": "node scripts/swiftformat.js",
|
||||
"format:check": "node scripts/swiftformat.js --lint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^8.1.0",
|
||||
"@commitlint/config-conventional": "^8.1.0",
|
||||
@@ -17,7 +27,9 @@
|
||||
"@semantic-release/git": "^7.0.16",
|
||||
"fontkit": "^1.8.0",
|
||||
"husky": "^3.0.4",
|
||||
"lint-staged": "^13.0.0",
|
||||
"marked": "^0.8.0",
|
||||
"semantic-release": "^15.13.24"
|
||||
"semantic-release": "^15.13.24",
|
||||
"glob": "^10.0.0"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 113 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 66 KiB |