mirror of
https://github.com/outbackdingo/nDPId.git
synced 2026-01-28 02:19:37 +00:00
Compare commits
114 Commits
1.2
...
add/nDPId-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0415cabfa | ||
|
|
98645285c8 | ||
|
|
8c092dacfe | ||
|
|
96b9129918 | ||
|
|
ae37631e23 | ||
|
|
ef94b83a62 | ||
|
|
fc442180da | ||
|
|
a606586a32 | ||
|
|
4a397ac646 | ||
|
|
28602ca095 | ||
|
|
b5d4da8793 | ||
|
|
a80b6d7271 | ||
|
|
cdaeb1632e | ||
|
|
2a8883a96e | ||
|
|
664a8a077d | ||
|
|
77a87254b6 | ||
|
|
3caf7727fd | ||
|
|
f5b0021413 | ||
|
|
73ca7fff3c | ||
|
|
4fde63b5c2 | ||
|
|
0385653023 | ||
|
|
a46fc4153d | ||
|
|
22a8d04c74 | ||
|
|
9aeff586bd | ||
|
|
c7bf94e9f1 | ||
|
|
a2547321bb | ||
|
|
c283b89afd | ||
|
|
db83f82d29 | ||
|
|
645aeaf5b4 | ||
|
|
9f9e881b3f | ||
|
|
65a9e5a18d | ||
|
|
c0b7bdacbc | ||
|
|
daaaa61519 | ||
|
|
ed1647b944 | ||
|
|
dd35d9da3f | ||
|
|
f884a538ce | ||
|
|
41757ecf1c | ||
|
|
6f1f9e65ea | ||
|
|
d0985a5732 | ||
|
|
e09dd8509f | ||
|
|
29c72fb30b | ||
|
|
46f68501d5 | ||
|
|
9db048c9d9 | ||
|
|
cb80c415d8 | ||
|
|
6fd6dff14d | ||
|
|
f9e4c58854 | ||
|
|
1a0d7ddbfa | ||
|
|
7022d0b1c5 | ||
|
|
80e1eedbef | ||
|
|
4bae9d0344 | ||
|
|
29a1b13e7a | ||
|
|
9e07a57566 | ||
|
|
a35fc1d5ea | ||
|
|
cfecf3e110 | ||
|
|
25b974af67 | ||
|
|
d389f04135 | ||
|
|
9075706714 | ||
|
|
1f6d1fbd67 | ||
|
|
d93c33aa74 | ||
|
|
8ecd1b48ef | ||
|
|
3af8de5a58 | ||
|
|
315f90f982 | ||
|
|
fe77c44e3f | ||
|
|
3726311276 | ||
|
|
a523c348f3 | ||
|
|
5a6b2aa261 | ||
|
|
992d3a207d | ||
|
|
7829bfe4e6 | ||
|
|
4fa1694b05 | ||
|
|
c5be804725 | ||
|
|
655f38b68f | ||
|
|
4edf3bf7e6 | ||
|
|
1fa53c5bf8 | ||
|
|
2a5e5a020b | ||
|
|
8e096b19c1 | ||
|
|
e54c2df63b | ||
|
|
c152e41cfb | ||
|
|
aa89800ff9 | ||
|
|
ea0b04d648 | ||
|
|
6faded3cc7 | ||
|
|
d48508b4af | ||
|
|
f4c8d96dd9 | ||
|
|
3a76035570 | ||
|
|
c32461b032 | ||
|
|
6f04807236 | ||
|
|
19e4038ce5 | ||
|
|
7d6366ebfc | ||
|
|
114365a480 | ||
|
|
db87d45edb | ||
|
|
fac7648326 | ||
|
|
98b11f814f | ||
|
|
e20280cb43 | ||
|
|
4d6ea33aa4 | ||
|
|
55ecf068b3 | ||
|
|
d3ebb84ce4 | ||
|
|
7daeee141d | ||
|
|
a41ddafa88 | ||
|
|
30502ff0a0 | ||
|
|
5954e46340 | ||
|
|
54e0601fec | ||
|
|
382706cd20 | ||
|
|
96dc563d91 | ||
|
|
12e0ae98b6 | ||
|
|
2a59c0513c | ||
|
|
e3d1a8a772 | ||
|
|
4b6ead68a1 | ||
|
|
9a1c2d0ea7 | ||
|
|
db39772aa7 | ||
|
|
9ffaeef24d | ||
|
|
3a0fbe7433 | ||
|
|
da4942b41c | ||
|
|
182867a071 | ||
|
|
241a7fdc4f | ||
|
|
fa079d2346 |
88
.github/workflows/build.yml
vendored
Normal file
88
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
name: Build
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
types: [opened, synchronize, reopened]
|
||||
release:
|
||||
types: [created]
|
||||
jobs:
|
||||
test:
|
||||
name: ${{ matrix.os }} ${{ matrix.gcrypt }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
CMAKE_C_COMPILER: ${{ matrix.compiler }}
|
||||
CMAKE_C_FLAGS: -Werror
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
os: ["ubuntu-latest", "ubuntu-18.04"]
|
||||
ndpid_gcrypt: ["-DNDPI_WITH_GCRYPT=OFF", "-DNDPI_WITH_GCRYPT=ON"]
|
||||
ndpid_zlib: ["-DENABLE_ZLIB=OFF", "-DENABLE_ZLIB=ON"]
|
||||
ndpi_min_version: ["4.4"]
|
||||
include:
|
||||
- compiler: "default-cc"
|
||||
os: "ubuntu-latest"
|
||||
- compiler: "clang-12"
|
||||
os: "ubuntu-latest"
|
||||
- compiler: "gcc-10"
|
||||
os: "ubuntu-latest"
|
||||
- compiler: "gcc-7"
|
||||
os: "ubuntu-latest"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
|
||||
- name: Install Ubuntu Prerequisites
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install autoconf automake cmake libtool pkg-config gettext libjson-c-dev flex bison libpcap-dev zlib1g-dev
|
||||
sudo apt-get install ${{ matrix.compiler }} lcov
|
||||
- name: Install Ubuntu Prerequisites (libgcrypt)
|
||||
if: startsWith(matrix.os, 'ubuntu') && startsWith(matrix.ndpid_gcrypt, '-DNDPI_WITH_GCRYPT=ON')
|
||||
run: |
|
||||
sudo apt-get install libgcrypt20-dev
|
||||
- name: Install Ubuntu Prerequisities (zlib)
|
||||
if: startsWith(matrix.os, 'ubuntu') && startsWith(matrix.ndpid_zlib, '-DENABLE_ZLIB=ON')
|
||||
run: |
|
||||
sudo apt-get install zlib1g-dev
|
||||
- name: Configure nDPId
|
||||
run: |
|
||||
mkdir build && cd build
|
||||
cmake .. -DENABLE_COVERAGE=ON -DBUILD_EXAMPLES=ON -DBUILD_NDPI=ON -DENABLE_SANITIZER=ON ${{ matrix.ndpid_zlib }} ${{ matrix.ndpid_gcrypt }}
|
||||
- name: Build nDPId
|
||||
run: |
|
||||
make -C build all VERBOSE=1
|
||||
- name: Test EXEC
|
||||
run: |
|
||||
./build/nDPId-test || test $? -eq 1
|
||||
./build/nDPId -h || test $? -eq 1
|
||||
- name: Test DIFF
|
||||
if: startsWith(matrix.os, 'ubuntu') && startsWith(matrix.ndpid_gcrypt, '-DNDPI_WITH_GCRYPT=OFF')
|
||||
run: |
|
||||
./test/run_tests.sh ./libnDPI ./build/nDPId-test
|
||||
- name: Daemon
|
||||
run: |
|
||||
make -C ./build daemon VERBOSE=1
|
||||
make -C ./build daemon VERBOSE=1
|
||||
- name: Coverage
|
||||
run: |
|
||||
make -C ./build coverage
|
||||
- name: Dist
|
||||
run: |
|
||||
make -C ./build dist
|
||||
- name: CPack DEB
|
||||
run: |
|
||||
cd ./build && cpack -G DEB && cd ..
|
||||
- name: Build against libnDPI-${{ matrix.ndpi_min_version }}
|
||||
run: |
|
||||
mkdir build-local-ndpi && cd build-local-ndpi
|
||||
wget 'https://github.com/ntop/nDPI/archive/refs/tags/${{ matrix.ndpi_min_version }}.tar.gz'
|
||||
tar -xzvf ${{ matrix.ndpi_min_version }}.tar.gz && cd nDPI-${{ matrix.ndpi_min_version }} && ./autogen.sh --prefix=/usr --with-only-libndpi CC=${{ matrix.compiler }} CXX=false CFLAGS='-Werror' && sudo make install && cd ..
|
||||
cmake .. -DENABLE_COVERAGE=ON -DBUILD_EXAMPLES=ON -DBUILD_NDPI=OFF -DENABLE_SANITIZER=ON ${{ matrix.ndpi_min_version }}
|
||||
make all VERBOSE=1
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -4,3 +4,9 @@ __pycache__
|
||||
|
||||
# go related
|
||||
*.sum
|
||||
|
||||
# lockfiles generated by some shell scripts
|
||||
*.lock
|
||||
|
||||
# building folder
|
||||
build
|
||||
|
||||
56
.gitlab-ci.yml
Normal file
56
.gitlab-ci.yml
Normal file
@@ -0,0 +1,56 @@
|
||||
image: debian:stable
|
||||
|
||||
stages:
|
||||
- build_and_test
|
||||
|
||||
before_script:
|
||||
- export DEBIAN_FRONTEND=noninteractive
|
||||
- apt-get update -qq
|
||||
- >
|
||||
apt-get install -y -qq \
|
||||
coreutils sudo \
|
||||
build-essential make cmake binutils gcc autoconf automake \
|
||||
libtool pkg-config git \
|
||||
libpcap-dev libgpg-error-dev libjson-c-dev zlib1g-dev \
|
||||
netcat-openbsd python3 python3-jsonschema tree lcov
|
||||
|
||||
after_script:
|
||||
- cat /tmp/nDPIsrvd.log
|
||||
- cat /tmp/nDPId.log
|
||||
|
||||
build_and_test:
|
||||
script:
|
||||
# static linked build
|
||||
- mkdir build-cmake-submodule
|
||||
- cd build-cmake-submodule
|
||||
- env CMAKE_C_FLAGS='-Werror' cmake .. -DENABLE_COVERAGE=ON -DBUILD_EXAMPLES=ON -DBUILD_NDPI=ON -DENABLE_SANITIZER=ON -DENABLE_ZLIB=ON
|
||||
- make libnDPI
|
||||
- tree libnDPI
|
||||
- make install VERBOSE=1 DESTDIR="$(realpath ../_install)"
|
||||
- cpack -G DEB
|
||||
- cd ..
|
||||
- ./test/run_tests.sh ./libnDPI ./build-cmake-submodule/nDPId-test
|
||||
# generate coverage report
|
||||
- make -C ./build-cmake-submodule coverage
|
||||
- >
|
||||
if ldd build/nDPId | grep -qoEi libndpi; then \
|
||||
echo 'nDPId linked against a static libnDPI should not contain a shared linked libnDPI.' >&2; false; fi
|
||||
# pkg-config dynamic linked build
|
||||
- mkdir build
|
||||
- cd build
|
||||
- export PKG_CONFIG_PATH="$(realpath ../build-cmake-submodule/libnDPI/lib/pkgconfig)"
|
||||
- env CMAKE_C_FLAGS='-Werror' cmake .. -DBUILD_EXAMPLES=ON -DENABLE_SANITIZER=ON -DENABLE_MEMORY_PROFILING=ON -DENABLE_ZLIB=ON
|
||||
- make all VERBOSE=1
|
||||
- cd ..
|
||||
- ./build/nDPId-test || test $? -eq 1
|
||||
- ./build/nDPId -h || test $? -eq 1
|
||||
# dameon start/stop test
|
||||
- NUSER=nobody make -C ./build daemon VERBOSE=1
|
||||
- NUSER=nobody make -C ./build daemon VERBOSE=1
|
||||
# make dist
|
||||
- make -C ./build dist
|
||||
artifacts:
|
||||
expire_in: 1 week
|
||||
paths:
|
||||
- _install/
|
||||
stage: build_and_test
|
||||
2
.gitmodules
vendored
2
.gitmodules
vendored
@@ -1,3 +1,5 @@
|
||||
[submodule "libnDPI"]
|
||||
path = libnDPI
|
||||
url = https://github.com/ntop/nDPI
|
||||
branch = dev
|
||||
update = rebase
|
||||
|
||||
13
.travis.yml
13
.travis.yml
@@ -1,13 +0,0 @@
|
||||
language: c
|
||||
before_install:
|
||||
- sudo apt-get -qq update
|
||||
- sudo apt-get install -y build-essential make binutils gcc autoconf automake libtool pkg-config git libpcap-dev libgcrypt-dev libgpg-error-dev libjson-c-dev netcat-openbsd python3 python3-jsonschema
|
||||
script:
|
||||
- git submodule update --init
|
||||
# static linked build
|
||||
- mkdir build-cmake-submodule && cd build-cmake-submodule && cmake .. -DBUILD_EXAMPLES=ON -DBUILD_NDPI=ON -DENABLE_SANITIZER=ON && make && cd ..
|
||||
# pkg-config dynamic linked build
|
||||
- PKG_CONFIG_PATH="$(realpath ./build-cmake-submodule/libnDPI/lib/pkgconfig)" cmake . -DBUILD_EXAMPLES=ON -DENABLE_SANITIZER=ON -DENABLE_MEMORY_PROFILING=ON && make
|
||||
- ./nDPId-test || test $? -eq 1
|
||||
- ./nDPId -h || test $? -eq 1
|
||||
- ./test/run_tests.sh
|
||||
359
CMakeLists.txt
359
CMakeLists.txt
@@ -1,71 +1,218 @@
|
||||
cmake_minimum_required(VERSION 3.12.4)
|
||||
project(nDPId C)
|
||||
if("${PROJECT_SOURCE_DIR}" STREQUAL "${PROJECT_BINARY_DIR}")
|
||||
message(FATAL_ERROR "In-source builds are not allowed.\n"
|
||||
"Please remove ${PROJECT_SOURCE_DIR}/CMakeCache.txt\n"
|
||||
"and\n"
|
||||
"${PROJECT_SOURCE_DIR}/CMakeFiles\n"
|
||||
"Create a build directory somewhere and run CMake again.")
|
||||
endif()
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
|
||||
set(CPACK_PACKAGE_CONTACT "toni@impl.cc")
|
||||
set(CPACK_DEBIAN_PACKAGE_NAME "nDPId")
|
||||
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
|
||||
set(CPACK_PACKAGE_VERSION_MAJOR 1)
|
||||
set(CPACK_PACKAGE_VERSION_MINOR 5)
|
||||
set(CPACK_PACKAGE_VERSION_PATCH 0)
|
||||
|
||||
include(CPack)
|
||||
include(CheckFunctionExists)
|
||||
|
||||
if(NOT MATH_FUNCTION_EXISTS AND NOT NEED_LINKING_AGAINST_LIBM)
|
||||
CHECK_FUNCTION_EXISTS(log2f MATH_FUNCTION_EXISTS)
|
||||
if(NOT MATH_FUNCTION_EXISTS)
|
||||
unset(MATH_FUNCTION_EXISTS CACHE)
|
||||
list(APPEND CMAKE_REQUIRED_LIBRARIES m)
|
||||
CHECK_FUNCTION_EXISTS(log2f MATH_FUNCTION_EXISTS)
|
||||
if(MATH_FUNCTION_EXISTS)
|
||||
set(NEED_LINKING_AGAINST_LIBM TRUE CACHE BOOL "" FORCE)
|
||||
else()
|
||||
message(FATAL_ERROR "Failed making the log2f() function available")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NEED_LINKING_AGAINST_LIBM)
|
||||
set(LIBM_LIB "-lm")
|
||||
else()
|
||||
set(LIBM_LIB "")
|
||||
endif()
|
||||
|
||||
option(ENABLE_COVERAGE "Generate a code coverage report using lcov/genhtml." OFF)
|
||||
option(ENABLE_SANITIZER "Enable ASAN/LSAN/UBSAN." OFF)
|
||||
option(ENABLE_SANITIZER_THREAD "Enable TSAN (does not work together with ASAN)." OFF)
|
||||
option(ENABLE_MEMORY_PROFILING "Enable dynamic memory tracking." OFF)
|
||||
option(ENABLE_ZLIB "Enable zlib support for nDPId (experimental)." OFF)
|
||||
option(BUILD_EXAMPLES "Build C examples." ON)
|
||||
option(BUILD_NDPI "Clone and build nDPI from github." OFF)
|
||||
option(NDPI_NO_PKGCONFIG "Do not use pkgconfig to search for libnDPI." OFF)
|
||||
if(BUILD_NDPI)
|
||||
unset(NDPI_NO_PKGCONFIG CACHE)
|
||||
unset(STATIC_LIBNDPI_INSTALLDIR CACHE)
|
||||
else()
|
||||
option(NDPI_NO_PKGCONFIG "Do not use pkgconfig to search for libnDPI." OFF)
|
||||
if(NDPI_NO_PKGCONFIG)
|
||||
set(STATIC_LIBNDPI_INSTALLDIR "/opt/libnDPI/usr" CACHE STRING "Path to a installation directory of libnDPI e.g. /opt/libnDPI/usr")
|
||||
if(STATIC_LIBNDPI_INSTALLDIR STREQUAL "")
|
||||
message(FATAL_ERROR "STATIC_LIBNDPI_INSTALLDIR can not be an empty string within your configuration!")
|
||||
endif()
|
||||
else()
|
||||
unset(STATIC_LIBNDPI_INSTALLDIR CACHE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(STATIC_LIBNDPI_INSTALLDIR "" CACHE STRING "Path to a installation directory of libnDPI e.g. /opt/libnDPI/usr")
|
||||
if(STATIC_LIBNDPI_INSTALLDIR OR BUILD_NDPI OR NDPI_NO_PKGCONFIG)
|
||||
option(NDPI_WITH_GCRYPT "Link static libndpi library against libgcrypt." OFF)
|
||||
option(NDPI_WITH_PCRE "Link static libndpi library against libpcre." OFF)
|
||||
option(NDPI_WITH_MAXMINDDB "Link static libndpi library against libmaxminddb." OFF)
|
||||
else()
|
||||
unset(NDPI_WITH_GCRYPT CACHE)
|
||||
unset(NDPI_WITH_PCRE CACHE)
|
||||
unset(NDPI_WITH_MAXMINDDB CACHE)
|
||||
endif()
|
||||
|
||||
set(CROSS_COMPILE_TRIPLET "" CACHE STRING "Host triplet used to enable cross compiling.")
|
||||
|
||||
add_executable(nDPId nDPId.c utils.c)
|
||||
add_executable(nDPIsrvd nDPIsrvd.c utils.c)
|
||||
add_executable(nDPId-test nDPId-test.c utils.c)
|
||||
add_executable(nDPId-test nDPId-test.c)
|
||||
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -DJSMN_STATIC=1 -DJSMN_STRICT=1")
|
||||
set(BUILD_NDPI_CONFIGURE_OPTS "")
|
||||
add_custom_target(dist)
|
||||
add_custom_command(
|
||||
TARGET dist
|
||||
COMMAND "${CMAKE_SOURCE_DIR}/scripts/make-dist.sh"
|
||||
)
|
||||
|
||||
if(ENABLE_MEMORY_PROFILING)
|
||||
set(MEMORY_PROFILING_CFLAGS "-DENABLE_MEMORY_PROFILING=1"
|
||||
"-Duthash_malloc=nDPIsrvd_uthash_malloc"
|
||||
"-Duthash_free=nDPIsrvd_uthash_free")
|
||||
else()
|
||||
set(MEMORY_PROFILING_CFLAGS "")
|
||||
add_custom_target(daemon)
|
||||
add_custom_command(
|
||||
TARGET daemon
|
||||
COMMAND "${CMAKE_SOURCE_DIR}/scripts/daemon.sh" "$<TARGET_FILE:nDPId>" "$<TARGET_FILE:nDPIsrvd>"
|
||||
DEPENDS nDPId nDPIsrvd
|
||||
)
|
||||
|
||||
if(NOT CROSS_COMPILE_TRIPLET STREQUAL "")
|
||||
set(CMAKE_C_COMPILER_TARGET ${CROSS_COMPILE_TRIPLET})
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
|
||||
endif()
|
||||
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
if(BUILD_NDPI)
|
||||
enable_testing()
|
||||
add_test(NAME run_tests
|
||||
COMMAND "${CMAKE_SOURCE_DIR}/test/run_tests.sh"
|
||||
"${CMAKE_SOURCE_DIR}/libnDPI"
|
||||
"$<TARGET_FILE:nDPId-test>")
|
||||
if(NDPI_WITH_PCRE OR NDPI_WITH_MAXMINDDB)
|
||||
message(WARNING "NDPI_WITH_PCRE or NDPI_WITH_MAXMINDDB enabled.\n"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/test/run_tests.sh or ctest will fail!")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(ENABLE_COVERAGE)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fprofile-arcs -ftest-coverage")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage")
|
||||
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} --coverage")
|
||||
add_custom_target(coverage)
|
||||
add_custom_command(
|
||||
TARGET coverage
|
||||
COMMAND "${CMAKE_SOURCE_DIR}/scripts/code-coverage.sh"
|
||||
DEPENDS nDPId nDPIsrvd nDPId-test
|
||||
)
|
||||
endif()
|
||||
if(ENABLE_SANITIZER)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fsanitize=undefined -fno-sanitize=alignment -fsanitize=enum -fsanitize=leak")
|
||||
endif()
|
||||
if(ENABLE_SANITIZER_THREAD)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=undefined -fno-sanitize=alignment -fsanitize=enum -fsanitize=thread")
|
||||
endif()
|
||||
if(ENABLE_ZLIB)
|
||||
set(ZLIB_DEFS "-DENABLE_ZLIB=1")
|
||||
pkg_check_modules(ZLIB REQUIRED zlib)
|
||||
endif()
|
||||
if(NDPI_WITH_GCRYPT)
|
||||
message(STATUS "Enable GCRYPT")
|
||||
set(NDPI_ADDITIONAL_ARGS "${NDPI_ADDITIONAL_ARGS} --with-local-libgcrypt")
|
||||
endif()
|
||||
if(NDPI_WITH_PCRE)
|
||||
message(STATUS "Enable PCRE")
|
||||
set(NDPI_ADDITIONAL_ARGS "${NDPI_ADDITIONAL_ARGS} --with-pcre")
|
||||
endif()
|
||||
if(NDPI_WITH_MAXMINDDB)
|
||||
message(STATUS "Enable MAXMINDDB")
|
||||
set(NDPI_ADDITIONAL_ARGS "${NDPI_ADDITIONAL_ARGS} --with-maxminddb")
|
||||
endif()
|
||||
|
||||
execute_process(
|
||||
COMMAND git describe --tags
|
||||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
||||
OUTPUT_VARIABLE GIT_VERSION ERROR_QUIET)
|
||||
string(STRIP "${GIT_VERSION}" GIT_VERSION)
|
||||
if(GIT_VERSION STREQUAL "" OR NOT IS_DIRECTORY "${CMAKE_SOURCE_DIR}/.git")
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "")
|
||||
set(GIT_VERSION "${CPACK_PACKAGE_VERSION}-pre")
|
||||
else()
|
||||
set(GIT_VERSION "${CPACK_PACKAGE_VERSION}-release")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra")
|
||||
set(NDPID_DEFS -DJSMN_STATIC=1 -DJSMN_STRICT=1 -DJSMN_PARENT_LINKS=1)
|
||||
set(NDPID_DEPS_INC "${CMAKE_SOURCE_DIR}"
|
||||
"${CMAKE_SOURCE_DIR}/dependencies"
|
||||
"${CMAKE_SOURCE_DIR}/dependencies/jsmn"
|
||||
"${CMAKE_SOURCE_DIR}/dependencies/uthash/src")
|
||||
if(ENABLE_MEMORY_PROFILING)
|
||||
message(WARNING "ENABLE_MEMORY_PROFILING should not be used in production environments.")
|
||||
add_definitions("-DENABLE_MEMORY_PROFILING=1"
|
||||
"-Duthash_malloc=nDPIsrvd_uthash_malloc"
|
||||
"-Duthash_free=nDPIsrvd_uthash_free")
|
||||
else()
|
||||
set(NDPID_TEST_MPROF_DEFS "-DENABLE_MEMORY_PROFILING=1")
|
||||
endif()
|
||||
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0 -g3 -fno-omit-frame-pointer -fno-inline")
|
||||
endif()
|
||||
|
||||
if(ENABLE_SANITIZER AND ENABLE_SANITIZER_THREAD)
|
||||
message(STATUS_FATAL "ENABLE_SANITIZER and ENABLE_SANITIZER_THREAD can not be used together!")
|
||||
message(FATAL_ERROR "ENABLE_SANITIZER and ENABLE_SANITIZER_THREAD can not be used together!")
|
||||
endif()
|
||||
|
||||
if(ENABLE_SANITIZER)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fsanitize=undefined -fsanitize=enum -fsanitize=leak")
|
||||
set(BUILD_NDPI_CONFIGURE_OPTS "${BUILD_NDPI_CONFIGURE_OPTS} --with-sanitizer")
|
||||
endif()
|
||||
|
||||
if(ENABLE_SANITIZER_THREAD)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=undefined -fsanitize=enum -fsanitize=thread")
|
||||
endif()
|
||||
|
||||
if(STATIC_LIBNDPI_INSTALLDIR STREQUAL "" AND BUILD_NDPI)
|
||||
if(BUILD_NDPI)
|
||||
include(ExternalProject)
|
||||
ExternalProject_Add(
|
||||
libnDPI
|
||||
SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libnDPI
|
||||
CONFIGURE_COMMAND git submodule update --init &&
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/libnDPI/autogen.sh
|
||||
--prefix=${CMAKE_CURRENT_BINARY_DIR}/libnDPI
|
||||
${BUILD_NDPI_CONFIGURE_OPTS}
|
||||
BUILD_COMMAND make
|
||||
DOWNLOAD_COMMAND ""
|
||||
CONFIGURE_COMMAND env
|
||||
CC=${CMAKE_C_COMPILER}
|
||||
CXX=false
|
||||
PKG_CONFIG=${PKG_CONFIG_EXECUTABLE}
|
||||
CFLAGS=${CMAKE_C_FLAGS}
|
||||
LDFLAGS=${CMAKE_MODULE_LINKER_FLAGS}
|
||||
CROSS_COMPILE_TRIPLET=${CROSS_COMPILE_TRIPLET}
|
||||
ADDITIONAL_ARGS=${NDPI_ADDITIONAL_ARGS}
|
||||
MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}
|
||||
DEST_INSTALL=${CMAKE_BINARY_DIR}/libnDPI
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/scripts/get-and-build-libndpi.sh
|
||||
BUILD_BYPRODUCTS ${CMAKE_BINARY_DIR}/libnDPI/lib/libndpi.a
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
BUILD_IN_SOURCE 1)
|
||||
|
||||
add_custom_target(clean-libnDPI
|
||||
COMMAND rm -rf ${CMAKE_BINARY_DIR}/libnDPI ${CMAKE_BINARY_DIR}/libnDPI-prefix
|
||||
)
|
||||
|
||||
set(STATIC_LIBNDPI_INSTALLDIR "${CMAKE_BINARY_DIR}/libnDPI")
|
||||
add_dependencies(nDPId libnDPI)
|
||||
add_dependencies(nDPId-test libnDPI)
|
||||
endif()
|
||||
|
||||
if(NOT STATIC_LIBNDPI_INSTALLDIR STREQUAL "" OR BUILD_NDPI OR NDPI_NO_PKGCONFIG)
|
||||
option(NDPI_WITH_GCRYPT "Link static libndpi library against libgcrypt." ON)
|
||||
option(NDPI_WITH_PCRE "Link static libndpi library against libpcre." OFF)
|
||||
option(NDPI_WITH_MAXMINDDB "Link static libndpi library against libmaxminddb." OFF)
|
||||
|
||||
if(STATIC_LIBNDPI_INSTALLDIR OR BUILD_NDPI OR NDPI_NO_PKGCONFIG)
|
||||
if(NDPI_WITH_GCRYPT)
|
||||
find_package(GCRYPT "1.4.2" REQUIRED)
|
||||
endif()
|
||||
@@ -79,102 +226,133 @@ if(NOT STATIC_LIBNDPI_INSTALLDIR STREQUAL "" OR BUILD_NDPI OR NDPI_NO_PKGCONFIG)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT STATIC_LIBNDPI_INSTALLDIR STREQUAL "" OR BUILD_NDPI)
|
||||
if(STATIC_LIBNDPI_INSTALLDIR OR BUILD_NDPI)
|
||||
add_definitions("-DLIBNDPI_STATIC=1")
|
||||
set(STATIC_LIBNDPI_INC "${STATIC_LIBNDPI_INSTALLDIR}/include/ndpi")
|
||||
set(STATIC_LIBNDPI_LIB "${STATIC_LIBNDPI_INSTALLDIR}/lib/libndpi.a")
|
||||
|
||||
if(STATIC_LIBNDPI_INSTALLDIR AND NOT BUILD_NDPI)
|
||||
if(NOT EXISTS "${STATIC_LIBNDPI_INC}" OR NOT EXISTS "${STATIC_LIBNDPI_LIB}")
|
||||
message(FATAL_ERROR "Include directory \"${STATIC_LIBNDPI_INC}\" or\n"
|
||||
"static library \"${STATIC_LIBNDPI_LIB}\" does not exist!")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
unset(DEFAULT_NDPI_INCLUDE CACHE)
|
||||
else()
|
||||
if(NOT NDPI_NO_PKGCONFIG)
|
||||
pkg_check_modules(NDPI REQUIRED libndpi>=3.5.0)
|
||||
pkg_check_modules(NDPI REQUIRED libndpi>=4.3.0)
|
||||
|
||||
set(STATIC_LIBNDPI_INC "")
|
||||
set(STATIC_LIBNDPI_LIB "")
|
||||
else()
|
||||
set(LIBNDPI_INC "" CACHE STRING "/usr/include/ndpi")
|
||||
set(LIBNDPI_LIB "" CACHE STRING "/usr/lib/libndpi.a")
|
||||
|
||||
set(STATIC_LIBNDPI_INC "${LIBNDPI_INC}")
|
||||
set(STATIC_LIBNDPI_LIB "${LIBNDPI_LIB}")
|
||||
unset(STATIC_LIBNDPI_INC CACHE)
|
||||
unset(STATIC_LIBNDPI_LIB CACHE)
|
||||
endif()
|
||||
|
||||
set(DEFAULT_NDPI_INCLUDE ${NDPI_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
find_package(PCAP "1.8.1" REQUIRED)
|
||||
target_compile_options(nDPId PRIVATE ${MEMORY_PROFILING_CFLAGS} "-pthread")
|
||||
target_include_directories(nDPId PRIVATE "${STATIC_LIBNDPI_INC}" "${NDPI_INCLUDEDIR}" "${NDPI_INCLUDEDIR}/ndpi")
|
||||
|
||||
target_compile_options(nDPId PRIVATE "-pthread")
|
||||
target_compile_definitions(nDPId PRIVATE -D_GNU_SOURCE=1 -DGIT_VERSION=\"${GIT_VERSION}\" ${NDPID_DEFS} ${ZLIB_DEFS})
|
||||
target_include_directories(nDPId PRIVATE "${STATIC_LIBNDPI_INC}" "${DEFAULT_NDPI_INCLUDE}" ${NDPID_DEPS_INC})
|
||||
target_link_libraries(nDPId "${STATIC_LIBNDPI_LIB}" "${pkgcfg_lib_NDPI_ndpi}"
|
||||
"${pkgcfg_lib_PCRE_pcre}" "${pkgcfg_lib_MAXMINDDB_maxminddb}"
|
||||
"${GCRYPT_LIBRARY}" "${PCAP_LIBRARY}"
|
||||
"${pkgcfg_lib_PCRE_pcre}" "${pkgcfg_lib_MAXMINDDB_maxminddb}" "${pkgcfg_lib_ZLIB_z}"
|
||||
"${GCRYPT_LIBRARY}" "${GCRYPT_ERROR_LIBRARY}" "${PCAP_LIBRARY}" "${LIBM_LIB}"
|
||||
"-pthread")
|
||||
|
||||
target_compile_options(nDPId PRIVATE ${MEMORY_PROFILING_CFLAGS})
|
||||
target_include_directories(nDPIsrvd PRIVATE
|
||||
"${CMAKE_SOURCE_DIR}"
|
||||
"${CMAKE_SOURCE_DIR}/dependencies"
|
||||
"${CMAKE_SOURCE_DIR}/dependencies/jsmn"
|
||||
"${CMAKE_SOURCE_DIR}/dependencies/uthash/src")
|
||||
target_compile_definitions(nDPIsrvd PRIVATE -D_GNU_SOURCE=1 -DGIT_VERSION=\"${GIT_VERSION}\" ${NDPID_DEFS})
|
||||
target_include_directories(nDPIsrvd PRIVATE ${NDPID_DEPS_INC})
|
||||
|
||||
target_include_directories(nDPId-test PRIVATE ${NDPID_DEPS_INC})
|
||||
target_compile_options(nDPId-test PRIVATE "-Wno-unused-function" "-pthread")
|
||||
target_compile_definitions(nDPId-test PRIVATE -D_GNU_SOURCE=1 -DNO_MAIN=1 -DGIT_VERSION=\"${GIT_VERSION}\"
|
||||
${NDPID_DEFS} ${ZLIB_DEFS} ${NDPID_TEST_MPROF_DEFS})
|
||||
target_include_directories(nDPId-test PRIVATE
|
||||
"${CMAKE_SOURCE_DIR}"
|
||||
"${CMAKE_SOURCE_DIR}/dependencies"
|
||||
"${CMAKE_SOURCE_DIR}/dependencies/jsmn"
|
||||
"${CMAKE_SOURCE_DIR}/dependencies/uthash/src")
|
||||
target_compile_options(nDPId-test PRIVATE ${MEMORY_PROFILING_CFLAGS} "-Wno-unused-function" "-pthread")
|
||||
target_include_directories(nDPId-test PRIVATE "${STATIC_LIBNDPI_INC}" "${NDPI_INCLUDEDIR}" "${NDPI_INCLUDEDIR}/ndpi")
|
||||
target_compile_definitions(nDPId-test PRIVATE "-D_GNU_SOURCE=1" "-DNO_MAIN=1" "-Dsyslog=mock_syslog_stderr")
|
||||
"${STATIC_LIBNDPI_INC}" "${DEFAULT_NDPI_INCLUDE}" ${NDPID_DEPS_INC})
|
||||
target_link_libraries(nDPId-test "${STATIC_LIBNDPI_LIB}" "${pkgcfg_lib_NDPI_ndpi}"
|
||||
"${pkgcfg_lib_PCRE_pcre}" "${pkgcfg_lib_MAXMINDDB_maxminddb}"
|
||||
"${GCRYPT_LIBRARY}" "${PCAP_LIBRARY}"
|
||||
"${pkgcfg_lib_PCRE_pcre}" "${pkgcfg_lib_MAXMINDDB_maxminddb}" "${pkgcfg_lib_ZLIB_z}"
|
||||
"${GCRYPT_LIBRARY}" "${GCRYPT_ERROR_LIBRARY}" "${PCAP_LIBRARY}" "${LIBM_LIB}"
|
||||
"-pthread")
|
||||
|
||||
if(BUILD_EXAMPLES)
|
||||
add_executable(nDPIsrvd-collectd examples/c-collectd/c-collectd.c)
|
||||
target_compile_options(nDPIsrvd-collectd PRIVATE ${MEMORY_PROFILING_CFLAGS})
|
||||
target_include_directories(nDPIsrvd-collectd PRIVATE
|
||||
"${CMAKE_SOURCE_DIR}"
|
||||
"${CMAKE_SOURCE_DIR}/dependencies"
|
||||
"${CMAKE_SOURCE_DIR}/dependencies/jsmn"
|
||||
"${CMAKE_SOURCE_DIR}/dependencies/uthash/src")
|
||||
target_compile_definitions(nDPIsrvd-collectd PRIVATE ${NDPID_DEFS})
|
||||
target_include_directories(nDPIsrvd-collectd PRIVATE ${NDPID_DEPS_INC})
|
||||
|
||||
add_executable(nDPIsrvd-captured examples/c-captured/c-captured.c utils.c)
|
||||
target_compile_options(nDPIsrvd-captured PRIVATE ${MEMORY_PROFILING_CFLAGS})
|
||||
if(BUILD_NDPI)
|
||||
add_dependencies(nDPIsrvd-captured libnDPI)
|
||||
endif()
|
||||
target_compile_definitions(nDPIsrvd-captured PRIVATE ${NDPID_DEFS})
|
||||
target_include_directories(nDPIsrvd-captured PRIVATE
|
||||
"${CMAKE_SOURCE_DIR}"
|
||||
"${CMAKE_SOURCE_DIR}/dependencies"
|
||||
"${CMAKE_SOURCE_DIR}/dependencies/jsmn"
|
||||
"${CMAKE_SOURCE_DIR}/dependencies/uthash/src")
|
||||
target_link_libraries(nDPIsrvd-captured "${PCAP_LIBRARY}")
|
||||
"${STATIC_LIBNDPI_INC}" "${DEFAULT_NDPI_INCLUDE}" "${CMAKE_SOURCE_DIR}" ${NDPID_DEPS_INC})
|
||||
target_link_libraries(nDPIsrvd-captured "${pkgcfg_lib_NDPI_ndpi}"
|
||||
"${pkgcfg_lib_PCRE_pcre}" "${pkgcfg_lib_MAXMINDDB_maxminddb}"
|
||||
"${GCRYPT_LIBRARY}" "${GCRYPT_ERROR_LIBRARY}" "${PCAP_LIBRARY}")
|
||||
|
||||
add_executable(nDPIsrvd-json-dump examples/c-json-stdout/c-json-stdout.c)
|
||||
target_include_directories(nDPIsrvd-json-dump PRIVATE
|
||||
"${CMAKE_SOURCE_DIR}"
|
||||
"${CMAKE_SOURCE_DIR}/dependencies/jsmn")
|
||||
target_compile_definitions(nDPIsrvd-json-dump PRIVATE ${NDPID_DEFS})
|
||||
target_include_directories(nDPIsrvd-json-dump PRIVATE ${NDPID_DEPS_INC})
|
||||
|
||||
install(TARGETS nDPIsrvd-collectd nDPIsrvd-captured nDPIsrvd-json-dump DESTINATION bin)
|
||||
add_executable(nDPIsrvd-simple examples/c-simple/c-simple.c)
|
||||
target_compile_definitions(nDPIsrvd-simple PRIVATE ${NDPID_DEFS})
|
||||
target_include_directories(nDPIsrvd-simple PRIVATE ${NDPID_DEPS_INC})
|
||||
target_link_libraries(nDPIsrvd-simple "${pkgcfg_lib_NDPI_ndpi}"
|
||||
"${pkgcfg_lib_PCRE_pcre}" "${pkgcfg_lib_MAXMINDDB_maxminddb}"
|
||||
"${GCRYPT_LIBRARY}" "${GCRYPT_ERROR_LIBRARY}" "${PCAP_LIBRARY}")
|
||||
|
||||
if(ENABLE_COVERAGE)
|
||||
add_dependencies(coverage nDPIsrvd-collectd nDPIsrvd-captured nDPIsrvd-json-dump nDPIsrvd-simple)
|
||||
endif()
|
||||
|
||||
install(TARGETS nDPIsrvd-collectd nDPIsrvd-captured nDPIsrvd-json-dump nDPIsrvd-simple DESTINATION bin)
|
||||
endif()
|
||||
|
||||
install(TARGETS nDPId DESTINATION sbin)
|
||||
install(TARGETS nDPIsrvd nDPId-test DESTINATION bin)
|
||||
install(FILES dependencies/nDPIsrvd.py DESTINATION share/nDPId)
|
||||
install(FILES examples/py-flow-info/flow-info.py DESTINATION bin RENAME nDPIsrvd-flow-info.py)
|
||||
install(FILES dependencies/nDPIsrvd.py examples/py-flow-dashboard/plotly_dash.py
|
||||
DESTINATION share/nDPId)
|
||||
install(FILES examples/py-flow-info/flow-info.py
|
||||
DESTINATION bin RENAME nDPIsrvd-flow-info.py
|
||||
PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
|
||||
install(FILES examples/py-flow-dashboard/flow-dash.py
|
||||
DESTINATION bin RENAME nDPIsrvd-flow-dash.py
|
||||
PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
|
||||
install(FILES examples/py-ja3-checker/py-ja3-checker.py
|
||||
DESTINATION bin RENAME nDPIsrvd-ja3-checker.py
|
||||
PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
|
||||
install(FILES examples/py-json-stdout/json-stdout.py
|
||||
DESTINATION bin RENAME nDPIsrvd-json-stdout.py
|
||||
PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
|
||||
install(FILES examples/py-schema-validation/py-schema-validation.py
|
||||
DESTINATION bin RENAME nDPIsrvd-schema-validation.py
|
||||
PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
|
||||
install(FILES examples/py-semantic-validation/py-semantic-validation.py
|
||||
DESTINATION bin RENAME nDPIsrvd-semantic-validation.py
|
||||
PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
|
||||
install(FILES schema/error_event_schema.json schema/daemon_event_schema.json
|
||||
schema/flow_event_schema.json schema/packet_event_schema.json DESTINATION share/nDPId)
|
||||
|
||||
message(STATUS "--------------------------")
|
||||
message(STATUS "nDPId GIT_VERSION........: ${GIT_VERSION}")
|
||||
message(STATUS "CROSS_COMPILE_TRIPLET....: ${CROSS_COMPILE_TRIPLET}")
|
||||
message(STATUS "CMAKE_BUILD_TYPE.........: ${CMAKE_BUILD_TYPE}")
|
||||
message(STATUS "CMAKE_C_FLAGS............: ${CMAKE_C_FLAGS}")
|
||||
if(ENABLE_MEMORY_PROFILING)
|
||||
message(STATUS "MEMORY_PROFILING_CFLAGS..: ${MEMORY_PROFILING_CFLAGS}")
|
||||
endif()
|
||||
message(STATUS "NDPID_DEFS...............: ${NDPID_DEFS}")
|
||||
message(STATUS "ENABLE_COVERAGE..........: ${ENABLE_COVERAGE}")
|
||||
message(STATUS "ENABLE_SANITIZER.........: ${ENABLE_SANITIZER}")
|
||||
message(STATUS "ENABLE_SANITIZER_THREAD..: ${ENABLE_SANITIZER_THREAD}")
|
||||
message(STATUS "ENABLE_MEMORY_PROFILING..: ${ENABLE_MEMORY_PROFILING}")
|
||||
if(NOT BUILD_NDPI AND NOT STATIC_LIBNDPI_INSTALLDIR STREQUAL "")
|
||||
message(STATUS "ENABLE_ZLIB..............: ${ENABLE_ZLIB}")
|
||||
if(STATIC_LIBNDPI_INSTALLDIR)
|
||||
message(STATUS "STATIC_LIBNDPI_INSTALLDIR: ${STATIC_LIBNDPI_INSTALLDIR}")
|
||||
endif()
|
||||
message(STATUS "BUILD_NDPI...............: ${BUILD_NDPI}")
|
||||
message(STATUS "NDPI_NO_PKGCONFIG........: ${NDPI_NO_PKGCONFIG}")
|
||||
if(NDPI_NO_PKGCONFIG)
|
||||
message(STATUS "LIBNDPI_INC..............: ${LIBNDPI_INC}")
|
||||
message(STATUS "LIBNDPI_LIB..............: ${LIBNDPI_LIB}")
|
||||
if(BUILD_NDPI)
|
||||
message(STATUS "NDPI_ADDITIONAL_ARGS.....: ${NDPI_ADDITIONAL_ARGS}")
|
||||
endif()
|
||||
if(NOT STATIC_LIBNDPI_INSTALLDIR STREQUAL "" OR BUILD_NDPI OR NDPI_NO_PKGCONFIG)
|
||||
message(STATUS "NDPI_NO_PKGCONFIG........: ${NDPI_NO_PKGCONFIG}")
|
||||
if(STATIC_LIBNDPI_INSTALLDIR OR BUILD_NDPI OR NDPI_NO_PKGCONFIG)
|
||||
message(STATUS "--------------------------")
|
||||
message(STATUS "- STATIC_LIBNDPI_INC....: ${STATIC_LIBNDPI_INC}")
|
||||
message(STATUS "- STATIC_LIBNDPI_LIB....: ${STATIC_LIBNDPI_LIB}")
|
||||
@@ -182,4 +360,7 @@ message(STATUS "- NDPI_WITH_GCRYPT......: ${NDPI_WITH_GCRYPT}")
|
||||
message(STATUS "- NDPI_WITH_PCRE........: ${NDPI_WITH_PCRE}")
|
||||
message(STATUS "- NDPI_WITH_MAXMINDDB...: ${NDPI_WITH_MAXMINDDB}")
|
||||
endif()
|
||||
if(NOT STATIC_LIBNDPI_INSTALLDIR AND NOT BUILD_NDPI)
|
||||
message(STATUS "- DEFAULT_NDPI_INCLUDE..: ${DEFAULT_NDPI_INCLUDE}")
|
||||
endif()
|
||||
message(STATUS "--------------------------")
|
||||
|
||||
140
README.md
140
README.md
@@ -1,67 +1,94 @@
|
||||
# abstract
|
||||
[](https://github.com/utoni/nDPId/actions/workflows/build.yml)
|
||||
[](https://gitlab.com/utoni/nDPId/-/pipelines)
|
||||
|
||||
nDPId is a set of daemons and tools to capture, process and classify network flows.
|
||||
It's only dependencies (besides a half-way modern c library and POSIX threads) are libnDPI (>= 3.6.0 or current github dev branch) and libpcap.
|
||||
# Abstract
|
||||
|
||||
The core daemon nDPId uses pthread but does use mutexes for performance reasons.
|
||||
nDPId is a set of daemons and tools to capture, process and classify network traffic.
|
||||
It's minimal dependencies (besides a half-way modern c library and POSIX threads) are libnDPI (>= 4.4.0 or current github dev branch) and libpcap.
|
||||
|
||||
The daemon `nDPId` is capable of multithreading for packet processing, but w/o mutexes for performance reasons.
|
||||
Instead synchronization is achieved by a packet distribution mechanism.
|
||||
To balance all workload to all threads (more or less) equally a hash value is calculated using the 5-tuple.
|
||||
This value serves as unique identifier for the processing thread. Multithreaded packet processing has to be flow-stable.
|
||||
To balance all workload to all threads (more or less) equally a unique identifier represented as hash value is calculated using a 3-tuple consisting of IPv4/IPv6 src/dst address, IP header value of the layer4 protocol and (for TCP/UDP) src/dst port. Other protocols e.g. ICMP/ICMPv6 are lacking relevance for DPI, thus nDPId does not distinguish between different ICMP/ICMPv6 flows coming from the same host. Saves memory and performance, but might change in the future.
|
||||
|
||||
nDPId uses libnDPI's JSON serialization to produce meaningful JSON output which it then sends to the nDPIsrvd for distribution.
|
||||
High level applications can connect to nDPIsrvd to get the latest flow/packet events from nDPId.
|
||||
`nDPId` uses libnDPI's JSON serialization interface to generate a JSON strings for each event it receive from the library and which it then sends out to a UNIX-socket (default: /tmp/ndpid-collector.sock ). From such a socket, `nDPIsrvd` (or other custom applications) can retrieve incoming JSON-messages and further proceed working/distributing messages to higher-level applications.
|
||||
|
||||
Unfortunately nDPIsrvd does currently not support any encryption/authentication for TCP connections.
|
||||
Unfortunately `nDPIsrvd` does currently not support any encryption/authentication for TCP connections (TODO!).
|
||||
|
||||
# architecture
|
||||
# Architecture
|
||||
|
||||
This project uses some kind of microservice architecture.
|
||||
|
||||
```text
|
||||
_______________________ __________________________
|
||||
| "producer" | | "consumer" |
|
||||
connect to UNIX socket [1] connect to UNIX/TCP socket [2]
|
||||
_______________________ | | __________________________
|
||||
| "producer" |___| |___| "consumer" |
|
||||
|---------------------| _____________________________ |------------------------|
|
||||
| | | nDPIsrvd | | |
|
||||
| nDPId --- Thread 1 >| ---> |> | <| <--- |< example/c-json-stdout |
|
||||
| (eth0) `- Thread 2 >| ---> |> collector | distributor <| <--- |________________________|
|
||||
| `- Thread N >| ---> |> >>> forward >>> <| <--- | |
|
||||
| nDPId --- Thread 1 >| ---> |> | <| ---> |< example/c-json-stdout |
|
||||
| (eth0) `- Thread 2 >| ---> |> collector | distributor <| ---> |________________________|
|
||||
| `- Thread N >| ---> |> >>> forward >>> <| ---> | |
|
||||
|_____________________| ^ |____________|______________| ^ |< example/py-flow-info |
|
||||
| | | | |________________________|
|
||||
| nDPId --- Thread 1 >| `- connect to UNIX socket | | |
|
||||
| (eth1) `- Thread 2 >| `- sends serialized data | |< example/... |
|
||||
| `- Thread N >| | |________________________|
|
||||
|_____________________| |
|
||||
`- connect to UNIX/TCP socket
|
||||
`- receives serialized data
|
||||
| nDPId --- Thread 1 >| `- send serialized data [1] | | |
|
||||
| (eth1) `- Thread 2 >| | |< example/... |
|
||||
| `- Thread N >| receive serialized data [2] -' |________________________|
|
||||
|_____________________|
|
||||
|
||||
```
|
||||
where:
|
||||
* `nDPId` capture traffic, extract traffic data (with libnDPI) and send a JSON-serialized output stream to an already existing UNIX-socket;
|
||||
* `nDPIsrvd`:
|
||||
* create and manage an "incoming" UNIX-socket (ref [1] above), to fetch data from a local `nDPId`;
|
||||
* apply a filtering logic to received data to select "flow_event_id" related JSONs;
|
||||
* create and manage an "outgoing" UNIX or TCP socket (ref [2] above) to relay matched events
|
||||
to connected clients
|
||||
* `consumers` are common/custom applications being able to receive selected flows/events, via both UNIX-socket or TCP-socket.
|
||||
|
||||
It doesn't use a producer/consumer design pattern, so the wording is not precise.
|
||||
|
||||
# JSON TCP protocol
|
||||
# JSON stream format
|
||||
|
||||
JSON messages streamed by both `nDPId` and `nDPIsrvd` are presented with:
|
||||
|
||||
* a 5-digit-number describing (as decimal number) of the **entire** JSON string including the newline `\n` at the end;
|
||||
* the JSON messages
|
||||
|
||||
All JSON strings sent need to be in the following format:
|
||||
```text
|
||||
[5-digit-number][JSON string]
|
||||
```
|
||||
|
||||
## Example:
|
||||
as with the following example:
|
||||
|
||||
```text
|
||||
00015{"key":"value"}
|
||||
01223{"flow_event_id":7,"flow_event_name":"detection-update","thread_id":12,"packet_id":307,"source":"wlan0",[...]}
|
||||
00458{"packet_event_id":2,"packet_event_name":"packet-flow","thread_id":11,"packet_id":324,"source":"wlan0",[...]]}
|
||||
00572{"flow_event_id":1,"flow_event_name":"new","thread_id":11,"packet_id":324,"source":"wlan0",[...]}
|
||||
```
|
||||
where `00015` describes the length of a **complete** JSON string.
|
||||
|
||||
TODO: Describe data format via JSON schema.
|
||||
The full stream of `nDPId` generated JSON-events can be retrieved directly from `nDPId`, without relying on `nDPIsrvd`, by providing a properly managed UNIX-socket.
|
||||
|
||||
# build (CMake)
|
||||
Technical details about JSON-messages format can be obtained from related `.schema` file included in the `schema` directory
|
||||
|
||||
|
||||
# Build (CMake)
|
||||
|
||||
`nDPId` build system is based on [CMake](https://cmake.org/)
|
||||
|
||||
```shell
|
||||
git clone https://github.com/utoni/nDPId.git
|
||||
[...]
|
||||
cd ndpid
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
[...]
|
||||
make
|
||||
```
|
||||
|
||||
or
|
||||
see below for a full/test live-session
|
||||
|
||||

|
||||
|
||||
Based on your building environment and/or desiderata, you could need:
|
||||
|
||||
```shell
|
||||
mkdir build
|
||||
@@ -88,7 +115,7 @@ cd build
|
||||
cmake .. -DSTATIC_LIBNDPI_INSTALLDIR=[path/to/your/libnDPI/installdir] -DNDPI_WITH_GCRYPT=ON -DNDPI_WITH_PCRE=OFF -DNDPI_WITH_MAXMINDDB=OFF
|
||||
```
|
||||
|
||||
Or if this is all too much for you, let CMake do it for you:
|
||||
Or let a shell script do the work for you:
|
||||
|
||||
```shell
|
||||
mkdir build
|
||||
@@ -96,14 +123,37 @@ cd build
|
||||
cmake .. -DBUILD_NDPI=ON
|
||||
```
|
||||
|
||||
The CMake cache variable `-DBUILD_NDPI=ON` builds a version of `libnDPI` residing as git submodule in this repository.
|
||||
|
||||
# run
|
||||
|
||||
Generate a nDPId compatible JSON dump:
|
||||
As mentioned above, in order to run `nDPId` a UNIX-socket need to be provided in order to stream our related JSON-data.
|
||||
|
||||
Such a UNIX-socket can be provided by both the included `nDPIsrvd` daemon, or, if you simply need a quick check, with the [ncat](https://nmap.org/book/ncat-man.html) utility, with a simple `ncat -U /tmp/listen.sock -l -k`
|
||||
|
||||
Once the socket is ready, you can run `nDPId` capturing and analyzing your own traffic, with something similar to:
|
||||
|
||||
Of course, both `ncat` and `nDPId` need to point to the same UNIX-socket (`nDPId` provides the `-c` option, exactly for this. As a default, `nDPId` refer to `/tmp/ndpid-collector.sock`, and the same default-path is also used by `nDPIsrvd` as for the incoming socket)
|
||||
|
||||
You also need to provide `nDPId` some real-traffic. You can capture your own traffic, with something similar to:
|
||||
|
||||
./nDPId -c /tmp/listen.sock -i wlan0 -l
|
||||
|
||||
or you can generate a nDPId-compatible JSON dump with:
|
||||
|
||||
```shell
|
||||
./nDPId-test [path-to-a-PCAP-file]
|
||||
```
|
||||
|
||||
You can also automatically fire both `nDPId` and `nDPIsrvd` automatically, with:
|
||||
|
||||
Daemons:
|
||||
```shell
|
||||
make -C [path-to-a-build-dir] daemon
|
||||
```
|
||||
|
||||
Or you can proceed with a manual approach with:
|
||||
|
||||
```shell
|
||||
./nDPIsrvd -d
|
||||
sudo ./nDPId -d
|
||||
@@ -129,19 +179,25 @@ or anything below `./examples`.
|
||||
|
||||
# test
|
||||
|
||||
You may want to run some integration tests using pcap files from nDPI:
|
||||
The recommended way to run integration / diff tests:
|
||||
|
||||
`./test/run_tests.sh /path/to/libnDPI/root/directory`
|
||||
```shell
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DBUILD_NDPI=ON
|
||||
make nDPId-test test
|
||||
```
|
||||
|
||||
Alternatively you can run some integration tests manually:
|
||||
|
||||
`./test/run_tests.sh [/path/to/libnDPI/root/directory] [/path/to/nDPId-test]`
|
||||
|
||||
e.g.:
|
||||
|
||||
`./test/run_tests.sh ${HOME}/git/nDPI`
|
||||
`./test/run_tests.sh [${HOME}/git/nDPI] [${HOME}/git/nDPId/build/nDPId-test]`
|
||||
|
||||
Remember that all test results are tied to a specific libnDPI commit hash
|
||||
as part of the `git submodule`. Using `test/run_tests.sh` for other commit hashes
|
||||
will most likely result in PCAP diff's.
|
||||
|
||||
For out-of-source builds, you'll need to specify a path to nDPId-test as well with:
|
||||
|
||||
`/test/run_tests.sh /path/to/libnDPI/root/directory /path/to/nDPId-test-executable`
|
||||
|
||||
For in-source builds and if CMake was configured with BUILD_NDPI=ON you can just type:
|
||||
|
||||
`/test/run_tests.sh`
|
||||
Why not use `examples/py-flow-dashboard/flow-dash.py` to visualize nDPId's output.
|
||||
|
||||
8
TODO.md
8
TODO.md
@@ -1,7 +1,5 @@
|
||||
# TODOs
|
||||
|
||||
1. unify `struct io_buffer` from nDPIsrvd.c and `struct nDPIsrvd_buffer` from nDPIsrvd.h
|
||||
2. improve nDPIsrvd buffer bloat handling (Do not fall back to blocking mode!)
|
||||
3. improve UDP/TCP timeout handling by reading netfilter conntrack timeouts from /proc
|
||||
4. detect interface / timeout changes and apply them to nDPId
|
||||
5. implement AEAD crypto via libsodium (at least for TCP communication)
|
||||
1. improve UDP/TCP timeout handling by reading netfilter conntrack timeouts from /proc (or just read conntrack table entries)
|
||||
2. detect interface / timeout changes and apply them to nDPId
|
||||
3. implement AEAD crypto via libsodium (at least for TCP communication)
|
||||
|
||||
21
config.h
21
config.h
@@ -11,25 +11,34 @@
|
||||
* NOTE: Buffer size needs to keep in sync with other implementations
|
||||
* e.g. dependencies/nDPIsrvd.py
|
||||
*/
|
||||
#define NETWORK_BUFFER_MAX_SIZE 12288u /* 8192 + 4096 */
|
||||
#define NETWORK_BUFFER_MAX_SIZE 16384u /* 8192 + 8192 */
|
||||
#define NETWORK_BUFFER_LENGTH_DIGITS 5u
|
||||
#define NETWORK_BUFFER_LENGTH_DIGITS_STR "5"
|
||||
|
||||
/* nDPId default config options */
|
||||
#define nDPId_PIDFILE "/tmp/ndpid.pid"
|
||||
#define nDPId_MAX_FLOWS_PER_THREAD 4096u
|
||||
#define nDPId_MAX_IDLE_FLOWS_PER_THREAD 512u
|
||||
#define nDPId_MAX_IDLE_FLOWS_PER_THREAD (nDPId_MAX_FLOWS_PER_THREAD / 32u)
|
||||
#define nDPId_TICK_RESOLUTION 1000u
|
||||
#define nDPId_MAX_READER_THREADS 32u
|
||||
#define nDPId_IDLE_SCAN_PERIOD 10000u /* 10 sec */
|
||||
#define nDPId_IDLE_TIME 600000u /* 600 sec */
|
||||
#define nDPId_TCP_POST_END_FLOW_TIME 60000u /* 60 sec */
|
||||
#define nDPId_DAEMON_STATUS_INTERVAL 600000u /* 600 sec */
|
||||
#define nDPId_MEMORY_PROFILING_LOG_INTERVAL 5000u /* 5 sec */
|
||||
#define nDPId_COMPRESSION_SCAN_INTERVAL 20000u /* 20 sec */
|
||||
#define nDPId_COMPRESSION_FLOW_INACTIVITY 30000u /* 30 sec */
|
||||
#define nDPId_FLOW_SCAN_INTERVAL 10000u /* 10 sec */
|
||||
#define nDPId_GENERIC_IDLE_TIME 600000u /* 600 sec */
|
||||
#define nDPId_ICMP_IDLE_TIME 120000u /* 120 sec */
|
||||
#define nDPId_TCP_IDLE_TIME 7440000u /* 7440 sec */
|
||||
#define nDPId_UDP_IDLE_TIME 180000u /* 180 sec */
|
||||
#define nDPId_TCP_POST_END_FLOW_TIME 120000u /* 120 sec */
|
||||
#define nDPId_THREAD_DISTRIBUTION_SEED 0x03dd018b
|
||||
#define nDPId_PACKETS_PER_FLOW_TO_SEND 15u
|
||||
#define nDPId_PACKETS_PER_FLOW_TO_PROCESS 255u
|
||||
#define nDPId_PACKETS_PER_FLOW_TO_PROCESS NDPI_DEFAULT_MAX_NUM_PKTS_PER_FLOW_TO_DISSECT
|
||||
#define nDPId_FLOW_STRUCT_SEED 0x5defc104
|
||||
|
||||
/* nDPIsrvd default config options */
|
||||
#define nDPIsrvd_PIDFILE "/tmp/ndpisrvd.pid"
|
||||
#define nDPIsrvd_MAX_REMOTE_DESCRIPTORS 32
|
||||
#define nDPIsrvd_MAX_WRITE_BUFFERS 1024
|
||||
|
||||
#endif
|
||||
|
||||
10
dependencies/jsmn/README.md
vendored
10
dependencies/jsmn/README.md
vendored
@@ -89,7 +89,7 @@ jsmn_parser p;
|
||||
jsmntok_t t[128]; /* We expect no more than 128 JSON tokens */
|
||||
|
||||
jsmn_init(&p);
|
||||
r = jsmn_parse(&p, s, strlen(s), t, 128);
|
||||
r = jsmn_parse(&p, s, strlen(s), t, 128); // "s" is the char array holding the json content
|
||||
```
|
||||
|
||||
Since jsmn is a single-header, header-only library, for more complex use cases
|
||||
@@ -113,10 +113,10 @@ Token types are described by `jsmntype_t`:
|
||||
|
||||
typedef enum {
|
||||
JSMN_UNDEFINED = 0,
|
||||
JSMN_OBJECT = 1,
|
||||
JSMN_ARRAY = 2,
|
||||
JSMN_STRING = 3,
|
||||
JSMN_PRIMITIVE = 4
|
||||
JSMN_OBJECT = 1 << 0,
|
||||
JSMN_ARRAY = 1 << 1,
|
||||
JSMN_STRING = 1 << 2,
|
||||
JSMN_PRIMITIVE = 1 << 3
|
||||
} jsmntype_t;
|
||||
|
||||
**Note:** Unlike JSON data types, primitive tokens are not divided into
|
||||
|
||||
8
dependencies/jsmn/jsmn.h
vendored
8
dependencies/jsmn/jsmn.h
vendored
@@ -45,10 +45,10 @@ extern "C" {
|
||||
*/
|
||||
typedef enum {
|
||||
JSMN_UNDEFINED = 0,
|
||||
JSMN_OBJECT = 1,
|
||||
JSMN_ARRAY = 2,
|
||||
JSMN_STRING = 3,
|
||||
JSMN_PRIMITIVE = 4
|
||||
JSMN_OBJECT = 1 << 0,
|
||||
JSMN_ARRAY = 1 << 1,
|
||||
JSMN_STRING = 1 << 2,
|
||||
JSMN_PRIMITIVE = 1 << 3
|
||||
} jsmntype_t;
|
||||
|
||||
enum jsmnerr {
|
||||
|
||||
880
dependencies/nDPIsrvd.h
vendored
880
dependencies/nDPIsrvd.h
vendored
File diff suppressed because it is too large
Load Diff
407
dependencies/nDPIsrvd.py
vendored
407
dependencies/nDPIsrvd.py
vendored
@@ -2,7 +2,6 @@
|
||||
|
||||
import argparse
|
||||
import array
|
||||
import base64
|
||||
import json
|
||||
import re
|
||||
import os
|
||||
@@ -17,17 +16,12 @@ except ImportError:
|
||||
sys.stderr.write('Python module colorama not found, using fallback.\n')
|
||||
USE_COLORAMA=False
|
||||
|
||||
try:
|
||||
import scapy.all
|
||||
except ImportError:
|
||||
sys.stderr.write('Python module scapy not found, PCAP generation will fail!\n')
|
||||
|
||||
DEFAULT_HOST = '127.0.0.1'
|
||||
DEFAULT_PORT = 7000
|
||||
DEFAULT_UNIX = '/tmp/ndpid-distributor.sock'
|
||||
|
||||
NETWORK_BUFFER_MIN_SIZE = 6 # NETWORK_BUFFER_LENGTH_DIGITS + 1
|
||||
NETWORK_BUFFER_MAX_SIZE = 12288 # Please keep this value in sync with the one in config.h
|
||||
NETWORK_BUFFER_MAX_SIZE = 16384 # Please keep this value in sync with the one in config.h
|
||||
|
||||
PKT_TYPE_ETH_IP4 = 0x0800
|
||||
PKT_TYPE_ETH_IP6 = 0x86DD
|
||||
@@ -81,37 +75,202 @@ class TermColor:
|
||||
else:
|
||||
return '{}{}{}'.format(TermColor.BOLD, string, TermColor.END)
|
||||
|
||||
class ThreadData:
|
||||
pass
|
||||
|
||||
class Instance:
|
||||
|
||||
def __init__(self, alias, source):
|
||||
self.alias = str(alias)
|
||||
self.source = str(source)
|
||||
self.flows = dict()
|
||||
self.thread_data = dict()
|
||||
|
||||
def __str__(self):
|
||||
return '<%s.%s object at %s with alias %s, source %s>' % (
|
||||
self.__class__.__module__,
|
||||
self.__class__.__name__,
|
||||
hex(id(self)),
|
||||
self.alias,
|
||||
self.source
|
||||
)
|
||||
|
||||
def getThreadData(self, thread_id):
|
||||
if thread_id not in self.thread_data:
|
||||
return None
|
||||
return self.thread_data[thread_id]
|
||||
|
||||
def getThreadDataFromJSON(self, json_dict):
|
||||
if 'thread_id' not in json_dict:
|
||||
return None
|
||||
return self.getThreadData(json_dict['thread_id'])
|
||||
|
||||
def getMostRecentFlowTime(self, thread_id):
|
||||
return self.thread_data[thread_id].most_recent_flow_time
|
||||
|
||||
def setMostRecentFlowTime(self, thread_id, most_recent_flow_time):
|
||||
if thread_id in self.thread_data:
|
||||
return self.thread_data[thread_id]
|
||||
|
||||
self.thread_data[thread_id] = ThreadData()
|
||||
self.thread_data[thread_id].most_recent_flow_time = most_recent_flow_time
|
||||
return self.thread_data[thread_id]
|
||||
|
||||
def getMostRecentFlowTimeFromJSON(self, json_dict):
|
||||
if 'thread_id' not in json_dict:
|
||||
return 0
|
||||
return self.getThreadData(json_dict['thread_id']).most_recent_flow_time
|
||||
|
||||
def setMostRecentFlowTimeFromJSON(self, json_dict):
|
||||
if 'thread_id' not in json_dict:
|
||||
return
|
||||
thread_id = json_dict['thread_id']
|
||||
if 'thread_ts_msec' in json_dict:
|
||||
mrtf = self.getMostRecentFlowTime(thread_id) if thread_id in self.thread_data else 0
|
||||
self.setMostRecentFlowTime(thread_id, max(json_dict['thread_ts_msec'], mrtf))
|
||||
|
||||
class Flow:
|
||||
flow_id = -1
|
||||
|
||||
def __init__(self, flow_id, thread_id):
|
||||
self.flow_id = flow_id
|
||||
self.thread_id = thread_id
|
||||
self.flow_last_seen = -1
|
||||
self.flow_idle_time = -1
|
||||
self.cleanup_reason = -1
|
||||
|
||||
def __str__(self):
|
||||
return '<%s.%s object at %s with flow id %d>' % (
|
||||
self.__class__.__module__,
|
||||
self.__class__.__name__,
|
||||
hex(id(self)),
|
||||
self.flow_id
|
||||
)
|
||||
|
||||
class FlowManager:
|
||||
def __init__(self):
|
||||
self.__flows = dict()
|
||||
CLEANUP_REASON_INVALID = 0
|
||||
CLEANUP_REASON_DAEMON_INIT = 1 # can happen if kill -SIGKILL $(pidof nDPId) or restart after SIGSEGV
|
||||
CLEANUP_REASON_DAEMON_SHUTDOWN = 2 # graceful shutdown e.g. kill -SIGTERM $(pidof nDPId)
|
||||
CLEANUP_REASON_FLOW_END = 3
|
||||
CLEANUP_REASON_FLOW_IDLE = 4
|
||||
CLEANUP_REASON_FLOW_TIMEOUT = 5 # nDPId died a long time ago w/o restart?
|
||||
CLEANUP_REASON_APP_SHUTDOWN = 6 # your python app called FlowManager.doShutdown()
|
||||
|
||||
def __buildFlowKey(self, json_dict):
|
||||
if 'flow_id' not in json_dict or \
|
||||
'alias' not in json_dict or \
|
||||
def __init__(self):
|
||||
self.instances = dict()
|
||||
|
||||
def getInstance(self, json_dict):
|
||||
if 'alias' not in json_dict or \
|
||||
'source' not in json_dict:
|
||||
return None
|
||||
|
||||
return str(json_dict['alias']) + str(json_dict['source']) + str(json_dict['flow_id'])
|
||||
alias = json_dict['alias']
|
||||
source = json_dict['source']
|
||||
|
||||
def getFlow(self, json_dict):
|
||||
event = json_dict['flow_event_name'].lower() if 'flow_event_name' in json_dict else ''
|
||||
flow_key = self.__buildFlowKey(json_dict)
|
||||
flow = None
|
||||
if alias not in self.instances:
|
||||
self.instances[alias] = dict()
|
||||
if source not in self.instances[alias]:
|
||||
self.instances[alias][source] = dict()
|
||||
self.instances[alias][source] = Instance(alias, source)
|
||||
|
||||
if flow_key is None:
|
||||
self.instances[alias][source].setMostRecentFlowTimeFromJSON(json_dict)
|
||||
|
||||
return self.instances[alias][source]
|
||||
|
||||
def getFlow(self, instance, json_dict):
|
||||
if 'flow_id' not in json_dict:
|
||||
return None
|
||||
if flow_key not in self.__flows:
|
||||
self.__flows[flow_key] = Flow()
|
||||
self.__flows[flow_key].flow_id = int(json_dict['flow_id'])
|
||||
flow = self.__flows[flow_key]
|
||||
if event == 'end' or event == 'idle':
|
||||
flow = self.__flows[flow_key]
|
||||
del self.__flows[flow_key]
|
||||
|
||||
return flow
|
||||
flow_id = int(json_dict['flow_id'])
|
||||
|
||||
if flow_id in instance.flows:
|
||||
instance.flows[flow_id].flow_last_seen = int(json_dict['flow_last_seen'])
|
||||
instance.flows[flow_id].flow_idle_time = int(json_dict['flow_idle_time'])
|
||||
return instance.flows[flow_id]
|
||||
|
||||
thread_id = int(json_dict['thread_id'])
|
||||
instance.flows[flow_id] = Flow(flow_id, thread_id)
|
||||
instance.flows[flow_id].flow_last_seen = int(json_dict['flow_last_seen'])
|
||||
instance.flows[flow_id].flow_idle_time = int(json_dict['flow_idle_time'])
|
||||
instance.flows[flow_id].cleanup_reason = FlowManager.CLEANUP_REASON_INVALID
|
||||
|
||||
return instance.flows[flow_id]
|
||||
|
||||
def getFlowsToCleanup(self, instance, json_dict):
|
||||
flows = dict()
|
||||
|
||||
if 'daemon_event_name' in json_dict:
|
||||
if json_dict['daemon_event_name'].lower() == 'init' or \
|
||||
json_dict['daemon_event_name'].lower() == 'shutdown':
|
||||
# invalidate all existing flows with that alias/source/thread_id
|
||||
for flow_id in instance.flows:
|
||||
flow = instance.flows[flow_id]
|
||||
if flow.thread_id != int(json_dict['thread_id']):
|
||||
continue
|
||||
if json_dict['daemon_event_name'].lower() == 'init':
|
||||
flow.cleanup_reason = FlowManager.CLEANUP_REASON_DAEMON_INIT
|
||||
else:
|
||||
flow.cleanup_reason = FlowManager.CLEANUP_REASON_DAEMON_SHUTDOWN
|
||||
flows[flow_id] = flow
|
||||
for flow_id in flows:
|
||||
del instance.flows[flow_id]
|
||||
if len(instance.flows) == 0:
|
||||
del self.instances[instance.alias][instance.source]
|
||||
|
||||
elif 'flow_event_name' in json_dict and \
|
||||
(json_dict['flow_event_name'].lower() == 'end' or \
|
||||
json_dict['flow_event_name'].lower() == 'idle' or \
|
||||
json_dict['flow_event_name'].lower() == 'guessed' or \
|
||||
json_dict['flow_event_name'].lower() == 'not-detected' or \
|
||||
json_dict['flow_event_name'].lower() == 'detected'):
|
||||
flow_id = json_dict['flow_id']
|
||||
if json_dict['flow_event_name'].lower() == 'end':
|
||||
instance.flows[flow_id].cleanup_reason = FlowManager.CLEANUP_REASON_FLOW_END
|
||||
elif json_dict['flow_event_name'].lower() == 'idle':
|
||||
instance.flows[flow_id].cleanup_reason = FlowManager.CLEANUP_REASON_FLOW_IDLE
|
||||
# TODO: Flow Guessing/Detection can happen right before an idle event.
|
||||
# We need to prevent that it results in a CLEANUP_REASON_FLOW_TIMEOUT.
|
||||
# This may cause inconsistency and needs to be handled in another way.
|
||||
if json_dict['flow_event_name'].lower() != 'guessed' and \
|
||||
json_dict['flow_event_name'].lower() != 'not-detected' and \
|
||||
json_dict['flow_event_name'].lower() != 'detected':
|
||||
flows[flow_id] = instance.flows.pop(flow_id)
|
||||
|
||||
elif 'flow_last_seen' in json_dict:
|
||||
if int(json_dict['flow_last_seen']) + int(json_dict['flow_idle_time']) < \
|
||||
instance.getMostRecentFlowTimeFromJSON(json_dict):
|
||||
flow_id = json_dict['flow_id']
|
||||
instance.flows[flow_id].cleanup_reason = FlowManager.CLEANUP_REASON_FLOW_TIMEOUT
|
||||
flows[flow_id] = instance.flows.pop(flow_id)
|
||||
|
||||
return flows
|
||||
|
||||
def doShutdown(self):
|
||||
flows = dict()
|
||||
|
||||
for alias in self.instances:
|
||||
for source in self.instances[alias]:
|
||||
for flow_id in self.instances[alias][source].flows:
|
||||
flow = self.instances[alias][source].flows[flow_id]
|
||||
flow.cleanup_reason = FlowManager.CLEANUP_REASON_APP_SHUTDOWN
|
||||
flows[flow_id] = flow
|
||||
|
||||
del self.instances
|
||||
|
||||
return flows
|
||||
|
||||
def verifyFlows(self):
|
||||
invalid_flows = list()
|
||||
|
||||
for alias in self.instances:
|
||||
for source in self.instances[alias]:
|
||||
for flow_id in self.instances[alias][source].flows:
|
||||
thread_id = self.instances[alias][source].flows[flow_id].thread_id
|
||||
if self.instances[alias][source].flows[flow_id].flow_last_seen + \
|
||||
self.instances[alias][source].flows[flow_id].flow_idle_time < \
|
||||
self.instances[alias][source].getMostRecentFlowTime(thread_id):
|
||||
invalid_flows += [flow_id]
|
||||
|
||||
return invalid_flows
|
||||
|
||||
class nDPIsrvdException(Exception):
|
||||
UNSUPPORTED_ADDRESS_TYPE = 1
|
||||
@@ -119,6 +278,7 @@ class nDPIsrvdException(Exception):
|
||||
SOCKET_CONNECTION_BROKEN = 3
|
||||
INVALID_LINE_RECEIVED = 4
|
||||
CALLBACK_RETURNED_FALSE = 5
|
||||
SOCKET_TIMEOUT = 6
|
||||
|
||||
def __init__(self, etype):
|
||||
self.etype = etype
|
||||
@@ -159,10 +319,17 @@ class CallbackReturnedFalse(nDPIsrvdException):
|
||||
def __str__(self):
|
||||
return 'Callback returned False, abort.'
|
||||
|
||||
class SocketTimeout(nDPIsrvdException):
|
||||
def __init__(self):
|
||||
super().__init__(nDPIsrvdException.SOCKET_TIMEOUT)
|
||||
def __str__(self):
|
||||
return 'Socket timeout.'
|
||||
|
||||
class nDPIsrvdSocket:
|
||||
def __init__(self):
|
||||
self.sock_family = None
|
||||
self.flow_mgr = FlowManager()
|
||||
self.received_bytes = 0
|
||||
|
||||
def connect(self, addr):
|
||||
if type(addr) is tuple:
|
||||
@@ -179,6 +346,9 @@ class nDPIsrvdSocket:
|
||||
self.digitlen = 0
|
||||
self.lines = []
|
||||
|
||||
def timeout(self, timeout):
|
||||
self.sock.settimeout(timeout)
|
||||
|
||||
def receive(self):
|
||||
if len(self.buffer) == NETWORK_BUFFER_MAX_SIZE:
|
||||
raise BufferCapacityReached(len(self.buffer), NETWORK_BUFFER_MAX_SIZE)
|
||||
@@ -189,6 +359,11 @@ class nDPIsrvdSocket:
|
||||
except ConnectionResetError:
|
||||
connection_finished = True
|
||||
recvd = bytes()
|
||||
except TimeoutError:
|
||||
raise SocketTimeout()
|
||||
except socket.timeout:
|
||||
raise SocketTimeout()
|
||||
|
||||
if len(recvd) == 0:
|
||||
connection_finished = True
|
||||
|
||||
@@ -212,6 +387,7 @@ class nDPIsrvdSocket:
|
||||
self.lines += [(recvd,self.msglen,self.digitlen)]
|
||||
new_data_avail = True
|
||||
|
||||
self.received_bytes += self.msglen + self.digitlen
|
||||
self.msglen = 0
|
||||
self.digitlen = 0
|
||||
|
||||
@@ -220,21 +396,31 @@ class nDPIsrvdSocket:
|
||||
|
||||
return new_data_avail
|
||||
|
||||
def parse(self, callback, global_user_data):
|
||||
def parse(self, callback_json, callback_flow_cleanup, global_user_data):
|
||||
retval = True
|
||||
index = 0
|
||||
for received_json_line in self.lines:
|
||||
json_dict = json.loads(received_json_line[0].decode('ascii', errors='replace'), strict=True)
|
||||
if callback(json_dict, self.flow_mgr.getFlow(json_dict), global_user_data) is not True:
|
||||
|
||||
for received_line in self.lines:
|
||||
json_dict = json.loads(received_line[0].decode('ascii', errors='replace'), strict=True)
|
||||
instance = self.flow_mgr.getInstance(json_dict)
|
||||
if instance is None:
|
||||
retval = False
|
||||
break
|
||||
continue
|
||||
|
||||
if callback_json(json_dict, instance, self.flow_mgr.getFlow(instance, json_dict), global_user_data) is not True:
|
||||
retval = False
|
||||
for _, flow in self.flow_mgr.getFlowsToCleanup(instance, json_dict).items():
|
||||
if callback_flow_cleanup is None:
|
||||
pass
|
||||
elif callback_flow_cleanup(instance, flow, global_user_data) is not True:
|
||||
retval = False
|
||||
index += 1
|
||||
|
||||
self.lines = self.lines[index:]
|
||||
|
||||
return retval
|
||||
|
||||
def loop(self, callback, global_user_data):
|
||||
def loop(self, callback_json, callback_flow_cleanup, global_user_data):
|
||||
throw_ex = None
|
||||
|
||||
while True:
|
||||
@@ -244,119 +430,21 @@ class nDPIsrvdSocket:
|
||||
except Exception as err:
|
||||
throw_ex = err
|
||||
|
||||
if self.parse(callback, global_user_data) is False:
|
||||
if self.parse(callback_json, callback_flow_cleanup, global_user_data) is False:
|
||||
raise CallbackReturnedFalse()
|
||||
|
||||
if throw_ex is not None:
|
||||
raise throw_ex
|
||||
|
||||
class PcapPacket:
|
||||
def __init__(self):
|
||||
self.pktdump = None
|
||||
self.flow_id = 0
|
||||
self.packets = []
|
||||
self.__suffix = ''
|
||||
self.__dump = False
|
||||
self.__dumped = False
|
||||
def shutdown(self):
|
||||
return self.flow_mgr.doShutdown().items()
|
||||
|
||||
@staticmethod
|
||||
def isInitialized(current_flow):
|
||||
return current_flow is not None and hasattr(current_flow, 'pcap_packet')
|
||||
def verify(self):
|
||||
return self.flow_mgr.verifyFlows()
|
||||
|
||||
@staticmethod
|
||||
def handleJSON(json_dict, current_flow):
|
||||
if 'flow_event_name' in json_dict:
|
||||
|
||||
if json_dict['flow_event_name'] == 'new':
|
||||
|
||||
current_flow.pcap_packet = PcapPacket()
|
||||
current_flow.pcap_packet.current_packet = 0
|
||||
current_flow.pcap_packet.max_packets = json_dict['flow_max_packets']
|
||||
current_flow.pcap_packet.flow_id = json_dict['flow_id']
|
||||
|
||||
elif PcapPacket.isInitialized(current_flow) is not True:
|
||||
|
||||
pass
|
||||
|
||||
elif json_dict['flow_event_name'] == 'end' or json_dict['flow_event_name'] == 'idle':
|
||||
|
||||
try:
|
||||
current_flow.pcap_packet.fin()
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
elif PcapPacket.isInitialized(current_flow) is True and \
|
||||
('packet_event_name' in json_dict and json_dict['packet_event_name'] == 'packet-flow' and current_flow.pcap_packet.flow_id > 0) or \
|
||||
('packet_event_name' in json_dict and json_dict['packet_event_name'] == 'packet' and 'pkt' in json_dict):
|
||||
|
||||
buffer_decoded = base64.b64decode(json_dict['pkt'], validate=True)
|
||||
current_flow.pcap_packet.packets += [ ( buffer_decoded, json_dict['pkt_type'], json_dict['pkt_l3_offset'] ) ]
|
||||
current_flow.pcap_packet.current_packet += 1
|
||||
|
||||
if current_flow.pcap_packet.current_packet != int(json_dict['flow_packet_id']):
|
||||
raise RuntimeError('Packet IDs not in sync (local: {}, remote: {}).'.format(current_flow.pcap_packet.current_packet, int(json_dict['flow_packet_id'])))
|
||||
|
||||
@staticmethod
|
||||
def getIp(packet):
|
||||
if packet[1] == PKT_TYPE_ETH_IP4:
|
||||
return scapy.all.IP(packet[0][packet[2]:])
|
||||
elif packet[1] == PKT_TYPE_ETH_IP6:
|
||||
return scapy.all.IPv6(packet[0][packet[2]:])
|
||||
else:
|
||||
raise RuntimeError('packet type unknown: {}'.format(packet[1]))
|
||||
|
||||
@staticmethod
|
||||
def getTCPorUDP(packet):
|
||||
p = PcapPacket.getIp(packet)
|
||||
if p.haslayer(scapy.all.TCP):
|
||||
return p.getlayer(scapy.all.TCP)
|
||||
elif p.haslayer(scapy.all.UDP):
|
||||
return p.getlayer(scapy.all.UDP)
|
||||
else:
|
||||
return None
|
||||
|
||||
def setSuffix(self, filename_suffix):
|
||||
self.__suffix = filename_suffix
|
||||
|
||||
def doDump(self):
|
||||
self.__dump = True
|
||||
|
||||
def fin(self):
|
||||
if self.__dumped is True:
|
||||
raise RuntimeError('Flow {} already dumped.'.format(self.flow_id))
|
||||
if self.__dump is False:
|
||||
raise RuntimeError('Flow {} should not be dumped.'.format(self.flow_id))
|
||||
|
||||
emptyTCPorUDPcount = 0;
|
||||
for packet in self.packets:
|
||||
p = PcapPacket.getTCPorUDP(packet)
|
||||
if p is not None:
|
||||
if p.haslayer(scapy.all.Padding) and len(p.payload) - len(p[scapy.all.Padding]) == 0:
|
||||
emptyTCPorUDPcount += 1
|
||||
elif len(p.payload) == 0:
|
||||
emptyTCPorUDPcount += 1
|
||||
|
||||
if emptyTCPorUDPcount == len(self.packets):
|
||||
raise RuntimeError('Flow {} does not contain any packets({}) with non-empty layer4 payload.'.format(self.flow_id, len(self.packets)))
|
||||
|
||||
if self.pktdump is None:
|
||||
if self.flow_id == 0:
|
||||
self.pktdump = scapy.all.PcapWriter('packet-{}.pcap'.format(self.__suffix),
|
||||
append=True, sync=True)
|
||||
else:
|
||||
self.pktdump = scapy.all.PcapWriter('flow-{}-{}.pcap'.format(self.__suffix, self.flow_id),
|
||||
append=False, sync=True)
|
||||
|
||||
for packet in self.packets:
|
||||
self.pktdump.write(PcapPacket.getIp(packet))
|
||||
|
||||
self.pktdump.close()
|
||||
self.__dumped = True
|
||||
|
||||
return True
|
||||
|
||||
def defaultArgumentParser():
|
||||
parser = argparse.ArgumentParser(description='nDPIsrvd options', formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
def defaultArgumentParser(desc='nDPIsrvd Python Interface',
|
||||
help_formatter=argparse.ArgumentDefaultsHelpFormatter):
|
||||
parser = argparse.ArgumentParser(description=desc, formatter_class=help_formatter)
|
||||
parser.add_argument('--host', type=str, help='nDPIsrvd host IP')
|
||||
parser.add_argument('--port', type=int, default=DEFAULT_PORT, help='nDPIsrvd TCP port')
|
||||
parser.add_argument('--unix', type=str, help='nDPIsrvd unix socket path')
|
||||
@@ -390,27 +478,50 @@ def validateAddress(args):
|
||||
return address
|
||||
|
||||
global schema
|
||||
schema = {'packet_event_schema' : None, 'basic_event_schema' : None, 'daemon_event_schema' : None, 'flow_event_schema' : None}
|
||||
schema = {'packet_event_schema' : None, 'error_event_schema' : None, 'daemon_event_schema' : None, 'flow_event_schema' : None}
|
||||
|
||||
def initSchemaValidator(schema_dirs=[]):
|
||||
if len(schema_dirs) == 0:
|
||||
schema_dirs += [os.path.dirname(sys.argv[0]) + '/../../schema']
|
||||
schema_dirs += [os.path.dirname(sys.argv[0]) + '/../share/nDPId']
|
||||
schema_dirs += [sys.base_prefix + '/share/nDPId']
|
||||
|
||||
def initSchemaValidator(schema_dir='./schema'):
|
||||
for key in schema:
|
||||
with open(schema_dir + '/' + str(key) + '.json', 'r') as schema_file:
|
||||
schema[key] = json.load(schema_file)
|
||||
for schema_dir in schema_dirs:
|
||||
try:
|
||||
with open(schema_dir + '/' + str(key) + '.json', 'r') as schema_file:
|
||||
schema[key] = json.load(schema_file)
|
||||
except FileNotFoundError:
|
||||
continue
|
||||
else:
|
||||
break
|
||||
|
||||
def validateAgainstSchema(json_dict):
|
||||
import jsonschema
|
||||
|
||||
if 'packet_event_id' in json_dict:
|
||||
jsonschema.validate(instance=json_dict, schema=schema['packet_event_schema'])
|
||||
try:
|
||||
jsonschema.Draft7Validator(schema=schema['packet_event_schema']).validate(instance=json_dict)
|
||||
except AttributeError:
|
||||
jsonschema.validate(instance=json_dict, schema=schema['packet_event_schema'])
|
||||
return True
|
||||
if 'basic_event_id' in json_dict:
|
||||
jsonschema.validate(instance=json_dict, schema=schema['basic_event_schema'])
|
||||
if 'error_event_id' in json_dict:
|
||||
try:
|
||||
jsonschema.Draft7Validator(schema=schema['error_event_schema']).validate(instance=json_dict)
|
||||
except AttributeError:
|
||||
jsonschema.validate(instance=json_dict, schema=schema['error_event_schema'])
|
||||
return True
|
||||
if 'daemon_event_id' in json_dict:
|
||||
jsonschema.validate(instance=json_dict, schema=schema['daemon_event_schema'])
|
||||
try:
|
||||
jsonschema.Draft7Validator(schema=schema['daemon_event_schema']).validate(instance=json_dict)
|
||||
except AttributeError:
|
||||
jsonschema.validate(instance=json_dict, schema=schema['daemon_event_schema'])
|
||||
return True
|
||||
if 'flow_event_id' in json_dict:
|
||||
jsonschema.validate(instance=json_dict, schema=schema['flow_event_schema'])
|
||||
try:
|
||||
jsonschema.Draft7Validator(schema=schema['flow_event_schema']).validate(instance=json_dict)
|
||||
except AttributeError:
|
||||
jsonschema.validate(instance=json_dict, schema=schema['flow_event_schema'])
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
2
dependencies/uthash/.travis.yml
vendored
2
dependencies/uthash/.travis.yml
vendored
@@ -7,7 +7,7 @@ matrix:
|
||||
compiler: clang
|
||||
- os: osx
|
||||
script:
|
||||
- make -C tests EXTRA_CFLAGS="-W -Wall -Wextra"
|
||||
- make -C tests EXTRA_CFLAGS="-W -Wall -Wextra -Wswitch-default"
|
||||
- make -C tests clean ; make -C tests pedantic
|
||||
- make -C tests clean ; make -C tests pedantic EXTRA_CFLAGS=-DNO_DECLTYPE
|
||||
- make -C tests clean ; make -C tests cplusplus
|
||||
|
||||
2
dependencies/uthash/LICENSE
vendored
2
dependencies/uthash/LICENSE
vendored
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2005-2018, Troy D. Hanson http://troydhanson.github.com/uthash/
|
||||
Copyright (c) 2005-2021, Troy D. Hanson http://troydhanson.github.io/uthash/
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
||||
4
dependencies/uthash/README.md
vendored
4
dependencies/uthash/README.md
vendored
@@ -1,8 +1,8 @@
|
||||
|
||||
[](https://travis-ci.org/troydhanson/uthash)
|
||||
[](https://travis-ci.org/troydhanson/uthash)
|
||||
|
||||
Documentation for uthash is available at:
|
||||
|
||||
http://troydhanson.github.com/uthash/
|
||||
https://troydhanson.github.io/uthash/
|
||||
|
||||
|
||||
|
||||
17
dependencies/uthash/doc/ChangeLog.txt
vendored
17
dependencies/uthash/doc/ChangeLog.txt
vendored
@@ -5,6 +5,21 @@ Click to return to the link:index.html[uthash home page].
|
||||
|
||||
NOTE: This ChangeLog may be incomplete and/or incorrect. See the git commit log.
|
||||
|
||||
Version 2.3.0 (2021-02-25)
|
||||
--------------------------
|
||||
* remove HASH_FCN; the HASH_FUNCTION and HASH_KEYCMP macros now behave similarly
|
||||
* remove uthash_memcmp (deprecated in v2.1.0) in favor of HASH_KEYCMP
|
||||
* silence -Wswitch-default warnings (thanks, Olaf Bergmann!)
|
||||
|
||||
Version 2.2.0 (2020-12-17)
|
||||
--------------------------
|
||||
* add HASH_NO_STDINT for platforms without C99 <stdint.h>
|
||||
* silence many -Wcast-qual warnings (thanks, Olaf Bergmann!)
|
||||
* skip hash computation when finding in an empty hash (thanks, Huansong Fu!)
|
||||
* rename oom to utarray_oom, in utarray.h (thanks, Hong Xu!)
|
||||
* rename oom to utstring_oom, in utstring.h (thanks, Hong Xu!)
|
||||
* remove MurmurHash/HASH_MUR
|
||||
|
||||
Version 2.1.0 (2018-12-20)
|
||||
--------------------------
|
||||
* silence some Clang static analysis warnings
|
||||
@@ -56,7 +71,7 @@ Version 1.9.8 (2013-03-10)
|
||||
* `HASH_REPLACE` now in uthash (thanks, Nick Vatamaniuc!)
|
||||
* fixed clang warnings (thanks wynnw!)
|
||||
* fixed `utarray_insert` when inserting past array end (thanks Rob Willett!)
|
||||
* you can now find http://troydhanson.github.com/uthash/[uthash on GitHub]
|
||||
* you can now find http://troydhanson.github.io/uthash/[uthash on GitHub]
|
||||
* there's a https://groups.google.com/d/forum/uthash[uthash Google Group]
|
||||
* uthash has been downloaded 29,000+ times since 2006 on SourceForge
|
||||
|
||||
|
||||
8
dependencies/uthash/doc/index.html
vendored
8
dependencies/uthash/doc/index.html
vendored
@@ -14,7 +14,7 @@
|
||||
|
||||
<div id="topnav">
|
||||
<a href="http://github.com/troydhanson/uthash">GitHub page</a> >
|
||||
uthash home <!-- http://troydhanson.github.com/uthash/ -->
|
||||
uthash home <!-- http://troydhanson.github.io/uthash/ -->
|
||||
|
||||
<a href="https://twitter.com/share" class="twitter-share-button" data-via="troydhanson">Tweet</a>
|
||||
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>
|
||||
@@ -72,7 +72,7 @@ struct my_struct {
|
||||
struct my_struct *users = NULL;
|
||||
|
||||
void add_user(struct my_struct *s) {
|
||||
HASH_ADD_INT( users, id, s );
|
||||
HASH_ADD_INT(users, id, s);
|
||||
}
|
||||
|
||||
</pre>
|
||||
@@ -86,7 +86,7 @@ Example 2. Looking up an item in a hash.
|
||||
struct my_struct *find_user(int user_id) {
|
||||
struct my_struct *s;
|
||||
|
||||
HASH_FIND_INT( users, &user_id, s );
|
||||
HASH_FIND_INT(users, &user_id, s);
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ Example 3. Deleting an item from a hash.
|
||||
|
||||
<pre>
|
||||
void delete_user(struct my_struct *user) {
|
||||
HASH_DEL( users, user);
|
||||
HASH_DEL(users, user);
|
||||
}
|
||||
|
||||
</pre>
|
||||
|
||||
4
dependencies/uthash/doc/license.html
vendored
4
dependencies/uthash/doc/license.html
vendored
@@ -13,7 +13,7 @@
|
||||
</div> <!-- banner -->
|
||||
|
||||
<div id="topnav">
|
||||
<a href="http://troydhanson.github.com/uthash/">uthash home</a> >
|
||||
<a href="http://troydhanson.github.io/uthash/">uthash home</a> >
|
||||
BSD license
|
||||
</div>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<div id="mid">
|
||||
<div id="main">
|
||||
<pre>
|
||||
Copyright (c) 2005-2018, Troy D. Hanson http://troydhanson.github.com/uthash/
|
||||
Copyright (c) 2005-2021, Troy D. Hanson http://troydhanson.github.io/uthash/
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
||||
222
dependencies/uthash/doc/userguide.txt
vendored
222
dependencies/uthash/doc/userguide.txt
vendored
@@ -1,7 +1,7 @@
|
||||
uthash User Guide
|
||||
=================
|
||||
Troy D. Hanson, Arthur O'Dwyer
|
||||
v2.1.0, December 2018
|
||||
v2.3.0, February 2021
|
||||
|
||||
To download uthash, follow this link back to the
|
||||
https://github.com/troydhanson/uthash[GitHub project page].
|
||||
@@ -215,10 +215,10 @@ a unique value. Then call `HASH_ADD`. (Here we use the convenience macro
|
||||
void add_user(int user_id, char *name) {
|
||||
struct my_struct *s;
|
||||
|
||||
s = malloc(sizeof(struct my_struct));
|
||||
s = malloc(sizeof *s);
|
||||
s->id = user_id;
|
||||
strcpy(s->name, name);
|
||||
HASH_ADD_INT( users, id, s ); /* id: name of key field */
|
||||
HASH_ADD_INT(users, id, s); /* id: name of key field */
|
||||
}
|
||||
----------------------------------------------------------------------
|
||||
|
||||
@@ -227,7 +227,7 @@ second parameter is the 'name' of the key field. Here, this is `id`. The
|
||||
last parameter is a pointer to the structure being added.
|
||||
|
||||
[[validc]]
|
||||
.Wait.. the field name is a parameter?
|
||||
.Wait.. the parameter is a field name?
|
||||
*******************************************************************************
|
||||
If you find it strange that `id`, which is the 'name of a field' in the
|
||||
structure, can be passed as a parameter... welcome to the world of macros. Don't
|
||||
@@ -256,10 +256,10 @@ Otherwise we just modify the structure that already exists.
|
||||
struct my_struct *s;
|
||||
|
||||
HASH_FIND_INT(users, &user_id, s); /* id already in the hash? */
|
||||
if (s==NULL) {
|
||||
if (s == NULL) {
|
||||
s = (struct my_struct *)malloc(sizeof *s);
|
||||
s->id = user_id;
|
||||
HASH_ADD_INT( users, id, s ); /* id: name of key field */
|
||||
HASH_ADD_INT(users, id, s); /* id: name of key field */
|
||||
}
|
||||
strcpy(s->name, name);
|
||||
}
|
||||
@@ -284,7 +284,7 @@ right.
|
||||
/* bad */
|
||||
void add_user(struct my_struct *users, int user_id, char *name) {
|
||||
...
|
||||
HASH_ADD_INT( users, id, s );
|
||||
HASH_ADD_INT(users, id, s);
|
||||
}
|
||||
|
||||
You really need to pass 'a pointer' to the hash pointer:
|
||||
@@ -292,7 +292,7 @@ You really need to pass 'a pointer' to the hash pointer:
|
||||
/* good */
|
||||
void add_user(struct my_struct **users, int user_id, char *name) { ...
|
||||
...
|
||||
HASH_ADD_INT( *users, id, s );
|
||||
HASH_ADD_INT(*users, id, s);
|
||||
}
|
||||
|
||||
Note that we dereferenced the pointer in the `HASH_ADD` also.
|
||||
@@ -319,7 +319,7 @@ To look up a structure in a hash, you need its key. Then call `HASH_FIND`.
|
||||
struct my_struct *find_user(int user_id) {
|
||||
struct my_struct *s;
|
||||
|
||||
HASH_FIND_INT( users, &user_id, s ); /* s: output pointer */
|
||||
HASH_FIND_INT(users, &user_id, s); /* s: output pointer */
|
||||
return s;
|
||||
}
|
||||
----------------------------------------------------------------------
|
||||
@@ -376,8 +376,8 @@ void delete_all() {
|
||||
struct my_struct *current_user, *tmp;
|
||||
|
||||
HASH_ITER(hh, users, current_user, tmp) {
|
||||
HASH_DEL(users,current_user); /* delete; users advances to next */
|
||||
free(current_user); /* optional- if you want to free */
|
||||
HASH_DEL(users, current_user); /* delete; users advances to next */
|
||||
free(current_user); /* optional- if you want to free */
|
||||
}
|
||||
}
|
||||
----------------------------------------------------------------------
|
||||
@@ -387,7 +387,7 @@ All-at-once deletion
|
||||
If you only want to delete all the items, but not free them or do any
|
||||
per-element clean up, you can do this more efficiently in a single operation:
|
||||
|
||||
HASH_CLEAR(hh,users);
|
||||
HASH_CLEAR(hh, users);
|
||||
|
||||
Afterward, the list head (here, `users`) will be set to `NULL`.
|
||||
|
||||
@@ -403,7 +403,7 @@ num_users = HASH_COUNT(users);
|
||||
printf("there are %u users\n", num_users);
|
||||
----------------------------------------------------------------------
|
||||
|
||||
Incidentally, this works even the list (`users`, here) is `NULL`, in
|
||||
Incidentally, this works even if the list head (here, `users`) is `NULL`, in
|
||||
which case the count is 0.
|
||||
|
||||
Iterating and sorting
|
||||
@@ -417,7 +417,7 @@ following the `hh.next` pointer.
|
||||
void print_users() {
|
||||
struct my_struct *s;
|
||||
|
||||
for(s=users; s != NULL; s=s->hh.next) {
|
||||
for (s = users; s != NULL; s = s->hh.next) {
|
||||
printf("user id %d: name %s\n", s->id, s->name);
|
||||
}
|
||||
}
|
||||
@@ -430,7 +430,7 @@ the hash, starting from any known item.
|
||||
Deletion-safe iteration
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
In the example above, it would not be safe to delete and free `s` in the body
|
||||
of the 'for' loop, (because `s` is derefenced each time the loop iterates).
|
||||
of the 'for' loop, (because `s` is dereferenced each time the loop iterates).
|
||||
This is easy to rewrite correctly (by copying the `s->hh.next` pointer to a
|
||||
temporary variable 'before' freeing `s`), but it comes up often enough that a
|
||||
deletion-safe iteration macro, `HASH_ITER`, is included. It expands to a
|
||||
@@ -452,14 +452,14 @@ doubly-linked list.
|
||||
*******************************************************************************
|
||||
|
||||
If you're using uthash in a C++ program, you need an extra cast on the `for`
|
||||
iterator, e.g., `s=(struct my_struct*)s->hh.next`.
|
||||
iterator, e.g., `s = static_cast<my_struct*>(s->hh.next)`.
|
||||
|
||||
Sorting
|
||||
^^^^^^^
|
||||
The items in the hash are visited in "insertion order" when you follow the
|
||||
`hh.next` pointer. You can sort the items into a new order using `HASH_SORT`.
|
||||
|
||||
HASH_SORT( users, name_sort );
|
||||
HASH_SORT(users, name_sort);
|
||||
|
||||
The second argument is a pointer to a comparison function. It must accept two
|
||||
pointer arguments (the items to compare), and must return an `int` which is
|
||||
@@ -479,20 +479,20 @@ Below, `name_sort` and `id_sort` are two examples of sort functions.
|
||||
|
||||
.Sorting the items in the hash
|
||||
----------------------------------------------------------------------
|
||||
int name_sort(struct my_struct *a, struct my_struct *b) {
|
||||
return strcmp(a->name,b->name);
|
||||
int by_name(const struct my_struct *a, const struct my_struct *b) {
|
||||
return strcmp(a->name, b->name);
|
||||
}
|
||||
|
||||
int id_sort(struct my_struct *a, struct my_struct *b) {
|
||||
int by_id(const struct my_struct *a, const struct my_struct *b) {
|
||||
return (a->id - b->id);
|
||||
}
|
||||
|
||||
void sort_by_name() {
|
||||
HASH_SORT(users, name_sort);
|
||||
HASH_SORT(users, by_name);
|
||||
}
|
||||
|
||||
void sort_by_id() {
|
||||
HASH_SORT(users, id_sort);
|
||||
HASH_SORT(users, by_id);
|
||||
}
|
||||
----------------------------------------------------------------------
|
||||
|
||||
@@ -516,85 +516,100 @@ Follow the prompts to try the program.
|
||||
|
||||
.A complete program
|
||||
----------------------------------------------------------------------
|
||||
#include <stdio.h> /* gets */
|
||||
#include <stdio.h> /* printf */
|
||||
#include <stdlib.h> /* atoi, malloc */
|
||||
#include <string.h> /* strcpy */
|
||||
#include "uthash.h"
|
||||
|
||||
struct my_struct {
|
||||
int id; /* key */
|
||||
char name[10];
|
||||
char name[21];
|
||||
UT_hash_handle hh; /* makes this structure hashable */
|
||||
};
|
||||
|
||||
struct my_struct *users = NULL;
|
||||
|
||||
void add_user(int user_id, char *name) {
|
||||
void add_user(int user_id, const char *name)
|
||||
{
|
||||
struct my_struct *s;
|
||||
|
||||
HASH_FIND_INT(users, &user_id, s); /* id already in the hash? */
|
||||
if (s==NULL) {
|
||||
s = (struct my_struct *)malloc(sizeof *s);
|
||||
s->id = user_id;
|
||||
HASH_ADD_INT( users, id, s ); /* id: name of key field */
|
||||
if (s == NULL) {
|
||||
s = (struct my_struct*)malloc(sizeof *s);
|
||||
s->id = user_id;
|
||||
HASH_ADD_INT(users, id, s); /* id is the key field */
|
||||
}
|
||||
strcpy(s->name, name);
|
||||
}
|
||||
|
||||
struct my_struct *find_user(int user_id) {
|
||||
struct my_struct *find_user(int user_id)
|
||||
{
|
||||
struct my_struct *s;
|
||||
|
||||
HASH_FIND_INT( users, &user_id, s ); /* s: output pointer */
|
||||
HASH_FIND_INT(users, &user_id, s); /* s: output pointer */
|
||||
return s;
|
||||
}
|
||||
|
||||
void delete_user(struct my_struct *user) {
|
||||
void delete_user(struct my_struct *user)
|
||||
{
|
||||
HASH_DEL(users, user); /* user: pointer to deletee */
|
||||
free(user);
|
||||
}
|
||||
|
||||
void delete_all() {
|
||||
struct my_struct *current_user, *tmp;
|
||||
void delete_all()
|
||||
{
|
||||
struct my_struct *current_user;
|
||||
struct my_struct *tmp;
|
||||
|
||||
HASH_ITER(hh, users, current_user, tmp) {
|
||||
HASH_DEL(users, current_user); /* delete it (users advances to next) */
|
||||
free(current_user); /* free it */
|
||||
}
|
||||
HASH_ITER(hh, users, current_user, tmp) {
|
||||
HASH_DEL(users, current_user); /* delete it (users advances to next) */
|
||||
free(current_user); /* free it */
|
||||
}
|
||||
}
|
||||
|
||||
void print_users() {
|
||||
void print_users()
|
||||
{
|
||||
struct my_struct *s;
|
||||
|
||||
for(s=users; s != NULL; s=(struct my_struct*)(s->hh.next)) {
|
||||
for (s = users; s != NULL; s = (struct my_struct*)(s->hh.next)) {
|
||||
printf("user id %d: name %s\n", s->id, s->name);
|
||||
}
|
||||
}
|
||||
|
||||
int name_sort(struct my_struct *a, struct my_struct *b) {
|
||||
return strcmp(a->name,b->name);
|
||||
int by_name(const struct my_struct *a, const struct my_struct *b)
|
||||
{
|
||||
return strcmp(a->name, b->name);
|
||||
}
|
||||
|
||||
int id_sort(struct my_struct *a, struct my_struct *b) {
|
||||
int by_id(const struct my_struct *a, const struct my_struct *b)
|
||||
{
|
||||
return (a->id - b->id);
|
||||
}
|
||||
|
||||
void sort_by_name() {
|
||||
HASH_SORT(users, name_sort);
|
||||
const char *getl(const char *prompt)
|
||||
{
|
||||
static char buf[21];
|
||||
char *p;
|
||||
printf("%s? ", prompt); fflush(stdout);
|
||||
p = fgets(buf, sizeof(buf), stdin);
|
||||
if (p == NULL || (p = strchr(buf, '\n')) == NULL) {
|
||||
puts("Invalid input!");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
*p = '\0';
|
||||
return buf;
|
||||
}
|
||||
|
||||
void sort_by_id() {
|
||||
HASH_SORT(users, id_sort);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
char in[10];
|
||||
int id=1, running=1;
|
||||
int main()
|
||||
{
|
||||
int id = 1;
|
||||
int running = 1;
|
||||
struct my_struct *s;
|
||||
unsigned num_users;
|
||||
int temp;
|
||||
|
||||
while (running) {
|
||||
printf(" 1. add user\n");
|
||||
printf(" 2. add/rename user by id\n");
|
||||
printf(" 2. add or rename user by id\n");
|
||||
printf(" 3. find user\n");
|
||||
printf(" 4. delete user\n");
|
||||
printf(" 5. delete all users\n");
|
||||
@@ -603,47 +618,44 @@ int main(int argc, char *argv[]) {
|
||||
printf(" 8. print users\n");
|
||||
printf(" 9. count users\n");
|
||||
printf("10. quit\n");
|
||||
gets(in);
|
||||
switch(atoi(in)) {
|
||||
switch (atoi(getl("Command"))) {
|
||||
case 1:
|
||||
printf("name?\n");
|
||||
add_user(id++, gets(in));
|
||||
add_user(id++, getl("Name (20 char max)"));
|
||||
break;
|
||||
case 2:
|
||||
printf("id?\n");
|
||||
gets(in); id = atoi(in);
|
||||
printf("name?\n");
|
||||
add_user(id, gets(in));
|
||||
temp = atoi(getl("ID"));
|
||||
add_user(temp, getl("Name (20 char max)"));
|
||||
break;
|
||||
case 3:
|
||||
printf("id?\n");
|
||||
s = find_user(atoi(gets(in)));
|
||||
s = find_user(atoi(getl("ID to find")));
|
||||
printf("user: %s\n", s ? s->name : "unknown");
|
||||
break;
|
||||
case 4:
|
||||
printf("id?\n");
|
||||
s = find_user(atoi(gets(in)));
|
||||
if (s) delete_user(s);
|
||||
else printf("id unknown\n");
|
||||
s = find_user(atoi(getl("ID to delete")));
|
||||
if (s) {
|
||||
delete_user(s);
|
||||
} else {
|
||||
printf("id unknown\n");
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
delete_all();
|
||||
break;
|
||||
case 6:
|
||||
sort_by_name();
|
||||
HASH_SORT(users, by_name);
|
||||
break;
|
||||
case 7:
|
||||
sort_by_id();
|
||||
HASH_SORT(users, by_id);
|
||||
break;
|
||||
case 8:
|
||||
print_users();
|
||||
break;
|
||||
case 9:
|
||||
num_users=HASH_COUNT(users);
|
||||
printf("there are %u users\n", num_users);
|
||||
temp = HASH_COUNT(users);
|
||||
printf("there are %d users\n", temp);
|
||||
break;
|
||||
case 10:
|
||||
running=0;
|
||||
running = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -720,10 +732,10 @@ int main(int argc, char *argv[]) {
|
||||
s = (struct my_struct *)malloc(sizeof *s);
|
||||
strcpy(s->name, names[i]);
|
||||
s->id = i;
|
||||
HASH_ADD_STR( users, name, s );
|
||||
HASH_ADD_STR(users, name, s);
|
||||
}
|
||||
|
||||
HASH_FIND_STR( users, "betty", s);
|
||||
HASH_FIND_STR(users, "betty", s);
|
||||
if (s) printf("betty's id is %d\n", s->id);
|
||||
|
||||
/* free the hash table contents */
|
||||
@@ -766,10 +778,10 @@ int main(int argc, char *argv[]) {
|
||||
s = (struct my_struct *)malloc(sizeof *s);
|
||||
s->name = names[i];
|
||||
s->id = i;
|
||||
HASH_ADD_KEYPTR( hh, users, s->name, strlen(s->name), s );
|
||||
HASH_ADD_KEYPTR(hh, users, s->name, strlen(s->name), s);
|
||||
}
|
||||
|
||||
HASH_FIND_STR( users, "betty", s);
|
||||
HASH_FIND_STR(users, "betty", s);
|
||||
if (s) printf("betty's id is %d\n", s->id);
|
||||
|
||||
/* free the hash table contents */
|
||||
@@ -812,12 +824,12 @@ int main() {
|
||||
if (!e) return -1;
|
||||
e->key = (void*)someaddr;
|
||||
e->i = 1;
|
||||
HASH_ADD_PTR(hash,key,e);
|
||||
HASH_ADD_PTR(hash, key, e);
|
||||
HASH_FIND_PTR(hash, &someaddr, d);
|
||||
if (d) printf("found\n");
|
||||
|
||||
/* release memory */
|
||||
HASH_DEL(hash,e);
|
||||
HASH_DEL(hash, e);
|
||||
free(e);
|
||||
return 0;
|
||||
}
|
||||
@@ -924,7 +936,7 @@ int main(int argc, char *argv[]) {
|
||||
int beijing[] = {0x5317, 0x4eac}; /* UTF-32LE for 北京 */
|
||||
|
||||
/* allocate and initialize our structure */
|
||||
msg = (msg_t *)malloc( sizeof(msg_t) + sizeof(beijing) );
|
||||
msg = (msg_t *)malloc(sizeof(msg_t) + sizeof(beijing));
|
||||
memset(msg, 0, sizeof(msg_t)+sizeof(beijing)); /* zero fill */
|
||||
msg->len = sizeof(beijing);
|
||||
msg->encoding = UTF32;
|
||||
@@ -936,16 +948,16 @@ int main(int argc, char *argv[]) {
|
||||
- offsetof(msg_t, encoding); /* offset of first key field */
|
||||
|
||||
/* add our structure to the hash table */
|
||||
HASH_ADD( hh, msgs, encoding, keylen, msg);
|
||||
HASH_ADD(hh, msgs, encoding, keylen, msg);
|
||||
|
||||
/* look it up to prove that it worked :-) */
|
||||
msg=NULL;
|
||||
msg = NULL;
|
||||
|
||||
lookup_key = (lookup_key_t *)malloc(sizeof(*lookup_key) + sizeof(beijing));
|
||||
memset(lookup_key, 0, sizeof(*lookup_key) + sizeof(beijing));
|
||||
lookup_key->encoding = UTF32;
|
||||
memcpy(lookup_key->text, beijing, sizeof(beijing));
|
||||
HASH_FIND( hh, msgs, &lookup_key->encoding, keylen, msg );
|
||||
HASH_FIND(hh, msgs, &lookup_key->encoding, keylen, msg);
|
||||
if (msg) printf("found \n");
|
||||
free(lookup_key);
|
||||
|
||||
@@ -1028,7 +1040,7 @@ typedef struct item {
|
||||
UT_hash_handle hh;
|
||||
} item_t;
|
||||
|
||||
item_t *items=NULL;
|
||||
item_t *items = NULL;
|
||||
|
||||
int main(int argc, char *argvp[]) {
|
||||
item_t *item1, *item2, *tmp1, *tmp2;
|
||||
@@ -1119,7 +1131,7 @@ always used with the `users_by_name` hash table).
|
||||
int i;
|
||||
char *name;
|
||||
|
||||
s = malloc(sizeof(struct my_struct));
|
||||
s = malloc(sizeof *s);
|
||||
s->id = 1;
|
||||
strcpy(s->username, "thanson");
|
||||
|
||||
@@ -1128,7 +1140,7 @@ always used with the `users_by_name` hash table).
|
||||
HASH_ADD(hh2, users_by_name, username, strlen(s->username), s);
|
||||
|
||||
/* find user by ID in the "users_by_id" hash table */
|
||||
i=1;
|
||||
i = 1;
|
||||
HASH_FIND(hh1, users_by_id, &i, sizeof(int), s);
|
||||
if (s) printf("found id %d: %s\n", i, s->username);
|
||||
|
||||
@@ -1155,7 +1167,7 @@ The `HASH_ADD_INORDER*` macros work just like their `HASH_ADD*` counterparts, bu
|
||||
with an additional comparison-function argument:
|
||||
|
||||
int name_sort(struct my_struct *a, struct my_struct *b) {
|
||||
return strcmp(a->name,b->name);
|
||||
return strcmp(a->name, b->name);
|
||||
}
|
||||
|
||||
HASH_ADD_KEYPTR_INORDER(hh, items, &item->name, strlen(item->name), item, name_sort);
|
||||
@@ -1183,7 +1195,7 @@ Now we can define two sort functions, then use `HASH_SRT`.
|
||||
}
|
||||
|
||||
int sort_by_name(struct my_struct *a, struct my_struct *b) {
|
||||
return strcmp(a->username,b->username);
|
||||
return strcmp(a->username, b->username);
|
||||
}
|
||||
|
||||
HASH_SRT(hh1, users_by_id, sort_by_id);
|
||||
@@ -1240,7 +1252,8 @@ for a structure to be usable with `HASH_SELECT`, it must have two or more hash
|
||||
handles. (As described <<multihash,here>>, a structure can exist in many
|
||||
hash tables at the same time; it must have a separate hash handle for each one).
|
||||
|
||||
user_t *users=NULL, *admins=NULL; /* two hash tables */
|
||||
user_t *users = NULL; /* hash table of users */
|
||||
user_t *admins = NULL; /* hash table of admins */
|
||||
|
||||
typedef struct {
|
||||
int id;
|
||||
@@ -1252,25 +1265,26 @@ Now suppose we have added some users, and want to select just the administrator
|
||||
users who have id's less than 1024.
|
||||
|
||||
#define is_admin(x) (((user_t*)x)->id < 1024)
|
||||
HASH_SELECT(ah,admins,hh,users,is_admin);
|
||||
HASH_SELECT(ah, admins, hh, users, is_admin);
|
||||
|
||||
The first two parameters are the 'destination' hash handle and hash table, the
|
||||
second two parameters are the 'source' hash handle and hash table, and the last
|
||||
parameter is the 'select condition'. Here we used a macro `is_admin()` but we
|
||||
parameter is the 'select condition'. Here we used a macro `is_admin(x)` but we
|
||||
could just as well have used a function.
|
||||
|
||||
int is_admin(void *userv) {
|
||||
user_t *user = (user_t*)userv;
|
||||
int is_admin(const void *userv) {
|
||||
user_t *user = (const user_t*)userv;
|
||||
return (user->id < 1024) ? 1 : 0;
|
||||
}
|
||||
|
||||
If the select condition always evaluates to true, this operation is
|
||||
essentially a 'merge' of the source hash into the destination hash. Of course,
|
||||
the source hash remains unchanged under any use of `HASH_SELECT`. It only adds
|
||||
items to the destination hash selectively.
|
||||
essentially a 'merge' of the source hash into the destination hash.
|
||||
|
||||
The two hash handles must differ. An example of using `HASH_SELECT` is included
|
||||
in `tests/test36.c`.
|
||||
`HASH_SELECT` adds items to the destination without removing them from
|
||||
the source; the source hash table remains unchanged. The destination hash table
|
||||
must not be the same as the source hash table.
|
||||
|
||||
An example of using `HASH_SELECT` is included in `tests/test36.c`.
|
||||
|
||||
[[hash_keycompare]]
|
||||
Specifying an alternate key comparison function
|
||||
@@ -1290,7 +1304,7 @@ that do not provide `memcmp`, you can substitute your own implementation.
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
#undef HASH_KEYCMP
|
||||
#define HASH_KEYCMP(a,b,len) bcmp(a,b,len)
|
||||
#define HASH_KEYCMP(a,b,len) bcmp(a, b, len)
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
Another reason to substitute your own key comparison function is if your "key" is not
|
||||
@@ -1631,7 +1645,7 @@ If your application uses its own custom allocator, uthash can use them too.
|
||||
|
||||
/* re-define, specifying alternate functions */
|
||||
#define uthash_malloc(sz) my_malloc(sz)
|
||||
#define uthash_free(ptr,sz) my_free(ptr)
|
||||
#define uthash_free(ptr, sz) my_free(ptr)
|
||||
|
||||
...
|
||||
----------------------------------------------------------------------------
|
||||
@@ -1647,7 +1661,7 @@ provide these functions, you can substitute your own implementations.
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
#undef uthash_bzero
|
||||
#define uthash_bzero(a,len) my_bzero(a,len)
|
||||
#define uthash_bzero(a, len) my_bzero(a, len)
|
||||
|
||||
#undef uthash_strlen
|
||||
#define uthash_strlen(s) my_strlen(s)
|
||||
@@ -1754,7 +1768,7 @@ concurrent readers (since uthash 1.5).
|
||||
For example using pthreads you can create an rwlock like this:
|
||||
|
||||
pthread_rwlock_t lock;
|
||||
if (pthread_rwlock_init(&lock,NULL) != 0) fatal("can't create rwlock");
|
||||
if (pthread_rwlock_init(&lock, NULL) != 0) fatal("can't create rwlock");
|
||||
|
||||
Then, readers must acquire the read lock before doing any `HASH_FIND` calls or
|
||||
before iterating over the hash elements:
|
||||
@@ -1795,10 +1809,10 @@ In order to use the convenience macros,
|
||||
|===============================================================================
|
||||
|macro | arguments
|
||||
|HASH_ADD_INT | (head, keyfield_name, item_ptr)
|
||||
|HASH_REPLACE_INT | (head, keyfiled_name, item_ptr,replaced_item_ptr)
|
||||
|HASH_REPLACE_INT | (head, keyfield_name, item_ptr, replaced_item_ptr)
|
||||
|HASH_FIND_INT | (head, key_ptr, item_ptr)
|
||||
|HASH_ADD_STR | (head, keyfield_name, item_ptr)
|
||||
|HASH_REPLACE_STR | (head,keyfield_name, item_ptr, replaced_item_ptr)
|
||||
|HASH_REPLACE_STR | (head, keyfield_name, item_ptr, replaced_item_ptr)
|
||||
|HASH_FIND_STR | (head, key_ptr, item_ptr)
|
||||
|HASH_ADD_PTR | (head, keyfield_name, item_ptr)
|
||||
|HASH_REPLACE_PTR | (head, keyfield_name, item_ptr, replaced_item_ptr)
|
||||
|
||||
2
dependencies/uthash/doc/utarray.txt
vendored
2
dependencies/uthash/doc/utarray.txt
vendored
@@ -1,7 +1,7 @@
|
||||
utarray: dynamic array macros for C
|
||||
===================================
|
||||
Troy D. Hanson <tdh@tkhanson.net>
|
||||
v2.1.0, December 2018
|
||||
v2.3.0, February 2021
|
||||
|
||||
Here's a link back to the https://github.com/troydhanson/uthash[GitHub project page].
|
||||
|
||||
|
||||
2
dependencies/uthash/doc/utlist.txt
vendored
2
dependencies/uthash/doc/utlist.txt
vendored
@@ -1,7 +1,7 @@
|
||||
utlist: linked list macros for C structures
|
||||
===========================================
|
||||
Troy D. Hanson <tdh@tkhanson.net>
|
||||
v2.1.0, December 2018
|
||||
v2.3.0, February 2021
|
||||
|
||||
Here's a link back to the https://github.com/troydhanson/uthash[GitHub project page].
|
||||
|
||||
|
||||
2
dependencies/uthash/doc/utringbuffer.txt
vendored
2
dependencies/uthash/doc/utringbuffer.txt
vendored
@@ -1,7 +1,7 @@
|
||||
utringbuffer: dynamic ring-buffer macros for C
|
||||
==============================================
|
||||
Arthur O'Dwyer <arthur.j.odwyer@gmail.com>
|
||||
v2.1.0, December 2018
|
||||
v2.3.0, February 2021
|
||||
|
||||
Here's a link back to the https://github.com/troydhanson/uthash[GitHub project page].
|
||||
|
||||
|
||||
2
dependencies/uthash/doc/utstack.txt
vendored
2
dependencies/uthash/doc/utstack.txt
vendored
@@ -1,7 +1,7 @@
|
||||
utstack: intrusive stack macros for C
|
||||
=====================================
|
||||
Arthur O'Dwyer <arthur.j.odwyer@gmail.com>
|
||||
v2.1.0, December 2018
|
||||
v2.3.0, February 2021
|
||||
|
||||
Here's a link back to the https://github.com/troydhanson/uthash[GitHub project page].
|
||||
|
||||
|
||||
2
dependencies/uthash/doc/utstring.txt
vendored
2
dependencies/uthash/doc/utstring.txt
vendored
@@ -1,7 +1,7 @@
|
||||
utstring: dynamic string macros for C
|
||||
=====================================
|
||||
Troy D. Hanson <tdh@tkhanson.net>
|
||||
v2.1.0, December 2018
|
||||
v2.3.0, February 2021
|
||||
|
||||
Here's a link back to the https://github.com/troydhanson/uthash[GitHub project page].
|
||||
|
||||
|
||||
2
dependencies/uthash/package.json
vendored
2
dependencies/uthash/package.json
vendored
@@ -24,5 +24,5 @@
|
||||
"src/utstring.h"
|
||||
],
|
||||
|
||||
"version": "2.1.0"
|
||||
"version": "2.3.0"
|
||||
}
|
||||
|
||||
9
dependencies/uthash/src/utarray.h
vendored
9
dependencies/uthash/src/utarray.h
vendored
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (c) 2008-2018, Troy D. Hanson http://troydhanson.github.com/uthash/
|
||||
Copyright (c) 2008-2021, Troy D. Hanson http://troydhanson.github.io/uthash/
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
@@ -26,7 +26,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#ifndef UTARRAY_H
|
||||
#define UTARRAY_H
|
||||
|
||||
#define UTARRAY_VERSION 2.1.0
|
||||
#define UTARRAY_VERSION 2.3.0
|
||||
|
||||
#include <stddef.h> /* size_t */
|
||||
#include <string.h> /* memset, etc */
|
||||
@@ -232,8 +232,9 @@ typedef struct {
|
||||
|
||||
/* last we pre-define a few icd for common utarrays of ints and strings */
|
||||
static void utarray_str_cpy(void *dst, const void *src) {
|
||||
char **_src = (char**)src, **_dst = (char**)dst;
|
||||
*_dst = (*_src == NULL) ? NULL : strdup(*_src);
|
||||
char *const *srcc = (char *const *)src;
|
||||
char **dstc = (char**)dst;
|
||||
*dstc = (*srcc == NULL) ? NULL : strdup(*srcc);
|
||||
}
|
||||
static void utarray_str_dtor(void *elt) {
|
||||
char **eltc = (char**)elt;
|
||||
|
||||
64
dependencies/uthash/src/uthash.h
vendored
64
dependencies/uthash/src/uthash.h
vendored
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (c) 2003-2018, Troy D. Hanson http://troydhanson.github.com/uthash/
|
||||
Copyright (c) 2003-2021, Troy D. Hanson http://troydhanson.github.io/uthash/
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
@@ -24,12 +24,22 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#ifndef UTHASH_H
|
||||
#define UTHASH_H
|
||||
|
||||
#define UTHASH_VERSION 2.1.0
|
||||
#define UTHASH_VERSION 2.3.0
|
||||
|
||||
#include <string.h> /* memcmp, memset, strlen */
|
||||
#include <stddef.h> /* ptrdiff_t */
|
||||
#include <stdlib.h> /* exit */
|
||||
|
||||
#if defined(HASH_DEFINE_OWN_STDINT) && HASH_DEFINE_OWN_STDINT
|
||||
/* This codepath is provided for backward compatibility, but I plan to remove it. */
|
||||
#warning "HASH_DEFINE_OWN_STDINT is deprecated; please use HASH_NO_STDINT instead"
|
||||
typedef unsigned int uint32_t;
|
||||
typedef unsigned char uint8_t;
|
||||
#elif defined(HASH_NO_STDINT) && HASH_NO_STDINT
|
||||
#else
|
||||
#include <stdint.h> /* uint8_t, uint32_t */
|
||||
#endif
|
||||
|
||||
/* These macros use decltype or the earlier __typeof GNU extension.
|
||||
As decltype is only available in newer compilers (VS2010 or gcc 4.3+
|
||||
when compiling c++ source) this code uses whatever method is needed
|
||||
@@ -62,23 +72,6 @@ do {
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
/* a number of the hash function use uint32_t which isn't defined on Pre VS2010 */
|
||||
#if defined(_WIN32)
|
||||
#if defined(_MSC_VER) && _MSC_VER >= 1600
|
||||
#include <stdint.h>
|
||||
#elif defined(__WATCOMC__) || defined(__MINGW32__) || defined(__CYGWIN__)
|
||||
#include <stdint.h>
|
||||
#else
|
||||
typedef unsigned int uint32_t;
|
||||
typedef unsigned char uint8_t;
|
||||
#endif
|
||||
#elif defined(__GNUC__) && !defined(__VXWORKS__)
|
||||
#include <stdint.h>
|
||||
#else
|
||||
typedef unsigned int uint32_t;
|
||||
typedef unsigned char uint8_t;
|
||||
#endif
|
||||
|
||||
#ifndef uthash_malloc
|
||||
#define uthash_malloc(sz) malloc(sz) /* malloc fcn */
|
||||
#endif
|
||||
@@ -92,15 +85,12 @@ typedef unsigned char uint8_t;
|
||||
#define uthash_strlen(s) strlen(s)
|
||||
#endif
|
||||
|
||||
#ifdef uthash_memcmp
|
||||
/* This warning will not catch programs that define uthash_memcmp AFTER including uthash.h. */
|
||||
#warning "uthash_memcmp is deprecated; please use HASH_KEYCMP instead"
|
||||
#else
|
||||
#define uthash_memcmp(a,b,n) memcmp(a,b,n)
|
||||
#ifndef HASH_FUNCTION
|
||||
#define HASH_FUNCTION(keyptr,keylen,hashv) HASH_JEN(keyptr, keylen, hashv)
|
||||
#endif
|
||||
|
||||
#ifndef HASH_KEYCMP
|
||||
#define HASH_KEYCMP(a,b,n) uthash_memcmp(a,b,n)
|
||||
#define HASH_KEYCMP(a,b,n) memcmp(a,b,n)
|
||||
#endif
|
||||
|
||||
#ifndef uthash_noexpand_fyi
|
||||
@@ -158,7 +148,7 @@ do {
|
||||
|
||||
#define HASH_VALUE(keyptr,keylen,hashv) \
|
||||
do { \
|
||||
HASH_FCN(keyptr, keylen, hashv); \
|
||||
HASH_FUNCTION(keyptr, keylen, hashv); \
|
||||
} while (0)
|
||||
|
||||
#define HASH_FIND_BYHASHVALUE(hh,head,keyptr,keylen,hashval,out) \
|
||||
@@ -408,7 +398,7 @@ do {
|
||||
do { \
|
||||
IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \
|
||||
(add)->hh.hashv = (hashval); \
|
||||
(add)->hh.key = (char*) (keyptr); \
|
||||
(add)->hh.key = (const void*) (keyptr); \
|
||||
(add)->hh.keylen = (unsigned) (keylen_in); \
|
||||
if (!(head)) { \
|
||||
(add)->hh.next = NULL; \
|
||||
@@ -590,13 +580,6 @@ do {
|
||||
#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen)
|
||||
#endif
|
||||
|
||||
/* default to Jenkin's hash unless overridden e.g. DHASH_FUNCTION=HASH_SAX */
|
||||
#ifdef HASH_FUNCTION
|
||||
#define HASH_FCN HASH_FUNCTION
|
||||
#else
|
||||
#define HASH_FCN HASH_JEN
|
||||
#endif
|
||||
|
||||
/* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. */
|
||||
#define HASH_BER(key,keylen,hashv) \
|
||||
do { \
|
||||
@@ -695,7 +678,8 @@ do {
|
||||
case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); /* FALLTHROUGH */ \
|
||||
case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); /* FALLTHROUGH */ \
|
||||
case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); /* FALLTHROUGH */ \
|
||||
case 1: _hj_i += _hj_key[0]; \
|
||||
case 1: _hj_i += _hj_key[0]; /* FALLTHROUGH */ \
|
||||
default: ; \
|
||||
} \
|
||||
HASH_JEN_MIX(_hj_i, _hj_j, hashv); \
|
||||
} while (0)
|
||||
@@ -743,6 +727,8 @@ do {
|
||||
case 1: hashv += *_sfh_key; \
|
||||
hashv ^= hashv << 10; \
|
||||
hashv += hashv >> 1; \
|
||||
break; \
|
||||
default: ; \
|
||||
} \
|
||||
\
|
||||
/* Force "avalanching" of final 127 bits */ \
|
||||
@@ -764,7 +750,7 @@ do {
|
||||
} \
|
||||
while ((out) != NULL) { \
|
||||
if ((out)->hh.hashv == (hashval) && (out)->hh.keylen == (keylen_in)) { \
|
||||
if (HASH_KEYCMP((out)->hh.key, keyptr, keylen_in) == 0) { \
|
||||
if (HASH_KEYCMP((out)->hh.key, keyptr, keylen_in) == 0) { \
|
||||
break; \
|
||||
} \
|
||||
} \
|
||||
@@ -850,12 +836,12 @@ do {
|
||||
struct UT_hash_handle *_he_thh, *_he_hh_nxt; \
|
||||
UT_hash_bucket *_he_new_buckets, *_he_newbkt; \
|
||||
_he_new_buckets = (UT_hash_bucket*)uthash_malloc( \
|
||||
2UL * (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \
|
||||
sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \
|
||||
if (!_he_new_buckets) { \
|
||||
HASH_RECORD_OOM(oomed); \
|
||||
} else { \
|
||||
uthash_bzero(_he_new_buckets, \
|
||||
2UL * (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \
|
||||
sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \
|
||||
(tbl)->ideal_chain_maxlen = \
|
||||
((tbl)->num_items >> ((tbl)->log2_num_buckets+1U)) + \
|
||||
((((tbl)->num_items & (((tbl)->num_buckets*2U)-1U)) != 0U) ? 1U : 0U); \
|
||||
@@ -1142,7 +1128,7 @@ typedef struct UT_hash_handle {
|
||||
void *next; /* next element in app order */
|
||||
struct UT_hash_handle *hh_prev; /* previous hh in bucket order */
|
||||
struct UT_hash_handle *hh_next; /* next hh in bucket order */
|
||||
void *key; /* ptr to enclosing struct's key */
|
||||
const void *key; /* ptr to enclosing struct's key */
|
||||
unsigned keylen; /* enclosing struct's key len */
|
||||
unsigned hashv; /* result of hash-fcn(key) */
|
||||
} UT_hash_handle;
|
||||
|
||||
4
dependencies/uthash/src/utlist.h
vendored
4
dependencies/uthash/src/utlist.h
vendored
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (c) 2007-2018, Troy D. Hanson http://troydhanson.github.com/uthash/
|
||||
Copyright (c) 2007-2021, Troy D. Hanson http://troydhanson.github.io/uthash/
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
@@ -24,7 +24,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#ifndef UTLIST_H
|
||||
#define UTLIST_H
|
||||
|
||||
#define UTLIST_VERSION 2.1.0
|
||||
#define UTLIST_VERSION 2.3.0
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
|
||||
4
dependencies/uthash/src/utringbuffer.h
vendored
4
dependencies/uthash/src/utringbuffer.h
vendored
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (c) 2015-2018, Troy D. Hanson http://troydhanson.github.com/uthash/
|
||||
Copyright (c) 2015-2021, Troy D. Hanson http://troydhanson.github.io/uthash/
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
@@ -26,7 +26,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#ifndef UTRINGBUFFER_H
|
||||
#define UTRINGBUFFER_H
|
||||
|
||||
#define UTRINGBUFFER_VERSION 2.1.0
|
||||
#define UTRINGBUFFER_VERSION 2.3.0
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
8
dependencies/uthash/src/utstack.h
vendored
8
dependencies/uthash/src/utstack.h
vendored
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (c) 2018-2018, Troy D. Hanson http://troydhanson.github.com/uthash/
|
||||
Copyright (c) 2018-2021, Troy D. Hanson http://troydhanson.github.io/uthash/
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
@@ -24,7 +24,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#ifndef UTSTACK_H
|
||||
#define UTSTACK_H
|
||||
|
||||
#define UTSTACK_VERSION 2.1.0
|
||||
#define UTSTACK_VERSION 2.3.0
|
||||
|
||||
/*
|
||||
* This file contains macros to manipulate a singly-linked list as a stack.
|
||||
@@ -35,9 +35,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
* struct item {
|
||||
* int id;
|
||||
* struct item *next;
|
||||
* }
|
||||
* };
|
||||
*
|
||||
* struct item *stack = NULL:
|
||||
* struct item *stack = NULL;
|
||||
*
|
||||
* int main() {
|
||||
* int count;
|
||||
|
||||
4
dependencies/uthash/src/utstring.h
vendored
4
dependencies/uthash/src/utstring.h
vendored
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (c) 2008-2018, Troy D. Hanson http://troydhanson.github.com/uthash/
|
||||
Copyright (c) 2008-2021, Troy D. Hanson http://troydhanson.github.io/uthash/
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
@@ -26,7 +26,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#ifndef UTSTRING_H
|
||||
#define UTSTRING_H
|
||||
|
||||
#define UTSTRING_VERSION 2.1.0
|
||||
#define UTSTRING_VERSION 2.3.0
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
2
dependencies/uthash/tests/Makefile
vendored
2
dependencies/uthash/tests/Makefile
vendored
@@ -12,7 +12,7 @@ PROGS = test1 test2 test3 test4 test5 test6 test7 test8 test9 \
|
||||
test66 test67 test68 test69 test70 test71 test72 test73 \
|
||||
test74 test75 test76 test77 test78 test79 test80 test81 \
|
||||
test82 test83 test84 test85 test86 test87 test88 test89 \
|
||||
test90 test91 test92 test93 test94 test95
|
||||
test90 test91 test92 test93 test94 test95 test96
|
||||
CFLAGS += -I$(HASHDIR)
|
||||
#CFLAGS += -DHASH_BLOOM=16
|
||||
#CFLAGS += -O2
|
||||
|
||||
9
dependencies/uthash/tests/README
vendored
9
dependencies/uthash/tests/README
vendored
@@ -7,7 +7,7 @@ test2: make 10-item hash, lookup items with even keys, print
|
||||
test3: make 10-item hash, delete items with even keys, print others
|
||||
test4: 10 structs have dual hash handles, separate keys
|
||||
test5: 10 structs have dual hash handles, lookup evens by alt key
|
||||
test6: test alt malloc macros (and alt memcmp macro)
|
||||
test6: test alt malloc macros (and alt key-comparison macro)
|
||||
test7: test alt malloc macros with 1000 structs so bucket expansion occurs
|
||||
test8: test num_items counter in UT_hash_handle
|
||||
test9: test "find" after bucket expansion
|
||||
@@ -89,10 +89,15 @@ test84: test HASH_REPLACE_STR with char* key
|
||||
test85: test HASH_OVERHEAD on null and non null hash
|
||||
test86: test *_APPEND_ELEM / *_PREPEND_ELEM (Thilo Schulz)
|
||||
test87: test HASH_ADD_INORDER() macro (Thilo Schulz)
|
||||
test88: test alt memcmp and strlen macros
|
||||
test88: test alt key-comparison and strlen macros
|
||||
test89: test code from the tinydtls project
|
||||
test90: regression-test HASH_ADD_KEYPTR_INORDER (IronBug)
|
||||
test91: test LL_INSERT_INORDER etc.
|
||||
test92: HASH_NONFATAL_OOM
|
||||
test93: alt_fatal
|
||||
test94: utlist with fields named other than 'next' and 'prev'
|
||||
test95: utstack
|
||||
test96: HASH_FUNCTION + HASH_KEYCMP
|
||||
|
||||
Other Make targets
|
||||
================================================================================
|
||||
|
||||
86
dependencies/uthash/tests/example.c
vendored
86
dependencies/uthash/tests/example.c
vendored
@@ -1,25 +1,25 @@
|
||||
#include <stdio.h> /* gets */
|
||||
#include <stdio.h> /* printf */
|
||||
#include <stdlib.h> /* atoi, malloc */
|
||||
#include <string.h> /* strcpy */
|
||||
#include "uthash.h"
|
||||
|
||||
struct my_struct {
|
||||
int id; /* key */
|
||||
char name[10];
|
||||
char name[21];
|
||||
UT_hash_handle hh; /* makes this structure hashable */
|
||||
};
|
||||
|
||||
struct my_struct *users = NULL;
|
||||
|
||||
void add_user(int user_id, char *name)
|
||||
void add_user(int user_id, const char *name)
|
||||
{
|
||||
struct my_struct *s;
|
||||
|
||||
HASH_FIND_INT(users, &user_id, s); /* id already in the hash? */
|
||||
if (s==NULL) {
|
||||
s = (struct my_struct*)malloc(sizeof(struct my_struct));
|
||||
if (s == NULL) {
|
||||
s = (struct my_struct*)malloc(sizeof *s);
|
||||
s->id = user_id;
|
||||
HASH_ADD_INT( users, id, s ); /* id: name of key field */
|
||||
HASH_ADD_INT(users, id, s); /* id is the key field */
|
||||
}
|
||||
strcpy(s->name, name);
|
||||
}
|
||||
@@ -28,23 +28,24 @@ struct my_struct *find_user(int user_id)
|
||||
{
|
||||
struct my_struct *s;
|
||||
|
||||
HASH_FIND_INT( users, &user_id, s ); /* s: output pointer */
|
||||
HASH_FIND_INT(users, &user_id, s); /* s: output pointer */
|
||||
return s;
|
||||
}
|
||||
|
||||
void delete_user(struct my_struct *user)
|
||||
{
|
||||
HASH_DEL( users, user); /* user: pointer to deletee */
|
||||
HASH_DEL(users, user); /* user: pointer to deletee */
|
||||
free(user);
|
||||
}
|
||||
|
||||
void delete_all()
|
||||
{
|
||||
struct my_struct *current_user, *tmp;
|
||||
struct my_struct *current_user;
|
||||
struct my_struct *tmp;
|
||||
|
||||
HASH_ITER(hh, users, current_user, tmp) {
|
||||
HASH_DEL(users,current_user); /* delete it (users advances to next) */
|
||||
free(current_user); /* free it */
|
||||
HASH_DEL(users, current_user); /* delete it (users advances to next) */
|
||||
free(current_user); /* free it */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,41 +53,45 @@ void print_users()
|
||||
{
|
||||
struct my_struct *s;
|
||||
|
||||
for(s=users; s != NULL; s=(struct my_struct*)(s->hh.next)) {
|
||||
for (s = users; s != NULL; s = (struct my_struct*)(s->hh.next)) {
|
||||
printf("user id %d: name %s\n", s->id, s->name);
|
||||
}
|
||||
}
|
||||
|
||||
int name_sort(struct my_struct *a, struct my_struct *b)
|
||||
int by_name(const struct my_struct *a, const struct my_struct *b)
|
||||
{
|
||||
return strcmp(a->name,b->name);
|
||||
return strcmp(a->name, b->name);
|
||||
}
|
||||
|
||||
int id_sort(struct my_struct *a, struct my_struct *b)
|
||||
int by_id(const struct my_struct *a, const struct my_struct *b)
|
||||
{
|
||||
return (a->id - b->id);
|
||||
}
|
||||
|
||||
void sort_by_name()
|
||||
const char *getl(const char *prompt)
|
||||
{
|
||||
HASH_SORT(users, name_sort);
|
||||
}
|
||||
|
||||
void sort_by_id()
|
||||
{
|
||||
HASH_SORT(users, id_sort);
|
||||
static char buf[21];
|
||||
char *p;
|
||||
printf("%s? ", prompt); fflush(stdout);
|
||||
p = fgets(buf, sizeof(buf), stdin);
|
||||
if (p == NULL || (p = strchr(buf, '\n')) == NULL) {
|
||||
puts("Invalid input!");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
*p = '\0';
|
||||
return buf;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
char in[10];
|
||||
int id=1, running=1;
|
||||
int id = 1;
|
||||
int running = 1;
|
||||
struct my_struct *s;
|
||||
unsigned num_users;
|
||||
int temp;
|
||||
|
||||
while (running) {
|
||||
printf(" 1. add user\n");
|
||||
printf(" 2. add/rename user by id\n");
|
||||
printf(" 2. add or rename user by id\n");
|
||||
printf(" 3. find user\n");
|
||||
printf(" 4. delete user\n");
|
||||
printf(" 5. delete all users\n");
|
||||
@@ -95,27 +100,20 @@ int main()
|
||||
printf(" 8. print users\n");
|
||||
printf(" 9. count users\n");
|
||||
printf("10. quit\n");
|
||||
gets(in);
|
||||
switch(atoi(in)) {
|
||||
switch (atoi(getl("Command"))) {
|
||||
case 1:
|
||||
printf("name?\n");
|
||||
add_user(id++, gets(in));
|
||||
add_user(id++, getl("Name (20 char max)"));
|
||||
break;
|
||||
case 2:
|
||||
printf("id?\n");
|
||||
gets(in);
|
||||
id = atoi(in);
|
||||
printf("name?\n");
|
||||
add_user(id, gets(in));
|
||||
temp = atoi(getl("ID"));
|
||||
add_user(temp, getl("Name (20 char max)"));
|
||||
break;
|
||||
case 3:
|
||||
printf("id?\n");
|
||||
s = find_user(atoi(gets(in)));
|
||||
s = find_user(atoi(getl("ID to find")));
|
||||
printf("user: %s\n", s ? s->name : "unknown");
|
||||
break;
|
||||
case 4:
|
||||
printf("id?\n");
|
||||
s = find_user(atoi(gets(in)));
|
||||
s = find_user(atoi(getl("ID to delete")));
|
||||
if (s) {
|
||||
delete_user(s);
|
||||
} else {
|
||||
@@ -126,20 +124,20 @@ int main()
|
||||
delete_all();
|
||||
break;
|
||||
case 6:
|
||||
sort_by_name();
|
||||
HASH_SORT(users, by_name);
|
||||
break;
|
||||
case 7:
|
||||
sort_by_id();
|
||||
HASH_SORT(users, by_id);
|
||||
break;
|
||||
case 8:
|
||||
print_users();
|
||||
break;
|
||||
case 9:
|
||||
num_users=HASH_COUNT(users);
|
||||
printf("there are %u users\n", num_users);
|
||||
temp = HASH_COUNT(users);
|
||||
printf("there are %d users\n", temp);
|
||||
break;
|
||||
case 10:
|
||||
running=0;
|
||||
running = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
2
dependencies/uthash/tests/hashscan.c
vendored
2
dependencies/uthash/tests/hashscan.c
vendored
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (c) 2005-2018, Troy D. Hanson http://troydhanson.github.com/uthash/
|
||||
Copyright (c) 2005-2021, Troy D. Hanson http://troydhanson.github.io/uthash/
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
||||
4
dependencies/uthash/tests/keystats
vendored
4
dependencies/uthash/tests/keystats
vendored
@@ -12,9 +12,11 @@ sub usage {
|
||||
|
||||
usage if ((@ARGV == 0) or ($ARGV[0] eq '-h'));
|
||||
|
||||
my @exes = glob "$FindBin::Bin/keystat.???";
|
||||
my @exes = glob "'$FindBin::Bin/keystat.???'";
|
||||
|
||||
my %stats;
|
||||
for my $exe (@exes) {
|
||||
$exe =~ s/\ /\\ /g;
|
||||
$stats{$exe} = `$exe @ARGV`;
|
||||
delete $stats{$exe} if ($? != 0); # omit hash functions that fail to produce stats (nx)
|
||||
}
|
||||
|
||||
3
dependencies/uthash/tests/test10.c
vendored
3
dependencies/uthash/tests/test10.c
vendored
@@ -47,5 +47,8 @@ int main()
|
||||
HASH_FIND(alth,altusers,&i,sizeof(int),tmp);
|
||||
printf("%d %s in alth\n", i, (tmp != NULL) ? "found" : "not found");
|
||||
|
||||
HASH_CLEAR(hh, users);
|
||||
HASH_CLEAR(alth, altusers);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
13
dependencies/uthash/tests/test6.c
vendored
13
dependencies/uthash/tests/test6.c
vendored
@@ -7,15 +7,16 @@
|
||||
/* Set up macros for alternative malloc/free functions */
|
||||
#undef uthash_malloc
|
||||
#undef uthash_free
|
||||
#undef uthash_memcmp
|
||||
#undef uthash_strlen
|
||||
#undef uthash_bzero
|
||||
#define uthash_malloc(sz) alt_malloc(sz)
|
||||
#define uthash_free(ptr,sz) alt_free(ptr,sz)
|
||||
#define uthash_memcmp(a,b,n) alt_memcmp(a,b,n)
|
||||
#define uthash_strlen(s) ..fail_to_compile..
|
||||
#define uthash_bzero(a,n) alt_bzero(a,n)
|
||||
|
||||
#undef HASH_KEYCMP
|
||||
#define HASH_KEYCMP(a,b,n) alt_keycmp(a,b,n)
|
||||
|
||||
typedef struct example_user_t {
|
||||
int id;
|
||||
int cookie;
|
||||
@@ -41,10 +42,10 @@ static void alt_free(void *ptr, size_t sz)
|
||||
free(ptr);
|
||||
}
|
||||
|
||||
static int alt_memcmp_count = 0;
|
||||
static int alt_memcmp(const void *a, const void *b, size_t n)
|
||||
static int alt_keycmp_count = 0;
|
||||
static int alt_keycmp(const void *a, const void *b, size_t n)
|
||||
{
|
||||
++alt_memcmp_count;
|
||||
++alt_keycmp_count;
|
||||
return memcmp(a,b,n);
|
||||
}
|
||||
|
||||
@@ -115,7 +116,7 @@ int main()
|
||||
#else
|
||||
assert(alt_bzero_count == 2);
|
||||
#endif
|
||||
assert(alt_memcmp_count == 10);
|
||||
assert(alt_keycmp_count == 10);
|
||||
assert(alt_malloc_balance == 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
2
dependencies/uthash/tests/test65.c
vendored
2
dependencies/uthash/tests/test65.c
vendored
@@ -3,7 +3,7 @@
|
||||
#include "uthash.h"
|
||||
|
||||
// this is an example of how to do a LRU cache in C using uthash
|
||||
// http://troydhanson.github.com/uthash/
|
||||
// http://troydhanson.github.io/uthash/
|
||||
// by Jehiah Czebotar 2011 - jehiah@gmail.com
|
||||
// this code is in the public domain http://unlicense.org/
|
||||
|
||||
|
||||
7
dependencies/uthash/tests/test76.c
vendored
7
dependencies/uthash/tests/test76.c
vendored
@@ -8,8 +8,8 @@ int main()
|
||||
char V_NeedleStr[] = "needle\0s";
|
||||
long *V_KMP_Table;
|
||||
long V_FindPos;
|
||||
size_t V_StartPos;
|
||||
size_t V_FindCnt;
|
||||
size_t V_StartPos = 0;
|
||||
size_t V_FindCnt = 0;
|
||||
|
||||
|
||||
utstring_new(s);
|
||||
@@ -24,9 +24,6 @@ int main()
|
||||
if (V_KMP_Table != NULL) {
|
||||
_utstring_BuildTable(utstring_body(t), utstring_len(t), V_KMP_Table);
|
||||
|
||||
V_FindCnt = 0;
|
||||
V_FindPos = 0;
|
||||
V_StartPos = 0;
|
||||
do {
|
||||
V_FindPos = _utstring_find(utstring_body(s) + V_StartPos,
|
||||
utstring_len(s) - V_StartPos,
|
||||
|
||||
4
dependencies/uthash/tests/test77.c
vendored
4
dependencies/uthash/tests/test77.c
vendored
@@ -9,7 +9,7 @@ int main()
|
||||
long *V_KMP_Table;
|
||||
long V_FindPos;
|
||||
size_t V_StartPos;
|
||||
size_t V_FindCnt;
|
||||
size_t V_FindCnt = 0;
|
||||
|
||||
|
||||
utstring_new(s);
|
||||
@@ -24,8 +24,6 @@ int main()
|
||||
if (V_KMP_Table != NULL) {
|
||||
_utstring_BuildTableR(utstring_body(t), utstring_len(t), V_KMP_Table);
|
||||
|
||||
V_FindCnt = 0;
|
||||
V_FindPos = 0;
|
||||
V_StartPos = utstring_len(s) - 1;
|
||||
do {
|
||||
V_FindPos = _utstring_findR(utstring_body(s),
|
||||
|
||||
20
dependencies/uthash/tests/test88.ans
vendored
20
dependencies/uthash/tests/test88.ans
vendored
@@ -9,22 +9,22 @@ alt_strlen
|
||||
alt_strlen
|
||||
alt_strlen
|
||||
alt_strlen
|
||||
alt_memcmp
|
||||
alt_keycmp
|
||||
alt_strlen
|
||||
alt_memcmp
|
||||
alt_keycmp
|
||||
alt_strlen
|
||||
alt_memcmp
|
||||
alt_keycmp
|
||||
alt_strlen
|
||||
alt_memcmp
|
||||
alt_keycmp
|
||||
alt_strlen
|
||||
alt_memcmp
|
||||
alt_keycmp
|
||||
alt_strlen
|
||||
alt_memcmp
|
||||
alt_keycmp
|
||||
alt_strlen
|
||||
alt_memcmp
|
||||
alt_keycmp
|
||||
alt_strlen
|
||||
alt_memcmp
|
||||
alt_keycmp
|
||||
alt_strlen
|
||||
alt_memcmp
|
||||
alt_keycmp
|
||||
alt_strlen
|
||||
alt_memcmp
|
||||
alt_keycmp
|
||||
|
||||
8
dependencies/uthash/tests/test88.c
vendored
8
dependencies/uthash/tests/test88.c
vendored
@@ -8,9 +8,9 @@
|
||||
|
||||
/* This is mostly a copy of test6.c. */
|
||||
|
||||
#undef uthash_memcmp
|
||||
#undef HASH_KEYCMP
|
||||
#undef uthash_strlen
|
||||
#define uthash_memcmp(a,b,n) alt_memcmp(a,b,n)
|
||||
#define HASH_KEYCMP(a,b,n) alt_keycmp(a,b,n)
|
||||
#define uthash_strlen(s) alt_strlen(s)
|
||||
|
||||
typedef struct example_user_t {
|
||||
@@ -19,9 +19,9 @@ typedef struct example_user_t {
|
||||
UT_hash_handle hh;
|
||||
} example_user_t;
|
||||
|
||||
static int alt_memcmp(const void *a, const void *b, size_t n)
|
||||
static int alt_keycmp(const void *a, const void *b, size_t n)
|
||||
{
|
||||
puts("alt_memcmp");
|
||||
puts("alt_keycmp");
|
||||
return memcmp(a,b,n);
|
||||
}
|
||||
|
||||
|
||||
37
dependencies/uthash/tests/test93.c
vendored
37
dependencies/uthash/tests/test93.c
vendored
@@ -39,43 +39,39 @@ static void alt_fatal(char const * s) {
|
||||
longjmp(j_buf, 1);
|
||||
}
|
||||
|
||||
static example_user_t * init_user(int need_malloc_cnt) {
|
||||
users = 0;
|
||||
static void init_users(int need_malloc_cnt) {
|
||||
users = NULL;
|
||||
example_user_t * user = (example_user_t*)malloc(sizeof(example_user_t));
|
||||
user->id = user_id;
|
||||
is_fatal = 0;
|
||||
malloc_cnt = need_malloc_cnt;
|
||||
/* printf("adding to hash...\n"); */
|
||||
if (!setjmp(j_buf)) {
|
||||
HASH_ADD_INT(users, id, user);
|
||||
} else {
|
||||
free(user);
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
example_user_t *user;
|
||||
|
||||
#define init(a) do { \
|
||||
} while(0)
|
||||
|
||||
example_user_t * user;
|
||||
|
||||
user = init_user(3); /* bloom filter must fail */
|
||||
init_users(3); /* bloom filter must fail */
|
||||
if (!is_fatal) {
|
||||
printf("fatal not called after bloom failure\n");
|
||||
}
|
||||
|
||||
user = init_user(2); /* bucket creation must fail */
|
||||
init_users(2); /* bucket creation must fail */
|
||||
if (!is_fatal) {
|
||||
printf("fatal not called after bucket creation failure\n");
|
||||
}
|
||||
|
||||
user = init_user(1); /* table creation must fail */
|
||||
init_users(1); /* table creation must fail */
|
||||
if (!is_fatal) {
|
||||
printf("fatal not called after table creation failure\n");
|
||||
}
|
||||
|
||||
user = init_user(4); /* hash must create OK */
|
||||
init_users(4); /* hash must create OK */
|
||||
if (is_fatal) {
|
||||
printf("fatal error when creating hash normally\n");
|
||||
/* bad idea to continue running */
|
||||
@@ -83,19 +79,20 @@ int main()
|
||||
}
|
||||
|
||||
/* let's add users until expansion fails */
|
||||
users = 0;
|
||||
users = NULL;
|
||||
malloc_cnt = 4;
|
||||
while (1) {
|
||||
user = (example_user_t*)malloc(sizeof(example_user_t));
|
||||
user->id = user_id;
|
||||
if (user_id++ == 1000) {
|
||||
printf("there is no way 1000 iterations didn't require realloc\n");
|
||||
break;
|
||||
}
|
||||
user = (example_user_t*)malloc(sizeof(example_user_t));
|
||||
user->id = user_id;
|
||||
if (!setjmp(j_buf)) {
|
||||
HASH_ADD_INT(users, id, user);
|
||||
} else {
|
||||
free(user);
|
||||
}
|
||||
malloc_cnt = 0;
|
||||
if (malloc_failed) {
|
||||
|
||||
if (!is_fatal) {
|
||||
@@ -108,12 +105,12 @@ int main()
|
||||
/* we can't really do anything, the hash is not in consistent
|
||||
* state, so assume this is a success. */
|
||||
break;
|
||||
|
||||
}
|
||||
malloc_cnt = 0;
|
||||
}
|
||||
|
||||
HASH_CLEAR(hh, users);
|
||||
|
||||
printf("End\n");
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
40
dependencies/uthash/tests/test96.ans
vendored
Normal file
40
dependencies/uthash/tests/test96.ans
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
time 56 not found, inserting it
|
||||
time 7 not found, inserting it
|
||||
time 10 not found, inserting it
|
||||
time 39 not found, inserting it
|
||||
time 82 found with value 10
|
||||
time 15 found with value 39
|
||||
time 31 found with value 7
|
||||
time 26 not found, inserting it
|
||||
time 51 found with value 39
|
||||
time 83 not found, inserting it
|
||||
time 46 found with value 10
|
||||
time 92 found with value 56
|
||||
time 49 not found, inserting it
|
||||
time 25 found with value 49
|
||||
time 80 found with value 56
|
||||
time 54 not found, inserting it
|
||||
time 97 found with value 49
|
||||
time 9 not found, inserting it
|
||||
time 34 found with value 10
|
||||
time 86 found with value 26
|
||||
time 87 found with value 39
|
||||
time 28 not found, inserting it
|
||||
time 13 found with value 49
|
||||
time 91 found with value 7
|
||||
time 95 found with value 83
|
||||
time 63 found with value 39
|
||||
time 71 found with value 83
|
||||
time 100 found with value 28
|
||||
time 44 found with value 56
|
||||
time 42 found with value 54
|
||||
time 16 found with value 28
|
||||
time 32 found with value 56
|
||||
time 6 found with value 54
|
||||
time 85 found with value 49
|
||||
time 40 found with value 28
|
||||
time 20 found with value 56
|
||||
time 18 found with value 54
|
||||
time 99 found with value 39
|
||||
time 22 found with value 10
|
||||
time 1 found with value 49
|
||||
48
dependencies/uthash/tests/test96.c
vendored
Normal file
48
dependencies/uthash/tests/test96.c
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define HASH_FUNCTION(a,n,hv) (hv = clockface_hash(*(const int*)(a)))
|
||||
#define HASH_KEYCMP(a,b,n) clockface_neq(*(const int*)(a), *(const int*)(b))
|
||||
|
||||
#include "uthash.h"
|
||||
|
||||
struct clockface {
|
||||
int time;
|
||||
UT_hash_handle hh;
|
||||
};
|
||||
|
||||
int clockface_hash(int time)
|
||||
{
|
||||
return (time % 4);
|
||||
}
|
||||
|
||||
int clockface_neq(int t1, int t2)
|
||||
{
|
||||
return ((t1 % 12) != (t2 % 12));
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
int random_data[] = {
|
||||
56, 7, 10, 39, 82, 15, 31, 26, 51, 83,
|
||||
46, 92, 49, 25, 80, 54, 97, 9, 34, 86,
|
||||
87, 28, 13, 91, 95, 63, 71, 100, 44, 42,
|
||||
16, 32, 6, 85, 40, 20, 18, 99, 22, 1
|
||||
};
|
||||
|
||||
struct clockface *times = NULL;
|
||||
for (int i=0; i < 40; ++i) {
|
||||
struct clockface *elt = (struct clockface *)malloc(sizeof(*elt));
|
||||
struct clockface *found = NULL;
|
||||
elt->time = random_data[i];
|
||||
HASH_FIND_INT(times, &elt->time, found);
|
||||
if (found) {
|
||||
printf("time %d found with value %d\n", elt->time, found->time);
|
||||
} else {
|
||||
printf("time %d not found, inserting it\n", elt->time);
|
||||
HASH_ADD_INT(times, time, elt);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -17,14 +17,28 @@ A collecd-exec compatible middleware that gathers statistic values from nDPId.
|
||||
|
||||
Tiny nDPId json dumper. Does not provide any useful funcationality besides dumping parsed JSON objects.
|
||||
|
||||
## go-dashboard
|
||||
## c-simple
|
||||
|
||||
A discontinued tty UI nDPId dashboard. I've figured out that Go + UI is a bad idea, in particular if performance is a concern.
|
||||
Very tiny integration example.
|
||||
|
||||
## ~~go-dashboard~~ (DISCONTINUED!)
|
||||
|
||||
A discontinued tty UI nDPId dashboard.
|
||||
Removed with commit 29c72fb30bb7d5614c0a8ebb73bee2ac7eca6608.
|
||||
|
||||
## py-flow-info
|
||||
|
||||
Prints prettyfied information about flow events.
|
||||
|
||||
## py-flow-dashboard
|
||||
|
||||
A realtime web based graph using Plotly/Dash.
|
||||
Probably the most informative example.
|
||||
|
||||
## py-flow-multiprocess
|
||||
|
||||
Simple Python Multiprocess example spawning two worker processes, one connecting to nDPIsrvd and one printing flow id's to STDOUT.
|
||||
|
||||
## py-flow-undetected-to-pcap
|
||||
|
||||
Captures and saves undetected flows to a PCAP file.
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <netinet/udp.h>
|
||||
#include <pcap/pcap.h>
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -16,6 +17,9 @@
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <ndpi_typedefs.h>
|
||||
#include <ndpi_api.h>
|
||||
|
||||
#include "nDPIsrvd.h"
|
||||
#include "utarray.h"
|
||||
#include "utils.h"
|
||||
@@ -29,7 +33,8 @@ struct packet_data
|
||||
nDPIsrvd_ull packet_ts_usec;
|
||||
nDPIsrvd_ull packet_len;
|
||||
int base64_packet_size;
|
||||
union {
|
||||
union
|
||||
{
|
||||
char * base64_packet;
|
||||
char const * base64_packet_const;
|
||||
};
|
||||
@@ -45,6 +50,7 @@ struct flow_user_data
|
||||
uint8_t midstream;
|
||||
nDPIsrvd_ull flow_datalink;
|
||||
nDPIsrvd_ull flow_max_packets;
|
||||
nDPIsrvd_ull flow_tot_l4_payload_len;
|
||||
UT_array * packets;
|
||||
};
|
||||
|
||||
@@ -61,8 +67,22 @@ static char * group = NULL;
|
||||
static char * datadir = NULL;
|
||||
static uint8_t process_guessed = 0;
|
||||
static uint8_t process_undetected = 0;
|
||||
static uint8_t process_risky = 0;
|
||||
static ndpi_risk process_risky = NDPI_NO_RISK;
|
||||
static uint8_t process_midstream = 0;
|
||||
static uint8_t ignore_empty_flows = 0;
|
||||
|
||||
#ifdef ENABLE_MEMORY_PROFILING
|
||||
void nDPIsrvd_memprof_log(char const * const format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, format);
|
||||
fprintf(stderr, "%s", "nDPIsrvd MemoryProfiler: ");
|
||||
vfprintf(stderr, format, ap);
|
||||
fprintf(stderr, "%s\n", "");
|
||||
va_end(ap);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void packet_data_copy(void * dst, const void * src)
|
||||
{
|
||||
@@ -93,6 +113,35 @@ static void packet_data_dtor(void * elt)
|
||||
|
||||
static const UT_icd packet_data_icd = {sizeof(struct packet_data), NULL, packet_data_copy, packet_data_dtor};
|
||||
|
||||
static void set_ndpi_risk(ndpi_risk * const risk, nDPIsrvd_ull risk_to_add)
|
||||
{
|
||||
if (risk_to_add == 0)
|
||||
{
|
||||
*risk = (ndpi_risk)-1;
|
||||
}
|
||||
else
|
||||
{
|
||||
*risk |= 1ull << --risk_to_add;
|
||||
}
|
||||
}
|
||||
|
||||
static void unset_ndpi_risk(ndpi_risk * const risk, nDPIsrvd_ull risk_to_del)
|
||||
{
|
||||
if (risk_to_del == 0)
|
||||
{
|
||||
*risk = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
*risk &= ~(1ull << --risk_to_del);
|
||||
}
|
||||
}
|
||||
|
||||
static int has_ndpi_risk(ndpi_risk * const risk, nDPIsrvd_ull risk_to_check)
|
||||
{
|
||||
return (*risk & (1ull << --risk_to_check)) != 0;
|
||||
}
|
||||
|
||||
static char * generate_pcap_filename(struct nDPIsrvd_flow const * const flow,
|
||||
struct flow_user_data const * const flow_user,
|
||||
char * const dest,
|
||||
@@ -288,11 +337,16 @@ static enum nDPIsrvd_conversion_return perror_ull(enum nDPIsrvd_conversion_retur
|
||||
}
|
||||
|
||||
static enum nDPIsrvd_callback_return captured_json_callback(struct nDPIsrvd_socket * const sock,
|
||||
struct nDPIsrvd_instance * const instance,
|
||||
struct nDPIsrvd_thread_data * const thread_data,
|
||||
struct nDPIsrvd_flow * const flow)
|
||||
{
|
||||
(void)instance;
|
||||
(void)thread_data;
|
||||
|
||||
if (flow == NULL)
|
||||
{
|
||||
return CALLBACK_OK; // We do not care for non flow/packet-flow events for NOW.
|
||||
return CALLBACK_OK; // We do not care for non-flow events for NOW except for packet-flow events.
|
||||
}
|
||||
|
||||
struct flow_user_data * const flow_user = (struct flow_user_data *)flow->flow_user_data;
|
||||
@@ -318,11 +372,8 @@ static enum nDPIsrvd_callback_return captured_json_callback(struct nDPIsrvd_sock
|
||||
return CALLBACK_ERROR;
|
||||
}
|
||||
|
||||
nDPIsrvd_ull pkt_ts_sec = 0ull;
|
||||
perror_ull(TOKEN_VALUE_TO_ULL(TOKEN_GET_SZ(sock, "pkt_ts_sec"), &pkt_ts_sec), "pkt_ts_sec");
|
||||
|
||||
nDPIsrvd_ull pkt_ts_usec = 0ull;
|
||||
perror_ull(TOKEN_VALUE_TO_ULL(TOKEN_GET_SZ(sock, "pkt_ts_usec"), &pkt_ts_usec), "pkt_ts_usec");
|
||||
nDPIsrvd_ull thread_ts_msec = 0ull;
|
||||
perror_ull(TOKEN_VALUE_TO_ULL(TOKEN_GET_SZ(sock, "thread_ts_msec"), &thread_ts_msec), "thread_ts_msec");
|
||||
|
||||
nDPIsrvd_ull pkt_len = 0ull;
|
||||
perror_ull(TOKEN_VALUE_TO_ULL(TOKEN_GET_SZ(sock, "pkt_len"), &pkt_len), "pkt_len");
|
||||
@@ -333,8 +384,8 @@ static enum nDPIsrvd_callback_return captured_json_callback(struct nDPIsrvd_sock
|
||||
nDPIsrvd_ull pkt_l4_offset = 0ull;
|
||||
perror_ull(TOKEN_VALUE_TO_ULL(TOKEN_GET_SZ(sock, "pkt_l4_offset"), &pkt_l4_offset), "pkt_l4_offset");
|
||||
|
||||
struct packet_data pd = {.packet_ts_sec = pkt_ts_sec,
|
||||
.packet_ts_usec = pkt_ts_usec,
|
||||
struct packet_data pd = {.packet_ts_sec = thread_ts_msec / 1000,
|
||||
.packet_ts_usec = (thread_ts_msec % 1000) * 1000,
|
||||
.packet_len = pkt_len,
|
||||
.base64_packet_size = pkt->value_length,
|
||||
.base64_packet_const = pkt->value};
|
||||
@@ -343,6 +394,14 @@ static enum nDPIsrvd_callback_return captured_json_callback(struct nDPIsrvd_sock
|
||||
|
||||
{
|
||||
struct nDPIsrvd_json_token const * const flow_event_name = TOKEN_GET_SZ(sock, "flow_event_name");
|
||||
|
||||
if (flow_event_name != NULL)
|
||||
{
|
||||
perror_ull(TOKEN_VALUE_TO_ULL(TOKEN_GET_SZ(sock, "flow_tot_l4_payload_len"),
|
||||
&flow_user->flow_tot_l4_payload_len),
|
||||
"flow_tot_l4_payload_len");
|
||||
}
|
||||
|
||||
if (TOKEN_VALUE_EQUALS_SZ(flow_event_name, "new") != 0)
|
||||
{
|
||||
flow_user->flow_new_seen = 1;
|
||||
@@ -369,11 +428,25 @@ static enum nDPIsrvd_callback_return captured_json_callback(struct nDPIsrvd_sock
|
||||
}
|
||||
else if (TOKEN_VALUE_EQUALS_SZ(flow_event_name, "detected") != 0)
|
||||
{
|
||||
struct nDPIsrvd_json_token const * const flow_risk = TOKEN_GET_SZ(sock, "flow_risk");
|
||||
struct nDPIsrvd_json_token const * current = NULL;
|
||||
int next_child_index = -1;
|
||||
|
||||
flow_user->detected = 1;
|
||||
flow_user->detection_finished = 1;
|
||||
if (TOKEN_GET_SZ(sock, "flow_risk") != NULL)
|
||||
|
||||
if (flow_risk != NULL)
|
||||
{
|
||||
flow_user->risky = 1;
|
||||
while ((current = token_get_next_child(sock, flow_risk, &next_child_index)) != NULL)
|
||||
{
|
||||
nDPIsrvd_ull numeric_risk_value = (nDPIsrvd_ull)-1;
|
||||
|
||||
if (TOKEN_KEY_TO_ULL(current, &numeric_risk_value) == CONVERSION_OK &&
|
||||
numeric_risk_value < NDPI_MAX_RISK && has_ndpi_risk(&process_risky, numeric_risk_value) != 0)
|
||||
{
|
||||
flow_user->risky = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,6 +467,7 @@ static enum nDPIsrvd_callback_return captured_json_callback(struct nDPIsrvd_sock
|
||||
(flow_user->midstream != 0 && process_midstream != 0)))
|
||||
{
|
||||
packet_data_print(flow_user->packets);
|
||||
if (ignore_empty_flows == 0 || flow_user->flow_tot_l4_payload_len > 0)
|
||||
{
|
||||
char pcap_filename[PATH_MAX];
|
||||
if (generate_pcap_filename(flow, flow_user, pcap_filename, sizeof(pcap_filename)) == NULL)
|
||||
@@ -418,19 +492,90 @@ static enum nDPIsrvd_callback_return captured_json_callback(struct nDPIsrvd_sock
|
||||
return CALLBACK_OK;
|
||||
}
|
||||
|
||||
static void nDPIsrvd_write_flow_info_cb(struct nDPIsrvd_socket const * sock,
|
||||
struct nDPIsrvd_instance const * instance,
|
||||
struct nDPIsrvd_thread_data const * thread_data,
|
||||
struct nDPIsrvd_flow const * flow,
|
||||
void * user_data)
|
||||
{
|
||||
(void)sock;
|
||||
(void)instance;
|
||||
(void)thread_data;
|
||||
(void)user_data;
|
||||
|
||||
struct flow_user_data const * const flow_user = (struct flow_user_data const *)flow->flow_user_data;
|
||||
|
||||
fprintf(stderr,
|
||||
"[Flow %4llu][ptr: "
|
||||
#ifdef __LP64__
|
||||
"0x%016llx"
|
||||
#else
|
||||
"0x%08lx"
|
||||
#endif
|
||||
"][last-seen: %13llu][new-seen: %u][finished: %u][detected: %u][risky: "
|
||||
"%u][total-L4-payload-length: "
|
||||
"%4llu][packets-captured: %u]\n",
|
||||
flow->id_as_ull,
|
||||
#ifdef __LP64__
|
||||
(unsigned long long int)flow,
|
||||
#else
|
||||
(unsigned long int)flow,
|
||||
#endif
|
||||
flow->last_seen,
|
||||
flow_user->flow_new_seen,
|
||||
flow_user->detection_finished,
|
||||
flow_user->detected,
|
||||
flow_user->risky,
|
||||
flow_user->flow_tot_l4_payload_len,
|
||||
flow_user->packets != NULL ? utarray_len(flow_user->packets) : 0);
|
||||
|
||||
syslog(LOG_DAEMON,
|
||||
"[Flow %4llu][ptr: "
|
||||
#ifdef __LP64__
|
||||
"0x%016llx"
|
||||
#else
|
||||
"0x%08lx"
|
||||
#endif
|
||||
"][last-seen: %13llu][new-seen: %u][finished: %u][detected: %u][risky: "
|
||||
"%u][total-L4-payload-length: "
|
||||
"%4llu][packets-captured: %u]",
|
||||
flow->id_as_ull,
|
||||
#ifdef __LP64__
|
||||
(unsigned long long int)flow,
|
||||
#else
|
||||
(unsigned long int)flow,
|
||||
#endif
|
||||
flow->last_seen,
|
||||
flow_user->flow_new_seen,
|
||||
flow_user->detection_finished,
|
||||
flow_user->detected,
|
||||
flow_user->risky,
|
||||
flow_user->flow_tot_l4_payload_len,
|
||||
flow_user->packets != NULL ? utarray_len(flow_user->packets) : 0);
|
||||
}
|
||||
|
||||
static void sighandler(int signum)
|
||||
{
|
||||
(void)signum;
|
||||
|
||||
if (main_thread_shutdown == 0)
|
||||
if (signum == SIGUSR1)
|
||||
{
|
||||
nDPIsrvd_flow_info(sock, nDPIsrvd_write_flow_info_cb, NULL);
|
||||
}
|
||||
else if (main_thread_shutdown == 0)
|
||||
{
|
||||
main_thread_shutdown = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void captured_flow_end_callback(struct nDPIsrvd_socket * const sock, struct nDPIsrvd_flow * const flow)
|
||||
static void captured_flow_cleanup_callback(struct nDPIsrvd_socket * const sock,
|
||||
struct nDPIsrvd_instance * const instance,
|
||||
struct nDPIsrvd_thread_data * const thread_data,
|
||||
struct nDPIsrvd_flow * const flow,
|
||||
enum nDPIsrvd_cleanup_reason reason)
|
||||
{
|
||||
(void)sock;
|
||||
(void)instance;
|
||||
(void)thread_data;
|
||||
(void)reason;
|
||||
|
||||
#ifdef VERBOSE
|
||||
printf("flow %llu end, remaining flows: %u\n", flow->id_as_ull, sock->flow_table->hh.tbl->num_items);
|
||||
@@ -443,13 +588,12 @@ static void captured_flow_end_callback(struct nDPIsrvd_socket * const sock, stru
|
||||
}
|
||||
}
|
||||
|
||||
static int parse_options(int argc, char ** argv)
|
||||
static void print_usage(char const * const arg0)
|
||||
{
|
||||
int opt;
|
||||
|
||||
static char const usage[] =
|
||||
"Usage: %s "
|
||||
"[-d] [-p pidfile] [-s host] [-r rotate-every-n-seconds] [-u user] [-g group] [-D dir] [-G] [-U] [-R] [-M]\n\n"
|
||||
"[-d] [-p pidfile] [-s host] [-r rotate-every-n-seconds]\n"
|
||||
"\t \t[-u user] [-g group] [-D dir] [-G] [-U] [-R risk] [-M]\n\n"
|
||||
"\t-d\tForking into background after initialization.\n"
|
||||
"\t-p\tWrite the daemon PID to the given file path.\n"
|
||||
"\t-s\tDestination where nDPIsrvd is listening on.\n"
|
||||
@@ -460,10 +604,34 @@ static int parse_options(int argc, char ** argv)
|
||||
"\t-D\tDatadir - Where to store PCAP files.\n"
|
||||
"\t-G\tGuessed - Dump guessed flows to a PCAP file.\n"
|
||||
"\t-U\tUndetected - Dump undetected flows to a PCAP file.\n"
|
||||
"\t-R\tRisky - Dump risky flows to a PCAP file.\n"
|
||||
"\t-M\tMidstream - Dump midstream flows to a PCAP file.\n";
|
||||
"\t-R\tRisky - Dump risky flows to a PCAP file. See additional help below.\n"
|
||||
"\t-M\tMidstream - Dump midstream flows to a PCAP file.\n"
|
||||
"\t-E\tEmpty - Ignore flows w/o any layer 4 payload\n\n"
|
||||
"\tPossible options for `-R' (can be specified multiple times, processed from left to right, ~ disables a "
|
||||
"risk):\n"
|
||||
"\t \tExample: -R0 -R~15 would enable all risks except risk with id 15\n";
|
||||
|
||||
while ((opt = getopt(argc, argv, "hdp:s:r:u:g:D:GURM")) != -1)
|
||||
fprintf(stderr, usage, arg0);
|
||||
#ifndef LIBNDPI_STATIC
|
||||
fprintf(stderr, "\t\t%d - %s\n", 0, "Capture all risks");
|
||||
#else
|
||||
fprintf(stderr, "\t\t%d - %s\n\t\t", 0, "Capture all risks");
|
||||
#endif
|
||||
for (int risk = NDPI_NO_RISK + 1; risk < NDPI_MAX_RISK; ++risk)
|
||||
{
|
||||
#ifndef LIBNDPI_STATIC
|
||||
fprintf(stderr, "\t\t%d - %s%s", risk, ndpi_risk2str(risk), (risk == NDPI_MAX_RISK - 1 ? "\n\n" : "\n"));
|
||||
#else
|
||||
fprintf(stderr, "%d%s", risk, (risk == NDPI_MAX_RISK - 1 ? "\n" : ","));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
static int parse_options(int argc, char ** argv)
|
||||
{
|
||||
int opt;
|
||||
|
||||
while ((opt = getopt(argc, argv, "hdp:s:r:u:g:D:GUR:ME")) != -1)
|
||||
{
|
||||
switch (opt)
|
||||
{
|
||||
@@ -482,6 +650,7 @@ static int parse_options(int argc, char ** argv)
|
||||
if (perror_ull(str_value_to_ull(optarg, &pcap_filename_rotation), "pcap_filename_rotation") !=
|
||||
CONVERSION_OK)
|
||||
{
|
||||
fprintf(stderr, "%s: Argument for `-r' is not a number: %s\n", argv[0], optarg);
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
@@ -504,13 +673,37 @@ static int parse_options(int argc, char ** argv)
|
||||
process_undetected = 1;
|
||||
break;
|
||||
case 'R':
|
||||
process_risky = 1;
|
||||
{
|
||||
char * value = (optarg[0] == '~' ? optarg + 1 : optarg);
|
||||
nDPIsrvd_ull risk;
|
||||
if (perror_ull(str_value_to_ull(value, &risk), "process_risky") != CONVERSION_OK)
|
||||
{
|
||||
fprintf(stderr, "%s: Argument for `-R' is not a number: %s\n", argv[0], optarg);
|
||||
return 1;
|
||||
}
|
||||
if (risk >= NDPI_MAX_RISK)
|
||||
{
|
||||
fprintf(stderr, "%s: Invalid risk set: %s\n", argv[0], optarg);
|
||||
return 1;
|
||||
}
|
||||
if (optarg[0] == '~')
|
||||
{
|
||||
unset_ndpi_risk(&process_risky, risk);
|
||||
}
|
||||
else
|
||||
{
|
||||
set_ndpi_risk(&process_risky, risk);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'M':
|
||||
process_midstream = 1;
|
||||
break;
|
||||
case 'E':
|
||||
ignore_empty_flows = 1;
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, usage, argv[0]);
|
||||
print_usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -540,7 +733,7 @@ static int parse_options(int argc, char ** argv)
|
||||
if (optind < argc)
|
||||
{
|
||||
fprintf(stderr, "Unexpected argument after options\n\n");
|
||||
fprintf(stderr, usage, argv[0]);
|
||||
print_usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -564,8 +757,14 @@ static int parse_options(int argc, char ** argv)
|
||||
|
||||
static int mainloop(void)
|
||||
{
|
||||
sigset_t sigusr1_block;
|
||||
|
||||
sigemptyset(&sigusr1_block);
|
||||
sigaddset(&sigusr1_block, SIGUSR1);
|
||||
|
||||
while (main_thread_shutdown == 0)
|
||||
{
|
||||
sigprocmask(SIG_BLOCK, &sigusr1_block, NULL);
|
||||
errno = 0;
|
||||
enum nDPIsrvd_read_return read_ret = nDPIsrvd_read(sock);
|
||||
if (read_ret != READ_OK)
|
||||
@@ -580,6 +779,7 @@ static int mainloop(void)
|
||||
syslog(LOG_DAEMON | LOG_ERR, "nDPIsrvd parse failed with: %s", nDPIsrvd_enum_to_string(parse_ret));
|
||||
return 1;
|
||||
}
|
||||
sigprocmask(SIG_UNBLOCK, &sigusr1_block, NULL);
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -587,7 +787,8 @@ static int mainloop(void)
|
||||
|
||||
int main(int argc, char ** argv)
|
||||
{
|
||||
sock = nDPIsrvd_init(0, sizeof(struct flow_user_data), captured_json_callback, captured_flow_end_callback);
|
||||
sock = nDPIsrvd_socket_init(
|
||||
0, 0, 0, sizeof(struct flow_user_data), captured_json_callback, NULL, captured_flow_cleanup_callback);
|
||||
if (sock == NULL)
|
||||
{
|
||||
fprintf(stderr, "%s: nDPIsrvd socket memory allocation failed!\n", argv[0]);
|
||||
@@ -606,10 +807,11 @@ int main(int argc, char ** argv)
|
||||
if (connect_ret != CONNECT_OK)
|
||||
{
|
||||
fprintf(stderr, "%s: nDPIsrvd socket connect to %s failed!\n", argv[0], serv_optarg);
|
||||
nDPIsrvd_free(&sock);
|
||||
nDPIsrvd_socket_free(&sock);
|
||||
return 1;
|
||||
}
|
||||
|
||||
signal(SIGUSR1, sighandler);
|
||||
signal(SIGINT, sighandler);
|
||||
signal(SIGTERM, sighandler);
|
||||
signal(SIGPIPE, sighandler);
|
||||
@@ -637,7 +839,7 @@ int main(int argc, char ** argv)
|
||||
|
||||
int retval = mainloop();
|
||||
|
||||
nDPIsrvd_free(&sock);
|
||||
nDPIsrvd_socket_free(&sock);
|
||||
daemonize_shutdown(pidfile);
|
||||
closelog();
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
#include "nDPIsrvd.h"
|
||||
|
||||
#define DEFAULT_COLLECTD_EXEC_INST "exec-nDPIsrvd"
|
||||
|
||||
#define LOG(flags, format, ...) \
|
||||
if (quiet == 0) \
|
||||
{ \
|
||||
@@ -21,13 +23,14 @@
|
||||
syslog(flags, format, __VA_ARGS__); \
|
||||
}
|
||||
|
||||
static struct nDPIsrvd_socket * sock = NULL;
|
||||
static int main_thread_shutdown = 0;
|
||||
static int collectd_timerfd = -1;
|
||||
static pid_t collectd_pid;
|
||||
|
||||
static char * serv_optarg = NULL;
|
||||
static char * collectd_hostname = NULL;
|
||||
static char * collectd_interval = NULL;
|
||||
static char * instance_name = NULL;
|
||||
static nDPIsrvd_ull collectd_interval_ull = 0uL;
|
||||
static int quiet = 0;
|
||||
|
||||
@@ -41,7 +44,6 @@ static struct
|
||||
uint64_t flow_detection_update_count;
|
||||
uint64_t flow_not_detected_count;
|
||||
|
||||
uint64_t flow_packet_count;
|
||||
uint64_t flow_total_bytes;
|
||||
uint64_t flow_risky_count;
|
||||
|
||||
@@ -93,6 +95,19 @@ static struct
|
||||
uint64_t flow_l4_other_count;
|
||||
} collectd_statistics = {};
|
||||
|
||||
#ifdef ENABLE_MEMORY_PROFILING
|
||||
void nDPIsrvd_memprof_log(char const * const format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, format);
|
||||
fprintf(stderr, "%s", "nDPIsrvd MemoryProfiler: ");
|
||||
vfprintf(stderr, format, ap);
|
||||
fprintf(stderr, "%s\n", "");
|
||||
va_end(ap);
|
||||
}
|
||||
#endif
|
||||
|
||||
static int set_collectd_timer(void)
|
||||
{
|
||||
const time_t interval = collectd_interval_ull * 1000;
|
||||
@@ -128,22 +143,25 @@ static void sighandler(int signum)
|
||||
}
|
||||
}
|
||||
|
||||
static int parse_options(int argc, char ** argv)
|
||||
static int parse_options(int argc, char ** argv, struct nDPIsrvd_socket * const sock)
|
||||
{
|
||||
int opt;
|
||||
|
||||
static char const usage[] =
|
||||
"Usage: %s "
|
||||
"[-s host] [-c hostname] [-i interval] [-q]\n\n"
|
||||
"[-s host] [-c hostname] [-n collectd-instance-name] [-i interval] [-q]\n\n"
|
||||
"\t-s\tDestination where nDPIsrvd is listening on.\n"
|
||||
"\t-c\tCollectd hostname.\n"
|
||||
"\t \tThis value defaults to the environment variable COLLECTD_HOSTNAME.\n"
|
||||
"\t-n\tName of the collectd(-exec) instance.\n"
|
||||
"\t \tDefaults to: " DEFAULT_COLLECTD_EXEC_INST
|
||||
"\n"
|
||||
"\t-i\tInterval between print statistics to stdout.\n"
|
||||
"\t \tThis value defaults to the environment variable COLLECTD_INTERVAL.\n"
|
||||
"\t-q\tDo not print anything except collectd statistics.\n"
|
||||
"\t \tAutomatically enabled if environment variables mentioned above are set.\n";
|
||||
|
||||
while ((opt = getopt(argc, argv, "hs:c:i:q")) != -1)
|
||||
while ((opt = getopt(argc, argv, "hs:c:n:i:q")) != -1)
|
||||
{
|
||||
switch (opt)
|
||||
{
|
||||
@@ -155,6 +173,10 @@ static int parse_options(int argc, char ** argv)
|
||||
free(collectd_hostname);
|
||||
collectd_hostname = strdup(optarg);
|
||||
break;
|
||||
case 'n':
|
||||
free(instance_name);
|
||||
instance_name = strdup(optarg);
|
||||
break;
|
||||
case 'i':
|
||||
free(collectd_interval);
|
||||
collectd_interval = strdup(optarg);
|
||||
@@ -182,6 +204,11 @@ static int parse_options(int argc, char ** argv)
|
||||
}
|
||||
}
|
||||
|
||||
if (instance_name == NULL)
|
||||
{
|
||||
instance_name = strdup(DEFAULT_COLLECTD_EXEC_INST);
|
||||
}
|
||||
|
||||
if (collectd_interval == NULL)
|
||||
{
|
||||
collectd_interval = getenv("COLLECTD_INTERVAL");
|
||||
@@ -217,9 +244,9 @@ static int parse_options(int argc, char ** argv)
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define COLLECTD_PUTVAL_N_FORMAT(name) "PUTVAL %s/nDPId/" #name " interval=%llu %llu:%llu\n"
|
||||
#define COLLECTD_PUTVAL_N_FORMAT(name) "PUTVAL %s/%s/" #name " interval=%llu %llu:%llu\n"
|
||||
#define COLLECTD_PUTVAL_N(value) \
|
||||
collectd_hostname, collectd_interval_ull, (unsigned long long int)now, \
|
||||
collectd_hostname, instance_name, collectd_interval_ull, (unsigned long long int)now, \
|
||||
(unsigned long long int)collectd_statistics.value
|
||||
static void print_collectd_exec_output(void)
|
||||
{
|
||||
@@ -228,7 +255,7 @@ static void print_collectd_exec_output(void)
|
||||
printf(COLLECTD_PUTVAL_N_FORMAT(flow_new_count) COLLECTD_PUTVAL_N_FORMAT(flow_end_count)
|
||||
COLLECTD_PUTVAL_N_FORMAT(flow_idle_count) COLLECTD_PUTVAL_N_FORMAT(flow_guessed_count)
|
||||
COLLECTD_PUTVAL_N_FORMAT(flow_detected_count) COLLECTD_PUTVAL_N_FORMAT(flow_detection_update_count)
|
||||
COLLECTD_PUTVAL_N_FORMAT(flow_not_detected_count) COLLECTD_PUTVAL_N_FORMAT(flow_packet_count)
|
||||
COLLECTD_PUTVAL_N_FORMAT(flow_not_detected_count)
|
||||
COLLECTD_PUTVAL_N_FORMAT(flow_total_bytes) COLLECTD_PUTVAL_N_FORMAT(flow_risky_count),
|
||||
|
||||
COLLECTD_PUTVAL_N(flow_new_count),
|
||||
@@ -238,7 +265,6 @@ static void print_collectd_exec_output(void)
|
||||
COLLECTD_PUTVAL_N(flow_detected_count),
|
||||
COLLECTD_PUTVAL_N(flow_detection_update_count),
|
||||
COLLECTD_PUTVAL_N(flow_not_detected_count),
|
||||
COLLECTD_PUTVAL_N(flow_packet_count),
|
||||
COLLECTD_PUTVAL_N(flow_total_bytes),
|
||||
COLLECTD_PUTVAL_N(flow_risky_count));
|
||||
|
||||
@@ -328,7 +354,7 @@ static void print_collectd_exec_output(void)
|
||||
memset(&collectd_statistics, 0, sizeof(collectd_statistics));
|
||||
}
|
||||
|
||||
static int mainloop(int epollfd)
|
||||
static int mainloop(int epollfd, struct nDPIsrvd_socket * const sock)
|
||||
{
|
||||
struct epoll_event events[32];
|
||||
size_t const events_size = sizeof(events) / sizeof(events[0]);
|
||||
@@ -349,6 +375,16 @@ static int mainloop(int epollfd)
|
||||
{
|
||||
uint64_t expirations;
|
||||
|
||||
/*
|
||||
* Check if collectd parent process is still running.
|
||||
* May happen if collectd was killed with singals e.g. SIGKILL.
|
||||
*/
|
||||
if (getppid() != collectd_pid)
|
||||
{
|
||||
LOG(LOG_DAEMON | LOG_ERR, "Parent process %d exited. Nothing left to do here, bye.", collectd_pid);
|
||||
return 1;
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
if (read(collectd_timerfd, &expirations, sizeof(expirations)) != sizeof(expirations))
|
||||
{
|
||||
@@ -390,7 +426,7 @@ static uint64_t get_total_flow_bytes(struct nDPIsrvd_socket * const sock)
|
||||
{
|
||||
nDPIsrvd_ull total_bytes_ull = 0;
|
||||
|
||||
if (TOKEN_VALUE_TO_ULL(TOKEN_GET_SZ(sock, "flow_tot_l4_data_len"), &total_bytes_ull) == CONVERSION_OK)
|
||||
if (TOKEN_VALUE_TO_ULL(TOKEN_GET_SZ(sock, "flow_tot_l4_payload_len"), &total_bytes_ull) == CONVERSION_OK)
|
||||
{
|
||||
return total_bytes_ull;
|
||||
}
|
||||
@@ -401,9 +437,13 @@ static uint64_t get_total_flow_bytes(struct nDPIsrvd_socket * const sock)
|
||||
}
|
||||
|
||||
static enum nDPIsrvd_callback_return captured_json_callback(struct nDPIsrvd_socket * const sock,
|
||||
struct nDPIsrvd_instance * const instance,
|
||||
struct nDPIsrvd_thread_data * const thread_data,
|
||||
struct nDPIsrvd_flow * const flow)
|
||||
{
|
||||
(void)sock;
|
||||
(void)instance;
|
||||
(void)thread_data;
|
||||
(void)flow;
|
||||
|
||||
struct nDPIsrvd_json_token const * const flow_event_name = TOKEN_GET_SZ(sock, "flow_event_name");
|
||||
@@ -628,30 +668,25 @@ static enum nDPIsrvd_callback_return captured_json_callback(struct nDPIsrvd_sock
|
||||
collectd_statistics.flow_not_detected_count++;
|
||||
}
|
||||
|
||||
if (TOKEN_GET_SZ(sock, "packet_event_name") != NULL)
|
||||
{
|
||||
collectd_statistics.flow_packet_count++;
|
||||
}
|
||||
|
||||
return CALLBACK_OK;
|
||||
}
|
||||
|
||||
int main(int argc, char ** argv)
|
||||
{
|
||||
int retval = 1;
|
||||
int retval = 1, epollfd = -1;
|
||||
|
||||
openlog("nDPIsrvd-collectd", LOG_CONS, LOG_DAEMON);
|
||||
|
||||
sock = nDPIsrvd_init(0, 0, captured_json_callback, NULL);
|
||||
struct nDPIsrvd_socket * sock = nDPIsrvd_socket_init(0, 0, 0, 0, captured_json_callback, NULL, NULL);
|
||||
if (sock == NULL)
|
||||
{
|
||||
LOG(LOG_DAEMON | LOG_ERR, "%s", "nDPIsrvd socket memory allocation failed!");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (parse_options(argc, argv) != 0)
|
||||
if (parse_options(argc, argv, sock) != 0)
|
||||
{
|
||||
return 1;
|
||||
goto failure;
|
||||
}
|
||||
|
||||
if (getenv("COLLECTD_HOSTNAME") == NULL && getenv("COLLECTD_INTERVAL") == NULL)
|
||||
@@ -670,25 +705,32 @@ int main(int argc, char ** argv)
|
||||
if (connect_ret != CONNECT_OK)
|
||||
{
|
||||
LOG(LOG_DAEMON | LOG_ERR, "nDPIsrvd socket connect to %s failed!", serv_optarg);
|
||||
nDPIsrvd_free(&sock);
|
||||
return 1;
|
||||
goto failure;
|
||||
}
|
||||
|
||||
if (nDPIsrvd_set_nonblock(sock) != 0)
|
||||
{
|
||||
LOG(LOG_DAEMON | LOG_ERR, "nDPIsrvd set nonblock failed: %s", strerror(errno));
|
||||
goto failure;
|
||||
}
|
||||
|
||||
signal(SIGINT, sighandler);
|
||||
signal(SIGTERM, sighandler);
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
int epollfd = epoll_create1(0);
|
||||
collectd_pid = getppid();
|
||||
|
||||
epollfd = epoll_create1(0);
|
||||
if (epollfd < 0)
|
||||
{
|
||||
LOG(LOG_DAEMON | LOG_ERR, "Error creating epoll: %s", strerror(errno));
|
||||
return 1;
|
||||
goto failure;
|
||||
}
|
||||
|
||||
if (create_collectd_timer() != 0)
|
||||
{
|
||||
LOG(LOG_DAEMON | LOG_ERR, "Error creating timer: %s", strerror(errno));
|
||||
return 1;
|
||||
goto failure;
|
||||
}
|
||||
|
||||
{
|
||||
@@ -696,7 +738,7 @@ int main(int argc, char ** argv)
|
||||
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, collectd_timerfd, &timer_event) < 0)
|
||||
{
|
||||
LOG(LOG_DAEMON | LOG_ERR, "Error adding JSON fd to epoll: %s", strerror(errno));
|
||||
return 1;
|
||||
goto failure;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -705,14 +747,15 @@ int main(int argc, char ** argv)
|
||||
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, sock->fd, &socket_event) < 0)
|
||||
{
|
||||
LOG(LOG_DAEMON | LOG_ERR, "Error adding nDPIsrvd socket fd to epoll: %s", strerror(errno));
|
||||
return 1;
|
||||
goto failure;
|
||||
}
|
||||
}
|
||||
|
||||
LOG(LOG_DAEMON | LOG_NOTICE, "%s", "Initialization succeeded.");
|
||||
retval = mainloop(epollfd);
|
||||
retval = mainloop(epollfd, sock);
|
||||
|
||||
nDPIsrvd_free(&sock);
|
||||
failure:
|
||||
nDPIsrvd_socket_free(&sock);
|
||||
close(collectd_timerfd);
|
||||
close(epollfd);
|
||||
closelog();
|
||||
|
||||
@@ -11,7 +11,6 @@ flow_detection_update_count value:GAUGE:0:U
|
||||
flow_not_detected_count value:GAUGE:0:U
|
||||
|
||||
# flow additional counters
|
||||
flow_packet_count value:GAUGE:0:U
|
||||
flow_total_bytes value:GAUGE:0:U
|
||||
flow_risky_count value:GAUGE:0:U
|
||||
|
||||
@@ -60,6 +59,7 @@ flow_category_unknown_count value:GAUGE:0:U
|
||||
flow_l3_ip4_count value:GAUGE:0:U
|
||||
flow_l3_ip6_count value:GAUGE:0:U
|
||||
flow_l3_other_count value:GAUGE:0:U
|
||||
flow_l4_icmp_count value:GAUGE:0:U
|
||||
flow_l4_tcp_count value:GAUGE:0:U
|
||||
flow_l4_udp_count value:GAUGE:0:U
|
||||
flow_l4_other_count value:GAUGE:0:U
|
||||
|
||||
@@ -115,6 +115,9 @@ int main(void)
|
||||
{
|
||||
if (i % 2 == 1)
|
||||
{
|
||||
#ifdef JSMN_PARENT_LINKS
|
||||
printf("[%d][%d]", i, tokens[i].parent);
|
||||
#endif
|
||||
printf("[%.*s : ", tokens[i].end - tokens[i].start, (char *)(buf + json_start) + tokens[i].start);
|
||||
}
|
||||
else
|
||||
|
||||
228
examples/c-simple/c-simple.c
Normal file
228
examples/c-simple/c-simple.c
Normal file
@@ -0,0 +1,228 @@
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "nDPIsrvd.h"
|
||||
|
||||
static int main_thread_shutdown = 0;
|
||||
static struct nDPIsrvd_socket * sock = NULL;
|
||||
|
||||
#ifdef ENABLE_MEMORY_PROFILING
|
||||
void nDPIsrvd_memprof_log(char const * const format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, format);
|
||||
fprintf(stderr, "%s", "nDPIsrvd MemoryProfiler: ");
|
||||
vfprintf(stderr, format, ap);
|
||||
fprintf(stderr, "%s\n", "");
|
||||
va_end(ap);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void nDPIsrvd_write_flow_info_cb(struct nDPIsrvd_socket const * sock,
|
||||
struct nDPIsrvd_instance const * instance,
|
||||
struct nDPIsrvd_thread_data const * thread_data,
|
||||
struct nDPIsrvd_flow const * flow,
|
||||
void * user_data)
|
||||
{
|
||||
(void)sock;
|
||||
(void)instance;
|
||||
(void)user_data;
|
||||
|
||||
fprintf(stderr,
|
||||
"[Thread %2d][Flow %5llu][ptr: "
|
||||
#ifdef __LP64__
|
||||
"0x%016llx"
|
||||
#else
|
||||
"0x%08lx"
|
||||
#endif
|
||||
"][last-seen: %13llu][idle-time: %7llu][time-until-timeout: %7llu]\n",
|
||||
flow->thread_id,
|
||||
flow->id_as_ull,
|
||||
#ifdef __LP64__
|
||||
(unsigned long long int)flow,
|
||||
#else
|
||||
(unsigned long int)flow,
|
||||
#endif
|
||||
flow->last_seen,
|
||||
flow->idle_time,
|
||||
(flow->last_seen + flow->idle_time >= thread_data->most_recent_flow_time
|
||||
? flow->last_seen + flow->idle_time - thread_data->most_recent_flow_time
|
||||
: 0));
|
||||
}
|
||||
|
||||
static void nDPIsrvd_verify_flows_cb(struct nDPIsrvd_thread_data const * const thread_data,
|
||||
struct nDPIsrvd_flow const * const flow,
|
||||
void * user_data)
|
||||
{
|
||||
(void)user_data;
|
||||
|
||||
if (thread_data != NULL)
|
||||
{
|
||||
if (flow->last_seen + flow->idle_time >= thread_data->most_recent_flow_time)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"Thread %d / %d, Flow %llu verification failed\n",
|
||||
thread_data->thread_key,
|
||||
flow->thread_id,
|
||||
flow->id_as_ull);
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr,
|
||||
"Thread %d / %d, Flow %llu verification failed, diff: %llu\n",
|
||||
thread_data->thread_key,
|
||||
flow->thread_id,
|
||||
flow->id_as_ull,
|
||||
thread_data->most_recent_flow_time - flow->last_seen + flow->idle_time);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "Thread [UNKNOWN], Flow %llu verification failed\n", flow->id_as_ull);
|
||||
}
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static void sighandler(int signum)
|
||||
{
|
||||
struct nDPIsrvd_instance * current_instance;
|
||||
struct nDPIsrvd_instance * itmp;
|
||||
int verification_failed = 0;
|
||||
|
||||
if (signum == SIGUSR1)
|
||||
{
|
||||
nDPIsrvd_flow_info(sock, nDPIsrvd_write_flow_info_cb, NULL);
|
||||
|
||||
HASH_ITER(hh, sock->instance_table, current_instance, itmp)
|
||||
{
|
||||
if (nDPIsrvd_verify_flows(current_instance, nDPIsrvd_verify_flows_cb, NULL) != 0)
|
||||
{
|
||||
fprintf(stderr, "Flow verification failed for instance %d\n", current_instance->alias_source_key);
|
||||
verification_failed = 1;
|
||||
}
|
||||
}
|
||||
if (verification_failed == 0)
|
||||
{
|
||||
fprintf(stderr, "%s\n", "Flow verification succeeded.");
|
||||
}
|
||||
}
|
||||
else if (main_thread_shutdown == 0)
|
||||
{
|
||||
main_thread_shutdown = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static enum nDPIsrvd_callback_return simple_json_callback(struct nDPIsrvd_socket * const sock,
|
||||
struct nDPIsrvd_instance * const instance,
|
||||
struct nDPIsrvd_thread_data * const thread_data,
|
||||
struct nDPIsrvd_flow * const flow)
|
||||
{
|
||||
(void)sock;
|
||||
(void)thread_data;
|
||||
|
||||
if (flow == NULL)
|
||||
{
|
||||
return CALLBACK_OK;
|
||||
}
|
||||
|
||||
struct nDPIsrvd_json_token const * const flow_event_name = TOKEN_GET_SZ(sock, "flow_event_name");
|
||||
if (TOKEN_VALUE_EQUALS_SZ(flow_event_name, "new") != 0)
|
||||
{
|
||||
printf("Instance 0x%x, Thread %d, Flow %llu new\n",
|
||||
instance->alias_source_key,
|
||||
flow->thread_id,
|
||||
flow->id_as_ull);
|
||||
}
|
||||
|
||||
return CALLBACK_OK;
|
||||
}
|
||||
|
||||
static void simple_flow_cleanup_callback(struct nDPIsrvd_socket * const sock,
|
||||
struct nDPIsrvd_instance * const instance,
|
||||
struct nDPIsrvd_thread_data * const thread_data,
|
||||
struct nDPIsrvd_flow * const flow,
|
||||
enum nDPIsrvd_cleanup_reason reason)
|
||||
{
|
||||
(void)sock;
|
||||
(void)thread_data;
|
||||
|
||||
char const * const reason_str = nDPIsrvd_enum_to_string(reason);
|
||||
printf("Instance 0x%x, Thread %d, Flow %llu cleanup, reason: %s\n",
|
||||
instance->alias_source_key,
|
||||
flow->thread_id,
|
||||
flow->id_as_ull,
|
||||
(reason_str != NULL ? reason_str : "UNKNOWN"));
|
||||
|
||||
if (reason == CLEANUP_REASON_FLOW_TIMEOUT)
|
||||
{
|
||||
fprintf(stderr, "Flow %llu timeouted.\n", flow->id_as_ull);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char ** argv)
|
||||
{
|
||||
signal(SIGUSR1, sighandler);
|
||||
signal(SIGINT, sighandler);
|
||||
signal(SIGTERM, sighandler);
|
||||
signal(SIGPIPE, sighandler);
|
||||
|
||||
sock = nDPIsrvd_socket_init(0, 0, 0, 0, simple_json_callback, NULL, simple_flow_cleanup_callback);
|
||||
if (sock == NULL)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (nDPIsrvd_setup_address(&sock->address, (argc > 1 ? argv[1] : "127.0.0.1:7000")) != 0)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (nDPIsrvd_connect(sock) != CONNECT_OK)
|
||||
{
|
||||
nDPIsrvd_socket_free(&sock);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (nDPIsrvd_set_read_timeout(sock, 3, 0) != 0)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
enum nDPIsrvd_read_return read_ret = READ_OK;
|
||||
while (main_thread_shutdown == 0)
|
||||
{
|
||||
read_ret = nDPIsrvd_read(sock);
|
||||
if (errno == EINTR)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (read_ret == READ_TIMEOUT)
|
||||
{
|
||||
printf("No data received during the last %llu second(s).\n",
|
||||
(long long unsigned int)sock->read_timeout.tv_sec);
|
||||
continue;
|
||||
}
|
||||
if (read_ret != READ_OK)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
enum nDPIsrvd_parse_return parse_ret = nDPIsrvd_parse_all(sock);
|
||||
if (parse_ret != PARSE_NEED_MORE_DATA)
|
||||
{
|
||||
printf("Could not parse json string: %s\n", nDPIsrvd_enum_to_string(parse_ret));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (main_thread_shutdown == 0 && read_ret != READ_OK)
|
||||
{
|
||||
printf("Parse read %s\n", nDPIsrvd_enum_to_string(read_ret));
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
module github.com/lnslbrty/nDPId/examples/go-dashboard
|
||||
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
ui v0.0.0-00010101000000-000000000000
|
||||
)
|
||||
|
||||
replace ui => ./ui
|
||||
@@ -1,218 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"ui"
|
||||
)
|
||||
|
||||
var (
|
||||
WarningLogger *log.Logger
|
||||
InfoLogger *log.Logger
|
||||
ErrorLogger *log.Logger
|
||||
|
||||
NETWORK_BUFFER_MAX_SIZE uint16 = 12288
|
||||
NETWORK_BUFFER_LENGTH_DIGITS uint16 = 5
|
||||
)
|
||||
|
||||
type packet_event struct {
|
||||
ThreadID uint8 `json:"thread_id"`
|
||||
PacketID uint64 `json:"packet_id"`
|
||||
|
||||
FlowID uint32 `json:"flow_id"`
|
||||
FlowPacketID uint64 `json:"flow_packet_id"`
|
||||
|
||||
PacketEventID uint8 `json:"packet_event_id"`
|
||||
PacketEventName string `json:"packet_event_name"`
|
||||
PacketOversize bool `json:"pkt_oversize"`
|
||||
PacketTimestampS uint64 `json:"pkt_ts_sec"`
|
||||
PacketTimestampUs uint64 `json:"pkt_ts_usec"`
|
||||
PacketLength uint32 `json:"pkt_len"`
|
||||
PacketL4Length uint32 `json:"pkt_l4_len"`
|
||||
Packet string `json:"pkt"`
|
||||
PacketCaptureLength uint32 `json:"pkt_caplen"`
|
||||
PacketType uint32 `json:"pkt_type"`
|
||||
PacketL3Offset uint32 `json:"pkt_l3_offset"`
|
||||
PacketL4Offset uint32 `json:"pkt_l4_offset"`
|
||||
}
|
||||
|
||||
type flow_event struct {
|
||||
ThreadID uint8 `json:"thread_id"`
|
||||
PacketID uint64 `json:"packet_id"`
|
||||
|
||||
FlowID uint32 `json:"flow_id"`
|
||||
FlowPacketID uint64 `json:"flow_packet_id"`
|
||||
FlowFirstSeen uint64 `json:"flow_first_seen"`
|
||||
FlowLastSeen uint64 `json:"flow_last_seen"`
|
||||
FlowTotalLayer4DataLength uint64 `json:"flow_tot_l4_data_len"`
|
||||
FlowMinLayer4DataLength uint64 `json:"flow_min_l4_data_len"`
|
||||
FlowMaxLayer4DataLength uint64 `json:"flow_max_l4_data_len"`
|
||||
FlowAvgLayer4DataLength uint64 `json:"flow_avg_l4_data_len"`
|
||||
FlowDatalinkLayer uint8 `json:"flow_datalink"`
|
||||
MaxPackets uint8 `json:"flow_max_packets"`
|
||||
IsMidstreamFlow uint32 `json:"midstream"`
|
||||
}
|
||||
|
||||
type basic_event struct {
|
||||
ThreadID uint8 `json:"thread_id"`
|
||||
PacketID uint64 `json:"packet_id"`
|
||||
|
||||
BasicEventID uint8 `json:"basic_event_id"`
|
||||
BasicEventName string `json:"basic_event_name"`
|
||||
}
|
||||
|
||||
func processJson(jsonStr string) {
|
||||
jsonMap := make(map[string]interface{})
|
||||
err := json.Unmarshal([]byte(jsonStr), &jsonMap)
|
||||
if err != nil {
|
||||
ErrorLogger.Printf("BUG: JSON error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if jsonMap["packet_event_id"] != nil {
|
||||
pe := packet_event{}
|
||||
if err := json.Unmarshal([]byte(jsonStr), &pe); err != nil {
|
||||
ErrorLogger.Printf("BUG: JSON Unmarshal error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
InfoLogger.Printf("PACKET EVENT %v\n", pe)
|
||||
} else if jsonMap["flow_event_id"] != nil {
|
||||
fe := flow_event{}
|
||||
if err := json.Unmarshal([]byte(jsonStr), &fe); err != nil {
|
||||
ErrorLogger.Printf("BUG: JSON Unmarshal error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
InfoLogger.Printf("FLOW EVENT %v\n", fe)
|
||||
} else if jsonMap["basic_event_id"] != nil {
|
||||
be := basic_event{}
|
||||
if err := json.Unmarshal([]byte(jsonStr), &be); err != nil {
|
||||
ErrorLogger.Printf("BUG: JSON Unmarshal error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
InfoLogger.Printf("BASIC EVENT %v\n", be)
|
||||
} else {
|
||||
ErrorLogger.Printf("BUG: Unknown JSON: %v\n", jsonStr)
|
||||
os.Exit(1)
|
||||
}
|
||||
//InfoLogger.Printf("JSON map: %v\n-------------------------------------------------------\n", jsonMap)
|
||||
}
|
||||
|
||||
func eventHandler(ui *ui.Tui, wdgts *ui.Widgets, reader chan string) {
|
||||
for {
|
||||
select {
|
||||
case <-ui.MainTicker.C:
|
||||
if err := wdgts.RawJson.Write(fmt.Sprintf("%s\n", "--- HEARTBEAT ---")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
case <-ui.Context.Done():
|
||||
return
|
||||
|
||||
case jsonStr := <-reader:
|
||||
if err := wdgts.RawJson.Write(fmt.Sprintf("%s\n", jsonStr)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
InfoLogger = log.New(os.Stderr, "INFO: ", log.Ldate|log.Ltime|log.Lshortfile)
|
||||
WarningLogger = log.New(os.Stderr, "WARNING: ", log.Ldate|log.Ltime|log.Lshortfile)
|
||||
ErrorLogger = log.New(os.Stderr, "ERROR: ", log.Ldate|log.Ltime|log.Lshortfile)
|
||||
|
||||
writer := make(chan string, 256)
|
||||
|
||||
go func(writer chan string) {
|
||||
con, err := net.Dial("tcp", "127.0.0.1:7000")
|
||||
if err != nil {
|
||||
ErrorLogger.Printf("Connection failed: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
buf := make([]byte, NETWORK_BUFFER_MAX_SIZE)
|
||||
jsonStr := string("")
|
||||
jsonStrLen := uint16(0)
|
||||
jsonLen := uint16(0)
|
||||
brd := bufio.NewReaderSize(con, int(NETWORK_BUFFER_MAX_SIZE))
|
||||
|
||||
for {
|
||||
nread, err := brd.Read(buf)
|
||||
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
ErrorLogger.Printf("Read Error: %v\n", err)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if nread == 0 || err == io.EOF {
|
||||
WarningLogger.Printf("Disconnect from Server\n")
|
||||
break
|
||||
}
|
||||
|
||||
jsonStr += string(buf[:nread])
|
||||
jsonStrLen += uint16(nread)
|
||||
|
||||
for {
|
||||
if jsonStrLen < NETWORK_BUFFER_LENGTH_DIGITS+1 {
|
||||
break
|
||||
}
|
||||
|
||||
if jsonStr[NETWORK_BUFFER_LENGTH_DIGITS] != '{' {
|
||||
ErrorLogger.Printf("BUG: JSON invalid opening character at position %d: '%s' (%x)\n",
|
||||
NETWORK_BUFFER_LENGTH_DIGITS,
|
||||
string(jsonStr[:NETWORK_BUFFER_LENGTH_DIGITS]), jsonStr[NETWORK_BUFFER_LENGTH_DIGITS])
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if jsonLen == 0 {
|
||||
var tmp uint64
|
||||
if tmp, err = strconv.ParseUint(strings.TrimLeft(jsonStr[:NETWORK_BUFFER_LENGTH_DIGITS], "0"), 10, 16); err != nil {
|
||||
ErrorLogger.Printf("BUG: Could not parse length of a JSON string: %v\n", err)
|
||||
os.Exit(1)
|
||||
} else {
|
||||
jsonLen = uint16(tmp)
|
||||
}
|
||||
}
|
||||
|
||||
if jsonStrLen < jsonLen+NETWORK_BUFFER_LENGTH_DIGITS {
|
||||
break
|
||||
}
|
||||
|
||||
if jsonStr[jsonLen+NETWORK_BUFFER_LENGTH_DIGITS-2] != '}' || jsonStr[jsonLen+NETWORK_BUFFER_LENGTH_DIGITS-1] != '\n' {
|
||||
ErrorLogger.Printf("BUG: JSON invalid closing character at position %d: '%s'\n",
|
||||
jsonLen+NETWORK_BUFFER_LENGTH_DIGITS,
|
||||
string(jsonStr[jsonLen+NETWORK_BUFFER_LENGTH_DIGITS-1]))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
writer <- jsonStr[NETWORK_BUFFER_LENGTH_DIGITS : NETWORK_BUFFER_LENGTH_DIGITS+jsonLen]
|
||||
|
||||
jsonStr = jsonStr[jsonLen+NETWORK_BUFFER_LENGTH_DIGITS:]
|
||||
jsonStrLen -= (jsonLen + NETWORK_BUFFER_LENGTH_DIGITS)
|
||||
jsonLen = 0
|
||||
}
|
||||
}
|
||||
}(writer)
|
||||
|
||||
tui, wdgts := ui.Init()
|
||||
go eventHandler(tui, wdgts, writer)
|
||||
ui.Run(tui)
|
||||
|
||||
/*
|
||||
for {
|
||||
select {
|
||||
case _ = <-writer:
|
||||
break
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
module github.com/lnslbrty/nDPId/examples/go-dashboard/ui
|
||||
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/mum4k/termdash v0.12.3-0.20200901030524-fe3e97353191
|
||||
github.com/nsf/termbox-go v0.0.0-20200418040025-38ba6e5628f1 // indirect
|
||||
)
|
||||
@@ -1,104 +0,0 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/mum4k/termdash"
|
||||
"github.com/mum4k/termdash/container"
|
||||
"github.com/mum4k/termdash/keyboard"
|
||||
"github.com/mum4k/termdash/linestyle"
|
||||
"github.com/mum4k/termdash/terminal/termbox"
|
||||
"github.com/mum4k/termdash/terminal/terminalapi"
|
||||
"github.com/mum4k/termdash/widgets/text"
|
||||
)
|
||||
|
||||
const rootID = "root"
|
||||
const redrawInterval = 250 * time.Millisecond
|
||||
|
||||
type Tui struct {
|
||||
Term terminalapi.Terminal
|
||||
Context context.Context
|
||||
Cancel context.CancelFunc
|
||||
Container *container.Container
|
||||
MainTicker *time.Ticker
|
||||
}
|
||||
|
||||
type Widgets struct {
|
||||
Menu *text.Text
|
||||
RawJson *text.Text
|
||||
}
|
||||
|
||||
func newWidgets(ctx context.Context) (*Widgets, error) {
|
||||
menu, err := text.New()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
rawJson, err := text.New(text.RollContent(), text.WrapAtWords())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &Widgets{
|
||||
Menu: menu,
|
||||
RawJson: rawJson,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func Init() (*Tui, *Widgets) {
|
||||
var err error
|
||||
|
||||
ui := Tui{}
|
||||
|
||||
ui.Term, err = termbox.New(termbox.ColorMode(terminalapi.ColorMode256))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ui.Context, ui.Cancel = context.WithCancel(context.Background())
|
||||
|
||||
wdgts, err := newWidgets(ui.Context)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ui.Container, err = container.New(ui.Term,
|
||||
container.Border(linestyle.None),
|
||||
container.BorderTitle("[ESC to Quit]"),
|
||||
container.SplitHorizontal(
|
||||
container.Top(
|
||||
container.Border(linestyle.Light),
|
||||
container.BorderTitle("Go nDPId Dashboard"),
|
||||
container.PlaceWidget(wdgts.Menu),
|
||||
),
|
||||
container.Bottom(
|
||||
container.Border(linestyle.Light),
|
||||
container.BorderTitle("Raw JSON"),
|
||||
container.PlaceWidget(wdgts.RawJson),
|
||||
),
|
||||
container.SplitFixed(3),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ui.MainTicker = time.NewTicker(1 * time.Second)
|
||||
|
||||
return &ui, wdgts
|
||||
}
|
||||
|
||||
func Run(ui *Tui) {
|
||||
defer ui.Term.Close()
|
||||
|
||||
quitter := func(k *terminalapi.Keyboard) {
|
||||
if k.Key == keyboard.KeyEsc || k.Key == keyboard.KeyCtrlC {
|
||||
ui.Cancel()
|
||||
}
|
||||
}
|
||||
|
||||
if err := termdash.Run(ui.Context, ui.Term, ui.Container, termdash.KeyboardSubscriber(quitter), termdash.RedrawInterval(redrawInterval)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
16
examples/go-dashboard/vendor/github.com/mattn/go-runewidth/.travis.yml
generated
vendored
16
examples/go-dashboard/vendor/github.com/mattn/go-runewidth/.travis.yml
generated
vendored
@@ -1,16 +0,0 @@
|
||||
language: go
|
||||
sudo: false
|
||||
go:
|
||||
- 1.13.x
|
||||
- tip
|
||||
|
||||
before_install:
|
||||
- go get -t -v ./...
|
||||
|
||||
script:
|
||||
- go generate
|
||||
- git diff --cached --exit-code
|
||||
- ./go.test.sh
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
21
examples/go-dashboard/vendor/github.com/mattn/go-runewidth/LICENSE
generated
vendored
21
examples/go-dashboard/vendor/github.com/mattn/go-runewidth/LICENSE
generated
vendored
@@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Yasuhiro Matsumoto
|
||||
|
||||
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.
|
||||
27
examples/go-dashboard/vendor/github.com/mattn/go-runewidth/README.md
generated
vendored
27
examples/go-dashboard/vendor/github.com/mattn/go-runewidth/README.md
generated
vendored
@@ -1,27 +0,0 @@
|
||||
go-runewidth
|
||||
============
|
||||
|
||||
[](https://travis-ci.org/mattn/go-runewidth)
|
||||
[](https://codecov.io/gh/mattn/go-runewidth)
|
||||
[](http://godoc.org/github.com/mattn/go-runewidth)
|
||||
[](https://goreportcard.com/report/github.com/mattn/go-runewidth)
|
||||
|
||||
Provides functions to get fixed width of the character or string.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
```go
|
||||
runewidth.StringWidth("つのだ☆HIRO") == 12
|
||||
```
|
||||
|
||||
|
||||
Author
|
||||
------
|
||||
|
||||
Yasuhiro Matsumoto
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
under the MIT License: http://mattn.mit-license.org/2013
|
||||
3
examples/go-dashboard/vendor/github.com/mattn/go-runewidth/go.mod
generated
vendored
3
examples/go-dashboard/vendor/github.com/mattn/go-runewidth/go.mod
generated
vendored
@@ -1,3 +0,0 @@
|
||||
module github.com/mattn/go-runewidth
|
||||
|
||||
go 1.9
|
||||
12
examples/go-dashboard/vendor/github.com/mattn/go-runewidth/go.test.sh
generated
vendored
12
examples/go-dashboard/vendor/github.com/mattn/go-runewidth/go.test.sh
generated
vendored
@@ -1,12 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
echo "" > coverage.txt
|
||||
|
||||
for d in $(go list ./... | grep -v vendor); do
|
||||
go test -race -coverprofile=profile.out -covermode=atomic "$d"
|
||||
if [ -f profile.out ]; then
|
||||
cat profile.out >> coverage.txt
|
||||
rm profile.out
|
||||
fi
|
||||
done
|
||||
257
examples/go-dashboard/vendor/github.com/mattn/go-runewidth/runewidth.go
generated
vendored
257
examples/go-dashboard/vendor/github.com/mattn/go-runewidth/runewidth.go
generated
vendored
@@ -1,257 +0,0 @@
|
||||
package runewidth
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
//go:generate go run script/generate.go
|
||||
|
||||
var (
|
||||
// EastAsianWidth will be set true if the current locale is CJK
|
||||
EastAsianWidth bool
|
||||
|
||||
// ZeroWidthJoiner is flag to set to use UTR#51 ZWJ
|
||||
ZeroWidthJoiner bool
|
||||
|
||||
// DefaultCondition is a condition in current locale
|
||||
DefaultCondition = &Condition{}
|
||||
)
|
||||
|
||||
func init() {
|
||||
handleEnv()
|
||||
}
|
||||
|
||||
func handleEnv() {
|
||||
env := os.Getenv("RUNEWIDTH_EASTASIAN")
|
||||
if env == "" {
|
||||
EastAsianWidth = IsEastAsian()
|
||||
} else {
|
||||
EastAsianWidth = env == "1"
|
||||
}
|
||||
// update DefaultCondition
|
||||
DefaultCondition.EastAsianWidth = EastAsianWidth
|
||||
DefaultCondition.ZeroWidthJoiner = ZeroWidthJoiner
|
||||
}
|
||||
|
||||
type interval struct {
|
||||
first rune
|
||||
last rune
|
||||
}
|
||||
|
||||
type table []interval
|
||||
|
||||
func inTables(r rune, ts ...table) bool {
|
||||
for _, t := range ts {
|
||||
if inTable(r, t) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func inTable(r rune, t table) bool {
|
||||
if r < t[0].first {
|
||||
return false
|
||||
}
|
||||
|
||||
bot := 0
|
||||
top := len(t) - 1
|
||||
for top >= bot {
|
||||
mid := (bot + top) >> 1
|
||||
|
||||
switch {
|
||||
case t[mid].last < r:
|
||||
bot = mid + 1
|
||||
case t[mid].first > r:
|
||||
top = mid - 1
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
var private = table{
|
||||
{0x00E000, 0x00F8FF}, {0x0F0000, 0x0FFFFD}, {0x100000, 0x10FFFD},
|
||||
}
|
||||
|
||||
var nonprint = table{
|
||||
{0x0000, 0x001F}, {0x007F, 0x009F}, {0x00AD, 0x00AD},
|
||||
{0x070F, 0x070F}, {0x180B, 0x180E}, {0x200B, 0x200F},
|
||||
{0x2028, 0x202E}, {0x206A, 0x206F}, {0xD800, 0xDFFF},
|
||||
{0xFEFF, 0xFEFF}, {0xFFF9, 0xFFFB}, {0xFFFE, 0xFFFF},
|
||||
}
|
||||
|
||||
// Condition have flag EastAsianWidth whether the current locale is CJK or not.
|
||||
type Condition struct {
|
||||
EastAsianWidth bool
|
||||
ZeroWidthJoiner bool
|
||||
}
|
||||
|
||||
// NewCondition return new instance of Condition which is current locale.
|
||||
func NewCondition() *Condition {
|
||||
return &Condition{
|
||||
EastAsianWidth: EastAsianWidth,
|
||||
ZeroWidthJoiner: ZeroWidthJoiner,
|
||||
}
|
||||
}
|
||||
|
||||
// RuneWidth returns the number of cells in r.
|
||||
// See http://www.unicode.org/reports/tr11/
|
||||
func (c *Condition) RuneWidth(r rune) int {
|
||||
switch {
|
||||
case r < 0 || r > 0x10FFFF || inTables(r, nonprint, combining, notassigned):
|
||||
return 0
|
||||
case (c.EastAsianWidth && IsAmbiguousWidth(r)) || inTables(r, doublewidth):
|
||||
return 2
|
||||
default:
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Condition) stringWidth(s string) (width int) {
|
||||
for _, r := range []rune(s) {
|
||||
width += c.RuneWidth(r)
|
||||
}
|
||||
return width
|
||||
}
|
||||
|
||||
func (c *Condition) stringWidthZeroJoiner(s string) (width int) {
|
||||
r1, r2 := rune(0), rune(0)
|
||||
for _, r := range []rune(s) {
|
||||
if r == 0xFE0E || r == 0xFE0F {
|
||||
continue
|
||||
}
|
||||
w := c.RuneWidth(r)
|
||||
if r2 == 0x200D && inTables(r, emoji) && inTables(r1, emoji) {
|
||||
if width < w {
|
||||
width = w
|
||||
}
|
||||
} else {
|
||||
width += w
|
||||
}
|
||||
r1, r2 = r2, r
|
||||
}
|
||||
return width
|
||||
}
|
||||
|
||||
// StringWidth return width as you can see
|
||||
func (c *Condition) StringWidth(s string) (width int) {
|
||||
if c.ZeroWidthJoiner {
|
||||
return c.stringWidthZeroJoiner(s)
|
||||
}
|
||||
return c.stringWidth(s)
|
||||
}
|
||||
|
||||
// Truncate return string truncated with w cells
|
||||
func (c *Condition) Truncate(s string, w int, tail string) string {
|
||||
if c.StringWidth(s) <= w {
|
||||
return s
|
||||
}
|
||||
r := []rune(s)
|
||||
tw := c.StringWidth(tail)
|
||||
w -= tw
|
||||
width := 0
|
||||
i := 0
|
||||
for ; i < len(r); i++ {
|
||||
cw := c.RuneWidth(r[i])
|
||||
if width+cw > w {
|
||||
break
|
||||
}
|
||||
width += cw
|
||||
}
|
||||
return string(r[0:i]) + tail
|
||||
}
|
||||
|
||||
// Wrap return string wrapped with w cells
|
||||
func (c *Condition) Wrap(s string, w int) string {
|
||||
width := 0
|
||||
out := ""
|
||||
for _, r := range []rune(s) {
|
||||
cw := RuneWidth(r)
|
||||
if r == '\n' {
|
||||
out += string(r)
|
||||
width = 0
|
||||
continue
|
||||
} else if width+cw > w {
|
||||
out += "\n"
|
||||
width = 0
|
||||
out += string(r)
|
||||
width += cw
|
||||
continue
|
||||
}
|
||||
out += string(r)
|
||||
width += cw
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// FillLeft return string filled in left by spaces in w cells
|
||||
func (c *Condition) FillLeft(s string, w int) string {
|
||||
width := c.StringWidth(s)
|
||||
count := w - width
|
||||
if count > 0 {
|
||||
b := make([]byte, count)
|
||||
for i := range b {
|
||||
b[i] = ' '
|
||||
}
|
||||
return string(b) + s
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// FillRight return string filled in left by spaces in w cells
|
||||
func (c *Condition) FillRight(s string, w int) string {
|
||||
width := c.StringWidth(s)
|
||||
count := w - width
|
||||
if count > 0 {
|
||||
b := make([]byte, count)
|
||||
for i := range b {
|
||||
b[i] = ' '
|
||||
}
|
||||
return s + string(b)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// RuneWidth returns the number of cells in r.
|
||||
// See http://www.unicode.org/reports/tr11/
|
||||
func RuneWidth(r rune) int {
|
||||
return DefaultCondition.RuneWidth(r)
|
||||
}
|
||||
|
||||
// IsAmbiguousWidth returns whether is ambiguous width or not.
|
||||
func IsAmbiguousWidth(r rune) bool {
|
||||
return inTables(r, private, ambiguous)
|
||||
}
|
||||
|
||||
// IsNeutralWidth returns whether is neutral width or not.
|
||||
func IsNeutralWidth(r rune) bool {
|
||||
return inTable(r, neutral)
|
||||
}
|
||||
|
||||
// StringWidth return width as you can see
|
||||
func StringWidth(s string) (width int) {
|
||||
return DefaultCondition.StringWidth(s)
|
||||
}
|
||||
|
||||
// Truncate return string truncated with w cells
|
||||
func Truncate(s string, w int, tail string) string {
|
||||
return DefaultCondition.Truncate(s, w, tail)
|
||||
}
|
||||
|
||||
// Wrap return string wrapped with w cells
|
||||
func Wrap(s string, w int) string {
|
||||
return DefaultCondition.Wrap(s, w)
|
||||
}
|
||||
|
||||
// FillLeft return string filled in left by spaces in w cells
|
||||
func FillLeft(s string, w int) string {
|
||||
return DefaultCondition.FillLeft(s, w)
|
||||
}
|
||||
|
||||
// FillRight return string filled in left by spaces in w cells
|
||||
func FillRight(s string, w int) string {
|
||||
return DefaultCondition.FillRight(s, w)
|
||||
}
|
||||
8
examples/go-dashboard/vendor/github.com/mattn/go-runewidth/runewidth_appengine.go
generated
vendored
8
examples/go-dashboard/vendor/github.com/mattn/go-runewidth/runewidth_appengine.go
generated
vendored
@@ -1,8 +0,0 @@
|
||||
// +build appengine
|
||||
|
||||
package runewidth
|
||||
|
||||
// IsEastAsian return true if the current locale is CJK
|
||||
func IsEastAsian() bool {
|
||||
return false
|
||||
}
|
||||
9
examples/go-dashboard/vendor/github.com/mattn/go-runewidth/runewidth_js.go
generated
vendored
9
examples/go-dashboard/vendor/github.com/mattn/go-runewidth/runewidth_js.go
generated
vendored
@@ -1,9 +0,0 @@
|
||||
// +build js
|
||||
// +build !appengine
|
||||
|
||||
package runewidth
|
||||
|
||||
func IsEastAsian() bool {
|
||||
// TODO: Implement this for the web. Detect east asian in a compatible way, and return true.
|
||||
return false
|
||||
}
|
||||
82
examples/go-dashboard/vendor/github.com/mattn/go-runewidth/runewidth_posix.go
generated
vendored
82
examples/go-dashboard/vendor/github.com/mattn/go-runewidth/runewidth_posix.go
generated
vendored
@@ -1,82 +0,0 @@
|
||||
// +build !windows
|
||||
// +build !js
|
||||
// +build !appengine
|
||||
|
||||
package runewidth
|
||||
|
||||
import (
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var reLoc = regexp.MustCompile(`^[a-z][a-z][a-z]?(?:_[A-Z][A-Z])?\.(.+)`)
|
||||
|
||||
var mblenTable = map[string]int{
|
||||
"utf-8": 6,
|
||||
"utf8": 6,
|
||||
"jis": 8,
|
||||
"eucjp": 3,
|
||||
"euckr": 2,
|
||||
"euccn": 2,
|
||||
"sjis": 2,
|
||||
"cp932": 2,
|
||||
"cp51932": 2,
|
||||
"cp936": 2,
|
||||
"cp949": 2,
|
||||
"cp950": 2,
|
||||
"big5": 2,
|
||||
"gbk": 2,
|
||||
"gb2312": 2,
|
||||
}
|
||||
|
||||
func isEastAsian(locale string) bool {
|
||||
charset := strings.ToLower(locale)
|
||||
r := reLoc.FindStringSubmatch(locale)
|
||||
if len(r) == 2 {
|
||||
charset = strings.ToLower(r[1])
|
||||
}
|
||||
|
||||
if strings.HasSuffix(charset, "@cjk_narrow") {
|
||||
return false
|
||||
}
|
||||
|
||||
for pos, b := range []byte(charset) {
|
||||
if b == '@' {
|
||||
charset = charset[:pos]
|
||||
break
|
||||
}
|
||||
}
|
||||
max := 1
|
||||
if m, ok := mblenTable[charset]; ok {
|
||||
max = m
|
||||
}
|
||||
if max > 1 && (charset[0] != 'u' ||
|
||||
strings.HasPrefix(locale, "ja") ||
|
||||
strings.HasPrefix(locale, "ko") ||
|
||||
strings.HasPrefix(locale, "zh")) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsEastAsian return true if the current locale is CJK
|
||||
func IsEastAsian() bool {
|
||||
locale := os.Getenv("LC_ALL")
|
||||
if locale == "" {
|
||||
locale = os.Getenv("LC_CTYPE")
|
||||
}
|
||||
if locale == "" {
|
||||
locale = os.Getenv("LANG")
|
||||
}
|
||||
|
||||
// ignore C locale
|
||||
if locale == "POSIX" || locale == "C" {
|
||||
return false
|
||||
}
|
||||
if len(locale) > 1 && locale[0] == 'C' && (locale[1] == '.' || locale[1] == '-') {
|
||||
return false
|
||||
}
|
||||
|
||||
return isEastAsian(locale)
|
||||
}
|
||||
437
examples/go-dashboard/vendor/github.com/mattn/go-runewidth/runewidth_table.go
generated
vendored
437
examples/go-dashboard/vendor/github.com/mattn/go-runewidth/runewidth_table.go
generated
vendored
@@ -1,437 +0,0 @@
|
||||
// Code generated by script/generate.go. DO NOT EDIT.
|
||||
|
||||
package runewidth
|
||||
|
||||
var combining = table{
|
||||
{0x0300, 0x036F}, {0x0483, 0x0489}, {0x07EB, 0x07F3},
|
||||
{0x0C00, 0x0C00}, {0x0C04, 0x0C04}, {0x0D00, 0x0D01},
|
||||
{0x135D, 0x135F}, {0x1A7F, 0x1A7F}, {0x1AB0, 0x1AC0},
|
||||
{0x1B6B, 0x1B73}, {0x1DC0, 0x1DF9}, {0x1DFB, 0x1DFF},
|
||||
{0x20D0, 0x20F0}, {0x2CEF, 0x2CF1}, {0x2DE0, 0x2DFF},
|
||||
{0x3099, 0x309A}, {0xA66F, 0xA672}, {0xA674, 0xA67D},
|
||||
{0xA69E, 0xA69F}, {0xA6F0, 0xA6F1}, {0xA8E0, 0xA8F1},
|
||||
{0xFE20, 0xFE2F}, {0x101FD, 0x101FD}, {0x10376, 0x1037A},
|
||||
{0x10EAB, 0x10EAC}, {0x10F46, 0x10F50}, {0x11300, 0x11301},
|
||||
{0x1133B, 0x1133C}, {0x11366, 0x1136C}, {0x11370, 0x11374},
|
||||
{0x16AF0, 0x16AF4}, {0x1D165, 0x1D169}, {0x1D16D, 0x1D172},
|
||||
{0x1D17B, 0x1D182}, {0x1D185, 0x1D18B}, {0x1D1AA, 0x1D1AD},
|
||||
{0x1D242, 0x1D244}, {0x1E000, 0x1E006}, {0x1E008, 0x1E018},
|
||||
{0x1E01B, 0x1E021}, {0x1E023, 0x1E024}, {0x1E026, 0x1E02A},
|
||||
{0x1E8D0, 0x1E8D6},
|
||||
}
|
||||
|
||||
var doublewidth = table{
|
||||
{0x1100, 0x115F}, {0x231A, 0x231B}, {0x2329, 0x232A},
|
||||
{0x23E9, 0x23EC}, {0x23F0, 0x23F0}, {0x23F3, 0x23F3},
|
||||
{0x25FD, 0x25FE}, {0x2614, 0x2615}, {0x2648, 0x2653},
|
||||
{0x267F, 0x267F}, {0x2693, 0x2693}, {0x26A1, 0x26A1},
|
||||
{0x26AA, 0x26AB}, {0x26BD, 0x26BE}, {0x26C4, 0x26C5},
|
||||
{0x26CE, 0x26CE}, {0x26D4, 0x26D4}, {0x26EA, 0x26EA},
|
||||
{0x26F2, 0x26F3}, {0x26F5, 0x26F5}, {0x26FA, 0x26FA},
|
||||
{0x26FD, 0x26FD}, {0x2705, 0x2705}, {0x270A, 0x270B},
|
||||
{0x2728, 0x2728}, {0x274C, 0x274C}, {0x274E, 0x274E},
|
||||
{0x2753, 0x2755}, {0x2757, 0x2757}, {0x2795, 0x2797},
|
||||
{0x27B0, 0x27B0}, {0x27BF, 0x27BF}, {0x2B1B, 0x2B1C},
|
||||
{0x2B50, 0x2B50}, {0x2B55, 0x2B55}, {0x2E80, 0x2E99},
|
||||
{0x2E9B, 0x2EF3}, {0x2F00, 0x2FD5}, {0x2FF0, 0x2FFB},
|
||||
{0x3000, 0x303E}, {0x3041, 0x3096}, {0x3099, 0x30FF},
|
||||
{0x3105, 0x312F}, {0x3131, 0x318E}, {0x3190, 0x31E3},
|
||||
{0x31F0, 0x321E}, {0x3220, 0x3247}, {0x3250, 0x4DBF},
|
||||
{0x4E00, 0xA48C}, {0xA490, 0xA4C6}, {0xA960, 0xA97C},
|
||||
{0xAC00, 0xD7A3}, {0xF900, 0xFAFF}, {0xFE10, 0xFE19},
|
||||
{0xFE30, 0xFE52}, {0xFE54, 0xFE66}, {0xFE68, 0xFE6B},
|
||||
{0xFF01, 0xFF60}, {0xFFE0, 0xFFE6}, {0x16FE0, 0x16FE4},
|
||||
{0x16FF0, 0x16FF1}, {0x17000, 0x187F7}, {0x18800, 0x18CD5},
|
||||
{0x18D00, 0x18D08}, {0x1B000, 0x1B11E}, {0x1B150, 0x1B152},
|
||||
{0x1B164, 0x1B167}, {0x1B170, 0x1B2FB}, {0x1F004, 0x1F004},
|
||||
{0x1F0CF, 0x1F0CF}, {0x1F18E, 0x1F18E}, {0x1F191, 0x1F19A},
|
||||
{0x1F200, 0x1F202}, {0x1F210, 0x1F23B}, {0x1F240, 0x1F248},
|
||||
{0x1F250, 0x1F251}, {0x1F260, 0x1F265}, {0x1F300, 0x1F320},
|
||||
{0x1F32D, 0x1F335}, {0x1F337, 0x1F37C}, {0x1F37E, 0x1F393},
|
||||
{0x1F3A0, 0x1F3CA}, {0x1F3CF, 0x1F3D3}, {0x1F3E0, 0x1F3F0},
|
||||
{0x1F3F4, 0x1F3F4}, {0x1F3F8, 0x1F43E}, {0x1F440, 0x1F440},
|
||||
{0x1F442, 0x1F4FC}, {0x1F4FF, 0x1F53D}, {0x1F54B, 0x1F54E},
|
||||
{0x1F550, 0x1F567}, {0x1F57A, 0x1F57A}, {0x1F595, 0x1F596},
|
||||
{0x1F5A4, 0x1F5A4}, {0x1F5FB, 0x1F64F}, {0x1F680, 0x1F6C5},
|
||||
{0x1F6CC, 0x1F6CC}, {0x1F6D0, 0x1F6D2}, {0x1F6D5, 0x1F6D7},
|
||||
{0x1F6EB, 0x1F6EC}, {0x1F6F4, 0x1F6FC}, {0x1F7E0, 0x1F7EB},
|
||||
{0x1F90C, 0x1F93A}, {0x1F93C, 0x1F945}, {0x1F947, 0x1F978},
|
||||
{0x1F97A, 0x1F9CB}, {0x1F9CD, 0x1F9FF}, {0x1FA70, 0x1FA74},
|
||||
{0x1FA78, 0x1FA7A}, {0x1FA80, 0x1FA86}, {0x1FA90, 0x1FAA8},
|
||||
{0x1FAB0, 0x1FAB6}, {0x1FAC0, 0x1FAC2}, {0x1FAD0, 0x1FAD6},
|
||||
{0x20000, 0x2FFFD}, {0x30000, 0x3FFFD},
|
||||
}
|
||||
|
||||
var ambiguous = table{
|
||||
{0x00A1, 0x00A1}, {0x00A4, 0x00A4}, {0x00A7, 0x00A8},
|
||||
{0x00AA, 0x00AA}, {0x00AD, 0x00AE}, {0x00B0, 0x00B4},
|
||||
{0x00B6, 0x00BA}, {0x00BC, 0x00BF}, {0x00C6, 0x00C6},
|
||||
{0x00D0, 0x00D0}, {0x00D7, 0x00D8}, {0x00DE, 0x00E1},
|
||||
{0x00E6, 0x00E6}, {0x00E8, 0x00EA}, {0x00EC, 0x00ED},
|
||||
{0x00F0, 0x00F0}, {0x00F2, 0x00F3}, {0x00F7, 0x00FA},
|
||||
{0x00FC, 0x00FC}, {0x00FE, 0x00FE}, {0x0101, 0x0101},
|
||||
{0x0111, 0x0111}, {0x0113, 0x0113}, {0x011B, 0x011B},
|
||||
{0x0126, 0x0127}, {0x012B, 0x012B}, {0x0131, 0x0133},
|
||||
{0x0138, 0x0138}, {0x013F, 0x0142}, {0x0144, 0x0144},
|
||||
{0x0148, 0x014B}, {0x014D, 0x014D}, {0x0152, 0x0153},
|
||||
{0x0166, 0x0167}, {0x016B, 0x016B}, {0x01CE, 0x01CE},
|
||||
{0x01D0, 0x01D0}, {0x01D2, 0x01D2}, {0x01D4, 0x01D4},
|
||||
{0x01D6, 0x01D6}, {0x01D8, 0x01D8}, {0x01DA, 0x01DA},
|
||||
{0x01DC, 0x01DC}, {0x0251, 0x0251}, {0x0261, 0x0261},
|
||||
{0x02C4, 0x02C4}, {0x02C7, 0x02C7}, {0x02C9, 0x02CB},
|
||||
{0x02CD, 0x02CD}, {0x02D0, 0x02D0}, {0x02D8, 0x02DB},
|
||||
{0x02DD, 0x02DD}, {0x02DF, 0x02DF}, {0x0300, 0x036F},
|
||||
{0x0391, 0x03A1}, {0x03A3, 0x03A9}, {0x03B1, 0x03C1},
|
||||
{0x03C3, 0x03C9}, {0x0401, 0x0401}, {0x0410, 0x044F},
|
||||
{0x0451, 0x0451}, {0x2010, 0x2010}, {0x2013, 0x2016},
|
||||
{0x2018, 0x2019}, {0x201C, 0x201D}, {0x2020, 0x2022},
|
||||
{0x2024, 0x2027}, {0x2030, 0x2030}, {0x2032, 0x2033},
|
||||
{0x2035, 0x2035}, {0x203B, 0x203B}, {0x203E, 0x203E},
|
||||
{0x2074, 0x2074}, {0x207F, 0x207F}, {0x2081, 0x2084},
|
||||
{0x20AC, 0x20AC}, {0x2103, 0x2103}, {0x2105, 0x2105},
|
||||
{0x2109, 0x2109}, {0x2113, 0x2113}, {0x2116, 0x2116},
|
||||
{0x2121, 0x2122}, {0x2126, 0x2126}, {0x212B, 0x212B},
|
||||
{0x2153, 0x2154}, {0x215B, 0x215E}, {0x2160, 0x216B},
|
||||
{0x2170, 0x2179}, {0x2189, 0x2189}, {0x2190, 0x2199},
|
||||
{0x21B8, 0x21B9}, {0x21D2, 0x21D2}, {0x21D4, 0x21D4},
|
||||
{0x21E7, 0x21E7}, {0x2200, 0x2200}, {0x2202, 0x2203},
|
||||
{0x2207, 0x2208}, {0x220B, 0x220B}, {0x220F, 0x220F},
|
||||
{0x2211, 0x2211}, {0x2215, 0x2215}, {0x221A, 0x221A},
|
||||
{0x221D, 0x2220}, {0x2223, 0x2223}, {0x2225, 0x2225},
|
||||
{0x2227, 0x222C}, {0x222E, 0x222E}, {0x2234, 0x2237},
|
||||
{0x223C, 0x223D}, {0x2248, 0x2248}, {0x224C, 0x224C},
|
||||
{0x2252, 0x2252}, {0x2260, 0x2261}, {0x2264, 0x2267},
|
||||
{0x226A, 0x226B}, {0x226E, 0x226F}, {0x2282, 0x2283},
|
||||
{0x2286, 0x2287}, {0x2295, 0x2295}, {0x2299, 0x2299},
|
||||
{0x22A5, 0x22A5}, {0x22BF, 0x22BF}, {0x2312, 0x2312},
|
||||
{0x2460, 0x24E9}, {0x24EB, 0x254B}, {0x2550, 0x2573},
|
||||
{0x2580, 0x258F}, {0x2592, 0x2595}, {0x25A0, 0x25A1},
|
||||
{0x25A3, 0x25A9}, {0x25B2, 0x25B3}, {0x25B6, 0x25B7},
|
||||
{0x25BC, 0x25BD}, {0x25C0, 0x25C1}, {0x25C6, 0x25C8},
|
||||
{0x25CB, 0x25CB}, {0x25CE, 0x25D1}, {0x25E2, 0x25E5},
|
||||
{0x25EF, 0x25EF}, {0x2605, 0x2606}, {0x2609, 0x2609},
|
||||
{0x260E, 0x260F}, {0x261C, 0x261C}, {0x261E, 0x261E},
|
||||
{0x2640, 0x2640}, {0x2642, 0x2642}, {0x2660, 0x2661},
|
||||
{0x2663, 0x2665}, {0x2667, 0x266A}, {0x266C, 0x266D},
|
||||
{0x266F, 0x266F}, {0x269E, 0x269F}, {0x26BF, 0x26BF},
|
||||
{0x26C6, 0x26CD}, {0x26CF, 0x26D3}, {0x26D5, 0x26E1},
|
||||
{0x26E3, 0x26E3}, {0x26E8, 0x26E9}, {0x26EB, 0x26F1},
|
||||
{0x26F4, 0x26F4}, {0x26F6, 0x26F9}, {0x26FB, 0x26FC},
|
||||
{0x26FE, 0x26FF}, {0x273D, 0x273D}, {0x2776, 0x277F},
|
||||
{0x2B56, 0x2B59}, {0x3248, 0x324F}, {0xE000, 0xF8FF},
|
||||
{0xFE00, 0xFE0F}, {0xFFFD, 0xFFFD}, {0x1F100, 0x1F10A},
|
||||
{0x1F110, 0x1F12D}, {0x1F130, 0x1F169}, {0x1F170, 0x1F18D},
|
||||
{0x1F18F, 0x1F190}, {0x1F19B, 0x1F1AC}, {0xE0100, 0xE01EF},
|
||||
{0xF0000, 0xFFFFD}, {0x100000, 0x10FFFD},
|
||||
}
|
||||
var notassigned = table{
|
||||
{0x27E6, 0x27ED}, {0x2985, 0x2986},
|
||||
}
|
||||
|
||||
var neutral = table{
|
||||
{0x0000, 0x001F}, {0x007F, 0x00A0}, {0x00A9, 0x00A9},
|
||||
{0x00AB, 0x00AB}, {0x00B5, 0x00B5}, {0x00BB, 0x00BB},
|
||||
{0x00C0, 0x00C5}, {0x00C7, 0x00CF}, {0x00D1, 0x00D6},
|
||||
{0x00D9, 0x00DD}, {0x00E2, 0x00E5}, {0x00E7, 0x00E7},
|
||||
{0x00EB, 0x00EB}, {0x00EE, 0x00EF}, {0x00F1, 0x00F1},
|
||||
{0x00F4, 0x00F6}, {0x00FB, 0x00FB}, {0x00FD, 0x00FD},
|
||||
{0x00FF, 0x0100}, {0x0102, 0x0110}, {0x0112, 0x0112},
|
||||
{0x0114, 0x011A}, {0x011C, 0x0125}, {0x0128, 0x012A},
|
||||
{0x012C, 0x0130}, {0x0134, 0x0137}, {0x0139, 0x013E},
|
||||
{0x0143, 0x0143}, {0x0145, 0x0147}, {0x014C, 0x014C},
|
||||
{0x014E, 0x0151}, {0x0154, 0x0165}, {0x0168, 0x016A},
|
||||
{0x016C, 0x01CD}, {0x01CF, 0x01CF}, {0x01D1, 0x01D1},
|
||||
{0x01D3, 0x01D3}, {0x01D5, 0x01D5}, {0x01D7, 0x01D7},
|
||||
{0x01D9, 0x01D9}, {0x01DB, 0x01DB}, {0x01DD, 0x0250},
|
||||
{0x0252, 0x0260}, {0x0262, 0x02C3}, {0x02C5, 0x02C6},
|
||||
{0x02C8, 0x02C8}, {0x02CC, 0x02CC}, {0x02CE, 0x02CF},
|
||||
{0x02D1, 0x02D7}, {0x02DC, 0x02DC}, {0x02DE, 0x02DE},
|
||||
{0x02E0, 0x02FF}, {0x0370, 0x0377}, {0x037A, 0x037F},
|
||||
{0x0384, 0x038A}, {0x038C, 0x038C}, {0x038E, 0x0390},
|
||||
{0x03AA, 0x03B0}, {0x03C2, 0x03C2}, {0x03CA, 0x0400},
|
||||
{0x0402, 0x040F}, {0x0450, 0x0450}, {0x0452, 0x052F},
|
||||
{0x0531, 0x0556}, {0x0559, 0x058A}, {0x058D, 0x058F},
|
||||
{0x0591, 0x05C7}, {0x05D0, 0x05EA}, {0x05EF, 0x05F4},
|
||||
{0x0600, 0x061C}, {0x061E, 0x070D}, {0x070F, 0x074A},
|
||||
{0x074D, 0x07B1}, {0x07C0, 0x07FA}, {0x07FD, 0x082D},
|
||||
{0x0830, 0x083E}, {0x0840, 0x085B}, {0x085E, 0x085E},
|
||||
{0x0860, 0x086A}, {0x08A0, 0x08B4}, {0x08B6, 0x08C7},
|
||||
{0x08D3, 0x0983}, {0x0985, 0x098C}, {0x098F, 0x0990},
|
||||
{0x0993, 0x09A8}, {0x09AA, 0x09B0}, {0x09B2, 0x09B2},
|
||||
{0x09B6, 0x09B9}, {0x09BC, 0x09C4}, {0x09C7, 0x09C8},
|
||||
{0x09CB, 0x09CE}, {0x09D7, 0x09D7}, {0x09DC, 0x09DD},
|
||||
{0x09DF, 0x09E3}, {0x09E6, 0x09FE}, {0x0A01, 0x0A03},
|
||||
{0x0A05, 0x0A0A}, {0x0A0F, 0x0A10}, {0x0A13, 0x0A28},
|
||||
{0x0A2A, 0x0A30}, {0x0A32, 0x0A33}, {0x0A35, 0x0A36},
|
||||
{0x0A38, 0x0A39}, {0x0A3C, 0x0A3C}, {0x0A3E, 0x0A42},
|
||||
{0x0A47, 0x0A48}, {0x0A4B, 0x0A4D}, {0x0A51, 0x0A51},
|
||||
{0x0A59, 0x0A5C}, {0x0A5E, 0x0A5E}, {0x0A66, 0x0A76},
|
||||
{0x0A81, 0x0A83}, {0x0A85, 0x0A8D}, {0x0A8F, 0x0A91},
|
||||
{0x0A93, 0x0AA8}, {0x0AAA, 0x0AB0}, {0x0AB2, 0x0AB3},
|
||||
{0x0AB5, 0x0AB9}, {0x0ABC, 0x0AC5}, {0x0AC7, 0x0AC9},
|
||||
{0x0ACB, 0x0ACD}, {0x0AD0, 0x0AD0}, {0x0AE0, 0x0AE3},
|
||||
{0x0AE6, 0x0AF1}, {0x0AF9, 0x0AFF}, {0x0B01, 0x0B03},
|
||||
{0x0B05, 0x0B0C}, {0x0B0F, 0x0B10}, {0x0B13, 0x0B28},
|
||||
{0x0B2A, 0x0B30}, {0x0B32, 0x0B33}, {0x0B35, 0x0B39},
|
||||
{0x0B3C, 0x0B44}, {0x0B47, 0x0B48}, {0x0B4B, 0x0B4D},
|
||||
{0x0B55, 0x0B57}, {0x0B5C, 0x0B5D}, {0x0B5F, 0x0B63},
|
||||
{0x0B66, 0x0B77}, {0x0B82, 0x0B83}, {0x0B85, 0x0B8A},
|
||||
{0x0B8E, 0x0B90}, {0x0B92, 0x0B95}, {0x0B99, 0x0B9A},
|
||||
{0x0B9C, 0x0B9C}, {0x0B9E, 0x0B9F}, {0x0BA3, 0x0BA4},
|
||||
{0x0BA8, 0x0BAA}, {0x0BAE, 0x0BB9}, {0x0BBE, 0x0BC2},
|
||||
{0x0BC6, 0x0BC8}, {0x0BCA, 0x0BCD}, {0x0BD0, 0x0BD0},
|
||||
{0x0BD7, 0x0BD7}, {0x0BE6, 0x0BFA}, {0x0C00, 0x0C0C},
|
||||
{0x0C0E, 0x0C10}, {0x0C12, 0x0C28}, {0x0C2A, 0x0C39},
|
||||
{0x0C3D, 0x0C44}, {0x0C46, 0x0C48}, {0x0C4A, 0x0C4D},
|
||||
{0x0C55, 0x0C56}, {0x0C58, 0x0C5A}, {0x0C60, 0x0C63},
|
||||
{0x0C66, 0x0C6F}, {0x0C77, 0x0C8C}, {0x0C8E, 0x0C90},
|
||||
{0x0C92, 0x0CA8}, {0x0CAA, 0x0CB3}, {0x0CB5, 0x0CB9},
|
||||
{0x0CBC, 0x0CC4}, {0x0CC6, 0x0CC8}, {0x0CCA, 0x0CCD},
|
||||
{0x0CD5, 0x0CD6}, {0x0CDE, 0x0CDE}, {0x0CE0, 0x0CE3},
|
||||
{0x0CE6, 0x0CEF}, {0x0CF1, 0x0CF2}, {0x0D00, 0x0D0C},
|
||||
{0x0D0E, 0x0D10}, {0x0D12, 0x0D44}, {0x0D46, 0x0D48},
|
||||
{0x0D4A, 0x0D4F}, {0x0D54, 0x0D63}, {0x0D66, 0x0D7F},
|
||||
{0x0D81, 0x0D83}, {0x0D85, 0x0D96}, {0x0D9A, 0x0DB1},
|
||||
{0x0DB3, 0x0DBB}, {0x0DBD, 0x0DBD}, {0x0DC0, 0x0DC6},
|
||||
{0x0DCA, 0x0DCA}, {0x0DCF, 0x0DD4}, {0x0DD6, 0x0DD6},
|
||||
{0x0DD8, 0x0DDF}, {0x0DE6, 0x0DEF}, {0x0DF2, 0x0DF4},
|
||||
{0x0E01, 0x0E3A}, {0x0E3F, 0x0E5B}, {0x0E81, 0x0E82},
|
||||
{0x0E84, 0x0E84}, {0x0E86, 0x0E8A}, {0x0E8C, 0x0EA3},
|
||||
{0x0EA5, 0x0EA5}, {0x0EA7, 0x0EBD}, {0x0EC0, 0x0EC4},
|
||||
{0x0EC6, 0x0EC6}, {0x0EC8, 0x0ECD}, {0x0ED0, 0x0ED9},
|
||||
{0x0EDC, 0x0EDF}, {0x0F00, 0x0F47}, {0x0F49, 0x0F6C},
|
||||
{0x0F71, 0x0F97}, {0x0F99, 0x0FBC}, {0x0FBE, 0x0FCC},
|
||||
{0x0FCE, 0x0FDA}, {0x1000, 0x10C5}, {0x10C7, 0x10C7},
|
||||
{0x10CD, 0x10CD}, {0x10D0, 0x10FF}, {0x1160, 0x1248},
|
||||
{0x124A, 0x124D}, {0x1250, 0x1256}, {0x1258, 0x1258},
|
||||
{0x125A, 0x125D}, {0x1260, 0x1288}, {0x128A, 0x128D},
|
||||
{0x1290, 0x12B0}, {0x12B2, 0x12B5}, {0x12B8, 0x12BE},
|
||||
{0x12C0, 0x12C0}, {0x12C2, 0x12C5}, {0x12C8, 0x12D6},
|
||||
{0x12D8, 0x1310}, {0x1312, 0x1315}, {0x1318, 0x135A},
|
||||
{0x135D, 0x137C}, {0x1380, 0x1399}, {0x13A0, 0x13F5},
|
||||
{0x13F8, 0x13FD}, {0x1400, 0x169C}, {0x16A0, 0x16F8},
|
||||
{0x1700, 0x170C}, {0x170E, 0x1714}, {0x1720, 0x1736},
|
||||
{0x1740, 0x1753}, {0x1760, 0x176C}, {0x176E, 0x1770},
|
||||
{0x1772, 0x1773}, {0x1780, 0x17DD}, {0x17E0, 0x17E9},
|
||||
{0x17F0, 0x17F9}, {0x1800, 0x180E}, {0x1810, 0x1819},
|
||||
{0x1820, 0x1878}, {0x1880, 0x18AA}, {0x18B0, 0x18F5},
|
||||
{0x1900, 0x191E}, {0x1920, 0x192B}, {0x1930, 0x193B},
|
||||
{0x1940, 0x1940}, {0x1944, 0x196D}, {0x1970, 0x1974},
|
||||
{0x1980, 0x19AB}, {0x19B0, 0x19C9}, {0x19D0, 0x19DA},
|
||||
{0x19DE, 0x1A1B}, {0x1A1E, 0x1A5E}, {0x1A60, 0x1A7C},
|
||||
{0x1A7F, 0x1A89}, {0x1A90, 0x1A99}, {0x1AA0, 0x1AAD},
|
||||
{0x1AB0, 0x1AC0}, {0x1B00, 0x1B4B}, {0x1B50, 0x1B7C},
|
||||
{0x1B80, 0x1BF3}, {0x1BFC, 0x1C37}, {0x1C3B, 0x1C49},
|
||||
{0x1C4D, 0x1C88}, {0x1C90, 0x1CBA}, {0x1CBD, 0x1CC7},
|
||||
{0x1CD0, 0x1CFA}, {0x1D00, 0x1DF9}, {0x1DFB, 0x1F15},
|
||||
{0x1F18, 0x1F1D}, {0x1F20, 0x1F45}, {0x1F48, 0x1F4D},
|
||||
{0x1F50, 0x1F57}, {0x1F59, 0x1F59}, {0x1F5B, 0x1F5B},
|
||||
{0x1F5D, 0x1F5D}, {0x1F5F, 0x1F7D}, {0x1F80, 0x1FB4},
|
||||
{0x1FB6, 0x1FC4}, {0x1FC6, 0x1FD3}, {0x1FD6, 0x1FDB},
|
||||
{0x1FDD, 0x1FEF}, {0x1FF2, 0x1FF4}, {0x1FF6, 0x1FFE},
|
||||
{0x2000, 0x200F}, {0x2011, 0x2012}, {0x2017, 0x2017},
|
||||
{0x201A, 0x201B}, {0x201E, 0x201F}, {0x2023, 0x2023},
|
||||
{0x2028, 0x202F}, {0x2031, 0x2031}, {0x2034, 0x2034},
|
||||
{0x2036, 0x203A}, {0x203C, 0x203D}, {0x203F, 0x2064},
|
||||
{0x2066, 0x2071}, {0x2075, 0x207E}, {0x2080, 0x2080},
|
||||
{0x2085, 0x208E}, {0x2090, 0x209C}, {0x20A0, 0x20A8},
|
||||
{0x20AA, 0x20AB}, {0x20AD, 0x20BF}, {0x20D0, 0x20F0},
|
||||
{0x2100, 0x2102}, {0x2104, 0x2104}, {0x2106, 0x2108},
|
||||
{0x210A, 0x2112}, {0x2114, 0x2115}, {0x2117, 0x2120},
|
||||
{0x2123, 0x2125}, {0x2127, 0x212A}, {0x212C, 0x2152},
|
||||
{0x2155, 0x215A}, {0x215F, 0x215F}, {0x216C, 0x216F},
|
||||
{0x217A, 0x2188}, {0x218A, 0x218B}, {0x219A, 0x21B7},
|
||||
{0x21BA, 0x21D1}, {0x21D3, 0x21D3}, {0x21D5, 0x21E6},
|
||||
{0x21E8, 0x21FF}, {0x2201, 0x2201}, {0x2204, 0x2206},
|
||||
{0x2209, 0x220A}, {0x220C, 0x220E}, {0x2210, 0x2210},
|
||||
{0x2212, 0x2214}, {0x2216, 0x2219}, {0x221B, 0x221C},
|
||||
{0x2221, 0x2222}, {0x2224, 0x2224}, {0x2226, 0x2226},
|
||||
{0x222D, 0x222D}, {0x222F, 0x2233}, {0x2238, 0x223B},
|
||||
{0x223E, 0x2247}, {0x2249, 0x224B}, {0x224D, 0x2251},
|
||||
{0x2253, 0x225F}, {0x2262, 0x2263}, {0x2268, 0x2269},
|
||||
{0x226C, 0x226D}, {0x2270, 0x2281}, {0x2284, 0x2285},
|
||||
{0x2288, 0x2294}, {0x2296, 0x2298}, {0x229A, 0x22A4},
|
||||
{0x22A6, 0x22BE}, {0x22C0, 0x2311}, {0x2313, 0x2319},
|
||||
{0x231C, 0x2328}, {0x232B, 0x23E8}, {0x23ED, 0x23EF},
|
||||
{0x23F1, 0x23F2}, {0x23F4, 0x2426}, {0x2440, 0x244A},
|
||||
{0x24EA, 0x24EA}, {0x254C, 0x254F}, {0x2574, 0x257F},
|
||||
{0x2590, 0x2591}, {0x2596, 0x259F}, {0x25A2, 0x25A2},
|
||||
{0x25AA, 0x25B1}, {0x25B4, 0x25B5}, {0x25B8, 0x25BB},
|
||||
{0x25BE, 0x25BF}, {0x25C2, 0x25C5}, {0x25C9, 0x25CA},
|
||||
{0x25CC, 0x25CD}, {0x25D2, 0x25E1}, {0x25E6, 0x25EE},
|
||||
{0x25F0, 0x25FC}, {0x25FF, 0x2604}, {0x2607, 0x2608},
|
||||
{0x260A, 0x260D}, {0x2610, 0x2613}, {0x2616, 0x261B},
|
||||
{0x261D, 0x261D}, {0x261F, 0x263F}, {0x2641, 0x2641},
|
||||
{0x2643, 0x2647}, {0x2654, 0x265F}, {0x2662, 0x2662},
|
||||
{0x2666, 0x2666}, {0x266B, 0x266B}, {0x266E, 0x266E},
|
||||
{0x2670, 0x267E}, {0x2680, 0x2692}, {0x2694, 0x269D},
|
||||
{0x26A0, 0x26A0}, {0x26A2, 0x26A9}, {0x26AC, 0x26BC},
|
||||
{0x26C0, 0x26C3}, {0x26E2, 0x26E2}, {0x26E4, 0x26E7},
|
||||
{0x2700, 0x2704}, {0x2706, 0x2709}, {0x270C, 0x2727},
|
||||
{0x2729, 0x273C}, {0x273E, 0x274B}, {0x274D, 0x274D},
|
||||
{0x274F, 0x2752}, {0x2756, 0x2756}, {0x2758, 0x2775},
|
||||
{0x2780, 0x2794}, {0x2798, 0x27AF}, {0x27B1, 0x27BE},
|
||||
{0x27C0, 0x27E5}, {0x27EE, 0x2984}, {0x2987, 0x2B1A},
|
||||
{0x2B1D, 0x2B4F}, {0x2B51, 0x2B54}, {0x2B5A, 0x2B73},
|
||||
{0x2B76, 0x2B95}, {0x2B97, 0x2C2E}, {0x2C30, 0x2C5E},
|
||||
{0x2C60, 0x2CF3}, {0x2CF9, 0x2D25}, {0x2D27, 0x2D27},
|
||||
{0x2D2D, 0x2D2D}, {0x2D30, 0x2D67}, {0x2D6F, 0x2D70},
|
||||
{0x2D7F, 0x2D96}, {0x2DA0, 0x2DA6}, {0x2DA8, 0x2DAE},
|
||||
{0x2DB0, 0x2DB6}, {0x2DB8, 0x2DBE}, {0x2DC0, 0x2DC6},
|
||||
{0x2DC8, 0x2DCE}, {0x2DD0, 0x2DD6}, {0x2DD8, 0x2DDE},
|
||||
{0x2DE0, 0x2E52}, {0x303F, 0x303F}, {0x4DC0, 0x4DFF},
|
||||
{0xA4D0, 0xA62B}, {0xA640, 0xA6F7}, {0xA700, 0xA7BF},
|
||||
{0xA7C2, 0xA7CA}, {0xA7F5, 0xA82C}, {0xA830, 0xA839},
|
||||
{0xA840, 0xA877}, {0xA880, 0xA8C5}, {0xA8CE, 0xA8D9},
|
||||
{0xA8E0, 0xA953}, {0xA95F, 0xA95F}, {0xA980, 0xA9CD},
|
||||
{0xA9CF, 0xA9D9}, {0xA9DE, 0xA9FE}, {0xAA00, 0xAA36},
|
||||
{0xAA40, 0xAA4D}, {0xAA50, 0xAA59}, {0xAA5C, 0xAAC2},
|
||||
{0xAADB, 0xAAF6}, {0xAB01, 0xAB06}, {0xAB09, 0xAB0E},
|
||||
{0xAB11, 0xAB16}, {0xAB20, 0xAB26}, {0xAB28, 0xAB2E},
|
||||
{0xAB30, 0xAB6B}, {0xAB70, 0xABED}, {0xABF0, 0xABF9},
|
||||
{0xD7B0, 0xD7C6}, {0xD7CB, 0xD7FB}, {0xD800, 0xDFFF},
|
||||
{0xFB00, 0xFB06}, {0xFB13, 0xFB17}, {0xFB1D, 0xFB36},
|
||||
{0xFB38, 0xFB3C}, {0xFB3E, 0xFB3E}, {0xFB40, 0xFB41},
|
||||
{0xFB43, 0xFB44}, {0xFB46, 0xFBC1}, {0xFBD3, 0xFD3F},
|
||||
{0xFD50, 0xFD8F}, {0xFD92, 0xFDC7}, {0xFDF0, 0xFDFD},
|
||||
{0xFE20, 0xFE2F}, {0xFE70, 0xFE74}, {0xFE76, 0xFEFC},
|
||||
{0xFEFF, 0xFEFF}, {0xFFF9, 0xFFFC}, {0x10000, 0x1000B},
|
||||
{0x1000D, 0x10026}, {0x10028, 0x1003A}, {0x1003C, 0x1003D},
|
||||
{0x1003F, 0x1004D}, {0x10050, 0x1005D}, {0x10080, 0x100FA},
|
||||
{0x10100, 0x10102}, {0x10107, 0x10133}, {0x10137, 0x1018E},
|
||||
{0x10190, 0x1019C}, {0x101A0, 0x101A0}, {0x101D0, 0x101FD},
|
||||
{0x10280, 0x1029C}, {0x102A0, 0x102D0}, {0x102E0, 0x102FB},
|
||||
{0x10300, 0x10323}, {0x1032D, 0x1034A}, {0x10350, 0x1037A},
|
||||
{0x10380, 0x1039D}, {0x1039F, 0x103C3}, {0x103C8, 0x103D5},
|
||||
{0x10400, 0x1049D}, {0x104A0, 0x104A9}, {0x104B0, 0x104D3},
|
||||
{0x104D8, 0x104FB}, {0x10500, 0x10527}, {0x10530, 0x10563},
|
||||
{0x1056F, 0x1056F}, {0x10600, 0x10736}, {0x10740, 0x10755},
|
||||
{0x10760, 0x10767}, {0x10800, 0x10805}, {0x10808, 0x10808},
|
||||
{0x1080A, 0x10835}, {0x10837, 0x10838}, {0x1083C, 0x1083C},
|
||||
{0x1083F, 0x10855}, {0x10857, 0x1089E}, {0x108A7, 0x108AF},
|
||||
{0x108E0, 0x108F2}, {0x108F4, 0x108F5}, {0x108FB, 0x1091B},
|
||||
{0x1091F, 0x10939}, {0x1093F, 0x1093F}, {0x10980, 0x109B7},
|
||||
{0x109BC, 0x109CF}, {0x109D2, 0x10A03}, {0x10A05, 0x10A06},
|
||||
{0x10A0C, 0x10A13}, {0x10A15, 0x10A17}, {0x10A19, 0x10A35},
|
||||
{0x10A38, 0x10A3A}, {0x10A3F, 0x10A48}, {0x10A50, 0x10A58},
|
||||
{0x10A60, 0x10A9F}, {0x10AC0, 0x10AE6}, {0x10AEB, 0x10AF6},
|
||||
{0x10B00, 0x10B35}, {0x10B39, 0x10B55}, {0x10B58, 0x10B72},
|
||||
{0x10B78, 0x10B91}, {0x10B99, 0x10B9C}, {0x10BA9, 0x10BAF},
|
||||
{0x10C00, 0x10C48}, {0x10C80, 0x10CB2}, {0x10CC0, 0x10CF2},
|
||||
{0x10CFA, 0x10D27}, {0x10D30, 0x10D39}, {0x10E60, 0x10E7E},
|
||||
{0x10E80, 0x10EA9}, {0x10EAB, 0x10EAD}, {0x10EB0, 0x10EB1},
|
||||
{0x10F00, 0x10F27}, {0x10F30, 0x10F59}, {0x10FB0, 0x10FCB},
|
||||
{0x10FE0, 0x10FF6}, {0x11000, 0x1104D}, {0x11052, 0x1106F},
|
||||
{0x1107F, 0x110C1}, {0x110CD, 0x110CD}, {0x110D0, 0x110E8},
|
||||
{0x110F0, 0x110F9}, {0x11100, 0x11134}, {0x11136, 0x11147},
|
||||
{0x11150, 0x11176}, {0x11180, 0x111DF}, {0x111E1, 0x111F4},
|
||||
{0x11200, 0x11211}, {0x11213, 0x1123E}, {0x11280, 0x11286},
|
||||
{0x11288, 0x11288}, {0x1128A, 0x1128D}, {0x1128F, 0x1129D},
|
||||
{0x1129F, 0x112A9}, {0x112B0, 0x112EA}, {0x112F0, 0x112F9},
|
||||
{0x11300, 0x11303}, {0x11305, 0x1130C}, {0x1130F, 0x11310},
|
||||
{0x11313, 0x11328}, {0x1132A, 0x11330}, {0x11332, 0x11333},
|
||||
{0x11335, 0x11339}, {0x1133B, 0x11344}, {0x11347, 0x11348},
|
||||
{0x1134B, 0x1134D}, {0x11350, 0x11350}, {0x11357, 0x11357},
|
||||
{0x1135D, 0x11363}, {0x11366, 0x1136C}, {0x11370, 0x11374},
|
||||
{0x11400, 0x1145B}, {0x1145D, 0x11461}, {0x11480, 0x114C7},
|
||||
{0x114D0, 0x114D9}, {0x11580, 0x115B5}, {0x115B8, 0x115DD},
|
||||
{0x11600, 0x11644}, {0x11650, 0x11659}, {0x11660, 0x1166C},
|
||||
{0x11680, 0x116B8}, {0x116C0, 0x116C9}, {0x11700, 0x1171A},
|
||||
{0x1171D, 0x1172B}, {0x11730, 0x1173F}, {0x11800, 0x1183B},
|
||||
{0x118A0, 0x118F2}, {0x118FF, 0x11906}, {0x11909, 0x11909},
|
||||
{0x1190C, 0x11913}, {0x11915, 0x11916}, {0x11918, 0x11935},
|
||||
{0x11937, 0x11938}, {0x1193B, 0x11946}, {0x11950, 0x11959},
|
||||
{0x119A0, 0x119A7}, {0x119AA, 0x119D7}, {0x119DA, 0x119E4},
|
||||
{0x11A00, 0x11A47}, {0x11A50, 0x11AA2}, {0x11AC0, 0x11AF8},
|
||||
{0x11C00, 0x11C08}, {0x11C0A, 0x11C36}, {0x11C38, 0x11C45},
|
||||
{0x11C50, 0x11C6C}, {0x11C70, 0x11C8F}, {0x11C92, 0x11CA7},
|
||||
{0x11CA9, 0x11CB6}, {0x11D00, 0x11D06}, {0x11D08, 0x11D09},
|
||||
{0x11D0B, 0x11D36}, {0x11D3A, 0x11D3A}, {0x11D3C, 0x11D3D},
|
||||
{0x11D3F, 0x11D47}, {0x11D50, 0x11D59}, {0x11D60, 0x11D65},
|
||||
{0x11D67, 0x11D68}, {0x11D6A, 0x11D8E}, {0x11D90, 0x11D91},
|
||||
{0x11D93, 0x11D98}, {0x11DA0, 0x11DA9}, {0x11EE0, 0x11EF8},
|
||||
{0x11FB0, 0x11FB0}, {0x11FC0, 0x11FF1}, {0x11FFF, 0x12399},
|
||||
{0x12400, 0x1246E}, {0x12470, 0x12474}, {0x12480, 0x12543},
|
||||
{0x13000, 0x1342E}, {0x13430, 0x13438}, {0x14400, 0x14646},
|
||||
{0x16800, 0x16A38}, {0x16A40, 0x16A5E}, {0x16A60, 0x16A69},
|
||||
{0x16A6E, 0x16A6F}, {0x16AD0, 0x16AED}, {0x16AF0, 0x16AF5},
|
||||
{0x16B00, 0x16B45}, {0x16B50, 0x16B59}, {0x16B5B, 0x16B61},
|
||||
{0x16B63, 0x16B77}, {0x16B7D, 0x16B8F}, {0x16E40, 0x16E9A},
|
||||
{0x16F00, 0x16F4A}, {0x16F4F, 0x16F87}, {0x16F8F, 0x16F9F},
|
||||
{0x1BC00, 0x1BC6A}, {0x1BC70, 0x1BC7C}, {0x1BC80, 0x1BC88},
|
||||
{0x1BC90, 0x1BC99}, {0x1BC9C, 0x1BCA3}, {0x1D000, 0x1D0F5},
|
||||
{0x1D100, 0x1D126}, {0x1D129, 0x1D1E8}, {0x1D200, 0x1D245},
|
||||
{0x1D2E0, 0x1D2F3}, {0x1D300, 0x1D356}, {0x1D360, 0x1D378},
|
||||
{0x1D400, 0x1D454}, {0x1D456, 0x1D49C}, {0x1D49E, 0x1D49F},
|
||||
{0x1D4A2, 0x1D4A2}, {0x1D4A5, 0x1D4A6}, {0x1D4A9, 0x1D4AC},
|
||||
{0x1D4AE, 0x1D4B9}, {0x1D4BB, 0x1D4BB}, {0x1D4BD, 0x1D4C3},
|
||||
{0x1D4C5, 0x1D505}, {0x1D507, 0x1D50A}, {0x1D50D, 0x1D514},
|
||||
{0x1D516, 0x1D51C}, {0x1D51E, 0x1D539}, {0x1D53B, 0x1D53E},
|
||||
{0x1D540, 0x1D544}, {0x1D546, 0x1D546}, {0x1D54A, 0x1D550},
|
||||
{0x1D552, 0x1D6A5}, {0x1D6A8, 0x1D7CB}, {0x1D7CE, 0x1DA8B},
|
||||
{0x1DA9B, 0x1DA9F}, {0x1DAA1, 0x1DAAF}, {0x1E000, 0x1E006},
|
||||
{0x1E008, 0x1E018}, {0x1E01B, 0x1E021}, {0x1E023, 0x1E024},
|
||||
{0x1E026, 0x1E02A}, {0x1E100, 0x1E12C}, {0x1E130, 0x1E13D},
|
||||
{0x1E140, 0x1E149}, {0x1E14E, 0x1E14F}, {0x1E2C0, 0x1E2F9},
|
||||
{0x1E2FF, 0x1E2FF}, {0x1E800, 0x1E8C4}, {0x1E8C7, 0x1E8D6},
|
||||
{0x1E900, 0x1E94B}, {0x1E950, 0x1E959}, {0x1E95E, 0x1E95F},
|
||||
{0x1EC71, 0x1ECB4}, {0x1ED01, 0x1ED3D}, {0x1EE00, 0x1EE03},
|
||||
{0x1EE05, 0x1EE1F}, {0x1EE21, 0x1EE22}, {0x1EE24, 0x1EE24},
|
||||
{0x1EE27, 0x1EE27}, {0x1EE29, 0x1EE32}, {0x1EE34, 0x1EE37},
|
||||
{0x1EE39, 0x1EE39}, {0x1EE3B, 0x1EE3B}, {0x1EE42, 0x1EE42},
|
||||
{0x1EE47, 0x1EE47}, {0x1EE49, 0x1EE49}, {0x1EE4B, 0x1EE4B},
|
||||
{0x1EE4D, 0x1EE4F}, {0x1EE51, 0x1EE52}, {0x1EE54, 0x1EE54},
|
||||
{0x1EE57, 0x1EE57}, {0x1EE59, 0x1EE59}, {0x1EE5B, 0x1EE5B},
|
||||
{0x1EE5D, 0x1EE5D}, {0x1EE5F, 0x1EE5F}, {0x1EE61, 0x1EE62},
|
||||
{0x1EE64, 0x1EE64}, {0x1EE67, 0x1EE6A}, {0x1EE6C, 0x1EE72},
|
||||
{0x1EE74, 0x1EE77}, {0x1EE79, 0x1EE7C}, {0x1EE7E, 0x1EE7E},
|
||||
{0x1EE80, 0x1EE89}, {0x1EE8B, 0x1EE9B}, {0x1EEA1, 0x1EEA3},
|
||||
{0x1EEA5, 0x1EEA9}, {0x1EEAB, 0x1EEBB}, {0x1EEF0, 0x1EEF1},
|
||||
{0x1F000, 0x1F003}, {0x1F005, 0x1F02B}, {0x1F030, 0x1F093},
|
||||
{0x1F0A0, 0x1F0AE}, {0x1F0B1, 0x1F0BF}, {0x1F0C1, 0x1F0CE},
|
||||
{0x1F0D1, 0x1F0F5}, {0x1F10B, 0x1F10F}, {0x1F12E, 0x1F12F},
|
||||
{0x1F16A, 0x1F16F}, {0x1F1AD, 0x1F1AD}, {0x1F1E6, 0x1F1FF},
|
||||
{0x1F321, 0x1F32C}, {0x1F336, 0x1F336}, {0x1F37D, 0x1F37D},
|
||||
{0x1F394, 0x1F39F}, {0x1F3CB, 0x1F3CE}, {0x1F3D4, 0x1F3DF},
|
||||
{0x1F3F1, 0x1F3F3}, {0x1F3F5, 0x1F3F7}, {0x1F43F, 0x1F43F},
|
||||
{0x1F441, 0x1F441}, {0x1F4FD, 0x1F4FE}, {0x1F53E, 0x1F54A},
|
||||
{0x1F54F, 0x1F54F}, {0x1F568, 0x1F579}, {0x1F57B, 0x1F594},
|
||||
{0x1F597, 0x1F5A3}, {0x1F5A5, 0x1F5FA}, {0x1F650, 0x1F67F},
|
||||
{0x1F6C6, 0x1F6CB}, {0x1F6CD, 0x1F6CF}, {0x1F6D3, 0x1F6D4},
|
||||
{0x1F6E0, 0x1F6EA}, {0x1F6F0, 0x1F6F3}, {0x1F700, 0x1F773},
|
||||
{0x1F780, 0x1F7D8}, {0x1F800, 0x1F80B}, {0x1F810, 0x1F847},
|
||||
{0x1F850, 0x1F859}, {0x1F860, 0x1F887}, {0x1F890, 0x1F8AD},
|
||||
{0x1F8B0, 0x1F8B1}, {0x1F900, 0x1F90B}, {0x1F93B, 0x1F93B},
|
||||
{0x1F946, 0x1F946}, {0x1FA00, 0x1FA53}, {0x1FA60, 0x1FA6D},
|
||||
{0x1FB00, 0x1FB92}, {0x1FB94, 0x1FBCA}, {0x1FBF0, 0x1FBF9},
|
||||
{0xE0001, 0xE0001}, {0xE0020, 0xE007F},
|
||||
}
|
||||
|
||||
var emoji = table{
|
||||
{0x203C, 0x203C}, {0x2049, 0x2049}, {0x2122, 0x2122},
|
||||
{0x2139, 0x2139}, {0x2194, 0x2199}, {0x21A9, 0x21AA},
|
||||
{0x231A, 0x231B}, {0x2328, 0x2328}, {0x2388, 0x2388},
|
||||
{0x23CF, 0x23CF}, {0x23E9, 0x23F3}, {0x23F8, 0x23FA},
|
||||
{0x24C2, 0x24C2}, {0x25AA, 0x25AB}, {0x25B6, 0x25B6},
|
||||
{0x25C0, 0x25C0}, {0x25FB, 0x25FE}, {0x2600, 0x2605},
|
||||
{0x2607, 0x2612}, {0x2614, 0x2685}, {0x2690, 0x2705},
|
||||
{0x2708, 0x2712}, {0x2714, 0x2714}, {0x2716, 0x2716},
|
||||
{0x271D, 0x271D}, {0x2721, 0x2721}, {0x2728, 0x2728},
|
||||
{0x2733, 0x2734}, {0x2744, 0x2744}, {0x2747, 0x2747},
|
||||
{0x274C, 0x274C}, {0x274E, 0x274E}, {0x2753, 0x2755},
|
||||
{0x2757, 0x2757}, {0x2763, 0x2767}, {0x2795, 0x2797},
|
||||
{0x27A1, 0x27A1}, {0x27B0, 0x27B0}, {0x27BF, 0x27BF},
|
||||
{0x2934, 0x2935}, {0x2B05, 0x2B07}, {0x2B1B, 0x2B1C},
|
||||
{0x2B50, 0x2B50}, {0x2B55, 0x2B55}, {0x3030, 0x3030},
|
||||
{0x303D, 0x303D}, {0x3297, 0x3297}, {0x3299, 0x3299},
|
||||
{0x1F000, 0x1F0FF}, {0x1F10D, 0x1F10F}, {0x1F12F, 0x1F12F},
|
||||
{0x1F16C, 0x1F171}, {0x1F17E, 0x1F17F}, {0x1F18E, 0x1F18E},
|
||||
{0x1F191, 0x1F19A}, {0x1F1AD, 0x1F1E5}, {0x1F201, 0x1F20F},
|
||||
{0x1F21A, 0x1F21A}, {0x1F22F, 0x1F22F}, {0x1F232, 0x1F23A},
|
||||
{0x1F23C, 0x1F23F}, {0x1F249, 0x1F3FA}, {0x1F400, 0x1F53D},
|
||||
{0x1F546, 0x1F64F}, {0x1F680, 0x1F6FF}, {0x1F774, 0x1F77F},
|
||||
{0x1F7D5, 0x1F7FF}, {0x1F80C, 0x1F80F}, {0x1F848, 0x1F84F},
|
||||
{0x1F85A, 0x1F85F}, {0x1F888, 0x1F88F}, {0x1F8AE, 0x1F8FF},
|
||||
{0x1F90C, 0x1F93A}, {0x1F93C, 0x1F945}, {0x1F947, 0x1FAFF},
|
||||
{0x1FC00, 0x1FFFD},
|
||||
}
|
||||
28
examples/go-dashboard/vendor/github.com/mattn/go-runewidth/runewidth_windows.go
generated
vendored
28
examples/go-dashboard/vendor/github.com/mattn/go-runewidth/runewidth_windows.go
generated
vendored
@@ -1,28 +0,0 @@
|
||||
// +build windows
|
||||
// +build !appengine
|
||||
|
||||
package runewidth
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var (
|
||||
kernel32 = syscall.NewLazyDLL("kernel32")
|
||||
procGetConsoleOutputCP = kernel32.NewProc("GetConsoleOutputCP")
|
||||
)
|
||||
|
||||
// IsEastAsian return true if the current locale is CJK
|
||||
func IsEastAsian() bool {
|
||||
r1, _, _ := procGetConsoleOutputCP.Call()
|
||||
if r1 == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
switch int(r1) {
|
||||
case 932, 51932, 936, 949, 950:
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
2
examples/go-dashboard/vendor/github.com/mum4k/termdash/.gitignore
generated
vendored
2
examples/go-dashboard/vendor/github.com/mum4k/termdash/.gitignore
generated
vendored
@@ -1,2 +0,0 @@
|
||||
# Exclude MacOS attribute files.
|
||||
.DS_Store
|
||||
17
examples/go-dashboard/vendor/github.com/mum4k/termdash/.travis.yml
generated
vendored
17
examples/go-dashboard/vendor/github.com/mum4k/termdash/.travis.yml
generated
vendored
@@ -1,17 +0,0 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.14.x
|
||||
- 1.15.x
|
||||
- stable
|
||||
script:
|
||||
- go get -t ./...
|
||||
- go get -u golang.org/x/lint/golint
|
||||
- go test ./...
|
||||
- CGO_ENABLED=1 go test -race ./...
|
||||
- go vet ./...
|
||||
- diff -u <(echo -n) <(gofmt -d -s .)
|
||||
- diff -u <(echo -n) <(./internal/scripts/autogen_licences.sh .)
|
||||
- diff -u <(echo -n) <(golint ./...)
|
||||
env:
|
||||
global:
|
||||
- CGO_ENABLED=0
|
||||
361
examples/go-dashboard/vendor/github.com/mum4k/termdash/CHANGELOG.md
generated
vendored
361
examples/go-dashboard/vendor/github.com/mum4k/termdash/CHANGELOG.md
generated
vendored
@@ -1,361 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project are documented here.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.12.2] - 31-Aug-2020
|
||||
|
||||
### Fixed
|
||||
|
||||
- advanced the CI Go versions up to Go 1.15.
|
||||
- fixed the build status badge to correctly point to travis-ci.com instead of
|
||||
travis-ci.org.
|
||||
|
||||
## [0.12.1] - 20-Jun-2020
|
||||
|
||||
### Fixed
|
||||
|
||||
- the `tcell` unit test can now pass in headless mode (when TERM="") which
|
||||
happens under bazel.
|
||||
- switching coveralls integration to Github application.
|
||||
|
||||
## [0.12.0] - 10-Apr-2020
|
||||
|
||||
### Added
|
||||
|
||||
- Migrating to [Go modules](https://blog.golang.org/using-go-modules).
|
||||
- Renamed directory `internal` to `private` so that external widget development
|
||||
is possible. Noted in
|
||||
[README.md](https://github.com/mum4k/termdash/blob/master/README.md) that packages in the
|
||||
`private` directory don't have any API stability guarantee.
|
||||
|
||||
## [0.11.0] - 7-Mar-2020
|
||||
|
||||
#### Breaking API changes
|
||||
|
||||
- Termdash now requires at least Go version 1.11.
|
||||
|
||||
### Added
|
||||
|
||||
- New [`tcell`](https://github.com/gdamore/tcell) based terminal implementation
|
||||
which implements the `terminalapi.Terminal` interface.
|
||||
- tcell implementation supports two initialization `Option`s:
|
||||
- `ColorMode` the terminal color output mode (defaults to 256 color mode)
|
||||
- `ClearStyle` the foreground and background color style to use when clearing
|
||||
the screen (defaults to the global ColorDefault for both foreground and
|
||||
background)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Improved test coverage of the `Gauge` widget.
|
||||
|
||||
## [0.10.0] - 5-Jun-2019
|
||||
|
||||
### Added
|
||||
|
||||
- Added `time.Duration` based `ValueFormatter` for the `LineChart` Y-axis labels.
|
||||
- Added round and suffix `ValueFormatter` for the `LineChart` Y-axis labels.
|
||||
- Added decimal and suffix `ValueFormatter` for the `LineChart` Y-axis labels.
|
||||
- Added a `container.SplitOption` that allows fixed size container splits.
|
||||
- Added `grid` functions that allow fixed size rows and columns.
|
||||
|
||||
### Changed
|
||||
|
||||
- The `LineChart` can format the labels on the Y-axis with a `ValueFormatter`.
|
||||
- The `SegmentDisplay` can now display dots and colons ('.' and ':').
|
||||
- The `Donut` widget now guarantees spacing between the donut and its label.
|
||||
- The continuous build on Travis CI now builds with cgo explicitly disabled to
|
||||
ensure both Termdash and its dependencies use pure Go.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Lint issues found on the Go report card.
|
||||
- An internal library belonging to the `Text` widget was incorrectly passing
|
||||
`math.MaxUint32` as an int argument.
|
||||
|
||||
## [0.9.1] - 15-May-2019
|
||||
|
||||
### Fixed
|
||||
|
||||
- Termdash could deadlock when a `Button` or a `TextInput` was configured to
|
||||
call the `Container.Update` method.
|
||||
|
||||
## [0.9.0] - 28-Apr-2019
|
||||
|
||||
### Added
|
||||
|
||||
- The `TextInput` widget, an input field allowing interactive text input.
|
||||
- The `Donut` widget can now display an optional text label under the donut.
|
||||
|
||||
### Changed
|
||||
|
||||
- Widgets now get information whether their container is focused when Draw is
|
||||
executed.
|
||||
- The SegmentDisplay widget now has a method that returns the observed character
|
||||
capacity the last time Draw was called.
|
||||
- The grid.Builder API now allows users to specify options for intermediate
|
||||
containers, i.e. containers that don't have widgets, but represent rows and
|
||||
columns.
|
||||
- Line chart widget now allows `math.NaN` values to represent "no value" (values
|
||||
that will not be rendered) in the values slice.
|
||||
|
||||
#### Breaking API changes
|
||||
|
||||
- The widgetapi.Widget.Draw method now accepts a second argument which provides
|
||||
widgets with additional metadata. This affects all implemented widgets.
|
||||
- Termdash now requires at least Go version 1.10, which allows us to utilize
|
||||
`math.Round` instead of our own implementation and `strings.Builder` instead
|
||||
of `bytes.Buffer`.
|
||||
- Terminal shortcuts like `Ctrl-A` no longer come as two separate events,
|
||||
Termdash now mirrors termbox-go and sends these as one event.
|
||||
|
||||
## [0.8.0] - 30-Mar-2019
|
||||
|
||||
### Added
|
||||
|
||||
- New API for building layouts, a grid.Builder. Allows defining the layout
|
||||
iteratively as repetitive Elements, Rows and Columns.
|
||||
- Containers now support margin around them and padding of their content.
|
||||
- Container now supports dynamic layout changes via the new Update method.
|
||||
|
||||
### Changed
|
||||
|
||||
- The Text widget now supports content wrapping on word boundaries.
|
||||
- The BarChart and SparkLine widgets now have a method that returns the
|
||||
observed value capacity the last time Draw was called.
|
||||
- Moving widgetapi out of the internal directory to allow external users to
|
||||
develop their own widgets.
|
||||
- Event delivery to widgets now has a stable defined order and happens when the
|
||||
container is unlocked so that widgets can trigger dynamic layout changes.
|
||||
|
||||
### Fixed
|
||||
|
||||
- The termdash_test now correctly waits until all subscribers processed events,
|
||||
not just received them.
|
||||
- Container focus tracker now correctly tracks focus changes in enlarged areas,
|
||||
i.e. when the terminal size increased.
|
||||
- The BarChart, LineChart and SegmentDisplay widgets now protect against
|
||||
external mutation of the values passed into them by copying the data they
|
||||
receive.
|
||||
|
||||
## [0.7.2] - 25-Feb-2019
|
||||
|
||||
### Added
|
||||
|
||||
- Test coverage for data only packages.
|
||||
|
||||
### Changed
|
||||
|
||||
- Refactoring packages that contained a mix of public and internal identifiers.
|
||||
|
||||
#### Breaking API changes
|
||||
|
||||
The following packages were refactored, no impact is expected as the removed
|
||||
identifiers shouldn't be used externally.
|
||||
|
||||
- Functions align.Text and align.Rectangle were moved to a new
|
||||
internal/alignfor package.
|
||||
- Types cell.Cell and cell.Buffer were moved into a new internal/canvas/buffer
|
||||
package.
|
||||
|
||||
## [0.7.1] - 24-Feb-2019
|
||||
|
||||
### Fixed
|
||||
|
||||
- Some of the packages that were moved into internal are required externally.
|
||||
This release makes them available again.
|
||||
|
||||
### Changed
|
||||
|
||||
#### Breaking API changes
|
||||
|
||||
- The draw.LineStyle enum was refactored into its own package
|
||||
linestyle.LineStyle. Users will have to replace:
|
||||
|
||||
- draw.LineStyleNone -> linestyle.None
|
||||
- draw.LineStyleLight -> linestyle.Light
|
||||
- draw.LineStyleDouble -> linestyle.Double
|
||||
- draw.LineStyleRound -> linestyle.Round
|
||||
|
||||
## [0.7.0] - 24-Feb-2019
|
||||
|
||||
### Added
|
||||
|
||||
#### New widgets
|
||||
|
||||
- The Button widget.
|
||||
|
||||
#### Improvements to documentation
|
||||
|
||||
- Clearly marked the public API surface by moving private packages into
|
||||
internal directory.
|
||||
- Started a GitHub wiki for Termdash.
|
||||
|
||||
#### Improvements to the LineChart widget
|
||||
|
||||
- The LineChart widget can display X axis labels in vertical orientation.
|
||||
- The LineChart widget allows the user to specify a custom scale for the Y
|
||||
axis.
|
||||
- The LineChart widget now has an option that disables scaling of the X axis.
|
||||
Useful for applications that want to continuously feed data and make them
|
||||
"roll" through the linechart.
|
||||
- The LineChart widget now has a method that returns the observed capacity of
|
||||
the LineChart the last time Draw was called.
|
||||
- The LineChart widget now supports zoom of the content triggered by mouse
|
||||
events.
|
||||
|
||||
#### Improvements to the Text widget
|
||||
|
||||
- The Text widget now has a Write option that atomically replaces the entire
|
||||
text content.
|
||||
|
||||
#### Improvements to the infrastructure
|
||||
|
||||
- A function that draws text vertically.
|
||||
- A non-blocking event distribution system that can throttle repetitive events.
|
||||
- Generalized mouse button FSM for use in widgets that need to track mouse
|
||||
button clicks.
|
||||
|
||||
### Changed
|
||||
|
||||
- Termbox is now initialized in 256 color mode by default.
|
||||
- The infrastructure now uses the non-blocking event distribution system to
|
||||
distribute events to subscribers. Each widget is now an individual
|
||||
subscriber.
|
||||
- The infrastructure now throttles event driven screen redraw rather than
|
||||
redrawing for each input event.
|
||||
- Widgets can now specify the scope at which they want to receive keyboard and
|
||||
mouse events.
|
||||
|
||||
#### Breaking API changes
|
||||
|
||||
##### High impact
|
||||
|
||||
- The constructors of all the widgets now also return an error so that they
|
||||
can validate the options. This is a breaking change for the following
|
||||
widgets: BarChart, Gauge, LineChart, SparkLine, Text. The callers will have
|
||||
to handle the returned error.
|
||||
|
||||
##### Low impact
|
||||
|
||||
- The container package no longer exports separate methods to receive Keyboard
|
||||
and Mouse events which were replaced by a Subscribe method for the event
|
||||
distribution system. This shouldn't affect users as the removed methods
|
||||
aren't needed by container users.
|
||||
- The widgetapi.Options struct now uses an enum instead of a boolean when
|
||||
widget specifies if it wants keyboard or mouse events. This only impacts
|
||||
development of new widgets.
|
||||
|
||||
### Fixed
|
||||
|
||||
- The LineChart widget now correctly determines the Y axis scale when multiple
|
||||
series are provided.
|
||||
- Lint issues in the codebase, and updated Travis configuration so that golint
|
||||
is executed on every run.
|
||||
- Termdash now correctly starts in locales like zh_CN.UTF-8 where some of the
|
||||
characters it uses internally can have ambiguous width.
|
||||
|
||||
## [0.6.1] - 12-Feb-2019
|
||||
|
||||
### Fixed
|
||||
|
||||
- The LineChart widget now correctly places custom labels.
|
||||
|
||||
## [0.6.0] - 07-Feb-2019
|
||||
|
||||
### Added
|
||||
|
||||
- The SegmentDisplay widget.
|
||||
- A CHANGELOG.
|
||||
- New line styles for borders.
|
||||
|
||||
### Changed
|
||||
|
||||
- Better recordings of the individual demos.
|
||||
|
||||
### Fixed
|
||||
|
||||
- The LineChart now has an option to change the behavior of the Y axis from
|
||||
zero anchored to adaptive.
|
||||
- Lint errors reported on the Go report card.
|
||||
- Widgets now correctly handle a race when new user data are supplied between
|
||||
calls to their Options() and Draw() methods.
|
||||
|
||||
## [0.5.0] - 21-Jan-2019
|
||||
|
||||
### Added
|
||||
|
||||
- Draw primitives for drawing circles.
|
||||
- The Donut widget.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Bugfixes in the braille canvas.
|
||||
- Lint errors reported on the Go report card.
|
||||
- Flaky behavior in termdash_test.
|
||||
|
||||
## [0.4.0] - 15-Jan-2019
|
||||
|
||||
### Added
|
||||
|
||||
- 256 color support.
|
||||
- Variable size container splits.
|
||||
- A more complete demo of the functionality.
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated documentation and README.
|
||||
|
||||
## [0.3.0] - 13-Jan-2019
|
||||
|
||||
### Added
|
||||
|
||||
- Primitives for drawing lines.
|
||||
- Implementation of a Braille canvas.
|
||||
- The LineChart widget.
|
||||
|
||||
## [0.2.0] - 02-Jul-2018
|
||||
|
||||
### Added
|
||||
|
||||
- The SparkLine widget.
|
||||
- The BarChart widget.
|
||||
- Manually triggered redraw.
|
||||
- Travis now checks for presence of licence headers.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixing races in termdash_test.
|
||||
|
||||
## 0.1.0 - 13-Jun-2018
|
||||
|
||||
### Added
|
||||
|
||||
- Documentation of the project and its goals.
|
||||
- Drawing infrastructure.
|
||||
- Testing infrastructure.
|
||||
- The Gauge widget.
|
||||
- The Text widget.
|
||||
|
||||
[unreleased]: https://github.com/mum4k/termdash/compare/v0.12.2...devel
|
||||
[0.12.2]: https://github.com/mum4k/termdash/compare/v0.12.1...v0.12.2
|
||||
[0.12.1]: https://github.com/mum4k/termdash/compare/v0.12.0...v0.12.1
|
||||
[0.12.0]: https://github.com/mum4k/termdash/compare/v0.11.0...v0.12.0
|
||||
[0.11.0]: https://github.com/mum4k/termdash/compare/v0.10.0...v0.11.0
|
||||
[0.10.0]: https://github.com/mum4k/termdash/compare/v0.9.1...v0.10.0
|
||||
[0.9.1]: https://github.com/mum4k/termdash/compare/v0.9.0...v0.9.1
|
||||
[0.9.0]: https://github.com/mum4k/termdash/compare/v0.8.0...v0.9.0
|
||||
[0.8.0]: https://github.com/mum4k/termdash/compare/v0.7.2...v0.8.0
|
||||
[0.7.2]: https://github.com/mum4k/termdash/compare/v0.7.1...v0.7.2
|
||||
[0.7.1]: https://github.com/mum4k/termdash/compare/v0.7.0...v0.7.1
|
||||
[0.7.0]: https://github.com/mum4k/termdash/compare/v0.6.1...v0.7.0
|
||||
[0.6.1]: https://github.com/mum4k/termdash/compare/v0.6.0...v0.6.1
|
||||
[0.6.0]: https://github.com/mum4k/termdash/compare/v0.5.0...v0.6.0
|
||||
[0.5.0]: https://github.com/mum4k/termdash/compare/v0.4.0...v0.5.0
|
||||
[0.4.0]: https://github.com/mum4k/termdash/compare/v0.3.0...v0.4.0
|
||||
[0.3.0]: https://github.com/mum4k/termdash/compare/v0.2.0...v0.3.0
|
||||
[0.2.0]: https://github.com/mum4k/termdash/compare/v0.1.0...v0.2.0
|
||||
38
examples/go-dashboard/vendor/github.com/mum4k/termdash/CONTRIBUTING.md
generated
vendored
38
examples/go-dashboard/vendor/github.com/mum4k/termdash/CONTRIBUTING.md
generated
vendored
@@ -1,38 +0,0 @@
|
||||
# How to Contribute
|
||||
|
||||
We'd love to accept your patches and contributions to this project. There are
|
||||
just a few small guidelines you need to follow.
|
||||
|
||||
## Fork and merge into the "devel" branch
|
||||
|
||||
All development in termdash repository must happen in the [devel
|
||||
branch](https://github.com/mum4k/termdash/tree/devel). The devel branch is
|
||||
merged into the master branch during release of each new version.
|
||||
|
||||
When you fork the termdash repository, be sure to checkout the devel branch.
|
||||
When you are creating a pull request, be sure to pull back into the devel
|
||||
branch.
|
||||
|
||||
## Contributor License Agreement
|
||||
|
||||
Contributions to this project must be accompanied by a Contributor License
|
||||
Agreement. You (or your employer) retain the copyright to your contribution;
|
||||
this simply gives us permission to use and redistribute your contributions as
|
||||
part of the project. Head over to <https://cla.developers.google.com/> to see
|
||||
your current agreements on file or to sign a new one.
|
||||
|
||||
You generally only need to submit a CLA once, so if you've already submitted one
|
||||
(even if it was for a different project), you probably don't need to do it
|
||||
again.
|
||||
|
||||
## Code reviews
|
||||
|
||||
All submissions, including submissions by project members, require review. We
|
||||
use GitHub pull requests for this purpose. Consult
|
||||
[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
|
||||
information on using pull requests.
|
||||
|
||||
## Community Guidelines
|
||||
|
||||
This project follows [Google's Open Source Community
|
||||
Guidelines](https://opensource.google.com/conduct/).
|
||||
201
examples/go-dashboard/vendor/github.com/mum4k/termdash/LICENSE
generated
vendored
201
examples/go-dashboard/vendor/github.com/mum4k/termdash/LICENSE
generated
vendored
@@ -1,201 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
215
examples/go-dashboard/vendor/github.com/mum4k/termdash/README.md
generated
vendored
215
examples/go-dashboard/vendor/github.com/mum4k/termdash/README.md
generated
vendored
@@ -1,215 +0,0 @@
|
||||
[](https://godoc.org/github.com/mum4k/termdash)
|
||||
[](https://travis-ci.com/mum4k/termdash)
|
||||
[](https://sourcegraph.com/github.com/mum4k/termdash?badge)
|
||||
[](https://coveralls.io/github/mum4k/termdash?branch=master)
|
||||
[](https://goreportcard.com/report/github.com/mum4k/termdash)
|
||||
[](https://github.com/mum4k/termdash/blob/master/LICENSE)
|
||||
[](https://github.com/avelino/awesome-go)
|
||||
|
||||
# [<img src="./doc/images/termdash.png" alt="termdashlogo" type="image/png" width="30%">](http://github.com/mum4k/termdash/wiki)
|
||||
|
||||
Termdash is a cross-platform customizable terminal based dashboard.
|
||||
|
||||
[<img src="./doc/images/termdashdemo_0_9_0.gif" alt="termdashdemo" type="image/gif">](termdashdemo/termdashdemo.go)
|
||||
|
||||
The feature set is inspired by the
|
||||
[gizak/termui](http://github.com/gizak/termui) project, which in turn was
|
||||
inspired by
|
||||
[yaronn/blessed-contrib](http://github.com/yaronn/blessed-contrib).
|
||||
|
||||
This rewrite focuses on code readability, maintainability and testability, see
|
||||
the [design goals](doc/design_goals.md). It aims to achieve the following
|
||||
[requirements](doc/requirements.md). See the [high-level design](doc/hld.md)
|
||||
for more details.
|
||||
|
||||
# Public API and status
|
||||
|
||||
The public API surface is documented in the
|
||||
[wiki](http://github.com/mum4k/termdash/wiki).
|
||||
|
||||
Private packages can be identified by the presence of the **/private/**
|
||||
directory in their import path. Stability of the private packages isn't
|
||||
guaranteed and changes won't be backward compatible.
|
||||
|
||||
There might still be breaking changes to the public API, at least until the
|
||||
project reaches version 1.0.0. Any breaking changes will be published in the
|
||||
[changelog](CHANGELOG.md).
|
||||
|
||||
# Current feature set
|
||||
|
||||
- Full support for terminal window resizing throughout the infrastructure.
|
||||
- Customizable layout, widget placement, borders, margins, padding, colors, etc.
|
||||
- Dynamic layout changes at runtime.
|
||||
- Binary tree and Grid forms of setting up the layout.
|
||||
- Focusable containers and widgets.
|
||||
- Processing of keyboard and mouse events.
|
||||
- Periodic and event driven screen redraw.
|
||||
- A library of widgets, see below.
|
||||
- UTF-8 for all text elements.
|
||||
- Drawing primitives (Go functions) for widget development with character and
|
||||
sub-character resolution.
|
||||
|
||||
# Installation
|
||||
|
||||
To install this library, run the following:
|
||||
|
||||
```go
|
||||
go get -u github.com/mum4k/termdash
|
||||
```
|
||||
|
||||
# Usage
|
||||
|
||||
The usage of most of these elements is demonstrated in
|
||||
[termdashdemo.go](termdashdemo/termdashdemo.go). To execute the demo:
|
||||
|
||||
```go
|
||||
go run github.com/mum4k/termdash/termdashdemo/termdashdemo.go
|
||||
```
|
||||
|
||||
# Documentation
|
||||
|
||||
Please refer to the [Termdash wiki](http://github.com/mum4k/termdash/wiki) for
|
||||
all documentation and resources.
|
||||
|
||||
# Implemented Widgets
|
||||
|
||||
## The Button
|
||||
|
||||
Allows users to interact with the application, each button press runs a callback function.
|
||||
Run the
|
||||
[buttondemo](widgets/button/buttondemo/buttondemo.go).
|
||||
|
||||
```go
|
||||
go run github.com/mum4k/termdash/widgets/button/buttondemo/buttondemo.go
|
||||
```
|
||||
|
||||
[<img src="./doc/images/buttondemo.gif" alt="buttondemo" type="image/gif" width="50%">](widgets/button/buttondemo/buttondemo.go)
|
||||
|
||||
## The TextInput
|
||||
|
||||
Allows users to interact with the application by entering, editing and
|
||||
submitting text data. Run the
|
||||
[textinputdemo](widgets/textinput/textinputdemo/textinputdemo.go).
|
||||
|
||||
```go
|
||||
go run github.com/mum4k/termdash/widgets/textinput/textinputdemo/textinputdemo.go
|
||||
```
|
||||
|
||||
[<img src="./doc/images/textinputdemo.gif" alt="textinputdemo" type="image/gif" width="80%">](widgets/textinput/textinputdemo/textinputdemo.go)
|
||||
|
||||
## The Gauge
|
||||
|
||||
Displays the progress of an operation. Run the
|
||||
[gaugedemo](widgets/gauge/gaugedemo/gaugedemo.go).
|
||||
|
||||
```go
|
||||
go run github.com/mum4k/termdash/widgets/gauge/gaugedemo/gaugedemo.go
|
||||
```
|
||||
|
||||
[<img src="./doc/images/gaugedemo.gif" alt="gaugedemo" type="image/gif">](widgets/gauge/gaugedemo/gaugedemo.go)
|
||||
|
||||
## The Donut
|
||||
|
||||
Visualizes progress of an operation as a partial or a complete donut. Run the
|
||||
[donutdemo](widgets/donut/donutdemo/donutdemo.go).
|
||||
|
||||
```go
|
||||
go run github.com/mum4k/termdash/widgets/donut/donutdemo/donutdemo.go
|
||||
```
|
||||
|
||||
[<img src="./doc/images/donutdemo.gif" alt="donutdemo" type="image/gif">](widgets/donut/donutdemo/donutdemo.go)
|
||||
|
||||
## The Text
|
||||
|
||||
Displays text content, supports trimming and scrolling of content. Run the
|
||||
[textdemo](widgets/text/textdemo/textdemo.go).
|
||||
|
||||
```go
|
||||
go run github.com/mum4k/termdash/widgets/text/textdemo/textdemo.go
|
||||
```
|
||||
|
||||
[<img src="./doc/images/textdemo.gif" alt="textdemo" type="image/gif">](widgets/text/textdemo/textdemo.go)
|
||||
|
||||
## The SparkLine
|
||||
|
||||
Draws a graph showing a series of values as vertical bars. The bars can have
|
||||
sub-cell height. Run the
|
||||
[sparklinedemo](widgets/sparkline/sparklinedemo/sparklinedemo.go).
|
||||
|
||||
```go
|
||||
go run github.com/mum4k/termdash/widgets/sparkline/sparklinedemo/sparklinedemo.go
|
||||
```
|
||||
|
||||
[<img src="./doc/images/sparklinedemo.gif" alt="sparklinedemo" type="image/gif" width="50%">](widgets/sparkline/sparklinedemo/sparklinedemo.go)
|
||||
|
||||
## The BarChart
|
||||
|
||||
Displays multiple bars showing relative ratios of values. Run the
|
||||
[barchartdemo](widgets/barchart/barchartdemo/barchartdemo.go).
|
||||
|
||||
```go
|
||||
go run github.com/mum4k/termdash/widgets/barchart/barchartdemo/barchartdemo.go
|
||||
```
|
||||
|
||||
[<img src="./doc/images/barchartdemo.gif" alt="barchartdemo" type="image/gif" width="50%">](widgets/barchart/barchartdemo/barchartdemo.go)
|
||||
|
||||
## The LineChart
|
||||
|
||||
Displays series of values on a line chart, supports zoom triggered by mouse
|
||||
events. Run the
|
||||
[linechartdemo](widgets/linechart/linechartdemo/linechartdemo.go).
|
||||
|
||||
```go
|
||||
go run github.com/mum4k/termdash/widgets/linechart/linechartdemo/linechartdemo.go
|
||||
```
|
||||
|
||||
[<img src="./doc/images/linechartdemo.gif" alt="linechartdemo" type="image/gif" width="70%">](widgets/linechart/linechartdemo/linechartdemo.go)
|
||||
|
||||
## The SegmentDisplay
|
||||
|
||||
Displays text by simulating a 16-segment display. Run the
|
||||
[segmentdisplaydemo](widgets/segmentdisplay/segmentdisplaydemo/segmentdisplaydemo.go).
|
||||
|
||||
```go
|
||||
go run github.com/mum4k/termdash/widgets/segmentdisplay/segmentdisplaydemo/segmentdisplaydemo.go
|
||||
```
|
||||
|
||||
[<img src="./doc/images/segmentdisplaydemo.gif" alt="segmentdisplaydemo" type="image/gif">](widgets/segmentdisplay/segmentdisplaydemo/segmentdisplaydemo.go)
|
||||
|
||||
# Contributing
|
||||
|
||||
If you are willing to contribute, improve the infrastructure or develop a
|
||||
widget, first of all Thank You! Your help is appreciated.
|
||||
|
||||
Please see the [CONTRIBUTING.md](CONTRIBUTING.md) file for guidelines related
|
||||
to the Google's CLA, and code review requirements.
|
||||
|
||||
As stated above the primary goal of this project is to develop readable, well
|
||||
designed code, the functionality and efficiency come second. This is achieved
|
||||
through detailed code reviews, design discussions and following of the [design
|
||||
guidelines](doc/design_guidelines.md). Please familiarize yourself with these
|
||||
before contributing.
|
||||
|
||||
If you're developing a new widget, please see the [widget
|
||||
development](doc/widget_development.md) section.
|
||||
|
||||
Termdash uses [this branching model](https://nvie.com/posts/a-successful-git-branching-model/). When you fork the repository, base your changes off the [devel](https://github.com/mum4k/termdash/tree/devel) branch and the pull request should merge it back to the devel branch. Commits to the master branch are limited to releases, major bug fixes and documentation updates.
|
||||
|
||||
# Similar projects in Go
|
||||
|
||||
- [clui](https://github.com/VladimirMarkelov/clui)
|
||||
- [gocui](https://github.com/jroimartin/gocui)
|
||||
- [gowid](https://github.com/gcla/gowid)
|
||||
- [termui](https://github.com/gizak/termui)
|
||||
- [tui-go](https://github.com/marcusolsson/tui-go)
|
||||
- [tview](https://github.com/rivo/tview)
|
||||
|
||||
# Projects using Termdash
|
||||
|
||||
- [datadash](https://github.com/keithknott26/datadash): Visualize streaming or tabular data inside the terminal.
|
||||
- [grafterm](https://github.com/slok/grafterm): Metrics dashboards visualization on the terminal.
|
||||
- [perfstat](https://github.com/flaviostutz/perfstat): Analyze and show tips about possible bottlenecks in Linux systems.
|
||||
|
||||
# Disclaimer
|
||||
|
||||
This is not an official Google product.
|
||||
70
examples/go-dashboard/vendor/github.com/mum4k/termdash/align/align.go
generated
vendored
70
examples/go-dashboard/vendor/github.com/mum4k/termdash/align/align.go
generated
vendored
@@ -1,70 +0,0 @@
|
||||
// Copyright 2018 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package align defines constants representing types of alignment.
|
||||
package align
|
||||
|
||||
// Horizontal indicates the type of horizontal alignment.
|
||||
type Horizontal int
|
||||
|
||||
// String implements fmt.Stringer()
|
||||
func (h Horizontal) String() string {
|
||||
if n, ok := horizontalNames[h]; ok {
|
||||
return n
|
||||
}
|
||||
return "HorizontalUnknown"
|
||||
}
|
||||
|
||||
// horizontalNames maps Horizontal values to human readable names.
|
||||
var horizontalNames = map[Horizontal]string{
|
||||
HorizontalLeft: "HorizontalLeft",
|
||||
HorizontalCenter: "HorizontalCenter",
|
||||
HorizontalRight: "HorizontalRight",
|
||||
}
|
||||
|
||||
const (
|
||||
// HorizontalLeft is left alignment along the horizontal axis.
|
||||
HorizontalLeft Horizontal = iota
|
||||
// HorizontalCenter is center alignment along the horizontal axis.
|
||||
HorizontalCenter
|
||||
// HorizontalRight is right alignment along the horizontal axis.
|
||||
HorizontalRight
|
||||
)
|
||||
|
||||
// Vertical indicates the type of vertical alignment.
|
||||
type Vertical int
|
||||
|
||||
// String implements fmt.Stringer()
|
||||
func (v Vertical) String() string {
|
||||
if n, ok := verticalNames[v]; ok {
|
||||
return n
|
||||
}
|
||||
return "VerticalUnknown"
|
||||
}
|
||||
|
||||
// verticalNames maps Vertical values to human readable names.
|
||||
var verticalNames = map[Vertical]string{
|
||||
VerticalTop: "VerticalTop",
|
||||
VerticalMiddle: "VerticalMiddle",
|
||||
VerticalBottom: "VerticalBottom",
|
||||
}
|
||||
|
||||
const (
|
||||
// VerticalTop is top alignment along the vertical axis.
|
||||
VerticalTop Vertical = iota
|
||||
// VerticalMiddle is middle alignment along the vertical axis.
|
||||
VerticalMiddle
|
||||
// VerticalBottom is bottom alignment along the vertical axis.
|
||||
VerticalBottom
|
||||
)
|
||||
64
examples/go-dashboard/vendor/github.com/mum4k/termdash/cell/cell.go
generated
vendored
64
examples/go-dashboard/vendor/github.com/mum4k/termdash/cell/cell.go
generated
vendored
@@ -1,64 +0,0 @@
|
||||
// Copyright 2018 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package cell implements cell options and attributes.
|
||||
package cell
|
||||
|
||||
// Option is used to provide options for cells on a 2-D terminal.
|
||||
type Option interface {
|
||||
// Set sets the provided option.
|
||||
Set(*Options)
|
||||
}
|
||||
|
||||
// Options stores the provided options.
|
||||
type Options struct {
|
||||
FgColor Color
|
||||
BgColor Color
|
||||
}
|
||||
|
||||
// Set allows existing options to be passed as an option.
|
||||
func (o *Options) Set(other *Options) {
|
||||
*other = *o
|
||||
}
|
||||
|
||||
// NewOptions returns a new Options instance after applying the provided options.
|
||||
func NewOptions(opts ...Option) *Options {
|
||||
o := &Options{}
|
||||
for _, opt := range opts {
|
||||
opt.Set(o)
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
// option implements Option.
|
||||
type option func(*Options)
|
||||
|
||||
// Set implements Option.set.
|
||||
func (co option) Set(opts *Options) {
|
||||
co(opts)
|
||||
}
|
||||
|
||||
// FgColor sets the foreground color of the cell.
|
||||
func FgColor(color Color) Option {
|
||||
return option(func(co *Options) {
|
||||
co.FgColor = color
|
||||
})
|
||||
}
|
||||
|
||||
// BgColor sets the background color of the cell.
|
||||
func BgColor(color Color) Option {
|
||||
return option(func(co *Options) {
|
||||
co.BgColor = color
|
||||
})
|
||||
}
|
||||
106
examples/go-dashboard/vendor/github.com/mum4k/termdash/cell/color.go
generated
vendored
106
examples/go-dashboard/vendor/github.com/mum4k/termdash/cell/color.go
generated
vendored
@@ -1,106 +0,0 @@
|
||||
// Copyright 2018 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cell
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// color.go defines constants for cell colors.
|
||||
|
||||
// Color is the color of a cell.
|
||||
type Color int
|
||||
|
||||
// String implements fmt.Stringer()
|
||||
func (cc Color) String() string {
|
||||
if n, ok := colorNames[cc]; ok {
|
||||
return n
|
||||
}
|
||||
return fmt.Sprintf("Color:%d", cc)
|
||||
}
|
||||
|
||||
// colorNames maps Color values to human readable names.
|
||||
var colorNames = map[Color]string{
|
||||
ColorDefault: "ColorDefault",
|
||||
ColorBlack: "ColorBlack",
|
||||
ColorRed: "ColorRed",
|
||||
ColorGreen: "ColorGreen",
|
||||
ColorYellow: "ColorYellow",
|
||||
ColorBlue: "ColorBlue",
|
||||
ColorMagenta: "ColorMagenta",
|
||||
ColorCyan: "ColorCyan",
|
||||
ColorWhite: "ColorWhite",
|
||||
}
|
||||
|
||||
// The supported terminal colors.
|
||||
const (
|
||||
ColorDefault Color = iota
|
||||
|
||||
// 8 "system" colors.
|
||||
ColorBlack
|
||||
ColorRed
|
||||
ColorGreen
|
||||
ColorYellow
|
||||
ColorBlue
|
||||
ColorMagenta
|
||||
ColorCyan
|
||||
ColorWhite
|
||||
)
|
||||
|
||||
// ColorNumber sets a color using its number.
|
||||
// Make sure your terminal is set to a terminalapi.ColorMode that supports the
|
||||
// target color. The provided value must be in the range 0-255.
|
||||
// Larger or smaller values will be reset to the default color.
|
||||
//
|
||||
// For reference on these colors see the Xterm number in:
|
||||
// https://jonasjacek.github.io/colors/
|
||||
func ColorNumber(n int) Color {
|
||||
if n < 0 || n > 255 {
|
||||
return ColorDefault
|
||||
}
|
||||
return Color(n + 1) // Colors are off-by-one due to ColorDefault being zero.
|
||||
}
|
||||
|
||||
// ColorRGB6 sets a color using the 6x6x6 terminal color.
|
||||
// Make sure your terminal is set to the terminalapi.ColorMode256 mode.
|
||||
// The provided values (r, g, b) must be in the range 0-5.
|
||||
// Larger or smaller values will be reset to the default color.
|
||||
//
|
||||
// For reference on these colors see:
|
||||
// https://superuser.com/questions/783656/whats-the-deal-with-terminal-colors
|
||||
func ColorRGB6(r, g, b int) Color {
|
||||
for _, c := range []int{r, g, b} {
|
||||
if c < 0 || c > 5 {
|
||||
return ColorDefault
|
||||
}
|
||||
}
|
||||
return Color(0x10 + 36*r + 6*g + b + 1) // Colors are off-by-one due to ColorDefault being zero.
|
||||
}
|
||||
|
||||
// ColorRGB24 sets a color using the 24 bit web color scheme.
|
||||
// Make sure your terminal is set to the terminalapi.ColorMode256 mode.
|
||||
// The provided values (r, g, b) must be in the range 0-255.
|
||||
// Larger or smaller values will be reset to the default color.
|
||||
//
|
||||
// For reference on these colors see the RGB column in:
|
||||
// https://jonasjacek.github.io/colors/
|
||||
func ColorRGB24(r, g, b int) Color {
|
||||
for _, c := range []int{r, g, b} {
|
||||
if c < 0 || c > 255 {
|
||||
return ColorDefault
|
||||
}
|
||||
}
|
||||
return ColorRGB6(r/51, g/51, b/51)
|
||||
}
|
||||
471
examples/go-dashboard/vendor/github.com/mum4k/termdash/container/container.go
generated
vendored
471
examples/go-dashboard/vendor/github.com/mum4k/termdash/container/container.go
generated
vendored
@@ -1,471 +0,0 @@
|
||||
// Copyright 2018 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/*
|
||||
Package container defines a type that wraps other containers or widgets.
|
||||
|
||||
The container supports splitting container into sub containers, defining
|
||||
container styles and placing widgets. The container also creates and manages
|
||||
canvases assigned to the placed widgets.
|
||||
*/
|
||||
package container
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"sync"
|
||||
|
||||
"github.com/mum4k/termdash/linestyle"
|
||||
"github.com/mum4k/termdash/private/alignfor"
|
||||
"github.com/mum4k/termdash/private/area"
|
||||
"github.com/mum4k/termdash/private/event"
|
||||
"github.com/mum4k/termdash/terminal/terminalapi"
|
||||
"github.com/mum4k/termdash/widgetapi"
|
||||
)
|
||||
|
||||
// Container wraps either sub containers or widgets and positions them on the
|
||||
// terminal.
|
||||
// This is thread-safe.
|
||||
type Container struct {
|
||||
// parent is the parent container, nil if this is the root container.
|
||||
parent *Container
|
||||
// The sub containers, if these aren't nil, the widget must be.
|
||||
first *Container
|
||||
second *Container
|
||||
|
||||
// term is the terminal this container is placed on.
|
||||
// All containers in the tree share the same terminal.
|
||||
term terminalapi.Terminal
|
||||
|
||||
// focusTracker tracks the active (focused) container.
|
||||
// All containers in the tree share the same tracker.
|
||||
focusTracker *focusTracker
|
||||
|
||||
// area is the area of the terminal this container has access to.
|
||||
// Initialized the first time Draw is called.
|
||||
area image.Rectangle
|
||||
|
||||
// opts are the options provided to the container.
|
||||
opts *options
|
||||
|
||||
// clearNeeded indicates if the terminal needs to be cleared next time we
|
||||
// are clearNeeded the container.
|
||||
// This is required if the container was updated and thus the layout might
|
||||
// have changed.
|
||||
clearNeeded bool
|
||||
|
||||
// mu protects the container tree.
|
||||
// All containers in the tree share the same lock.
|
||||
mu *sync.Mutex
|
||||
}
|
||||
|
||||
// String represents the container metadata in a human readable format.
|
||||
// Implements fmt.Stringer.
|
||||
func (c *Container) String() string {
|
||||
return fmt.Sprintf("Container@%p{parent:%p, first:%p, second:%p, area:%+v}", c, c.parent, c.first, c.second, c.area)
|
||||
}
|
||||
|
||||
// New returns a new root container that will use the provided terminal and
|
||||
// applies the provided options.
|
||||
func New(t terminalapi.Terminal, opts ...Option) (*Container, error) {
|
||||
root := &Container{
|
||||
term: t,
|
||||
opts: newOptions( /* parent = */ nil),
|
||||
mu: &sync.Mutex{},
|
||||
}
|
||||
|
||||
// Initially the root is focused.
|
||||
root.focusTracker = newFocusTracker(root)
|
||||
if err := applyOptions(root, opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateOptions(root); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return root, nil
|
||||
}
|
||||
|
||||
// newChild creates a new child container of the given parent.
|
||||
func newChild(parent *Container, opts []Option) (*Container, error) {
|
||||
child := &Container{
|
||||
parent: parent,
|
||||
term: parent.term,
|
||||
focusTracker: parent.focusTracker,
|
||||
opts: newOptions(parent.opts),
|
||||
mu: parent.mu,
|
||||
}
|
||||
if err := applyOptions(child, opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return child, nil
|
||||
}
|
||||
|
||||
// hasBorder determines if this container has a border.
|
||||
func (c *Container) hasBorder() bool {
|
||||
return c.opts.border != linestyle.None
|
||||
}
|
||||
|
||||
// hasWidget determines if this container has a widget.
|
||||
func (c *Container) hasWidget() bool {
|
||||
return c.opts.widget != nil
|
||||
}
|
||||
|
||||
// usable returns the usable area in this container.
|
||||
// This depends on whether the container has a border, etc.
|
||||
func (c *Container) usable() image.Rectangle {
|
||||
if c.hasBorder() {
|
||||
return area.ExcludeBorder(c.area)
|
||||
}
|
||||
return c.area
|
||||
}
|
||||
|
||||
// widgetArea returns the area in the container that is available for the
|
||||
// widget's canvas. Takes the container border, widget's requested maximum size
|
||||
// and ratio and container's alignment into account.
|
||||
// Returns a zero area if the container has no widget.
|
||||
func (c *Container) widgetArea() (image.Rectangle, error) {
|
||||
if !c.hasWidget() {
|
||||
return image.ZR, nil
|
||||
}
|
||||
|
||||
padded, err := c.opts.padding.apply(c.usable())
|
||||
if err != nil {
|
||||
return image.ZR, err
|
||||
}
|
||||
wOpts := c.opts.widget.Options()
|
||||
|
||||
adjusted := padded
|
||||
if maxX := wOpts.MaximumSize.X; maxX > 0 && adjusted.Dx() > maxX {
|
||||
adjusted.Max.X -= adjusted.Dx() - maxX
|
||||
}
|
||||
if maxY := wOpts.MaximumSize.Y; maxY > 0 && adjusted.Dy() > maxY {
|
||||
adjusted.Max.Y -= adjusted.Dy() - maxY
|
||||
}
|
||||
|
||||
if wOpts.Ratio.X > 0 && wOpts.Ratio.Y > 0 {
|
||||
adjusted = area.WithRatio(adjusted, wOpts.Ratio)
|
||||
}
|
||||
aligned, err := alignfor.Rectangle(padded, adjusted, c.opts.hAlign, c.opts.vAlign)
|
||||
if err != nil {
|
||||
return image.ZR, err
|
||||
}
|
||||
return aligned, nil
|
||||
}
|
||||
|
||||
// split splits the container's usable area into child areas.
|
||||
// Panics if the container isn't configured for a split.
|
||||
func (c *Container) split() (image.Rectangle, image.Rectangle, error) {
|
||||
ar, err := c.opts.padding.apply(c.usable())
|
||||
if err != nil {
|
||||
return image.ZR, image.ZR, err
|
||||
}
|
||||
if c.opts.splitFixed > DefaultSplitFixed {
|
||||
if c.opts.split == splitTypeVertical {
|
||||
return area.VSplitCells(ar, c.opts.splitFixed)
|
||||
}
|
||||
return area.HSplitCells(ar, c.opts.splitFixed)
|
||||
}
|
||||
|
||||
if c.opts.split == splitTypeVertical {
|
||||
return area.VSplit(ar, c.opts.splitPercent)
|
||||
}
|
||||
return area.HSplit(ar, c.opts.splitPercent)
|
||||
}
|
||||
|
||||
// createFirst creates and returns the first sub container of this container.
|
||||
func (c *Container) createFirst(opts []Option) error {
|
||||
first, err := newChild(c, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.first = first
|
||||
return nil
|
||||
}
|
||||
|
||||
// createSecond creates and returns the second sub container of this container.
|
||||
func (c *Container) createSecond(opts []Option) error {
|
||||
second, err := newChild(c, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.second = second
|
||||
return nil
|
||||
}
|
||||
|
||||
// Draw draws this container and all of its sub containers.
|
||||
func (c *Container) Draw() error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if c.clearNeeded {
|
||||
if err := c.term.Clear(); err != nil {
|
||||
return fmt.Errorf("term.Clear => error: %v", err)
|
||||
}
|
||||
c.clearNeeded = false
|
||||
}
|
||||
|
||||
// Update the area we are tracking for focus in case the terminal size
|
||||
// changed.
|
||||
ar, err := area.FromSize(c.term.Size())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.focusTracker.updateArea(ar)
|
||||
return drawTree(c)
|
||||
}
|
||||
|
||||
// Update updates container with the specified id by setting the provided
|
||||
// options. This can be used to perform dynamic layout changes, i.e. anything
|
||||
// between replacing the widget in the container and completely changing the
|
||||
// layout and splits.
|
||||
// The argument id must match exactly one container with that was created with
|
||||
// matching ID() option. The argument id must not be an empty string.
|
||||
func (c *Container) Update(id string, opts ...Option) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
target, err := findID(c, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.clearNeeded = true
|
||||
|
||||
if err := applyOptions(target, opts...); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateOptions(c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// The currently focused container might not be reachable anymore, because
|
||||
// it was under the target. If that is so, move the focus up to the target.
|
||||
if !c.focusTracker.reachableFrom(c) {
|
||||
c.focusTracker.setActive(target)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateFocus processes the mouse event and determines if it changes the
|
||||
// focused container.
|
||||
// Caller must hold c.mu.
|
||||
func (c *Container) updateFocus(m *terminalapi.Mouse) {
|
||||
target := pointCont(c, m.Position)
|
||||
if target == nil { // Ignore mouse clicks where no containers are.
|
||||
return
|
||||
}
|
||||
c.focusTracker.mouse(target, m)
|
||||
}
|
||||
|
||||
// processEvent processes events delivered to the container.
|
||||
func (c *Container) processEvent(ev terminalapi.Event) error {
|
||||
// This is done in two stages.
|
||||
// 1) under lock we traverse the container and identify all targets
|
||||
// (widgets) that should receive the event.
|
||||
// 2) lock is released and events are delivered to the widgets. Widgets
|
||||
// themselves are thread-safe. Lock must be releases when delivering,
|
||||
// because some widgets might try to mutate the container when they
|
||||
// receive the event, like dynamically change the layout.
|
||||
c.mu.Lock()
|
||||
sendFn, err := c.prepareEvTargets(ev)
|
||||
c.mu.Unlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sendFn()
|
||||
}
|
||||
|
||||
// prepareEvTargets returns a closure, that when called delivers the event to
|
||||
// widgets that registered for it.
|
||||
// Also processes the event on behalf of the container (tracks keyboard focus).
|
||||
// Caller must hold c.mu.
|
||||
func (c *Container) prepareEvTargets(ev terminalapi.Event) (func() error, error) {
|
||||
switch e := ev.(type) {
|
||||
case *terminalapi.Mouse:
|
||||
c.updateFocus(ev.(*terminalapi.Mouse))
|
||||
|
||||
targets, err := c.mouseEvTargets(e)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return func() error {
|
||||
for _, mt := range targets {
|
||||
if err := mt.widget.Mouse(mt.ev); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}, nil
|
||||
|
||||
case *terminalapi.Keyboard:
|
||||
targets := c.keyEvTargets()
|
||||
return func() error {
|
||||
for _, w := range targets {
|
||||
if err := w.Keyboard(e); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}, nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("container received an unsupported event type %T", ev)
|
||||
}
|
||||
}
|
||||
|
||||
// keyEvTargets returns those widgets found in the container that should
|
||||
// receive this keyboard event.
|
||||
// Caller must hold c.mu.
|
||||
func (c *Container) keyEvTargets() []widgetapi.Widget {
|
||||
var (
|
||||
errStr string
|
||||
widgets []widgetapi.Widget
|
||||
)
|
||||
|
||||
// All the widgets that should receive this event.
|
||||
// For now stable ordering (preOrder).
|
||||
preOrder(c, &errStr, visitFunc(func(cur *Container) error {
|
||||
if !cur.hasWidget() {
|
||||
return nil
|
||||
}
|
||||
|
||||
wOpt := cur.opts.widget.Options()
|
||||
switch wOpt.WantKeyboard {
|
||||
case widgetapi.KeyScopeNone:
|
||||
// Widget doesn't want any keyboard events.
|
||||
return nil
|
||||
|
||||
case widgetapi.KeyScopeFocused:
|
||||
if cur.focusTracker.isActive(cur) {
|
||||
widgets = append(widgets, cur.opts.widget)
|
||||
}
|
||||
|
||||
case widgetapi.KeyScopeGlobal:
|
||||
widgets = append(widgets, cur.opts.widget)
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
return widgets
|
||||
}
|
||||
|
||||
// mouseEvTarget contains a mouse event adjusted relative to the widget's area
|
||||
// and the widget that should receive it.
|
||||
type mouseEvTarget struct {
|
||||
// widget is the widget that should receive the mouse event.
|
||||
widget widgetapi.Widget
|
||||
// ev is the adjusted mouse event.
|
||||
ev *terminalapi.Mouse
|
||||
}
|
||||
|
||||
// newMouseEvTarget returns a new newMouseEvTarget.
|
||||
func newMouseEvTarget(w widgetapi.Widget, wArea image.Rectangle, ev *terminalapi.Mouse) *mouseEvTarget {
|
||||
return &mouseEvTarget{
|
||||
widget: w,
|
||||
ev: adjustMouseEv(ev, wArea),
|
||||
}
|
||||
}
|
||||
|
||||
// mouseEvTargets returns those widgets found in the container that should
|
||||
// receive this mouse event.
|
||||
// Caller must hold c.mu.
|
||||
func (c *Container) mouseEvTargets(m *terminalapi.Mouse) ([]*mouseEvTarget, error) {
|
||||
var (
|
||||
errStr string
|
||||
widgets []*mouseEvTarget
|
||||
)
|
||||
|
||||
// All the widgets that should receive this event.
|
||||
// For now stable ordering (preOrder).
|
||||
preOrder(c, &errStr, visitFunc(func(cur *Container) error {
|
||||
if !cur.hasWidget() {
|
||||
return nil
|
||||
}
|
||||
|
||||
wOpts := cur.opts.widget.Options()
|
||||
wa, err := cur.widgetArea()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch wOpts.WantMouse {
|
||||
case widgetapi.MouseScopeNone:
|
||||
// Widget doesn't want any mouse events.
|
||||
return nil
|
||||
|
||||
case widgetapi.MouseScopeWidget:
|
||||
// Only if the event falls inside of the widget's canvas.
|
||||
if m.Position.In(wa) {
|
||||
widgets = append(widgets, newMouseEvTarget(cur.opts.widget, wa, m))
|
||||
}
|
||||
|
||||
case widgetapi.MouseScopeContainer:
|
||||
// Only if the event falls inside the widget's parent container.
|
||||
if m.Position.In(cur.area) {
|
||||
widgets = append(widgets, newMouseEvTarget(cur.opts.widget, wa, m))
|
||||
}
|
||||
|
||||
case widgetapi.MouseScopeGlobal:
|
||||
// Widget wants all mouse events.
|
||||
widgets = append(widgets, newMouseEvTarget(cur.opts.widget, wa, m))
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
|
||||
if errStr != "" {
|
||||
return nil, errors.New(errStr)
|
||||
}
|
||||
return widgets, nil
|
||||
}
|
||||
|
||||
// Subscribe tells the container to subscribe itself and widgets to the
|
||||
// provided event distribution system.
|
||||
// This method is private to termdash, stability isn't guaranteed and changes
|
||||
// won't be backward compatible.
|
||||
func (c *Container) Subscribe(eds *event.DistributionSystem) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
// maxReps is the maximum number of repetitive events towards widgets
|
||||
// before we throttle them.
|
||||
const maxReps = 10
|
||||
|
||||
// Subscriber the container itself in order to track keyboard focus.
|
||||
want := []terminalapi.Event{
|
||||
&terminalapi.Keyboard{},
|
||||
&terminalapi.Mouse{},
|
||||
}
|
||||
eds.Subscribe(want, func(ev terminalapi.Event) {
|
||||
if err := c.processEvent(ev); err != nil {
|
||||
eds.Event(terminalapi.NewErrorf("failed to process event %v: %v", ev, err))
|
||||
}
|
||||
}, event.MaxRepetitive(maxReps))
|
||||
}
|
||||
|
||||
// adjustMouseEv adjusts the mouse event relative to the widget area.
|
||||
func adjustMouseEv(m *terminalapi.Mouse, wArea image.Rectangle) *terminalapi.Mouse {
|
||||
// The sent mouse coordinate is relative to the widget canvas, i.e. zero
|
||||
// based, even though the widget might not be in the top left corner on the
|
||||
// terminal.
|
||||
offset := wArea.Min
|
||||
if m.Position.In(wArea) {
|
||||
return &terminalapi.Mouse{
|
||||
Position: m.Position.Sub(offset),
|
||||
Button: m.Button,
|
||||
}
|
||||
}
|
||||
return &terminalapi.Mouse{
|
||||
Position: image.Point{-1, -1},
|
||||
Button: m.Button,
|
||||
}
|
||||
}
|
||||
175
examples/go-dashboard/vendor/github.com/mum4k/termdash/container/draw.go
generated
vendored
175
examples/go-dashboard/vendor/github.com/mum4k/termdash/container/draw.go
generated
vendored
@@ -1,175 +0,0 @@
|
||||
// Copyright 2018 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package container
|
||||
|
||||
// draw.go contains logic to draw containers and the contained widgets.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
|
||||
"github.com/mum4k/termdash/cell"
|
||||
"github.com/mum4k/termdash/private/area"
|
||||
"github.com/mum4k/termdash/private/canvas"
|
||||
"github.com/mum4k/termdash/private/draw"
|
||||
"github.com/mum4k/termdash/widgetapi"
|
||||
)
|
||||
|
||||
// drawTree draws this container and all of its sub containers.
|
||||
func drawTree(c *Container) error {
|
||||
var errStr string
|
||||
|
||||
root := rootCont(c)
|
||||
size := root.term.Size()
|
||||
ar, err := root.opts.margin.apply(image.Rect(0, 0, size.X, size.Y))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
root.area = ar
|
||||
|
||||
preOrder(root, &errStr, visitFunc(func(c *Container) error {
|
||||
first, second, err := c.split()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c.first != nil {
|
||||
ar, err := c.first.opts.margin.apply(first)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.first.area = ar
|
||||
}
|
||||
|
||||
if c.second != nil {
|
||||
ar, err := c.second.opts.margin.apply(second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.second.area = ar
|
||||
}
|
||||
return drawCont(c)
|
||||
}))
|
||||
if errStr != "" {
|
||||
return errors.New(errStr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// drawBorder draws the border around the container if requested.
|
||||
func drawBorder(c *Container) error {
|
||||
if !c.hasBorder() {
|
||||
return nil
|
||||
}
|
||||
|
||||
cvs, err := canvas.New(c.area)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ar, err := area.FromSize(cvs.Size())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var cOpts []cell.Option
|
||||
if c.focusTracker.isActive(c) {
|
||||
cOpts = append(cOpts, cell.FgColor(c.opts.inherited.focusedColor))
|
||||
} else {
|
||||
cOpts = append(cOpts, cell.FgColor(c.opts.inherited.borderColor))
|
||||
}
|
||||
|
||||
if err := draw.Border(cvs, ar,
|
||||
draw.BorderLineStyle(c.opts.border),
|
||||
draw.BorderTitle(c.opts.borderTitle, draw.OverrunModeThreeDot, cOpts...),
|
||||
draw.BorderTitleAlign(c.opts.borderTitleHAlign),
|
||||
draw.BorderCellOpts(cOpts...),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
return cvs.Apply(c.term)
|
||||
}
|
||||
|
||||
// drawWidget requests the widget to draw on the canvas.
|
||||
func drawWidget(c *Container) error {
|
||||
widgetArea, err := c.widgetArea()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if widgetArea == image.ZR {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !c.hasWidget() {
|
||||
return nil
|
||||
}
|
||||
|
||||
needSize := image.Point{1, 1}
|
||||
wOpts := c.opts.widget.Options()
|
||||
if wOpts.MinimumSize.X > 0 && wOpts.MinimumSize.Y > 0 {
|
||||
needSize = wOpts.MinimumSize
|
||||
}
|
||||
|
||||
if widgetArea.Dx() < needSize.X || widgetArea.Dy() < needSize.Y {
|
||||
return drawResize(c, c.usable())
|
||||
}
|
||||
|
||||
cvs, err := canvas.New(widgetArea)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
meta := &widgetapi.Meta{
|
||||
Focused: c.focusTracker.isActive(c),
|
||||
}
|
||||
|
||||
if err := c.opts.widget.Draw(cvs, meta); err != nil {
|
||||
return err
|
||||
}
|
||||
return cvs.Apply(c.term)
|
||||
}
|
||||
|
||||
// drawResize draws an unicode character indicating that the size is too small to draw this container.
|
||||
// Does nothing if the size is smaller than one cell, leaving no space for the character.
|
||||
func drawResize(c *Container, area image.Rectangle) error {
|
||||
if area.Dx() < 1 || area.Dy() < 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
cvs, err := canvas.New(area)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := draw.ResizeNeeded(cvs); err != nil {
|
||||
return err
|
||||
}
|
||||
return cvs.Apply(c.term)
|
||||
}
|
||||
|
||||
// drawCont draws the container and its widget.
|
||||
func drawCont(c *Container) error {
|
||||
if us := c.usable(); us.Dx() <= 0 || us.Dy() <= 0 {
|
||||
return drawResize(c, c.area)
|
||||
}
|
||||
|
||||
if err := drawBorder(c); err != nil {
|
||||
return fmt.Errorf("unable to draw container border: %v", err)
|
||||
}
|
||||
|
||||
if err := drawWidget(c); err != nil {
|
||||
return fmt.Errorf("unable to draw widget %T: %v", c.opts.widget, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
116
examples/go-dashboard/vendor/github.com/mum4k/termdash/container/focus.go
generated
vendored
116
examples/go-dashboard/vendor/github.com/mum4k/termdash/container/focus.go
generated
vendored
@@ -1,116 +0,0 @@
|
||||
// Copyright 2018 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package container
|
||||
|
||||
// focus.go contains code that tracks the focused container.
|
||||
|
||||
import (
|
||||
"image"
|
||||
|
||||
"github.com/mum4k/termdash/mouse"
|
||||
"github.com/mum4k/termdash/private/button"
|
||||
"github.com/mum4k/termdash/terminal/terminalapi"
|
||||
)
|
||||
|
||||
// pointCont finds the top-most (on the screen) container whose area contains
|
||||
// the given point. Returns nil if none of the containers in the tree contain
|
||||
// this point.
|
||||
func pointCont(c *Container, p image.Point) *Container {
|
||||
var (
|
||||
errStr string
|
||||
cont *Container
|
||||
)
|
||||
postOrder(rootCont(c), &errStr, visitFunc(func(c *Container) error {
|
||||
if p.In(c.area) && cont == nil {
|
||||
cont = c
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
return cont
|
||||
}
|
||||
|
||||
// focusTracker tracks the active (focused) container.
|
||||
// This is not thread-safe, the implementation assumes that the owner of
|
||||
// focusTracker performs locking.
|
||||
type focusTracker struct {
|
||||
// container is the currently focused container.
|
||||
container *Container
|
||||
|
||||
// candidate is the container that might become focused next. I.e. we got
|
||||
// a mouse click and now waiting for a release or a timeout.
|
||||
candidate *Container
|
||||
|
||||
// buttonFSM is a state machine tracking mouse clicks in containers and
|
||||
// moving focus from one container to the next.
|
||||
buttonFSM *button.FSM
|
||||
}
|
||||
|
||||
// newFocusTracker returns a new focus tracker with focus set at the provided
|
||||
// container.
|
||||
func newFocusTracker(c *Container) *focusTracker {
|
||||
return &focusTracker{
|
||||
container: c,
|
||||
// Mouse FSM tracking clicks inside the entire area for the root
|
||||
// container.
|
||||
buttonFSM: button.NewFSM(mouse.ButtonLeft, c.area),
|
||||
}
|
||||
}
|
||||
|
||||
// isActive determines if the provided container is the currently active container.
|
||||
func (ft *focusTracker) isActive(c *Container) bool {
|
||||
return ft.container == c
|
||||
}
|
||||
|
||||
// setActive sets the currently active container to the one provided.
|
||||
func (ft *focusTracker) setActive(c *Container) {
|
||||
ft.container = c
|
||||
}
|
||||
|
||||
// mouse identifies mouse events that change the focused container and track
|
||||
// the focused container in the tree.
|
||||
// The argument c is the container onto which the mouse event landed.
|
||||
func (ft *focusTracker) mouse(target *Container, m *terminalapi.Mouse) {
|
||||
clicked, bs := ft.buttonFSM.Event(m)
|
||||
switch {
|
||||
case bs == button.Down:
|
||||
ft.candidate = target
|
||||
case bs == button.Up && clicked:
|
||||
if target == ft.candidate {
|
||||
ft.container = target
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// updateArea updates the area that the focus tracker considers active for
|
||||
// mouse clicks.
|
||||
func (ft *focusTracker) updateArea(ar image.Rectangle) {
|
||||
ft.buttonFSM.UpdateArea(ar)
|
||||
}
|
||||
|
||||
// reachableFrom asserts whether the currently focused container is reachable
|
||||
// from the provided node in the tree.
|
||||
func (ft *focusTracker) reachableFrom(node *Container) bool {
|
||||
var (
|
||||
errStr string
|
||||
reachable bool
|
||||
)
|
||||
preOrder(node, &errStr, visitFunc(func(c *Container) error {
|
||||
if c == ft.container {
|
||||
reachable = true
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
return reachable
|
||||
}
|
||||
817
examples/go-dashboard/vendor/github.com/mum4k/termdash/container/options.go
generated
vendored
817
examples/go-dashboard/vendor/github.com/mum4k/termdash/container/options.go
generated
vendored
@@ -1,817 +0,0 @@
|
||||
// Copyright 2018 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package container
|
||||
|
||||
// options.go defines container options.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
|
||||
"github.com/mum4k/termdash/align"
|
||||
"github.com/mum4k/termdash/cell"
|
||||
"github.com/mum4k/termdash/linestyle"
|
||||
"github.com/mum4k/termdash/private/area"
|
||||
"github.com/mum4k/termdash/widgetapi"
|
||||
)
|
||||
|
||||
// applyOptions applies the options to the container and validates them.
|
||||
func applyOptions(c *Container, opts ...Option) error {
|
||||
for _, opt := range opts {
|
||||
if err := opt.set(c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ensure all the container identifiers are either empty or unique.
|
||||
func validateIds(c *Container, seen map[string]bool) error {
|
||||
if c.opts.id == "" {
|
||||
return nil
|
||||
} else if seen[c.opts.id] {
|
||||
return fmt.Errorf("duplicate container ID %q", c.opts.id)
|
||||
}
|
||||
seen[c.opts.id] = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ensure all the container only have one split modifier.
|
||||
func validateSplits(c *Container) error {
|
||||
if c.opts.splitFixed > DefaultSplitFixed && c.opts.splitPercent != DefaultSplitPercent {
|
||||
return fmt.Errorf(
|
||||
"only one of splitFixed `%v` and splitPercent `%v` is allowed to be set per container",
|
||||
c.opts.splitFixed,
|
||||
c.opts.splitPercent,
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateOptions validates options set in the container tree.
|
||||
func validateOptions(c *Container) error {
|
||||
var errStr string
|
||||
seenID := map[string]bool{}
|
||||
preOrder(c, &errStr, func(c *Container) error {
|
||||
if err := validateIds(c, seenID); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateSplits(c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if errStr != "" {
|
||||
return errors.New(errStr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Option is used to provide options to a container.
|
||||
type Option interface {
|
||||
// set sets the provided option.
|
||||
set(*Container) error
|
||||
}
|
||||
|
||||
// options stores the options provided to the container.
|
||||
type options struct {
|
||||
// id is the identifier provided by the user.
|
||||
id string
|
||||
|
||||
// inherited are options that are inherited by child containers.
|
||||
inherited inherited
|
||||
|
||||
// split identifies how is this container split.
|
||||
split splitType
|
||||
splitPercent int
|
||||
splitFixed int
|
||||
|
||||
// widget is the widget in the container.
|
||||
// A container can have either two sub containers (left and right) or a
|
||||
// widget. But not both.
|
||||
widget widgetapi.Widget
|
||||
|
||||
// Alignment of the widget if present.
|
||||
hAlign align.Horizontal
|
||||
vAlign align.Vertical
|
||||
|
||||
// border is the border around the container.
|
||||
border linestyle.LineStyle
|
||||
borderTitle string
|
||||
borderTitleHAlign align.Horizontal
|
||||
|
||||
// padding is a space reserved between the outer edge of the container and
|
||||
// its content (the widget or other sub-containers).
|
||||
padding padding
|
||||
|
||||
// margin is a space reserved on the outside of the container.
|
||||
margin margin
|
||||
}
|
||||
|
||||
// margin stores the configured margin for the container.
|
||||
// For each margin direction, only one of the percentage or cells is set.
|
||||
type margin struct {
|
||||
topCells int
|
||||
topPerc int
|
||||
rightCells int
|
||||
rightPerc int
|
||||
bottomCells int
|
||||
bottomPerc int
|
||||
leftCells int
|
||||
leftPerc int
|
||||
}
|
||||
|
||||
// apply applies the configured margin to the area.
|
||||
func (p *margin) apply(ar image.Rectangle) (image.Rectangle, error) {
|
||||
switch {
|
||||
case p.topCells != 0 || p.rightCells != 0 || p.bottomCells != 0 || p.leftCells != 0:
|
||||
return area.Shrink(ar, p.topCells, p.rightCells, p.bottomCells, p.leftCells)
|
||||
case p.topPerc != 0 || p.rightPerc != 0 || p.bottomPerc != 0 || p.leftPerc != 0:
|
||||
return area.ShrinkPercent(ar, p.topPerc, p.rightPerc, p.bottomPerc, p.leftPerc)
|
||||
}
|
||||
return ar, nil
|
||||
}
|
||||
|
||||
// padding stores the configured padding for the container.
|
||||
// For each padding direction, only one of the percentage or cells is set.
|
||||
type padding struct {
|
||||
topCells int
|
||||
topPerc int
|
||||
rightCells int
|
||||
rightPerc int
|
||||
bottomCells int
|
||||
bottomPerc int
|
||||
leftCells int
|
||||
leftPerc int
|
||||
}
|
||||
|
||||
// apply applies the configured padding to the area.
|
||||
func (p *padding) apply(ar image.Rectangle) (image.Rectangle, error) {
|
||||
switch {
|
||||
case p.topCells != 0 || p.rightCells != 0 || p.bottomCells != 0 || p.leftCells != 0:
|
||||
return area.Shrink(ar, p.topCells, p.rightCells, p.bottomCells, p.leftCells)
|
||||
case p.topPerc != 0 || p.rightPerc != 0 || p.bottomPerc != 0 || p.leftPerc != 0:
|
||||
return area.ShrinkPercent(ar, p.topPerc, p.rightPerc, p.bottomPerc, p.leftPerc)
|
||||
}
|
||||
return ar, nil
|
||||
}
|
||||
|
||||
// inherited contains options that are inherited by child containers.
|
||||
type inherited struct {
|
||||
// borderColor is the color used for the border.
|
||||
borderColor cell.Color
|
||||
// focusedColor is the color used for the border when focused.
|
||||
focusedColor cell.Color
|
||||
}
|
||||
|
||||
// newOptions returns a new options instance with the default values.
|
||||
// Parent are the inherited options from the parent container or nil if these
|
||||
// options are for a container with no parent (the root).
|
||||
func newOptions(parent *options) *options {
|
||||
opts := &options{
|
||||
inherited: inherited{
|
||||
focusedColor: cell.ColorYellow,
|
||||
},
|
||||
hAlign: align.HorizontalCenter,
|
||||
vAlign: align.VerticalMiddle,
|
||||
splitPercent: DefaultSplitPercent,
|
||||
splitFixed: DefaultSplitFixed,
|
||||
}
|
||||
if parent != nil {
|
||||
opts.inherited = parent.inherited
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
// option implements Option.
|
||||
type option func(*Container) error
|
||||
|
||||
// set implements Option.set.
|
||||
func (o option) set(c *Container) error {
|
||||
return o(c)
|
||||
}
|
||||
|
||||
// SplitOption is used when splitting containers.
|
||||
type SplitOption interface {
|
||||
// setSplit sets the provided split option.
|
||||
setSplit(*options) error
|
||||
}
|
||||
|
||||
// splitOption implements SplitOption.
|
||||
type splitOption func(*options) error
|
||||
|
||||
// setSplit implements SplitOption.setSplit.
|
||||
func (so splitOption) setSplit(opts *options) error {
|
||||
return so(opts)
|
||||
}
|
||||
|
||||
// DefaultSplitPercent is the default value for the SplitPercent option.
|
||||
const DefaultSplitPercent = 50
|
||||
|
||||
// DefaultSplitFixed is the default value for the SplitFixed option.
|
||||
const DefaultSplitFixed = -1
|
||||
|
||||
// SplitPercent sets the relative size of the split as percentage of the available space.
|
||||
// When using SplitVertical, the provided size is applied to the new left
|
||||
// container, the new right container gets the reminder of the size.
|
||||
// When using SplitHorizontal, the provided size is applied to the new top
|
||||
// container, the new bottom container gets the reminder of the size.
|
||||
// The provided value must be a positive number in the range 0 < p < 100.
|
||||
// If not provided, defaults to DefaultSplitPercent.
|
||||
func SplitPercent(p int) SplitOption {
|
||||
return splitOption(func(opts *options) error {
|
||||
if min, max := 0, 100; p <= min || p >= max {
|
||||
return fmt.Errorf("invalid split percentage %d, must be in range %d < p < %d", p, min, max)
|
||||
}
|
||||
opts.splitPercent = p
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// SplitFixed sets the size of the first container to be a fixed value
|
||||
// and makes the second container take up the remaining space.
|
||||
// When using SplitVertical, the provided size is applied to the new left
|
||||
// container, the new right container gets the reminder of the size.
|
||||
// When using SplitHorizontal, the provided size is applied to the new top
|
||||
// container, the new bottom container gets the reminder of the size.
|
||||
// The provided value must be a positive number in the range 0 <= cells.
|
||||
// If SplitFixed() is not specified, it defaults to SplitPercent() and its given value.
|
||||
// Only one of SplitFixed() and SplitPercent() can be specified per container.
|
||||
func SplitFixed(cells int) SplitOption {
|
||||
return splitOption(func(opts *options) error {
|
||||
if cells < 0 {
|
||||
return fmt.Errorf("invalid fixed value %d, must be in range %d <= cells", cells, 0)
|
||||
}
|
||||
opts.splitFixed = cells
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// SplitVertical splits the container along the vertical axis into two sub
|
||||
// containers. The use of this option removes any widget placed at this
|
||||
// container, containers with sub containers cannot contain widgets.
|
||||
func SplitVertical(l LeftOption, r RightOption, opts ...SplitOption) Option {
|
||||
return option(func(c *Container) error {
|
||||
c.opts.split = splitTypeVertical
|
||||
c.opts.widget = nil
|
||||
for _, opt := range opts {
|
||||
if err := opt.setSplit(c.opts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.createFirst(l.lOpts()); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.createSecond(r.rOpts())
|
||||
})
|
||||
}
|
||||
|
||||
// SplitHorizontal splits the container along the horizontal axis into two sub
|
||||
// containers. The use of this option removes any widget placed at this
|
||||
// container, containers with sub containers cannot contain widgets.
|
||||
func SplitHorizontal(t TopOption, b BottomOption, opts ...SplitOption) Option {
|
||||
return option(func(c *Container) error {
|
||||
c.opts.split = splitTypeHorizontal
|
||||
c.opts.widget = nil
|
||||
for _, opt := range opts {
|
||||
if err := opt.setSplit(c.opts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.createFirst(t.tOpts()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.createSecond(b.bOpts())
|
||||
})
|
||||
}
|
||||
|
||||
// ID sets an identifier for this container.
|
||||
// This ID can be later used to perform dynamic layout changes by passing new
|
||||
// options to this container. When provided, it must be a non-empty string that
|
||||
// is unique among all the containers.
|
||||
func ID(id string) Option {
|
||||
return option(func(c *Container) error {
|
||||
if id == "" {
|
||||
return errors.New("the ID cannot be an empty string")
|
||||
}
|
||||
c.opts.id = id
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Clear clears this container.
|
||||
// If the container contains a widget, the widget is removed.
|
||||
// If the container had any sub containers or splits, they are removed.
|
||||
func Clear() Option {
|
||||
return option(func(c *Container) error {
|
||||
c.opts.widget = nil
|
||||
c.first = nil
|
||||
c.second = nil
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// PlaceWidget places the provided widget into the container.
|
||||
// The use of this option removes any sub containers. Containers with sub
|
||||
// containers cannot have widgets.
|
||||
func PlaceWidget(w widgetapi.Widget) Option {
|
||||
return option(func(c *Container) error {
|
||||
c.opts.widget = w
|
||||
c.first = nil
|
||||
c.second = nil
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// MarginTop sets reserved space outside of the container at its top.
|
||||
// The provided number is the absolute margin in cells and must be zero or a
|
||||
// positive integer. Only one of MarginTop or MarginTopPercent can be specified.
|
||||
func MarginTop(cells int) Option {
|
||||
return option(func(c *Container) error {
|
||||
if min := 0; cells < min {
|
||||
return fmt.Errorf("invalid MarginTop(%d), must be in range %d <= value", cells, min)
|
||||
}
|
||||
if c.opts.margin.topPerc > 0 {
|
||||
return fmt.Errorf("cannot specify both MarginTop(%d) and MarginTopPercent(%d)", cells, c.opts.margin.topPerc)
|
||||
}
|
||||
c.opts.margin.topCells = cells
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// MarginRight sets reserved space outside of the container at its right.
|
||||
// The provided number is the absolute margin in cells and must be zero or a
|
||||
// positive integer. Only one of MarginRight or MarginRightPercent can be specified.
|
||||
func MarginRight(cells int) Option {
|
||||
return option(func(c *Container) error {
|
||||
if min := 0; cells < min {
|
||||
return fmt.Errorf("invalid MarginRight(%d), must be in range %d <= value", cells, min)
|
||||
}
|
||||
if c.opts.margin.rightPerc > 0 {
|
||||
return fmt.Errorf("cannot specify both MarginRight(%d) and MarginRightPercent(%d)", cells, c.opts.margin.rightPerc)
|
||||
}
|
||||
c.opts.margin.rightCells = cells
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// MarginBottom sets reserved space outside of the container at its bottom.
|
||||
// The provided number is the absolute margin in cells and must be zero or a
|
||||
// positive integer. Only one of MarginBottom or MarginBottomPercent can be specified.
|
||||
func MarginBottom(cells int) Option {
|
||||
return option(func(c *Container) error {
|
||||
if min := 0; cells < min {
|
||||
return fmt.Errorf("invalid MarginBottom(%d), must be in range %d <= value", cells, min)
|
||||
}
|
||||
if c.opts.margin.bottomPerc > 0 {
|
||||
return fmt.Errorf("cannot specify both MarginBottom(%d) and MarginBottomPercent(%d)", cells, c.opts.margin.bottomPerc)
|
||||
}
|
||||
c.opts.margin.bottomCells = cells
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// MarginLeft sets reserved space outside of the container at its left.
|
||||
// The provided number is the absolute margin in cells and must be zero or a
|
||||
// positive integer. Only one of MarginLeft or MarginLeftPercent can be specified.
|
||||
func MarginLeft(cells int) Option {
|
||||
return option(func(c *Container) error {
|
||||
if min := 0; cells < min {
|
||||
return fmt.Errorf("invalid MarginLeft(%d), must be in range %d <= value", cells, min)
|
||||
}
|
||||
if c.opts.margin.leftPerc > 0 {
|
||||
return fmt.Errorf("cannot specify both MarginLeft(%d) and MarginLeftPercent(%d)", cells, c.opts.margin.leftPerc)
|
||||
}
|
||||
c.opts.margin.leftCells = cells
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// MarginTopPercent sets reserved space outside of the container at its top.
|
||||
// The provided number is a relative margin defined as percentage of the container's height.
|
||||
// Only one of MarginTop or MarginTopPercent can be specified.
|
||||
// The value must be in range 0 <= value <= 100.
|
||||
func MarginTopPercent(perc int) Option {
|
||||
return option(func(c *Container) error {
|
||||
if min, max := 0, 100; perc < min || perc > max {
|
||||
return fmt.Errorf("invalid MarginTopPercent(%d), must be in range %d <= value <= %d", perc, min, max)
|
||||
}
|
||||
if c.opts.margin.topCells > 0 {
|
||||
return fmt.Errorf("cannot specify both MarginTopPercent(%d) and MarginTop(%d)", perc, c.opts.margin.topCells)
|
||||
}
|
||||
c.opts.margin.topPerc = perc
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// MarginRightPercent sets reserved space outside of the container at its right.
|
||||
// The provided number is a relative margin defined as percentage of the container's height.
|
||||
// Only one of MarginRight or MarginRightPercent can be specified.
|
||||
// The value must be in range 0 <= value <= 100.
|
||||
func MarginRightPercent(perc int) Option {
|
||||
return option(func(c *Container) error {
|
||||
if min, max := 0, 100; perc < min || perc > max {
|
||||
return fmt.Errorf("invalid MarginRightPercent(%d), must be in range %d <= value <= %d", perc, min, max)
|
||||
}
|
||||
if c.opts.margin.rightCells > 0 {
|
||||
return fmt.Errorf("cannot specify both MarginRightPercent(%d) and MarginRight(%d)", perc, c.opts.margin.rightCells)
|
||||
}
|
||||
c.opts.margin.rightPerc = perc
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// MarginBottomPercent sets reserved space outside of the container at its bottom.
|
||||
// The provided number is a relative margin defined as percentage of the container's height.
|
||||
// Only one of MarginBottom or MarginBottomPercent can be specified.
|
||||
// The value must be in range 0 <= value <= 100.
|
||||
func MarginBottomPercent(perc int) Option {
|
||||
return option(func(c *Container) error {
|
||||
if min, max := 0, 100; perc < min || perc > max {
|
||||
return fmt.Errorf("invalid MarginBottomPercent(%d), must be in range %d <= value <= %d", perc, min, max)
|
||||
}
|
||||
if c.opts.margin.bottomCells > 0 {
|
||||
return fmt.Errorf("cannot specify both MarginBottomPercent(%d) and MarginBottom(%d)", perc, c.opts.margin.bottomCells)
|
||||
}
|
||||
c.opts.margin.bottomPerc = perc
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// MarginLeftPercent sets reserved space outside of the container at its left.
|
||||
// The provided number is a relative margin defined as percentage of the container's height.
|
||||
// Only one of MarginLeft or MarginLeftPercent can be specified.
|
||||
// The value must be in range 0 <= value <= 100.
|
||||
func MarginLeftPercent(perc int) Option {
|
||||
return option(func(c *Container) error {
|
||||
if min, max := 0, 100; perc < min || perc > max {
|
||||
return fmt.Errorf("invalid MarginLeftPercent(%d), must be in range %d <= value <= %d", perc, min, max)
|
||||
}
|
||||
if c.opts.margin.leftCells > 0 {
|
||||
return fmt.Errorf("cannot specify both MarginLeftPercent(%d) and MarginLeft(%d)", perc, c.opts.margin.leftCells)
|
||||
}
|
||||
c.opts.margin.leftPerc = perc
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// PaddingTop sets reserved space between container and the top side of its widget.
|
||||
// The widget's area size is decreased to accommodate the padding.
|
||||
// The provided number is the absolute padding in cells and must be zero or a
|
||||
// positive integer. Only one of PaddingTop or PaddingTopPercent can be specified.
|
||||
func PaddingTop(cells int) Option {
|
||||
return option(func(c *Container) error {
|
||||
if min := 0; cells < min {
|
||||
return fmt.Errorf("invalid PaddingTop(%d), must be in range %d <= value", cells, min)
|
||||
}
|
||||
if c.opts.padding.topPerc > 0 {
|
||||
return fmt.Errorf("cannot specify both PaddingTop(%d) and PaddingTopPercent(%d)", cells, c.opts.padding.topPerc)
|
||||
}
|
||||
c.opts.padding.topCells = cells
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// PaddingRight sets reserved space between container and the right side of its widget.
|
||||
// The widget's area size is decreased to accommodate the padding.
|
||||
// The provided number is the absolute padding in cells and must be zero or a
|
||||
// positive integer. Only one of PaddingRight or PaddingRightPercent can be specified.
|
||||
func PaddingRight(cells int) Option {
|
||||
return option(func(c *Container) error {
|
||||
if min := 0; cells < min {
|
||||
return fmt.Errorf("invalid PaddingRight(%d), must be in range %d <= value", cells, min)
|
||||
}
|
||||
if c.opts.padding.rightPerc > 0 {
|
||||
return fmt.Errorf("cannot specify both PaddingRight(%d) and PaddingRightPercent(%d)", cells, c.opts.padding.rightPerc)
|
||||
}
|
||||
c.opts.padding.rightCells = cells
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// PaddingBottom sets reserved space between container and the bottom side of its widget.
|
||||
// The widget's area size is decreased to accommodate the padding.
|
||||
// The provided number is the absolute padding in cells and must be zero or a
|
||||
// positive integer. Only one of PaddingBottom or PaddingBottomPercent can be specified.
|
||||
func PaddingBottom(cells int) Option {
|
||||
return option(func(c *Container) error {
|
||||
if min := 0; cells < min {
|
||||
return fmt.Errorf("invalid PaddingBottom(%d), must be in range %d <= value", cells, min)
|
||||
}
|
||||
if c.opts.padding.bottomPerc > 0 {
|
||||
return fmt.Errorf("cannot specify both PaddingBottom(%d) and PaddingBottomPercent(%d)", cells, c.opts.padding.bottomPerc)
|
||||
}
|
||||
c.opts.padding.bottomCells = cells
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// PaddingLeft sets reserved space between container and the left side of its widget.
|
||||
// The widget's area size is decreased to accommodate the padding.
|
||||
// The provided number is the absolute padding in cells and must be zero or a
|
||||
// positive integer. Only one of PaddingLeft or PaddingLeftPercent can be specified.
|
||||
func PaddingLeft(cells int) Option {
|
||||
return option(func(c *Container) error {
|
||||
if min := 0; cells < min {
|
||||
return fmt.Errorf("invalid PaddingLeft(%d), must be in range %d <= value", cells, min)
|
||||
}
|
||||
if c.opts.padding.leftPerc > 0 {
|
||||
return fmt.Errorf("cannot specify both PaddingLeft(%d) and PaddingLeftPercent(%d)", cells, c.opts.padding.leftPerc)
|
||||
}
|
||||
c.opts.padding.leftCells = cells
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// PaddingTopPercent sets reserved space between container and the top side of
|
||||
// its widget. The widget's area size is decreased to accommodate the padding.
|
||||
// The provided number is a relative padding defined as percentage of the
|
||||
// container's height. The value must be in range 0 <= value <= 100.
|
||||
// Only one of PaddingTop or PaddingTopPercent can be specified.
|
||||
func PaddingTopPercent(perc int) Option {
|
||||
return option(func(c *Container) error {
|
||||
if min, max := 0, 100; perc < min || perc > max {
|
||||
return fmt.Errorf("invalid PaddingTopPercent(%d), must be in range %d <= value <= %d", perc, min, max)
|
||||
}
|
||||
if c.opts.padding.topCells > 0 {
|
||||
return fmt.Errorf("cannot specify both PaddingTopPercent(%d) and PaddingTop(%d)", perc, c.opts.padding.topCells)
|
||||
}
|
||||
c.opts.padding.topPerc = perc
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// PaddingRightPercent sets reserved space between container and the right side of
|
||||
// its widget. The widget's area size is decreased to accommodate the padding.
|
||||
// The provided number is a relative padding defined as percentage of the
|
||||
// container's width. The value must be in range 0 <= value <= 100.
|
||||
// Only one of PaddingRight or PaddingRightPercent can be specified.
|
||||
func PaddingRightPercent(perc int) Option {
|
||||
return option(func(c *Container) error {
|
||||
if min, max := 0, 100; perc < min || perc > max {
|
||||
return fmt.Errorf("invalid PaddingRightPercent(%d), must be in range %d <= value <= %d", perc, min, max)
|
||||
}
|
||||
if c.opts.padding.rightCells > 0 {
|
||||
return fmt.Errorf("cannot specify both PaddingRightPercent(%d) and PaddingRight(%d)", perc, c.opts.padding.rightCells)
|
||||
}
|
||||
c.opts.padding.rightPerc = perc
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// PaddingBottomPercent sets reserved space between container and the bottom side of
|
||||
// its widget. The widget's area size is decreased to accommodate the padding.
|
||||
// The provided number is a relative padding defined as percentage of the
|
||||
// container's height. The value must be in range 0 <= value <= 100.
|
||||
// Only one of PaddingBottom or PaddingBottomPercent can be specified.
|
||||
func PaddingBottomPercent(perc int) Option {
|
||||
return option(func(c *Container) error {
|
||||
if min, max := 0, 100; perc < min || perc > max {
|
||||
return fmt.Errorf("invalid PaddingBottomPercent(%d), must be in range %d <= value <= %d", perc, min, max)
|
||||
}
|
||||
if c.opts.padding.bottomCells > 0 {
|
||||
return fmt.Errorf("cannot specify both PaddingBottomPercent(%d) and PaddingBottom(%d)", perc, c.opts.padding.bottomCells)
|
||||
}
|
||||
c.opts.padding.bottomPerc = perc
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// PaddingLeftPercent sets reserved space between container and the left side of
|
||||
// its widget. The widget's area size is decreased to accommodate the padding.
|
||||
// The provided number is a relative padding defined as percentage of the
|
||||
// container's width. The value must be in range 0 <= value <= 100.
|
||||
// Only one of PaddingLeft or PaddingLeftPercent can be specified.
|
||||
func PaddingLeftPercent(perc int) Option {
|
||||
return option(func(c *Container) error {
|
||||
if min, max := 0, 100; perc < min || perc > max {
|
||||
return fmt.Errorf("invalid PaddingLeftPercent(%d), must be in range %d <= value <= %d", perc, min, max)
|
||||
}
|
||||
if c.opts.padding.leftCells > 0 {
|
||||
return fmt.Errorf("cannot specify both PaddingLeftPercent(%d) and PaddingLeft(%d)", perc, c.opts.padding.leftCells)
|
||||
}
|
||||
c.opts.padding.leftPerc = perc
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// AlignHorizontal sets the horizontal alignment for the widget placed in the
|
||||
// container. Has no effect if the container contains no widget.
|
||||
// Defaults to alignment in the center.
|
||||
func AlignHorizontal(h align.Horizontal) Option {
|
||||
return option(func(c *Container) error {
|
||||
c.opts.hAlign = h
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// AlignVertical sets the vertical alignment for the widget placed in the container.
|
||||
// Has no effect if the container contains no widget.
|
||||
// Defaults to alignment in the middle.
|
||||
func AlignVertical(v align.Vertical) Option {
|
||||
return option(func(c *Container) error {
|
||||
c.opts.vAlign = v
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Border configures the container to have a border of the specified style.
|
||||
func Border(ls linestyle.LineStyle) Option {
|
||||
return option(func(c *Container) error {
|
||||
c.opts.border = ls
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// BorderTitle sets a text title within the border.
|
||||
func BorderTitle(title string) Option {
|
||||
return option(func(c *Container) error {
|
||||
c.opts.borderTitle = title
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// BorderTitleAlignLeft aligns the border title on the left.
|
||||
func BorderTitleAlignLeft() Option {
|
||||
return option(func(c *Container) error {
|
||||
c.opts.borderTitleHAlign = align.HorizontalLeft
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// BorderTitleAlignCenter aligns the border title in the center.
|
||||
func BorderTitleAlignCenter() Option {
|
||||
return option(func(c *Container) error {
|
||||
c.opts.borderTitleHAlign = align.HorizontalCenter
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// BorderTitleAlignRight aligns the border title on the right.
|
||||
func BorderTitleAlignRight() Option {
|
||||
return option(func(c *Container) error {
|
||||
c.opts.borderTitleHAlign = align.HorizontalRight
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// BorderColor sets the color of the border around the container.
|
||||
// This option is inherited to sub containers created by container splits.
|
||||
func BorderColor(color cell.Color) Option {
|
||||
return option(func(c *Container) error {
|
||||
c.opts.inherited.borderColor = color
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// FocusedColor sets the color of the border around the container when it has
|
||||
// keyboard focus.
|
||||
// This option is inherited to sub containers created by container splits.
|
||||
func FocusedColor(color cell.Color) Option {
|
||||
return option(func(c *Container) error {
|
||||
c.opts.inherited.focusedColor = color
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// splitType identifies how a container is split.
|
||||
type splitType int
|
||||
|
||||
// String implements fmt.Stringer()
|
||||
func (st splitType) String() string {
|
||||
if n, ok := splitTypeNames[st]; ok {
|
||||
return n
|
||||
}
|
||||
return "splitTypeUnknown"
|
||||
}
|
||||
|
||||
// splitTypeNames maps splitType values to human readable names.
|
||||
var splitTypeNames = map[splitType]string{
|
||||
splitTypeVertical: "splitTypeVertical",
|
||||
splitTypeHorizontal: "splitTypeHorizontal",
|
||||
}
|
||||
|
||||
const (
|
||||
splitTypeVertical splitType = iota
|
||||
splitTypeHorizontal
|
||||
)
|
||||
|
||||
// LeftOption is used to provide options to the left sub container after a
|
||||
// vertical split of the parent.
|
||||
type LeftOption interface {
|
||||
// lOpts returns the options.
|
||||
lOpts() []Option
|
||||
}
|
||||
|
||||
// leftOption implements LeftOption.
|
||||
type leftOption func() []Option
|
||||
|
||||
// lOpts implements LeftOption.lOpts.
|
||||
func (lo leftOption) lOpts() []Option {
|
||||
if lo == nil {
|
||||
return nil
|
||||
}
|
||||
return lo()
|
||||
}
|
||||
|
||||
// Left applies options to the left sub container after a vertical split of the parent.
|
||||
func Left(opts ...Option) LeftOption {
|
||||
return leftOption(func() []Option {
|
||||
return opts
|
||||
})
|
||||
}
|
||||
|
||||
// RightOption is used to provide options to the right sub container after a
|
||||
// vertical split of the parent.
|
||||
type RightOption interface {
|
||||
// rOpts returns the options.
|
||||
rOpts() []Option
|
||||
}
|
||||
|
||||
// rightOption implements RightOption.
|
||||
type rightOption func() []Option
|
||||
|
||||
// rOpts implements RightOption.rOpts.
|
||||
func (lo rightOption) rOpts() []Option {
|
||||
if lo == nil {
|
||||
return nil
|
||||
}
|
||||
return lo()
|
||||
}
|
||||
|
||||
// Right applies options to the right sub container after a vertical split of the parent.
|
||||
func Right(opts ...Option) RightOption {
|
||||
return rightOption(func() []Option {
|
||||
return opts
|
||||
})
|
||||
}
|
||||
|
||||
// TopOption is used to provide options to the top sub container after a
|
||||
// horizontal split of the parent.
|
||||
type TopOption interface {
|
||||
// tOpts returns the options.
|
||||
tOpts() []Option
|
||||
}
|
||||
|
||||
// topOption implements TopOption.
|
||||
type topOption func() []Option
|
||||
|
||||
// tOpts implements TopOption.tOpts.
|
||||
func (lo topOption) tOpts() []Option {
|
||||
if lo == nil {
|
||||
return nil
|
||||
}
|
||||
return lo()
|
||||
}
|
||||
|
||||
// Top applies options to the top sub container after a horizontal split of the parent.
|
||||
func Top(opts ...Option) TopOption {
|
||||
return topOption(func() []Option {
|
||||
return opts
|
||||
})
|
||||
}
|
||||
|
||||
// BottomOption is used to provide options to the bottom sub container after a
|
||||
// horizontal split of the parent.
|
||||
type BottomOption interface {
|
||||
// bOpts returns the options.
|
||||
bOpts() []Option
|
||||
}
|
||||
|
||||
// bottomOption implements BottomOption.
|
||||
type bottomOption func() []Option
|
||||
|
||||
// bOpts implements BottomOption.bOpts.
|
||||
func (lo bottomOption) bOpts() []Option {
|
||||
if lo == nil {
|
||||
return nil
|
||||
}
|
||||
return lo()
|
||||
}
|
||||
|
||||
// Bottom applies options to the bottom sub container after a horizontal split of the parent.
|
||||
func Bottom(opts ...Option) BottomOption {
|
||||
return bottomOption(func() []Option {
|
||||
return opts
|
||||
})
|
||||
}
|
||||
86
examples/go-dashboard/vendor/github.com/mum4k/termdash/container/traversal.go
generated
vendored
86
examples/go-dashboard/vendor/github.com/mum4k/termdash/container/traversal.go
generated
vendored
@@ -1,86 +0,0 @@
|
||||
// Copyright 2018 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package container
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// traversal.go provides functions that navigate the container tree.
|
||||
|
||||
// rootCont returns the root container.
|
||||
func rootCont(c *Container) *Container {
|
||||
for p := c.parent; p != nil; p = c.parent {
|
||||
c = p
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// visitFunc is executed during traversals when node is visited.
|
||||
// If the visit function returns an error, the traversal terminates and the
|
||||
// errStr is set to the text of the returned error.
|
||||
type visitFunc func(*Container) error
|
||||
|
||||
// preOrder performs pre-order DFS traversal on the container tree.
|
||||
func preOrder(c *Container, errStr *string, visit visitFunc) {
|
||||
if c == nil || *errStr != "" {
|
||||
return
|
||||
}
|
||||
|
||||
if err := visit(c); err != nil {
|
||||
*errStr = err.Error()
|
||||
return
|
||||
}
|
||||
preOrder(c.first, errStr, visit)
|
||||
preOrder(c.second, errStr, visit)
|
||||
}
|
||||
|
||||
// postOrder performs post-order DFS traversal on the container tree.
|
||||
func postOrder(c *Container, errStr *string, visit visitFunc) {
|
||||
if c == nil || *errStr != "" {
|
||||
return
|
||||
}
|
||||
|
||||
postOrder(c.first, errStr, visit)
|
||||
postOrder(c.second, errStr, visit)
|
||||
if err := visit(c); err != nil {
|
||||
*errStr = err.Error()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// findID finds container with the provided ID.
|
||||
// Returns an error of there is no container with the specified ID.
|
||||
func findID(root *Container, id string) (*Container, error) {
|
||||
if id == "" {
|
||||
return nil, errors.New("the container ID must not be empty")
|
||||
}
|
||||
|
||||
var (
|
||||
errStr string
|
||||
cont *Container
|
||||
)
|
||||
preOrder(root, &errStr, visitFunc(func(c *Container) error {
|
||||
if c.opts.id == id {
|
||||
cont = c
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
if cont == nil {
|
||||
return nil, fmt.Errorf("cannot find container with ID %q", id)
|
||||
}
|
||||
return cont, nil
|
||||
}
|
||||
10
examples/go-dashboard/vendor/github.com/mum4k/termdash/go.mod
generated
vendored
10
examples/go-dashboard/vendor/github.com/mum4k/termdash/go.mod
generated
vendored
@@ -1,10 +0,0 @@
|
||||
module github.com/mum4k/termdash
|
||||
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/gdamore/tcell v1.3.0
|
||||
github.com/kylelemons/godebug v1.1.0
|
||||
github.com/mattn/go-runewidth v0.0.9
|
||||
github.com/nsf/termbox-go v0.0.0-20200204031403-4d2b513ad8be
|
||||
)
|
||||
172
examples/go-dashboard/vendor/github.com/mum4k/termdash/keyboard/keyboard.go
generated
vendored
172
examples/go-dashboard/vendor/github.com/mum4k/termdash/keyboard/keyboard.go
generated
vendored
@@ -1,172 +0,0 @@
|
||||
// Copyright 2018 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package keyboard defines well known keyboard keys and shortcuts.
|
||||
package keyboard
|
||||
|
||||
// Key represents a single button on the keyboard.
|
||||
// Printable characters are set to their ASCII/Unicode rune value.
|
||||
// Non-printable (control) characters are equal to one of the constants defined
|
||||
// below.
|
||||
type Key rune
|
||||
|
||||
// String implements fmt.Stringer()
|
||||
func (b Key) String() string {
|
||||
if n, ok := buttonNames[b]; ok {
|
||||
return n
|
||||
} else if b >= 0 {
|
||||
return string(b)
|
||||
}
|
||||
return "KeyUnknown"
|
||||
}
|
||||
|
||||
// buttonNames maps Key values to human readable names.
|
||||
var buttonNames = map[Key]string{
|
||||
KeyF1: "KeyF1",
|
||||
KeyF2: "KeyF2",
|
||||
KeyF3: "KeyF3",
|
||||
KeyF4: "KeyF4",
|
||||
KeyF5: "KeyF5",
|
||||
KeyF6: "KeyF6",
|
||||
KeyF7: "KeyF7",
|
||||
KeyF8: "KeyF8",
|
||||
KeyF9: "KeyF9",
|
||||
KeyF10: "KeyF10",
|
||||
KeyF11: "KeyF11",
|
||||
KeyF12: "KeyF12",
|
||||
KeyInsert: "KeyInsert",
|
||||
KeyDelete: "KeyDelete",
|
||||
KeyHome: "KeyHome",
|
||||
KeyEnd: "KeyEnd",
|
||||
KeyPgUp: "KeyPgUp",
|
||||
KeyPgDn: "KeyPgDn",
|
||||
KeyArrowUp: "KeyArrowUp",
|
||||
KeyArrowDown: "KeyArrowDown",
|
||||
KeyArrowLeft: "KeyArrowLeft",
|
||||
KeyArrowRight: "KeyArrowRight",
|
||||
KeyCtrlTilde: "KeyCtrlTilde",
|
||||
KeyCtrlA: "KeyCtrlA",
|
||||
KeyCtrlB: "KeyCtrlB",
|
||||
KeyCtrlC: "KeyCtrlC",
|
||||
KeyCtrlD: "KeyCtrlD",
|
||||
KeyCtrlE: "KeyCtrlE",
|
||||
KeyCtrlF: "KeyCtrlF",
|
||||
KeyCtrlG: "KeyCtrlG",
|
||||
KeyBackspace: "KeyBackspace",
|
||||
KeyTab: "KeyTab",
|
||||
KeyCtrlJ: "KeyCtrlJ",
|
||||
KeyCtrlK: "KeyCtrlK",
|
||||
KeyCtrlL: "KeyCtrlL",
|
||||
KeyEnter: "KeyEnter",
|
||||
KeyCtrlN: "KeyCtrlN",
|
||||
KeyCtrlO: "KeyCtrlO",
|
||||
KeyCtrlP: "KeyCtrlP",
|
||||
KeyCtrlQ: "KeyCtrlQ",
|
||||
KeyCtrlR: "KeyCtrlR",
|
||||
KeyCtrlS: "KeyCtrlS",
|
||||
KeyCtrlT: "KeyCtrlT",
|
||||
KeyCtrlU: "KeyCtrlU",
|
||||
KeyCtrlV: "KeyCtrlV",
|
||||
KeyCtrlW: "KeyCtrlW",
|
||||
KeyCtrlX: "KeyCtrlX",
|
||||
KeyCtrlY: "KeyCtrlY",
|
||||
KeyCtrlZ: "KeyCtrlZ",
|
||||
KeyEsc: "KeyEsc",
|
||||
KeyCtrl4: "KeyCtrl4",
|
||||
KeyCtrl5: "KeyCtrl5",
|
||||
KeyCtrl6: "KeyCtrl6",
|
||||
KeyCtrl7: "KeyCtrl7",
|
||||
KeySpace: "KeySpace",
|
||||
KeyBackspace2: "KeyBackspace2",
|
||||
}
|
||||
|
||||
// Printable characters, but worth having constants for them.
|
||||
const (
|
||||
KeySpace = ' '
|
||||
)
|
||||
|
||||
// Negative values for non-printable characters.
|
||||
const (
|
||||
KeyF1 Key = -(iota + 1)
|
||||
KeyF2
|
||||
KeyF3
|
||||
KeyF4
|
||||
KeyF5
|
||||
KeyF6
|
||||
KeyF7
|
||||
KeyF8
|
||||
KeyF9
|
||||
KeyF10
|
||||
KeyF11
|
||||
KeyF12
|
||||
KeyInsert
|
||||
KeyDelete
|
||||
KeyHome
|
||||
KeyEnd
|
||||
KeyPgUp
|
||||
KeyPgDn
|
||||
KeyArrowUp
|
||||
KeyArrowDown
|
||||
KeyArrowLeft
|
||||
KeyArrowRight
|
||||
KeyCtrlTilde
|
||||
KeyCtrlA
|
||||
KeyCtrlB
|
||||
KeyCtrlC
|
||||
KeyCtrlD
|
||||
KeyCtrlE
|
||||
KeyCtrlF
|
||||
KeyCtrlG
|
||||
KeyBackspace
|
||||
KeyTab
|
||||
KeyCtrlJ
|
||||
KeyCtrlK
|
||||
KeyCtrlL
|
||||
KeyEnter
|
||||
KeyCtrlN
|
||||
KeyCtrlO
|
||||
KeyCtrlP
|
||||
KeyCtrlQ
|
||||
KeyCtrlR
|
||||
KeyCtrlS
|
||||
KeyCtrlT
|
||||
KeyCtrlU
|
||||
KeyCtrlV
|
||||
KeyCtrlW
|
||||
KeyCtrlX
|
||||
KeyCtrlY
|
||||
KeyCtrlZ
|
||||
KeyEsc
|
||||
KeyCtrl4
|
||||
KeyCtrl5
|
||||
KeyCtrl6
|
||||
KeyCtrl7
|
||||
KeyBackspace2
|
||||
)
|
||||
|
||||
// Keys declared as duplicates by termbox.
|
||||
const (
|
||||
KeyCtrl2 Key = KeyCtrlTilde
|
||||
KeyCtrlSpace Key = KeyCtrlTilde
|
||||
KeyCtrlH Key = KeyBackspace
|
||||
KeyCtrlI Key = KeyTab
|
||||
KeyCtrlM Key = KeyEnter
|
||||
KeyCtrlLsqBracket Key = KeyEsc
|
||||
KeyCtrl3 Key = KeyEsc
|
||||
KeyCtrlBackslash Key = KeyCtrl4
|
||||
KeyCtrlRsqBracket Key = KeyCtrl5
|
||||
KeyCtrlSlash Key = KeyCtrl7
|
||||
KeyCtrlUnderscore Key = KeyCtrl7
|
||||
KeyCtrl8 Key = KeyBackspace2
|
||||
)
|
||||
51
examples/go-dashboard/vendor/github.com/mum4k/termdash/linestyle/linestyle.go
generated
vendored
51
examples/go-dashboard/vendor/github.com/mum4k/termdash/linestyle/linestyle.go
generated
vendored
@@ -1,51 +0,0 @@
|
||||
// Copyright 2019 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package linestyle defines various line styles.
|
||||
package linestyle
|
||||
|
||||
// LineStyle defines the supported line styles.
|
||||
type LineStyle int
|
||||
|
||||
// String implements fmt.Stringer()
|
||||
func (ls LineStyle) String() string {
|
||||
if n, ok := lineStyleNames[ls]; ok {
|
||||
return n
|
||||
}
|
||||
return "LineStyleUnknown"
|
||||
}
|
||||
|
||||
// lineStyleNames maps LineStyle values to human readable names.
|
||||
var lineStyleNames = map[LineStyle]string{
|
||||
None: "LineStyleNone",
|
||||
Light: "LineStyleLight",
|
||||
Double: "LineStyleDouble",
|
||||
Round: "LineStyleRound",
|
||||
}
|
||||
|
||||
// Supported line styles.
|
||||
// See https://en.wikipedia.org/wiki/Box-drawing_character.
|
||||
const (
|
||||
// None indicates that no line should be present.
|
||||
None LineStyle = iota
|
||||
|
||||
// Light is line style using the '─' characters.
|
||||
Light
|
||||
|
||||
// Double is line style using the '═' characters.
|
||||
Double
|
||||
|
||||
// Round is line style using the rounded corners '╭' characters.
|
||||
Round
|
||||
)
|
||||
48
examples/go-dashboard/vendor/github.com/mum4k/termdash/mouse/mouse.go
generated
vendored
48
examples/go-dashboard/vendor/github.com/mum4k/termdash/mouse/mouse.go
generated
vendored
@@ -1,48 +0,0 @@
|
||||
// Copyright 2018 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package mouse defines known mouse buttons.
|
||||
package mouse
|
||||
|
||||
// Button represents a mouse button.
|
||||
type Button int
|
||||
|
||||
// String implements fmt.Stringer()
|
||||
func (b Button) String() string {
|
||||
if n, ok := buttonNames[b]; ok {
|
||||
return n
|
||||
}
|
||||
return "ButtonUnknown"
|
||||
}
|
||||
|
||||
// buttonNames maps Button values to human readable names.
|
||||
var buttonNames = map[Button]string{
|
||||
ButtonLeft: "ButtonLeft",
|
||||
ButtonRight: "ButtonRight",
|
||||
ButtonMiddle: "ButtonMiddle",
|
||||
ButtonRelease: "ButtonRelease",
|
||||
ButtonWheelUp: "ButtonWheelUp",
|
||||
ButtonWheelDown: "ButtonWheelDown",
|
||||
}
|
||||
|
||||
// Buttons recognized on the mouse.
|
||||
const (
|
||||
buttonUnknown Button = iota
|
||||
ButtonLeft
|
||||
ButtonRight
|
||||
ButtonMiddle
|
||||
ButtonRelease
|
||||
ButtonWheelUp
|
||||
ButtonWheelDown
|
||||
)
|
||||
128
examples/go-dashboard/vendor/github.com/mum4k/termdash/private/alignfor/alignfor.go
generated
vendored
128
examples/go-dashboard/vendor/github.com/mum4k/termdash/private/alignfor/alignfor.go
generated
vendored
@@ -1,128 +0,0 @@
|
||||
// Copyright 2018 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package alignfor provides functions that align elements.
|
||||
package alignfor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"strings"
|
||||
|
||||
"github.com/mum4k/termdash/align"
|
||||
"github.com/mum4k/termdash/private/runewidth"
|
||||
"github.com/mum4k/termdash/private/wrap"
|
||||
)
|
||||
|
||||
// hAlign aligns the given area in the rectangle horizontally.
|
||||
func hAlign(rect image.Rectangle, ar image.Rectangle, h align.Horizontal) (image.Rectangle, error) {
|
||||
gap := rect.Dx() - ar.Dx()
|
||||
switch h {
|
||||
case align.HorizontalRight:
|
||||
// Use gap from above.
|
||||
case align.HorizontalCenter:
|
||||
gap /= 2
|
||||
case align.HorizontalLeft:
|
||||
gap = 0
|
||||
default:
|
||||
return image.ZR, fmt.Errorf("unsupported horizontal alignment %v", h)
|
||||
}
|
||||
|
||||
return image.Rect(
|
||||
rect.Min.X+gap,
|
||||
ar.Min.Y,
|
||||
rect.Min.X+gap+ar.Dx(),
|
||||
ar.Max.Y,
|
||||
), nil
|
||||
}
|
||||
|
||||
// vAlign aligns the given area in the rectangle vertically.
|
||||
func vAlign(rect image.Rectangle, ar image.Rectangle, v align.Vertical) (image.Rectangle, error) {
|
||||
gap := rect.Dy() - ar.Dy()
|
||||
switch v {
|
||||
case align.VerticalBottom:
|
||||
// Use gap from above.
|
||||
case align.VerticalMiddle:
|
||||
gap /= 2
|
||||
case align.VerticalTop:
|
||||
gap = 0
|
||||
default:
|
||||
return image.ZR, fmt.Errorf("unsupported vertical alignment %v", v)
|
||||
}
|
||||
|
||||
return image.Rect(
|
||||
ar.Min.X,
|
||||
rect.Min.Y+gap,
|
||||
ar.Max.X,
|
||||
rect.Min.Y+gap+ar.Dy(),
|
||||
), nil
|
||||
}
|
||||
|
||||
// Rectangle aligns the area within the rectangle returning the
|
||||
// aligned area. The area must fall within the rectangle.
|
||||
func Rectangle(rect image.Rectangle, ar image.Rectangle, h align.Horizontal, v align.Vertical) (image.Rectangle, error) {
|
||||
if !ar.In(rect) {
|
||||
return image.ZR, fmt.Errorf("cannot align area %v inside rectangle %v, the area falls outside of the rectangle", ar, rect)
|
||||
}
|
||||
|
||||
aligned, err := hAlign(rect, ar, h)
|
||||
if err != nil {
|
||||
return image.ZR, err
|
||||
}
|
||||
aligned, err = vAlign(rect, aligned, v)
|
||||
if err != nil {
|
||||
return image.ZR, err
|
||||
}
|
||||
return aligned, nil
|
||||
}
|
||||
|
||||
// Text aligns the text within the given rectangle, returns the start point for the text.
|
||||
// For the purposes of the alignment this assumes that text will be trimmed if
|
||||
// it overruns the rectangle.
|
||||
// This only supports a single line of text, the text must not contain non-printable characters,
|
||||
// allows empty text.
|
||||
func Text(rect image.Rectangle, text string, h align.Horizontal, v align.Vertical) (image.Point, error) {
|
||||
if strings.ContainsRune(text, '\n') {
|
||||
return image.ZP, fmt.Errorf("the provided text contains a newline character: %q", text)
|
||||
}
|
||||
|
||||
if text != "" {
|
||||
if err := wrap.ValidText(text); err != nil {
|
||||
return image.ZP, fmt.Errorf("the provided text contains non printable character(s): %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
cells := runewidth.StringWidth(text)
|
||||
var textLen int
|
||||
if cells < rect.Dx() {
|
||||
textLen = cells
|
||||
} else {
|
||||
textLen = rect.Dx()
|
||||
}
|
||||
|
||||
textRect := image.Rect(
|
||||
rect.Min.X,
|
||||
rect.Min.Y,
|
||||
// For the purposes of aligning the text, assume that it will be
|
||||
// trimmed to the available space.
|
||||
rect.Min.X+textLen,
|
||||
rect.Min.Y+1,
|
||||
)
|
||||
|
||||
aligned, err := Rectangle(rect, textRect, h, v)
|
||||
if err != nil {
|
||||
return image.ZP, err
|
||||
}
|
||||
return image.Point{aligned.Min.X, aligned.Min.Y}, nil
|
||||
}
|
||||
258
examples/go-dashboard/vendor/github.com/mum4k/termdash/private/area/area.go
generated
vendored
258
examples/go-dashboard/vendor/github.com/mum4k/termdash/private/area/area.go
generated
vendored
@@ -1,258 +0,0 @@
|
||||
// Copyright 2018 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package area provides functions working with image areas.
|
||||
package area
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
|
||||
"github.com/mum4k/termdash/private/numbers"
|
||||
)
|
||||
|
||||
// Size returns the size of the provided area.
|
||||
func Size(area image.Rectangle) image.Point {
|
||||
return image.Point{
|
||||
area.Dx(),
|
||||
area.Dy(),
|
||||
}
|
||||
}
|
||||
|
||||
// FromSize returns the corresponding area for the provided size.
|
||||
func FromSize(size image.Point) (image.Rectangle, error) {
|
||||
if size.X < 0 || size.Y < 0 {
|
||||
return image.Rectangle{}, fmt.Errorf("cannot convert zero or negative size to an area, got: %+v", size)
|
||||
}
|
||||
return image.Rect(0, 0, size.X, size.Y), nil
|
||||
}
|
||||
|
||||
// HSplit returns two new areas created by splitting the provided area at the
|
||||
// specified percentage of its width. The percentage must be in the range
|
||||
// 0 <= heightPerc <= 100.
|
||||
// Can return zero size areas.
|
||||
func HSplit(area image.Rectangle, heightPerc int) (top image.Rectangle, bottom image.Rectangle, err error) {
|
||||
if min, max := 0, 100; heightPerc < min || heightPerc > max {
|
||||
return image.ZR, image.ZR, fmt.Errorf("invalid heightPerc %d, must be in range %d <= heightPerc <= %d", heightPerc, min, max)
|
||||
}
|
||||
height := area.Dy() * heightPerc / 100
|
||||
top = image.Rect(area.Min.X, area.Min.Y, area.Max.X, area.Min.Y+height)
|
||||
if top.Dy() == 0 {
|
||||
top = image.ZR
|
||||
}
|
||||
bottom = image.Rect(area.Min.X, area.Min.Y+height, area.Max.X, area.Max.Y)
|
||||
if bottom.Dy() == 0 {
|
||||
bottom = image.ZR
|
||||
}
|
||||
return top, bottom, nil
|
||||
}
|
||||
|
||||
// VSplit returns two new areas created by splitting the provided area at the
|
||||
// specified percentage of its width. The percentage must be in the range
|
||||
// 0 <= widthPerc <= 100.
|
||||
// Can return zero size areas.
|
||||
func VSplit(area image.Rectangle, widthPerc int) (left image.Rectangle, right image.Rectangle, err error) {
|
||||
if min, max := 0, 100; widthPerc < min || widthPerc > max {
|
||||
return image.ZR, image.ZR, fmt.Errorf("invalid widthPerc %d, must be in range %d <= widthPerc <= %d", widthPerc, min, max)
|
||||
}
|
||||
width := area.Dx() * widthPerc / 100
|
||||
left = image.Rect(area.Min.X, area.Min.Y, area.Min.X+width, area.Max.Y)
|
||||
if left.Dx() == 0 {
|
||||
left = image.ZR
|
||||
}
|
||||
right = image.Rect(area.Min.X+width, area.Min.Y, area.Max.X, area.Max.Y)
|
||||
if right.Dx() == 0 {
|
||||
right = image.ZR
|
||||
}
|
||||
return left, right, nil
|
||||
}
|
||||
|
||||
// VSplitCells returns two new areas created by splitting the provided area
|
||||
// after the specified amount of cells of its width. The number of cells must
|
||||
// be a zero or a positive integer. Providing a zero returns left=image.ZR,
|
||||
// right=area. Providing a number equal or larger to area's width returns
|
||||
// left=area, right=image.ZR.
|
||||
func VSplitCells(area image.Rectangle, cells int) (left image.Rectangle, right image.Rectangle, err error) {
|
||||
if min := 0; cells < min {
|
||||
return image.ZR, image.ZR, fmt.Errorf("invalid cells %d, must be a positive integer", cells)
|
||||
}
|
||||
if cells == 0 {
|
||||
return image.ZR, area, nil
|
||||
}
|
||||
|
||||
width := area.Dx()
|
||||
if cells >= width {
|
||||
return area, image.ZR, nil
|
||||
}
|
||||
|
||||
left = image.Rect(area.Min.X, area.Min.Y, area.Min.X+cells, area.Max.Y)
|
||||
right = image.Rect(area.Min.X+cells, area.Min.Y, area.Max.X, area.Max.Y)
|
||||
return left, right, nil
|
||||
}
|
||||
|
||||
// HSplitCells returns two new areas created by splitting the provided area
|
||||
// after the specified amount of cells of its height. The number of cells must
|
||||
// be a zero or a positive integer. Providing a zero returns top=image.ZR,
|
||||
// bottom=area. Providing a number equal or larger to area's height returns
|
||||
// top=area, bottom=image.ZR.
|
||||
func HSplitCells(area image.Rectangle, cells int) (top image.Rectangle, bottom image.Rectangle, err error) {
|
||||
if min := 0; cells < min {
|
||||
return image.ZR, image.ZR, fmt.Errorf("invalid cells %d, must be a positive integer", cells)
|
||||
}
|
||||
if cells == 0 {
|
||||
return image.ZR, area, nil
|
||||
}
|
||||
|
||||
height := area.Dy()
|
||||
if cells >= height {
|
||||
return area, image.ZR, nil
|
||||
}
|
||||
|
||||
top = image.Rect(area.Min.X, area.Min.Y, area.Max.X, area.Min.Y+cells)
|
||||
bottom = image.Rect(area.Min.X, area.Min.Y+cells, area.Max.X, area.Max.Y)
|
||||
return top, bottom, nil
|
||||
}
|
||||
|
||||
// ExcludeBorder returns a new area created by subtracting a border around the
|
||||
// provided area. Return the zero area if there isn't enough space to exclude
|
||||
// the border.
|
||||
func ExcludeBorder(area image.Rectangle) image.Rectangle {
|
||||
// If the area dimensions are smaller than this, subtracting a point for the
|
||||
// border on each of its sides results in a zero area.
|
||||
const minDim = 2
|
||||
if area.Dx() < minDim || area.Dy() < minDim {
|
||||
return image.ZR
|
||||
}
|
||||
return image.Rect(
|
||||
numbers.Abs(area.Min.X+1),
|
||||
numbers.Abs(area.Min.Y+1),
|
||||
numbers.Abs(area.Max.X-1),
|
||||
numbers.Abs(area.Max.Y-1),
|
||||
)
|
||||
}
|
||||
|
||||
// WithRatio returns the largest area that has the requested ratio but is
|
||||
// either equal or smaller than the provided area. Returns zero area if the
|
||||
// area or the ratio are zero, or if there is no such area.
|
||||
func WithRatio(area image.Rectangle, ratio image.Point) image.Rectangle {
|
||||
ratio = numbers.SimplifyRatio(ratio)
|
||||
if area == image.ZR || ratio == image.ZP {
|
||||
return image.ZR
|
||||
}
|
||||
|
||||
wFact := area.Dx() / ratio.X
|
||||
hFact := area.Dy() / ratio.Y
|
||||
|
||||
var fact int
|
||||
if wFact < hFact {
|
||||
fact = wFact
|
||||
} else {
|
||||
fact = hFact
|
||||
}
|
||||
return image.Rect(
|
||||
area.Min.X,
|
||||
area.Min.Y,
|
||||
ratio.X*fact+area.Min.X,
|
||||
ratio.Y*fact+area.Min.Y,
|
||||
)
|
||||
}
|
||||
|
||||
// Shrink returns a new area whose size is reduced by the specified amount of
|
||||
// cells. Can return a zero area if there is no space left in the area.
|
||||
// The values must be zero or positive integers.
|
||||
func Shrink(area image.Rectangle, topCells, rightCells, bottomCells, leftCells int) (image.Rectangle, error) {
|
||||
for _, v := range []struct {
|
||||
name string
|
||||
value int
|
||||
}{
|
||||
{"topCells", topCells},
|
||||
{"rightCells", rightCells},
|
||||
{"bottomCells", bottomCells},
|
||||
{"leftCells", leftCells},
|
||||
} {
|
||||
if min := 0; v.value < min {
|
||||
return image.ZR, fmt.Errorf("invalid %s(%d), must be in range %d <= value", v.name, v.value, min)
|
||||
}
|
||||
}
|
||||
|
||||
shrunk := area
|
||||
shrunk.Min.X, _ = numbers.MinMaxInts([]int{shrunk.Min.X + leftCells, shrunk.Max.X})
|
||||
_, shrunk.Max.X = numbers.MinMaxInts([]int{shrunk.Max.X - rightCells, shrunk.Min.X})
|
||||
shrunk.Min.Y, _ = numbers.MinMaxInts([]int{shrunk.Min.Y + topCells, shrunk.Max.Y})
|
||||
_, shrunk.Max.Y = numbers.MinMaxInts([]int{shrunk.Max.Y - bottomCells, shrunk.Min.Y})
|
||||
|
||||
if shrunk.Dx() == 0 || shrunk.Dy() == 0 {
|
||||
return image.ZR, nil
|
||||
}
|
||||
return shrunk, nil
|
||||
}
|
||||
|
||||
// ShrinkPercent returns a new area whose size is reduced by percentage of its
|
||||
// width or height. Can return a zero area if there is no space left in the area.
|
||||
// The topPerc and bottomPerc indicate the percentage of area's height.
|
||||
// The rightPerc and leftPerc indicate the percentage of area's width.
|
||||
// The percentages must be in range 0 <= v <= 100.
|
||||
func ShrinkPercent(area image.Rectangle, topPerc, rightPerc, bottomPerc, leftPerc int) (image.Rectangle, error) {
|
||||
for _, v := range []struct {
|
||||
name string
|
||||
value int
|
||||
}{
|
||||
{"topPerc", topPerc},
|
||||
{"rightPerc", rightPerc},
|
||||
{"bottomPerc", bottomPerc},
|
||||
{"leftPerc", leftPerc},
|
||||
} {
|
||||
if min, max := 0, 100; v.value < min || v.value > max {
|
||||
return image.ZR, fmt.Errorf("invalid %s(%d), must be in range %d <= value <= %d", v.name, v.value, min, max)
|
||||
}
|
||||
}
|
||||
|
||||
top := area.Dy() * topPerc / 100
|
||||
bottom := area.Dy() * bottomPerc / 100
|
||||
right := area.Dx() * rightPerc / 100
|
||||
left := area.Dx() * leftPerc / 100
|
||||
return Shrink(area, top, right, bottom, left)
|
||||
}
|
||||
|
||||
// MoveUp returns a new area that is moved up by the specified amount of cells.
|
||||
// Returns an error if the move would result in negative Y coordinates.
|
||||
// The values must be zero or positive integers.
|
||||
func MoveUp(area image.Rectangle, cells int) (image.Rectangle, error) {
|
||||
if min := 0; cells < min {
|
||||
return image.ZR, fmt.Errorf("cannot move area %v up by %d cells, must be in range %d <= value", area, cells, min)
|
||||
}
|
||||
|
||||
if area.Min.Y < cells {
|
||||
return image.ZR, fmt.Errorf("cannot move area %v up by %d cells, would result in negative Y coordinate", area, cells)
|
||||
}
|
||||
|
||||
moved := area
|
||||
moved.Min.Y -= cells
|
||||
moved.Max.Y -= cells
|
||||
return moved, nil
|
||||
}
|
||||
|
||||
// MoveDown returns a new area that is moved down by the specified amount of
|
||||
// cells.
|
||||
// The values must be zero or positive integers.
|
||||
func MoveDown(area image.Rectangle, cells int) (image.Rectangle, error) {
|
||||
if min := 0; cells < min {
|
||||
return image.ZR, fmt.Errorf("cannot move area %v down by %d cells, must be in range %d <= value", area, cells, min)
|
||||
}
|
||||
|
||||
moved := area
|
||||
moved.Min.Y += cells
|
||||
moved.Max.Y += cells
|
||||
return moved, nil
|
||||
}
|
||||
135
examples/go-dashboard/vendor/github.com/mum4k/termdash/private/button/button.go
generated
vendored
135
examples/go-dashboard/vendor/github.com/mum4k/termdash/private/button/button.go
generated
vendored
@@ -1,135 +0,0 @@
|
||||
// Copyright 2019 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package button implements a state machine that tracks mouse button clicks.
|
||||
package button
|
||||
|
||||
import (
|
||||
"image"
|
||||
|
||||
"github.com/mum4k/termdash/mouse"
|
||||
"github.com/mum4k/termdash/terminal/terminalapi"
|
||||
)
|
||||
|
||||
// State represents the state of the mouse button.
|
||||
type State int
|
||||
|
||||
// String implements fmt.Stringer()
|
||||
func (s State) String() string {
|
||||
if n, ok := stateNames[s]; ok {
|
||||
return n
|
||||
}
|
||||
return "StateUnknown"
|
||||
}
|
||||
|
||||
// stateNames maps State values to human readable names.
|
||||
var stateNames = map[State]string{
|
||||
Up: "StateUp",
|
||||
Down: "StateDown",
|
||||
}
|
||||
|
||||
const (
|
||||
// Up is the default idle state of the mouse button.
|
||||
Up State = iota
|
||||
|
||||
// Down is a state where the mouse button is pressed down and held.
|
||||
Down
|
||||
)
|
||||
|
||||
// FSM implements a finite-state machine that tracks mouse clicks within an
|
||||
// area.
|
||||
//
|
||||
// Simplifies tracking of mouse button clicks, i.e. when the caller wants to
|
||||
// perform an action only if both the button press and release happen within
|
||||
// the specified area.
|
||||
//
|
||||
// This object is not thread-safe.
|
||||
type FSM struct {
|
||||
// button is the mouse button whose state this FSM tracks.
|
||||
button mouse.Button
|
||||
|
||||
// area is the area provided to NewFSM.
|
||||
area image.Rectangle
|
||||
|
||||
// state is the current state of the FSM.
|
||||
state stateFn
|
||||
}
|
||||
|
||||
// NewFSM creates a new FSM instance that tracks the state of the specified
|
||||
// mouse button through button events that fall within the provided area.
|
||||
func NewFSM(button mouse.Button, area image.Rectangle) *FSM {
|
||||
return &FSM{
|
||||
button: button,
|
||||
area: area,
|
||||
state: wantPress,
|
||||
}
|
||||
}
|
||||
|
||||
// Event is used to forward mouse events to the state machine.
|
||||
// Only events related to the button specified on a call to NewFSM are
|
||||
// processed.
|
||||
//
|
||||
// Returns a bool indicating if an action guarded by the button should be
|
||||
// performed and the state of the button after the provided event.
|
||||
// The bool is true if the button click should take an effect, i.e. if the
|
||||
// FSM saw both the button click and its release.
|
||||
func (fsm *FSM) Event(m *terminalapi.Mouse) (bool, State) {
|
||||
clicked, bs, next := fsm.state(fsm, m)
|
||||
fsm.state = next
|
||||
return clicked, bs
|
||||
}
|
||||
|
||||
// UpdateArea informs FSM of an area change.
|
||||
// This method is idempotent.
|
||||
func (fsm *FSM) UpdateArea(area image.Rectangle) {
|
||||
fsm.area = area
|
||||
}
|
||||
|
||||
// stateFn is a single state in the state machine.
|
||||
// Returns bool indicating if a click happened, the state of the button and the
|
||||
// next state of the FSM.
|
||||
type stateFn func(fsm *FSM, m *terminalapi.Mouse) (bool, State, stateFn)
|
||||
|
||||
// wantPress is the initial state, expecting a button press inside the area.
|
||||
func wantPress(fsm *FSM, m *terminalapi.Mouse) (bool, State, stateFn) {
|
||||
if m.Button != fsm.button || !m.Position.In(fsm.area) {
|
||||
return false, Up, wantPress
|
||||
}
|
||||
return false, Down, wantRelease
|
||||
}
|
||||
|
||||
// wantRelease waits for a mouse button release in the same area as
|
||||
// the press.
|
||||
func wantRelease(fsm *FSM, m *terminalapi.Mouse) (bool, State, stateFn) {
|
||||
switch m.Button {
|
||||
case fsm.button:
|
||||
if m.Position.In(fsm.area) {
|
||||
// Remain in the same state, since termbox reports move of mouse with
|
||||
// button held down as a series of clicks, one per position.
|
||||
return false, Down, wantRelease
|
||||
}
|
||||
return false, Up, wantPress
|
||||
|
||||
case mouse.ButtonRelease:
|
||||
if m.Position.In(fsm.area) {
|
||||
// Seen both press and release, report a click.
|
||||
return true, Up, wantPress
|
||||
}
|
||||
// Release the button even if the release event happened outside of the area.
|
||||
return false, Up, wantPress
|
||||
|
||||
default:
|
||||
return false, Up, wantPress
|
||||
}
|
||||
}
|
||||
284
examples/go-dashboard/vendor/github.com/mum4k/termdash/private/canvas/braille/braille.go
generated
vendored
284
examples/go-dashboard/vendor/github.com/mum4k/termdash/private/canvas/braille/braille.go
generated
vendored
@@ -1,284 +0,0 @@
|
||||
// Copyright 2018 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/*
|
||||
Package braille provides a canvas that uses braille characters.
|
||||
|
||||
This is inspired by https://github.com/asciimoo/drawille.
|
||||
|
||||
The braille patterns documentation:
|
||||
http://www.alanwood.net/unicode/braille_patterns.html
|
||||
|
||||
The use of braille characters gives additional points (higher resolution) on
|
||||
the canvas, each character cell now has eight pixels that can be set
|
||||
independently. Specifically each cell has the following pixels, the axes grow
|
||||
right and down.
|
||||
|
||||
Each cell:
|
||||
|
||||
X→ 0 1 Y
|
||||
┌───┐ ↓
|
||||
│● ●│ 0
|
||||
│● ●│ 1
|
||||
│● ●│ 2
|
||||
│● ●│ 3
|
||||
└───┘
|
||||
|
||||
When using the braille canvas, the coordinates address the sub-cell points
|
||||
rather then cells themselves. However all points in the cell still share the
|
||||
same cell options.
|
||||
*/
|
||||
package braille
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
|
||||
"github.com/mum4k/termdash/cell"
|
||||
"github.com/mum4k/termdash/private/canvas"
|
||||
"github.com/mum4k/termdash/terminal/terminalapi"
|
||||
)
|
||||
|
||||
const (
|
||||
// ColMult is the resolution multiplier for the width, i.e. two pixels per cell.
|
||||
ColMult = 2
|
||||
|
||||
// RowMult is the resolution multiplier for the height, i.e. four pixels per cell.
|
||||
RowMult = 4
|
||||
|
||||
// brailleCharOffset is the offset of the braille pattern unicode characters.
|
||||
// From: http://www.alanwood.net/unicode/braille_patterns.html
|
||||
brailleCharOffset = 0x2800
|
||||
|
||||
// brailleLastChar is the last braille pattern rune.
|
||||
brailleLastChar = 0x28FF
|
||||
)
|
||||
|
||||
// pixelRunes maps points addressing individual pixels in a cell into character
|
||||
// offset. I.e. the correct character to set pixel(0,0) is
|
||||
// brailleCharOffset|pixelRunes[image.Point{0,0}].
|
||||
var pixelRunes = map[image.Point]rune{
|
||||
{0, 0}: 0x01, {1, 0}: 0x08,
|
||||
{0, 1}: 0x02, {1, 1}: 0x10,
|
||||
{0, 2}: 0x04, {1, 2}: 0x20,
|
||||
{0, 3}: 0x40, {1, 3}: 0x80,
|
||||
}
|
||||
|
||||
// Canvas is a canvas that uses the braille patterns. It is two times wider
|
||||
// and four times taller than a regular canvas that uses just plain characters,
|
||||
// since each cell now has 2x4 pixels that can be independently set.
|
||||
//
|
||||
// The braille canvas is an abstraction built on top of a regular character
|
||||
// canvas. After setting and toggling pixels on the braille canvas, it should
|
||||
// be copied to a regular character canvas or applied to a terminal which
|
||||
// results in setting of braille pattern characters.
|
||||
// See the examples for more details.
|
||||
//
|
||||
// The created braille canvas can be smaller and even misaligned relatively to
|
||||
// the regular character canvas or terminal, allowing the callers to create a
|
||||
// "view" of just a portion of the canvas or terminal.
|
||||
type Canvas struct {
|
||||
// regular is the regular character canvas the braille canvas is based on.
|
||||
regular *canvas.Canvas
|
||||
}
|
||||
|
||||
// New returns a new braille canvas for the provided area.
|
||||
func New(ar image.Rectangle) (*Canvas, error) {
|
||||
rc, err := canvas.New(ar)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Canvas{
|
||||
regular: rc,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Size returns the size of the braille canvas in pixels.
|
||||
func (c *Canvas) Size() image.Point {
|
||||
s := c.regular.Size()
|
||||
return image.Point{s.X * ColMult, s.Y * RowMult}
|
||||
}
|
||||
|
||||
// CellArea returns the area of the underlying cell canvas in cells.
|
||||
func (c *Canvas) CellArea() image.Rectangle {
|
||||
return c.regular.Area()
|
||||
}
|
||||
|
||||
// Area returns the area of the braille canvas in pixels.
|
||||
// This will be zero-based area that is two times wider and four times taller
|
||||
// than the area used to create the braille canvas.
|
||||
func (c *Canvas) Area() image.Rectangle {
|
||||
ar := c.regular.Area()
|
||||
return image.Rect(0, 0, ar.Dx()*ColMult, ar.Dy()*RowMult)
|
||||
}
|
||||
|
||||
// Clear clears all the content on the canvas.
|
||||
func (c *Canvas) Clear() error {
|
||||
return c.regular.Clear()
|
||||
}
|
||||
|
||||
// SetPixel turns on pixel at the specified point.
|
||||
// The provided cell options will be applied to the entire cell (all of its
|
||||
// pixels). This method is idempotent.
|
||||
func (c *Canvas) SetPixel(p image.Point, opts ...cell.Option) error {
|
||||
cp, err := c.cellPoint(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cell, err := c.regular.Cell(cp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var r rune
|
||||
if isBraille(cell.Rune) {
|
||||
// If the cell already has a braille pattern rune, we will be adding
|
||||
// the pixel.
|
||||
r = cell.Rune
|
||||
} else {
|
||||
r = brailleCharOffset
|
||||
}
|
||||
|
||||
r |= pixelRunes[pixelPoint(p)]
|
||||
if _, err := c.regular.SetCell(cp, r, opts...); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClearPixel turns off pixel at the specified point.
|
||||
// The provided cell options will be applied to the entire cell (all of its
|
||||
// pixels). This method is idempotent.
|
||||
func (c *Canvas) ClearPixel(p image.Point, opts ...cell.Option) error {
|
||||
cp, err := c.cellPoint(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cell, err := c.regular.Cell(cp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Clear is idempotent.
|
||||
if !isBraille(cell.Rune) || !pixelSet(cell.Rune, p) {
|
||||
return nil
|
||||
}
|
||||
|
||||
r := cell.Rune & ^pixelRunes[pixelPoint(p)]
|
||||
if _, err := c.regular.SetCell(cp, r, opts...); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TogglePixel toggles the state of the pixel at the specified point, i.e. it
|
||||
// either sets or clear it depending on its current state.
|
||||
// The provided cell options will be applied to the entire cell (all of its
|
||||
// pixels).
|
||||
func (c *Canvas) TogglePixel(p image.Point, opts ...cell.Option) error {
|
||||
cp, err := c.cellPoint(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
curCell, err := c.regular.Cell(cp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if isBraille(curCell.Rune) && pixelSet(curCell.Rune, p) {
|
||||
return c.ClearPixel(p, opts...)
|
||||
}
|
||||
return c.SetPixel(p, opts...)
|
||||
}
|
||||
|
||||
// SetCellOpts sets options on the specified cell of the braille canvas without
|
||||
// modifying the content of the cell.
|
||||
// Sets the default cell options if no options are provided.
|
||||
// This method is idempotent.
|
||||
func (c *Canvas) SetCellOpts(cellPoint image.Point, opts ...cell.Option) error {
|
||||
curCell, err := c.regular.Cell(cellPoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(opts) == 0 {
|
||||
// Set the default options.
|
||||
opts = []cell.Option{
|
||||
cell.FgColor(cell.ColorDefault),
|
||||
cell.BgColor(cell.ColorDefault),
|
||||
}
|
||||
}
|
||||
if _, err := c.regular.SetCell(cellPoint, curCell.Rune, opts...); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetAreaCellOpts is like SetCellOpts, but sets the specified options on all
|
||||
// the cells within the provided area.
|
||||
func (c *Canvas) SetAreaCellOpts(cellArea image.Rectangle, opts ...cell.Option) error {
|
||||
haveArea := c.regular.Area()
|
||||
if !cellArea.In(haveArea) {
|
||||
return fmt.Errorf("unable to set cell options in area %v, it must fit inside the available cell area is %v", cellArea, haveArea)
|
||||
}
|
||||
for col := cellArea.Min.X; col < cellArea.Max.X; col++ {
|
||||
for row := cellArea.Min.Y; row < cellArea.Max.Y; row++ {
|
||||
if err := c.SetCellOpts(image.Point{col, row}, opts...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Apply applies the canvas to the corresponding area of the terminal.
|
||||
// Guarantees to stay within limits of the area the canvas was created with.
|
||||
func (c *Canvas) Apply(t terminalapi.Terminal) error {
|
||||
return c.regular.Apply(t)
|
||||
}
|
||||
|
||||
// CopyTo copies the content of this canvas onto the destination canvas.
|
||||
// This canvas can have an offset when compared to the destination canvas, i.e.
|
||||
// the area of this canvas doesn't have to be zero-based.
|
||||
func (c *Canvas) CopyTo(dst *canvas.Canvas) error {
|
||||
return c.regular.CopyTo(dst)
|
||||
}
|
||||
|
||||
// cellPoint determines the point (coordinate) of the character cell given
|
||||
// coordinates in pixels.
|
||||
func (c *Canvas) cellPoint(p image.Point) (image.Point, error) {
|
||||
if p.X < 0 || p.Y < 0 {
|
||||
return image.ZP, fmt.Errorf("pixels cannot have negative coordinates: %v", p)
|
||||
}
|
||||
cp := image.Point{p.X / ColMult, p.Y / RowMult}
|
||||
if ar := c.regular.Area(); !cp.In(ar) {
|
||||
return image.ZP, fmt.Errorf("pixel at%v would be in a character cell at%v which falls outside of the canvas area %v", p, cp, ar)
|
||||
}
|
||||
return cp, nil
|
||||
}
|
||||
|
||||
// isBraille determines if the rune is a braille pattern rune.
|
||||
func isBraille(r rune) bool {
|
||||
return r >= brailleCharOffset && r <= brailleLastChar
|
||||
}
|
||||
|
||||
// pixelSet returns true if the provided rune has the specified pixel set.
|
||||
func pixelSet(r rune, p image.Point) bool {
|
||||
return r&pixelRunes[pixelPoint(p)] > 0
|
||||
}
|
||||
|
||||
// pixelPoint translates point within canvas to point within the target cell.
|
||||
func pixelPoint(p image.Point) image.Point {
|
||||
return image.Point{p.X % ColMult, p.Y % RowMult}
|
||||
}
|
||||
188
examples/go-dashboard/vendor/github.com/mum4k/termdash/private/canvas/buffer/buffer.go
generated
vendored
188
examples/go-dashboard/vendor/github.com/mum4k/termdash/private/canvas/buffer/buffer.go
generated
vendored
@@ -1,188 +0,0 @@
|
||||
// Copyright 2018 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package buffer implements a 2-D buffer of cells.
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
|
||||
"github.com/mum4k/termdash/cell"
|
||||
"github.com/mum4k/termdash/private/area"
|
||||
"github.com/mum4k/termdash/private/runewidth"
|
||||
)
|
||||
|
||||
// NewCells breaks the provided text into cells and applies the options.
|
||||
func NewCells(text string, opts ...cell.Option) []*Cell {
|
||||
var res []*Cell
|
||||
for _, r := range text {
|
||||
res = append(res, NewCell(r, opts...))
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// Cell represents a single cell on the terminal.
|
||||
type Cell struct {
|
||||
// Rune is the rune stored in the cell.
|
||||
Rune rune
|
||||
|
||||
// Opts are the cell options.
|
||||
Opts *cell.Options
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer.
|
||||
func (c *Cell) String() string {
|
||||
return fmt.Sprintf("{%q}", c.Rune)
|
||||
}
|
||||
|
||||
// NewCell returns a new cell.
|
||||
func NewCell(r rune, opts ...cell.Option) *Cell {
|
||||
return &Cell{
|
||||
Rune: r,
|
||||
Opts: cell.NewOptions(opts...),
|
||||
}
|
||||
}
|
||||
|
||||
// Copy returns a copy the cell.
|
||||
func (c *Cell) Copy() *Cell {
|
||||
return &Cell{
|
||||
Rune: c.Rune,
|
||||
Opts: cell.NewOptions(c.Opts),
|
||||
}
|
||||
}
|
||||
|
||||
// Apply applies the provided options to the cell.
|
||||
func (c *Cell) Apply(opts ...cell.Option) {
|
||||
for _, opt := range opts {
|
||||
opt.Set(c.Opts)
|
||||
}
|
||||
}
|
||||
|
||||
// Buffer is a 2-D buffer of cells.
|
||||
// The axes increase right and down.
|
||||
// Uninitialized buffer is invalid, use New to create an instance.
|
||||
// Don't set cells directly, use the SetCell method instead which safely
|
||||
// handles limits and wide unicode characters.
|
||||
type Buffer [][]*Cell
|
||||
|
||||
// New returns a new Buffer of the provided size.
|
||||
func New(size image.Point) (Buffer, error) {
|
||||
if size.X <= 0 {
|
||||
return nil, fmt.Errorf("invalid buffer width (size.X): %d, must be a positive number", size.X)
|
||||
}
|
||||
if size.Y <= 0 {
|
||||
return nil, fmt.Errorf("invalid buffer height (size.Y): %d, must be a positive number", size.Y)
|
||||
}
|
||||
|
||||
b := make([][]*Cell, size.X)
|
||||
for col := range b {
|
||||
b[col] = make([]*Cell, size.Y)
|
||||
for row := range b[col] {
|
||||
b[col][row] = NewCell(0)
|
||||
}
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// SetCell sets the rune of the specified cell in the buffer. Returns the
|
||||
// number of cells the rune occupies, wide runes can occupy multiple cells when
|
||||
// printed on the terminal. See http://www.unicode.org/reports/tr11/.
|
||||
// Use the options to specify which attributes to modify, if an attribute
|
||||
// option isn't specified, the attribute retains its previous value.
|
||||
func (b Buffer) SetCell(p image.Point, r rune, opts ...cell.Option) (int, error) {
|
||||
partial, err := b.IsPartial(p)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
if partial {
|
||||
return -1, fmt.Errorf("cannot set rune %q at point %v, it is a partial cell occupied by a wide rune in the previous cell", r, p)
|
||||
}
|
||||
|
||||
remW, err := b.RemWidth(p)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
rw := runewidth.RuneWidth(r)
|
||||
if rw == 0 {
|
||||
// Even if the rune is invisible, like the zero-value rune, it still
|
||||
// occupies at least the target cell.
|
||||
rw = 1
|
||||
}
|
||||
if rw > remW {
|
||||
return -1, fmt.Errorf("cannot set rune %q of width %d at point %v, only have %d remaining cells at this line", r, rw, p, remW)
|
||||
}
|
||||
|
||||
c := b[p.X][p.Y]
|
||||
c.Rune = r
|
||||
c.Apply(opts...)
|
||||
return rw, nil
|
||||
}
|
||||
|
||||
// IsPartial returns true if the cell at the specified point holds a part of a
|
||||
// full width rune from a previous cell. See
|
||||
// http://www.unicode.org/reports/tr11/.
|
||||
func (b Buffer) IsPartial(p image.Point) (bool, error) {
|
||||
size := b.Size()
|
||||
ar, err := area.FromSize(size)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !p.In(ar) {
|
||||
return false, fmt.Errorf("point %v falls outside of the area %v occupied by the buffer", p, ar)
|
||||
}
|
||||
|
||||
if p.X == 0 && p.Y == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
prevP := image.Point{p.X - 1, p.Y}
|
||||
if prevP.X < 0 {
|
||||
prevP = image.Point{size.X - 1, p.Y - 1}
|
||||
}
|
||||
|
||||
prevR := b[prevP.X][prevP.Y].Rune
|
||||
switch rw := runewidth.RuneWidth(prevR); rw {
|
||||
case 0, 1:
|
||||
return false, nil
|
||||
case 2:
|
||||
return true, nil
|
||||
default:
|
||||
return false, fmt.Errorf("buffer cell %v contains rune %q which has an unsupported rune with %d", prevP, prevR, rw)
|
||||
}
|
||||
}
|
||||
|
||||
// RemWidth returns the remaining width (horizontal row of cells) available
|
||||
// from and inclusive of the specified point.
|
||||
func (b Buffer) RemWidth(p image.Point) (int, error) {
|
||||
size := b.Size()
|
||||
ar, err := area.FromSize(size)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
if !p.In(ar) {
|
||||
return -1, fmt.Errorf("point %v falls outside of the area %v occupied by the buffer", p, ar)
|
||||
}
|
||||
return size.X - p.X, nil
|
||||
}
|
||||
|
||||
// Size returns the size of the buffer.
|
||||
func (b Buffer) Size() image.Point {
|
||||
return image.Point{
|
||||
len(b),
|
||||
len(b[0]),
|
||||
}
|
||||
}
|
||||
247
examples/go-dashboard/vendor/github.com/mum4k/termdash/private/canvas/canvas.go
generated
vendored
247
examples/go-dashboard/vendor/github.com/mum4k/termdash/private/canvas/canvas.go
generated
vendored
@@ -1,247 +0,0 @@
|
||||
// Copyright 2018 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package canvas defines the canvas that the widgets draw on.
|
||||
package canvas
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
|
||||
"github.com/mum4k/termdash/cell"
|
||||
"github.com/mum4k/termdash/private/area"
|
||||
"github.com/mum4k/termdash/private/canvas/buffer"
|
||||
"github.com/mum4k/termdash/private/runewidth"
|
||||
"github.com/mum4k/termdash/terminal/terminalapi"
|
||||
)
|
||||
|
||||
// Canvas is where a widget draws its output for display on the terminal.
|
||||
type Canvas struct {
|
||||
// area is the area the buffer was created for.
|
||||
// Contains absolute coordinates on the target terminal, while the buffer
|
||||
// contains relative zero-based coordinates for this canvas.
|
||||
area image.Rectangle
|
||||
|
||||
// buffer is where the drawing happens.
|
||||
buffer buffer.Buffer
|
||||
}
|
||||
|
||||
// New returns a new Canvas with a buffer for the provided area.
|
||||
func New(ar image.Rectangle) (*Canvas, error) {
|
||||
if ar.Min.X < 0 || ar.Min.Y < 0 || ar.Max.X < 0 || ar.Max.Y < 0 {
|
||||
return nil, fmt.Errorf("area cannot start or end on the negative axis, got: %+v", ar)
|
||||
}
|
||||
|
||||
b, err := buffer.New(area.Size(ar))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Canvas{
|
||||
area: ar,
|
||||
buffer: b,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Size returns the size of the 2-D canvas.
|
||||
func (c *Canvas) Size() image.Point {
|
||||
return c.buffer.Size()
|
||||
}
|
||||
|
||||
// Area returns the area of the 2-D canvas.
|
||||
func (c *Canvas) Area() image.Rectangle {
|
||||
s := c.buffer.Size()
|
||||
return image.Rect(0, 0, s.X, s.Y)
|
||||
}
|
||||
|
||||
// Clear clears all the content on the canvas.
|
||||
func (c *Canvas) Clear() error {
|
||||
b, err := buffer.New(c.Size())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.buffer = b
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetCell sets the rune of the specified cell on the canvas. Returns the
|
||||
// number of cells the rune occupies, wide runes can occupy multiple cells when
|
||||
// printed on the terminal. See http://www.unicode.org/reports/tr11/.
|
||||
// Use the options to specify which attributes to modify, if an attribute
|
||||
// option isn't specified, the attribute retains its previous value.
|
||||
func (c *Canvas) SetCell(p image.Point, r rune, opts ...cell.Option) (int, error) {
|
||||
return c.buffer.SetCell(p, r, opts...)
|
||||
}
|
||||
|
||||
// Cell returns a copy of the specified cell.
|
||||
func (c *Canvas) Cell(p image.Point) (*buffer.Cell, error) {
|
||||
ar, err := area.FromSize(c.Size())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !p.In(ar) {
|
||||
return nil, fmt.Errorf("point %v falls outside of the area %v occupied by the canvas", p, ar)
|
||||
}
|
||||
|
||||
return c.buffer[p.X][p.Y].Copy(), nil
|
||||
}
|
||||
|
||||
// SetCellOpts sets options on the specified cell of the canvas without
|
||||
// modifying the content of the cell.
|
||||
// Sets the default cell options if no options are provided.
|
||||
// This method is idempotent.
|
||||
func (c *Canvas) SetCellOpts(p image.Point, opts ...cell.Option) error {
|
||||
curCell, err := c.Cell(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(opts) == 0 {
|
||||
// Set the default options.
|
||||
opts = []cell.Option{
|
||||
cell.FgColor(cell.ColorDefault),
|
||||
cell.BgColor(cell.ColorDefault),
|
||||
}
|
||||
}
|
||||
if _, err := c.SetCell(p, curCell.Rune, opts...); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetAreaCells is like SetCell, but sets the specified rune and options on all
|
||||
// the cells within the provided area.
|
||||
// This method is idempotent.
|
||||
func (c *Canvas) SetAreaCells(cellArea image.Rectangle, r rune, opts ...cell.Option) error {
|
||||
haveArea := c.Area()
|
||||
if !cellArea.In(haveArea) {
|
||||
return fmt.Errorf("unable to set cell runes in area %v, it must fit inside the available cell area is %v", cellArea, haveArea)
|
||||
}
|
||||
|
||||
rw := runewidth.RuneWidth(r)
|
||||
for row := cellArea.Min.Y; row < cellArea.Max.Y; row++ {
|
||||
for col := cellArea.Min.X; col < cellArea.Max.X; {
|
||||
p := image.Point{col, row}
|
||||
if col+rw > cellArea.Max.X {
|
||||
break
|
||||
}
|
||||
cells, err := c.SetCell(p, r, opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
col += cells
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetAreaCellOpts is like SetCellOpts, but sets the specified options on all
|
||||
// the cells within the provided area.
|
||||
func (c *Canvas) SetAreaCellOpts(cellArea image.Rectangle, opts ...cell.Option) error {
|
||||
haveArea := c.Area()
|
||||
if !cellArea.In(haveArea) {
|
||||
return fmt.Errorf("unable to set cell options in area %v, it must fit inside the available cell area is %v", cellArea, haveArea)
|
||||
}
|
||||
for col := cellArea.Min.X; col < cellArea.Max.X; col++ {
|
||||
for row := cellArea.Min.Y; row < cellArea.Max.Y; row++ {
|
||||
if err := c.SetCellOpts(image.Point{col, row}, opts...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// setCellFunc is a function that sets cell content on a terminal or a canvas.
|
||||
type setCellFunc func(image.Point, rune, ...cell.Option) error
|
||||
|
||||
// copyTo is the internal implementation of code that copies the content of a
|
||||
// canvas. If a non zero offset is provided, all the copied points are offset by
|
||||
// this amount.
|
||||
// The dstSetCell function is called for every point in this canvas when
|
||||
// copying it to the destination.
|
||||
func (c *Canvas) copyTo(offset image.Point, dstSetCell setCellFunc) error {
|
||||
for col := range c.buffer {
|
||||
for row := range c.buffer[col] {
|
||||
partial, err := c.buffer.IsPartial(image.Point{col, row})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if partial {
|
||||
// Skip over partial cells, i.e. cells that follow a cell
|
||||
// containing a full-width rune. A full-width rune takes only
|
||||
// one cell in the buffer, but two on the terminal.
|
||||
// See http://www.unicode.org/reports/tr11/.
|
||||
continue
|
||||
}
|
||||
cell := c.buffer[col][row]
|
||||
p := image.Point{col, row}.Add(offset)
|
||||
if err := dstSetCell(p, cell.Rune, cell.Opts); err != nil {
|
||||
return fmt.Errorf("setCellFunc%v => error: %v", p, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Apply applies the canvas to the corresponding area of the terminal.
|
||||
// Guarantees to stay within limits of the area the canvas was created with.
|
||||
func (c *Canvas) Apply(t terminalapi.Terminal) error {
|
||||
termArea, err := area.FromSize(t.Size())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bufArea, err := area.FromSize(c.buffer.Size())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !bufArea.In(termArea) {
|
||||
return fmt.Errorf("the canvas area %+v doesn't fit onto the terminal %+v", bufArea, termArea)
|
||||
}
|
||||
|
||||
// The image.Point{0, 0} of this canvas isn't always exactly at
|
||||
// image.Point{0, 0} on the terminal.
|
||||
// Depends on area assigned by the container.
|
||||
offset := c.area.Min
|
||||
return c.copyTo(offset, t.SetCell)
|
||||
}
|
||||
|
||||
// CopyTo copies the content of this canvas onto the destination canvas.
|
||||
// This canvas can have an offset when compared to the destination canvas, i.e.
|
||||
// the area of this canvas doesn't have to be zero-based.
|
||||
func (c *Canvas) CopyTo(dst *Canvas) error {
|
||||
if !c.area.In(dst.Area()) {
|
||||
return fmt.Errorf("the canvas area %v doesn't fit or lie inside the destination canvas area %v", c.area, dst.Area())
|
||||
}
|
||||
|
||||
fn := setCellFunc(func(p image.Point, r rune, opts ...cell.Option) error {
|
||||
if _, err := dst.SetCell(p, r, opts...); err != nil {
|
||||
return fmt.Errorf("dst.SetCell => %v", err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
// Neither of the two canvases (source and destination) have to be zero
|
||||
// based. Canvas is not zero based if it is positioned elsewhere, i.e.
|
||||
// providing a smaller view of another canvas.
|
||||
// E.g. a widget can assign a smaller portion of its canvas to a component
|
||||
// in order to restrict drawing of this component to a smaller area. To do
|
||||
// this it can create a sub-canvas. This sub-canvas can have a specific
|
||||
// starting position other than image.Point{0, 0} relative to the parent
|
||||
// canvas. Copying this sub-canvas back onto the parent accounts for this
|
||||
// offset.
|
||||
offset := c.area.Min
|
||||
return c.copyTo(offset, fn)
|
||||
}
|
||||
182
examples/go-dashboard/vendor/github.com/mum4k/termdash/private/draw/border.go
generated
vendored
182
examples/go-dashboard/vendor/github.com/mum4k/termdash/private/draw/border.go
generated
vendored
@@ -1,182 +0,0 @@
|
||||
// Copyright 2018 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package draw
|
||||
|
||||
// border.go contains code that draws borders.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
|
||||
"github.com/mum4k/termdash/align"
|
||||
"github.com/mum4k/termdash/cell"
|
||||
"github.com/mum4k/termdash/linestyle"
|
||||
"github.com/mum4k/termdash/private/alignfor"
|
||||
"github.com/mum4k/termdash/private/canvas"
|
||||
)
|
||||
|
||||
// BorderOption is used to provide options to Border().
|
||||
type BorderOption interface {
|
||||
// set sets the provided option.
|
||||
set(*borderOptions)
|
||||
}
|
||||
|
||||
// borderOptions stores the provided options.
|
||||
type borderOptions struct {
|
||||
cellOpts []cell.Option
|
||||
lineStyle linestyle.LineStyle
|
||||
title string
|
||||
titleOM OverrunMode
|
||||
titleCellOpts []cell.Option
|
||||
titleHAlign align.Horizontal
|
||||
}
|
||||
|
||||
// borderOption implements BorderOption.
|
||||
type borderOption func(bOpts *borderOptions)
|
||||
|
||||
// set implements BorderOption.set.
|
||||
func (bo borderOption) set(bOpts *borderOptions) {
|
||||
bo(bOpts)
|
||||
}
|
||||
|
||||
// DefaultBorderLineStyle is the default value for the BorderLineStyle option.
|
||||
const DefaultBorderLineStyle = linestyle.Light
|
||||
|
||||
// BorderLineStyle sets the style of the line used to draw the border.
|
||||
func BorderLineStyle(ls linestyle.LineStyle) BorderOption {
|
||||
return borderOption(func(bOpts *borderOptions) {
|
||||
bOpts.lineStyle = ls
|
||||
})
|
||||
}
|
||||
|
||||
// BorderCellOpts sets options on the cells that create the border.
|
||||
func BorderCellOpts(opts ...cell.Option) BorderOption {
|
||||
return borderOption(func(bOpts *borderOptions) {
|
||||
bOpts.cellOpts = opts
|
||||
})
|
||||
}
|
||||
|
||||
// BorderTitle sets a title for the border.
|
||||
func BorderTitle(title string, overrun OverrunMode, opts ...cell.Option) BorderOption {
|
||||
return borderOption(func(bOpts *borderOptions) {
|
||||
bOpts.title = title
|
||||
bOpts.titleOM = overrun
|
||||
bOpts.titleCellOpts = opts
|
||||
})
|
||||
}
|
||||
|
||||
// BorderTitleAlign configures the horizontal alignment for the title.
|
||||
func BorderTitleAlign(h align.Horizontal) BorderOption {
|
||||
return borderOption(func(bOpts *borderOptions) {
|
||||
bOpts.titleHAlign = h
|
||||
})
|
||||
}
|
||||
|
||||
// borderChar returns the correct border character from the parts for the use
|
||||
// at the specified point of the border. Returns -1 if no character should be at
|
||||
// this point.
|
||||
func borderChar(p image.Point, border image.Rectangle, parts map[linePart]rune) rune {
|
||||
switch {
|
||||
case p.X == border.Min.X && p.Y == border.Min.Y:
|
||||
return parts[topLeftCorner]
|
||||
case p.X == border.Max.X-1 && p.Y == border.Min.Y:
|
||||
return parts[topRightCorner]
|
||||
case p.X == border.Min.X && p.Y == border.Max.Y-1:
|
||||
return parts[bottomLeftCorner]
|
||||
case p.X == border.Max.X-1 && p.Y == border.Max.Y-1:
|
||||
return parts[bottomRightCorner]
|
||||
case p.X == border.Min.X || p.X == border.Max.X-1:
|
||||
return parts[vLine]
|
||||
case p.Y == border.Min.Y || p.Y == border.Max.Y-1:
|
||||
return parts[hLine]
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// drawTitle draws a text title at the top of the border.
|
||||
func drawTitle(c *canvas.Canvas, border image.Rectangle, opt *borderOptions) error {
|
||||
// Don't attempt to draw the title if there isn't space for at least one rune.
|
||||
// The title must not overwrite any of the corner runes on the border so we
|
||||
// need the following minimum width.
|
||||
const minForTitle = 3
|
||||
if border.Dx() < minForTitle {
|
||||
return nil
|
||||
}
|
||||
|
||||
available := image.Rect(
|
||||
border.Min.X+1, // One space for the top left corner char.
|
||||
border.Min.Y,
|
||||
border.Max.X-1, // One space for the top right corner char.
|
||||
border.Min.Y+1,
|
||||
)
|
||||
start, err := alignfor.Text(available, opt.title, opt.titleHAlign, align.VerticalTop)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Text(
|
||||
c, opt.title, start,
|
||||
TextCellOpts(opt.titleCellOpts...),
|
||||
TextOverrunMode(opt.titleOM),
|
||||
TextMaxX(available.Max.X),
|
||||
)
|
||||
}
|
||||
|
||||
// Border draws a border on the canvas.
|
||||
func Border(c *canvas.Canvas, border image.Rectangle, opts ...BorderOption) error {
|
||||
if ar := c.Area(); !border.In(ar) {
|
||||
return fmt.Errorf("the requested border %+v falls outside of the provided canvas %+v", border, ar)
|
||||
}
|
||||
|
||||
const minSize = 2
|
||||
if border.Dx() < minSize || border.Dy() < minSize {
|
||||
return fmt.Errorf("the smallest supported border is %dx%d, got: %dx%d", minSize, minSize, border.Dx(), border.Dy())
|
||||
}
|
||||
|
||||
opt := &borderOptions{
|
||||
lineStyle: DefaultBorderLineStyle,
|
||||
}
|
||||
for _, o := range opts {
|
||||
o.set(opt)
|
||||
}
|
||||
|
||||
parts, err := lineParts(opt.lineStyle)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for col := border.Min.X; col < border.Max.X; col++ {
|
||||
for row := border.Min.Y; row < border.Max.Y; row++ {
|
||||
p := image.Point{col, row}
|
||||
r := borderChar(p, border, parts)
|
||||
if r == -1 {
|
||||
continue
|
||||
}
|
||||
|
||||
cells, err := c.SetCell(p, r, opt.cellOpts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cells != 1 {
|
||||
panic(fmt.Sprintf("invalid border rune %q, this rune occupies %d cells, border implementation only supports half-width runes that occupy exactly one cell", r, cells))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if opt.title != "" {
|
||||
return drawTitle(c, border, opt)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
263
examples/go-dashboard/vendor/github.com/mum4k/termdash/private/draw/braille_circle.go
generated
vendored
263
examples/go-dashboard/vendor/github.com/mum4k/termdash/private/draw/braille_circle.go
generated
vendored
@@ -1,263 +0,0 @@
|
||||
// Copyright 2019 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package draw
|
||||
|
||||
// braille_circle.go contains code that draws circles on a braille canvas.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
|
||||
"github.com/mum4k/termdash/cell"
|
||||
"github.com/mum4k/termdash/private/canvas/braille"
|
||||
"github.com/mum4k/termdash/private/numbers/trig"
|
||||
)
|
||||
|
||||
// BrailleCircleOption is used to provide options to BrailleCircle.
|
||||
type BrailleCircleOption interface {
|
||||
// set sets the provided option.
|
||||
set(*brailleCircleOptions)
|
||||
}
|
||||
|
||||
// brailleCircleOptions stores the provided options.
|
||||
type brailleCircleOptions struct {
|
||||
cellOpts []cell.Option
|
||||
filled bool
|
||||
pixelChange braillePixelChange
|
||||
|
||||
arcOnly bool
|
||||
startDegree int
|
||||
endDegree int
|
||||
}
|
||||
|
||||
// newBrailleCircleOptions returns a new brailleCircleOptions instance.
|
||||
func newBrailleCircleOptions() *brailleCircleOptions {
|
||||
return &brailleCircleOptions{
|
||||
pixelChange: braillePixelChangeSet,
|
||||
}
|
||||
}
|
||||
|
||||
// validate validates the provided options.
|
||||
func (opts *brailleCircleOptions) validate() error {
|
||||
if !opts.arcOnly {
|
||||
return nil
|
||||
}
|
||||
|
||||
if opts.startDegree == opts.endDegree {
|
||||
return fmt.Errorf("invalid degree range, start %d and end %d cannot be equal", opts.startDegree, opts.endDegree)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// brailleCircleOption implements BrailleCircleOption.
|
||||
type brailleCircleOption func(*brailleCircleOptions)
|
||||
|
||||
// set implements BrailleCircleOption.set.
|
||||
func (o brailleCircleOption) set(opts *brailleCircleOptions) {
|
||||
o(opts)
|
||||
}
|
||||
|
||||
// BrailleCircleCellOpts sets options on the cells that contain the circle.
|
||||
// Cell options on a braille canvas can only be set on the entire cell, not per
|
||||
// pixel.
|
||||
func BrailleCircleCellOpts(cOpts ...cell.Option) BrailleCircleOption {
|
||||
return brailleCircleOption(func(opts *brailleCircleOptions) {
|
||||
opts.cellOpts = cOpts
|
||||
})
|
||||
}
|
||||
|
||||
// BrailleCircleFilled indicates that the drawn circle should be filled.
|
||||
func BrailleCircleFilled() BrailleCircleOption {
|
||||
return brailleCircleOption(func(opts *brailleCircleOptions) {
|
||||
opts.filled = true
|
||||
})
|
||||
}
|
||||
|
||||
// BrailleCircleArcOnly indicates that only a portion of the circle should be drawn.
|
||||
// The arc will be between the two provided angles in degrees.
|
||||
// Each angle must be in range 0 <= angle <= 360. Start and end must not be equal.
|
||||
// The zero angle is on the X axis, angles grow counter-clockwise.
|
||||
func BrailleCircleArcOnly(startDegree, endDegree int) BrailleCircleOption {
|
||||
return brailleCircleOption(func(opts *brailleCircleOptions) {
|
||||
opts.arcOnly = true
|
||||
opts.startDegree = startDegree
|
||||
opts.endDegree = endDegree
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
// BrailleCircleClearPixels changes the behavior of BrailleCircle, so that it
|
||||
// clears the pixels belonging to the circle instead of setting them.
|
||||
// Useful in order to "erase" a circle from the canvas as opposed to drawing one.
|
||||
func BrailleCircleClearPixels() BrailleCircleOption {
|
||||
return brailleCircleOption(func(opts *brailleCircleOptions) {
|
||||
opts.pixelChange = braillePixelChangeClear
|
||||
})
|
||||
}
|
||||
|
||||
// BrailleCircle draws an approximated circle with the specified mid point and radius.
|
||||
// The mid point must be a valid pixel within the canvas.
|
||||
// All the points that form the circle must fit into the canvas.
|
||||
// The smallest valid radius is two.
|
||||
func BrailleCircle(bc *braille.Canvas, mid image.Point, radius int, opts ...BrailleCircleOption) error {
|
||||
if ar := bc.Area(); !mid.In(ar) {
|
||||
return fmt.Errorf("unable to draw circle with mid point %v which is outside of the braille canvas area %v", mid, ar)
|
||||
}
|
||||
if min := 2; radius < min {
|
||||
return fmt.Errorf("unable to draw circle with radius %d, must be in range %d <= radius", radius, min)
|
||||
}
|
||||
|
||||
opt := newBrailleCircleOptions()
|
||||
for _, o := range opts {
|
||||
o.set(opt)
|
||||
}
|
||||
|
||||
if err := opt.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
points := circlePoints(mid, radius)
|
||||
if opt.arcOnly {
|
||||
f, err := trig.FilterByAngle(points, mid, opt.startDegree, opt.endDegree)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
points = f
|
||||
if opt.filled && (opt.startDegree != 0 || opt.endDegree != 360) {
|
||||
points = append(points, openingPoints(mid, radius, opt)...)
|
||||
}
|
||||
}
|
||||
if err := drawPoints(bc, points, opt); err != nil {
|
||||
return fmt.Errorf("failed to draw circle with mid:%v, radius:%d, start:%d degrees, end:%d degrees: %v", mid, radius, opt.startDegree, opt.endDegree, err)
|
||||
}
|
||||
if opt.filled {
|
||||
return fillCircle(bc, points, mid, radius, opt)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// drawPoints draws the points onto the canvas.
|
||||
func drawPoints(bc *braille.Canvas, points []image.Point, opt *brailleCircleOptions) error {
|
||||
for _, p := range points {
|
||||
switch opt.pixelChange {
|
||||
case braillePixelChangeSet:
|
||||
if err := bc.SetPixel(p, opt.cellOpts...); err != nil {
|
||||
return fmt.Errorf("SetPixel => %v", err)
|
||||
}
|
||||
case braillePixelChangeClear:
|
||||
if err := bc.ClearPixel(p, opt.cellOpts...); err != nil {
|
||||
return fmt.Errorf("ClearPixel => %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// fillCircle fills a circle that consists of the provided point and has the
|
||||
// mid point and radius.
|
||||
func fillCircle(bc *braille.Canvas, points []image.Point, mid image.Point, radius int, opt *brailleCircleOptions) error {
|
||||
lineOpts := []BrailleLineOption{
|
||||
BrailleLineCellOpts(opt.cellOpts...),
|
||||
}
|
||||
fillOpts := []BrailleFillOption{
|
||||
BrailleFillCellOpts(opt.cellOpts...),
|
||||
}
|
||||
if opt.pixelChange == braillePixelChangeClear {
|
||||
lineOpts = append(lineOpts, BrailleLineClearPixels())
|
||||
fillOpts = append(fillOpts, BrailleFillClearPixels())
|
||||
}
|
||||
|
||||
// Determine a fill point that should be inside of the circle sector.
|
||||
midA, err := trig.RangeMid(opt.startDegree, opt.endDegree)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fp := trig.CirclePointAtAngle(midA, mid, radius-1)
|
||||
|
||||
// Ensure the fill point falls inside the circle.
|
||||
// If drawing a partial circle, it must also fall within points belonging
|
||||
// to the opening.
|
||||
// This might not be true if drawing a partial circle and the arc is very
|
||||
// small.
|
||||
shape := points
|
||||
if opt.arcOnly {
|
||||
startP := trig.CirclePointAtAngle(opt.startDegree, mid, radius-1)
|
||||
endP := trig.CirclePointAtAngle(opt.endDegree, mid, radius-1)
|
||||
shape = append(shape, startP, endP)
|
||||
}
|
||||
if trig.PointIsIn(fp, shape) {
|
||||
if err := BrailleFill(bc, fp, points, fillOpts...); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := BrailleLine(bc, mid, fp, lineOpts...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// openingPoints returns points on the lines from the mid point to the circle
|
||||
// opening when drawing an incomplete circle.
|
||||
func openingPoints(mid image.Point, radius int, opt *brailleCircleOptions) []image.Point {
|
||||
var points []image.Point
|
||||
startP := trig.CirclePointAtAngle(opt.startDegree, mid, radius)
|
||||
endP := trig.CirclePointAtAngle(opt.endDegree, mid, radius)
|
||||
points = append(points, brailleLinePoints(mid, startP)...)
|
||||
points = append(points, brailleLinePoints(mid, endP)...)
|
||||
return points
|
||||
}
|
||||
|
||||
// circlePoints returns a list of points that represent a circle with
|
||||
// the specified mid point and radius.
|
||||
func circlePoints(mid image.Point, radius int) []image.Point {
|
||||
var points []image.Point
|
||||
|
||||
// Bresenham algorithm.
|
||||
// https://en.wikipedia.org/wiki/Midpoint_circle_algorithm
|
||||
x := radius
|
||||
y := 0
|
||||
dx := 1
|
||||
dy := 1
|
||||
diff := dx - (radius << 1) // Cheap multiplication by two.
|
||||
|
||||
for x >= y {
|
||||
points = append(
|
||||
points,
|
||||
image.Point{mid.X + x, mid.Y + y},
|
||||
image.Point{mid.X + y, mid.Y + x},
|
||||
image.Point{mid.X - y, mid.Y + x},
|
||||
image.Point{mid.X - x, mid.Y + y},
|
||||
image.Point{mid.X - x, mid.Y - y},
|
||||
image.Point{mid.X - y, mid.Y - x},
|
||||
image.Point{mid.X + y, mid.Y - x},
|
||||
image.Point{mid.X + x, mid.Y - y},
|
||||
)
|
||||
|
||||
if diff <= 0 {
|
||||
y++
|
||||
diff += dy
|
||||
dy += 2
|
||||
}
|
||||
|
||||
if diff > 0 {
|
||||
x--
|
||||
dx += 2
|
||||
diff += dx - (radius << 1)
|
||||
}
|
||||
|
||||
}
|
||||
return points
|
||||
}
|
||||
160
examples/go-dashboard/vendor/github.com/mum4k/termdash/private/draw/braille_fill.go
generated
vendored
160
examples/go-dashboard/vendor/github.com/mum4k/termdash/private/draw/braille_fill.go
generated
vendored
@@ -1,160 +0,0 @@
|
||||
// Copyright 2019 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package draw
|
||||
|
||||
// braille_fill.go implements the flood-fill algorithm for filling shapes on the braille canvas.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
|
||||
"github.com/mum4k/termdash/cell"
|
||||
"github.com/mum4k/termdash/private/canvas/braille"
|
||||
)
|
||||
|
||||
// BrailleFillOption is used to provide options to BrailleFill.
|
||||
type BrailleFillOption interface {
|
||||
// set sets the provided option.
|
||||
set(*brailleFillOptions)
|
||||
}
|
||||
|
||||
// brailleFillOptions stores the provided options.
|
||||
type brailleFillOptions struct {
|
||||
cellOpts []cell.Option
|
||||
pixelChange braillePixelChange
|
||||
}
|
||||
|
||||
// newBrailleFillOptions returns a new brailleFillOptions instance.
|
||||
func newBrailleFillOptions() *brailleFillOptions {
|
||||
return &brailleFillOptions{
|
||||
pixelChange: braillePixelChangeSet,
|
||||
}
|
||||
}
|
||||
|
||||
// brailleFillOption implements BrailleFillOption.
|
||||
type brailleFillOption func(*brailleFillOptions)
|
||||
|
||||
// set implements BrailleFillOption.set.
|
||||
func (o brailleFillOption) set(opts *brailleFillOptions) {
|
||||
o(opts)
|
||||
}
|
||||
|
||||
// BrailleFillCellOpts sets options on the cells that are set as part of
|
||||
// filling shapes.
|
||||
// Cell options on a braille canvas can only be set on the entire cell, not per
|
||||
// pixel.
|
||||
func BrailleFillCellOpts(cOpts ...cell.Option) BrailleFillOption {
|
||||
return brailleFillOption(func(opts *brailleFillOptions) {
|
||||
opts.cellOpts = cOpts
|
||||
})
|
||||
}
|
||||
|
||||
// BrailleFillClearPixels changes the behavior of BrailleFill, so that it
|
||||
// clears the pixels instead of setting them.
|
||||
// Useful in order to "erase" the filled area as opposed to drawing one.
|
||||
func BrailleFillClearPixels() BrailleFillOption {
|
||||
return brailleFillOption(func(opts *brailleFillOptions) {
|
||||
opts.pixelChange = braillePixelChangeClear
|
||||
})
|
||||
}
|
||||
|
||||
// BrailleFill fills the braille canvas starting at the specified point.
|
||||
// The function will not fill or cross over any points in the defined border.
|
||||
// The start point must be in the canvas.
|
||||
func BrailleFill(bc *braille.Canvas, start image.Point, border []image.Point, opts ...BrailleFillOption) error {
|
||||
if ar := bc.Area(); !start.In(ar) {
|
||||
return fmt.Errorf("unable to start filling canvas at point %v which is outside of the braille canvas area %v", start, ar)
|
||||
}
|
||||
|
||||
opt := newBrailleFillOptions()
|
||||
for _, o := range opts {
|
||||
o.set(opt)
|
||||
}
|
||||
|
||||
b := map[image.Point]struct{}{}
|
||||
for _, p := range border {
|
||||
b[p] = struct{}{}
|
||||
}
|
||||
|
||||
v := newVisitable(bc.Area(), b)
|
||||
visitor := func(p image.Point) error {
|
||||
switch opt.pixelChange {
|
||||
case braillePixelChangeSet:
|
||||
return bc.SetPixel(p, opt.cellOpts...)
|
||||
case braillePixelChangeClear:
|
||||
return bc.ClearPixel(p, opt.cellOpts...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return brailleDFS(v, start, visitor)
|
||||
}
|
||||
|
||||
// visitable represents an area that can be visited.
|
||||
// It tracks nodes that are already visited.
|
||||
type visitable struct {
|
||||
area image.Rectangle
|
||||
visited map[image.Point]struct{}
|
||||
}
|
||||
|
||||
// newVisitable returns a new visitable object initialized for the provided
|
||||
// area and already visited nodes.
|
||||
func newVisitable(ar image.Rectangle, visited map[image.Point]struct{}) *visitable {
|
||||
if visited == nil {
|
||||
visited = map[image.Point]struct{}{}
|
||||
}
|
||||
return &visitable{
|
||||
area: ar,
|
||||
visited: visited,
|
||||
}
|
||||
}
|
||||
|
||||
// neighborsAt returns all valid neighbors for the specified point.
|
||||
func (v *visitable) neighborsAt(p image.Point) []image.Point {
|
||||
var res []image.Point
|
||||
for _, neigh := range []image.Point{
|
||||
{p.X - 1, p.Y}, // left
|
||||
{p.X + 1, p.Y}, // right
|
||||
{p.X, p.Y - 1}, // up
|
||||
{p.X, p.Y + 1}, // down
|
||||
} {
|
||||
if !neigh.In(v.area) {
|
||||
continue
|
||||
}
|
||||
if _, ok := v.visited[neigh]; ok {
|
||||
continue
|
||||
}
|
||||
v.visited[neigh] = struct{}{}
|
||||
res = append(res, neigh)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// brailleDFS visits every point in the area and runs the visitor function.
|
||||
func brailleDFS(v *visitable, p image.Point, visitFn func(image.Point) error) error {
|
||||
neigh := v.neighborsAt(p)
|
||||
if len(neigh) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, n := range neigh {
|
||||
if err := visitFn(n); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := brailleDFS(v, n, visitFn); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
204
examples/go-dashboard/vendor/github.com/mum4k/termdash/private/draw/braille_line.go
generated
vendored
204
examples/go-dashboard/vendor/github.com/mum4k/termdash/private/draw/braille_line.go
generated
vendored
@@ -1,204 +0,0 @@
|
||||
// Copyright 2018 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package draw
|
||||
|
||||
// braille_line.go contains code that draws lines on a braille canvas.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
|
||||
"github.com/mum4k/termdash/cell"
|
||||
"github.com/mum4k/termdash/private/canvas/braille"
|
||||
"github.com/mum4k/termdash/private/numbers"
|
||||
)
|
||||
|
||||
// braillePixelChange represents an action on a pixel on the braille canvas.
|
||||
type braillePixelChange int
|
||||
|
||||
// String implements fmt.Stringer()
|
||||
func (bpc braillePixelChange) String() string {
|
||||
if n, ok := braillePixelChangeNames[bpc]; ok {
|
||||
return n
|
||||
}
|
||||
return "braillePixelChangeUnknown"
|
||||
}
|
||||
|
||||
// braillePixelChangeNames maps braillePixelChange values to human readable names.
|
||||
var braillePixelChangeNames = map[braillePixelChange]string{
|
||||
braillePixelChangeSet: "braillePixelChangeSet",
|
||||
braillePixelChangeClear: "braillePixelChangeClear",
|
||||
}
|
||||
|
||||
const (
|
||||
braillePixelChangeUnknown braillePixelChange = iota
|
||||
|
||||
braillePixelChangeSet
|
||||
braillePixelChangeClear
|
||||
)
|
||||
|
||||
// BrailleLineOption is used to provide options to BrailleLine().
|
||||
type BrailleLineOption interface {
|
||||
// set sets the provided option.
|
||||
set(*brailleLineOptions)
|
||||
}
|
||||
|
||||
// brailleLineOptions stores the provided options.
|
||||
type brailleLineOptions struct {
|
||||
cellOpts []cell.Option
|
||||
pixelChange braillePixelChange
|
||||
}
|
||||
|
||||
// newBrailleLineOptions returns a new brailleLineOptions instance.
|
||||
func newBrailleLineOptions() *brailleLineOptions {
|
||||
return &brailleLineOptions{
|
||||
pixelChange: braillePixelChangeSet,
|
||||
}
|
||||
}
|
||||
|
||||
// brailleLineOption implements BrailleLineOption.
|
||||
type brailleLineOption func(*brailleLineOptions)
|
||||
|
||||
// set implements BrailleLineOption.set.
|
||||
func (o brailleLineOption) set(opts *brailleLineOptions) {
|
||||
o(opts)
|
||||
}
|
||||
|
||||
// BrailleLineCellOpts sets options on the cells that contain the line.
|
||||
// Cell options on a braille canvas can only be set on the entire cell, not per
|
||||
// pixel.
|
||||
func BrailleLineCellOpts(cOpts ...cell.Option) BrailleLineOption {
|
||||
return brailleLineOption(func(opts *brailleLineOptions) {
|
||||
opts.cellOpts = cOpts
|
||||
})
|
||||
}
|
||||
|
||||
// BrailleLineClearPixels changes the behavior of BrailleLine, so that it
|
||||
// clears the pixels belonging to the line instead of setting them.
|
||||
// Useful in order to "erase" a line from the canvas as opposed to drawing one.
|
||||
func BrailleLineClearPixels() BrailleLineOption {
|
||||
return brailleLineOption(func(opts *brailleLineOptions) {
|
||||
opts.pixelChange = braillePixelChangeClear
|
||||
})
|
||||
}
|
||||
|
||||
// BrailleLine draws an approximated line segment on the braille canvas between
|
||||
// the two provided points.
|
||||
// Both start and end must be valid points within the canvas. Start and end can
|
||||
// be the same point in which case only one pixel will be set on the braille
|
||||
// canvas.
|
||||
// The start or end coordinates must not be negative.
|
||||
func BrailleLine(bc *braille.Canvas, start, end image.Point, opts ...BrailleLineOption) error {
|
||||
if start.X < 0 || start.Y < 0 {
|
||||
return fmt.Errorf("the start coordinates cannot be negative, got: %v", start)
|
||||
}
|
||||
if end.X < 0 || end.Y < 0 {
|
||||
return fmt.Errorf("the end coordinates cannot be negative, got: %v", end)
|
||||
}
|
||||
|
||||
opt := newBrailleLineOptions()
|
||||
for _, o := range opts {
|
||||
o.set(opt)
|
||||
}
|
||||
|
||||
points := brailleLinePoints(start, end)
|
||||
for _, p := range points {
|
||||
switch opt.pixelChange {
|
||||
case braillePixelChangeSet:
|
||||
if err := bc.SetPixel(p, opt.cellOpts...); err != nil {
|
||||
return fmt.Errorf("bc.SetPixel(%v) => %v", p, err)
|
||||
}
|
||||
case braillePixelChangeClear:
|
||||
if err := bc.ClearPixel(p, opt.cellOpts...); err != nil {
|
||||
return fmt.Errorf("bc.ClearPixel(%v) => %v", p, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// brailleLinePoints returns the points to set when drawing the line.
|
||||
func brailleLinePoints(start, end image.Point) []image.Point {
|
||||
// Implements Bresenham's line algorithm.
|
||||
// https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
|
||||
|
||||
vertProj := numbers.Abs(end.Y - start.Y)
|
||||
horizProj := numbers.Abs(end.X - start.X)
|
||||
if vertProj < horizProj {
|
||||
if start.X > end.X {
|
||||
return lineLow(end.X, end.Y, start.X, start.Y)
|
||||
}
|
||||
return lineLow(start.X, start.Y, end.X, end.Y)
|
||||
}
|
||||
if start.Y > end.Y {
|
||||
return lineHigh(end.X, end.Y, start.X, start.Y)
|
||||
}
|
||||
return lineHigh(start.X, start.Y, end.X, end.Y)
|
||||
}
|
||||
|
||||
// lineLow returns points that create a line whose horizontal projection
|
||||
// (end.X - start.X) is longer than its vertical projection
|
||||
// (end.Y - start.Y).
|
||||
func lineLow(x0, y0, x1, y1 int) []image.Point {
|
||||
deltaX := x1 - x0
|
||||
deltaY := y1 - y0
|
||||
|
||||
stepY := 1
|
||||
if deltaY < 0 {
|
||||
stepY = -1
|
||||
deltaY = -deltaY
|
||||
}
|
||||
|
||||
var res []image.Point
|
||||
diff := 2*deltaY - deltaX
|
||||
y := y0
|
||||
for x := x0; x <= x1; x++ {
|
||||
res = append(res, image.Point{x, y})
|
||||
if diff > 0 {
|
||||
y += stepY
|
||||
diff -= 2 * deltaX
|
||||
}
|
||||
diff += 2 * deltaY
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// lineHigh returns points that createa line whose vertical projection
|
||||
// (end.Y - start.Y) is longer than its horizontal projection
|
||||
// (end.X - start.X).
|
||||
func lineHigh(x0, y0, x1, y1 int) []image.Point {
|
||||
deltaX := x1 - x0
|
||||
deltaY := y1 - y0
|
||||
|
||||
stepX := 1
|
||||
if deltaX < 0 {
|
||||
stepX = -1
|
||||
deltaX = -deltaX
|
||||
}
|
||||
|
||||
var res []image.Point
|
||||
diff := 2*deltaX - deltaY
|
||||
x := x0
|
||||
for y := y0; y <= y1; y++ {
|
||||
res = append(res, image.Point{x, y})
|
||||
|
||||
if diff > 0 {
|
||||
x += stepX
|
||||
diff -= 2 * deltaY
|
||||
}
|
||||
diff += 2 * deltaX
|
||||
}
|
||||
return res
|
||||
}
|
||||
17
examples/go-dashboard/vendor/github.com/mum4k/termdash/private/draw/draw.go
generated
vendored
17
examples/go-dashboard/vendor/github.com/mum4k/termdash/private/draw/draw.go
generated
vendored
@@ -1,17 +0,0 @@
|
||||
// Copyright 2018 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package draw provides functions that draw lines, shapes, etc on 2-D terminal
|
||||
// like canvases.
|
||||
package draw
|
||||
207
examples/go-dashboard/vendor/github.com/mum4k/termdash/private/draw/hv_line.go
generated
vendored
207
examples/go-dashboard/vendor/github.com/mum4k/termdash/private/draw/hv_line.go
generated
vendored
@@ -1,207 +0,0 @@
|
||||
// Copyright 2018 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package draw
|
||||
|
||||
// hv_line.go contains code that draws horizontal and vertical lines.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
|
||||
"github.com/mum4k/termdash/cell"
|
||||
"github.com/mum4k/termdash/linestyle"
|
||||
"github.com/mum4k/termdash/private/canvas"
|
||||
)
|
||||
|
||||
// HVLineOption is used to provide options to HVLine().
|
||||
type HVLineOption interface {
|
||||
// set sets the provided option.
|
||||
set(*hVLineOptions)
|
||||
}
|
||||
|
||||
// hVLineOptions stores the provided options.
|
||||
type hVLineOptions struct {
|
||||
cellOpts []cell.Option
|
||||
lineStyle linestyle.LineStyle
|
||||
}
|
||||
|
||||
// newHVLineOptions returns a new hVLineOptions instance.
|
||||
func newHVLineOptions() *hVLineOptions {
|
||||
return &hVLineOptions{
|
||||
lineStyle: DefaultLineStyle,
|
||||
}
|
||||
}
|
||||
|
||||
// hVLineOption implements HVLineOption.
|
||||
type hVLineOption func(*hVLineOptions)
|
||||
|
||||
// set implements HVLineOption.set.
|
||||
func (o hVLineOption) set(opts *hVLineOptions) {
|
||||
o(opts)
|
||||
}
|
||||
|
||||
// DefaultLineStyle is the default value for the HVLineStyle option.
|
||||
const DefaultLineStyle = linestyle.Light
|
||||
|
||||
// HVLineStyle sets the style of the line.
|
||||
// Defaults to DefaultLineStyle.
|
||||
func HVLineStyle(ls linestyle.LineStyle) HVLineOption {
|
||||
return hVLineOption(func(opts *hVLineOptions) {
|
||||
opts.lineStyle = ls
|
||||
})
|
||||
}
|
||||
|
||||
// HVLineCellOpts sets options on the cells that contain the line.
|
||||
func HVLineCellOpts(cOpts ...cell.Option) HVLineOption {
|
||||
return hVLineOption(func(opts *hVLineOptions) {
|
||||
opts.cellOpts = cOpts
|
||||
})
|
||||
}
|
||||
|
||||
// HVLine represents one horizontal or vertical line.
|
||||
type HVLine struct {
|
||||
// Start is the cell where the line starts.
|
||||
Start image.Point
|
||||
// End is the cell where the line ends.
|
||||
End image.Point
|
||||
}
|
||||
|
||||
// HVLines draws horizontal or vertical lines. Handles drawing of the correct
|
||||
// characters for locations where any two lines cross (e.g. a corner, a T shape
|
||||
// or a cross). Each line must be at least two cells long. Both start and end
|
||||
// must be on the same horizontal (same X coordinate) or same vertical (same Y
|
||||
// coordinate) line.
|
||||
func HVLines(c *canvas.Canvas, lines []HVLine, opts ...HVLineOption) error {
|
||||
opt := newHVLineOptions()
|
||||
for _, o := range opts {
|
||||
o.set(opt)
|
||||
}
|
||||
|
||||
g := newHVLineGraph()
|
||||
for _, l := range lines {
|
||||
line, err := newHVLine(c, l.Start, l.End, opt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.addLine(line)
|
||||
|
||||
switch {
|
||||
case line.horizontal():
|
||||
for curX := line.start.X; ; curX++ {
|
||||
cur := image.Point{curX, line.start.Y}
|
||||
if _, err := c.SetCell(cur, line.mainPart, opt.cellOpts...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if curX == line.end.X {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
case line.vertical():
|
||||
for curY := line.start.Y; ; curY++ {
|
||||
cur := image.Point{line.start.X, curY}
|
||||
if _, err := c.SetCell(cur, line.mainPart, opt.cellOpts...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if curY == line.end.Y {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, n := range g.multiEdgeNodes() {
|
||||
r, err := n.rune(opt.lineStyle)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := c.SetCell(n.p, r, opt.cellOpts...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// hVLine represents a line that will be drawn on the canvas.
|
||||
type hVLine struct {
|
||||
// start is the starting point of the line.
|
||||
start image.Point
|
||||
|
||||
// end is the ending point of the line.
|
||||
end image.Point
|
||||
|
||||
// mainPart is either parts[vLine] or parts[hLine] depending on whether
|
||||
// this is horizontal or vertical line.
|
||||
mainPart rune
|
||||
|
||||
// opts are the options provided in a call to HVLine().
|
||||
opts *hVLineOptions
|
||||
}
|
||||
|
||||
// newHVLine creates a new hVLine instance.
|
||||
// Swaps start and end if necessary, so that horizontal drawing is always left
|
||||
// to right and vertical is always top down.
|
||||
func newHVLine(c *canvas.Canvas, start, end image.Point, opts *hVLineOptions) (*hVLine, error) {
|
||||
if ar := c.Area(); !start.In(ar) || !end.In(ar) {
|
||||
return nil, fmt.Errorf("both the start%v and the end%v must be in the canvas area: %v", start, end, ar)
|
||||
}
|
||||
|
||||
parts, err := lineParts(opts.lineStyle)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var mainPart rune
|
||||
switch {
|
||||
case start.X != end.X && start.Y != end.Y:
|
||||
return nil, fmt.Errorf("can only draw horizontal (same X coordinates) or vertical (same Y coordinates), got start:%v end:%v", start, end)
|
||||
|
||||
case start.X == end.X && start.Y == end.Y:
|
||||
return nil, fmt.Errorf("the line must at least one cell long, got start%v, end%v", start, end)
|
||||
|
||||
case start.X == end.X:
|
||||
mainPart = parts[vLine]
|
||||
if start.Y > end.Y {
|
||||
start, end = end, start
|
||||
}
|
||||
|
||||
case start.Y == end.Y:
|
||||
mainPart = parts[hLine]
|
||||
if start.X > end.X {
|
||||
start, end = end, start
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return &hVLine{
|
||||
start: start,
|
||||
end: end,
|
||||
mainPart: mainPart,
|
||||
opts: opts,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// horizontal determines if this is a horizontal line.
|
||||
func (hvl *hVLine) horizontal() bool {
|
||||
return hvl.mainPart == lineStyleChars[hvl.opts.lineStyle][hLine]
|
||||
}
|
||||
|
||||
// vertical determines if this is a vertical line.
|
||||
func (hvl *hVLine) vertical() bool {
|
||||
return hvl.mainPart == lineStyleChars[hvl.opts.lineStyle][vLine]
|
||||
}
|
||||
206
examples/go-dashboard/vendor/github.com/mum4k/termdash/private/draw/hv_line_graph.go
generated
vendored
206
examples/go-dashboard/vendor/github.com/mum4k/termdash/private/draw/hv_line_graph.go
generated
vendored
@@ -1,206 +0,0 @@
|
||||
// Copyright 2018 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package draw
|
||||
|
||||
// hv_line_graph.go helps to keep track of locations where lines cross.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
|
||||
"github.com/mum4k/termdash/linestyle"
|
||||
)
|
||||
|
||||
// hVLineEdge is an edge between two points on the graph.
|
||||
type hVLineEdge struct {
|
||||
// from is the starting node of this edge.
|
||||
// From is guaranteed to be less than to.
|
||||
from image.Point
|
||||
|
||||
// to is the ending point of this edge.
|
||||
to image.Point
|
||||
}
|
||||
|
||||
// newHVLineEdge returns a new edge between the two points.
|
||||
func newHVLineEdge(from, to image.Point) hVLineEdge {
|
||||
return hVLineEdge{
|
||||
from: from,
|
||||
to: to,
|
||||
}
|
||||
}
|
||||
|
||||
// hVLineNode represents one node in the graph.
|
||||
// I.e. one cell.
|
||||
type hVLineNode struct {
|
||||
// p is the point where this node is.
|
||||
p image.Point
|
||||
|
||||
// edges are the edges between this node and the surrounding nodes.
|
||||
// The code only supports horizontal and vertical lines so there can only
|
||||
// ever be edges to nodes on these planes.
|
||||
edges map[hVLineEdge]bool
|
||||
}
|
||||
|
||||
// newHVLineNode creates a new newHVLineNode.
|
||||
func newHVLineNode(p image.Point) *hVLineNode {
|
||||
return &hVLineNode{
|
||||
p: p,
|
||||
edges: map[hVLineEdge]bool{},
|
||||
}
|
||||
}
|
||||
|
||||
// hasDown determines if this node has an edge to the one below it.
|
||||
func (n *hVLineNode) hasDown() bool {
|
||||
target := newHVLineEdge(n.p, image.Point{n.p.X, n.p.Y + 1})
|
||||
_, ok := n.edges[target]
|
||||
return ok
|
||||
}
|
||||
|
||||
// hasUp determines if this node has an edge to the one above it.
|
||||
func (n *hVLineNode) hasUp() bool {
|
||||
target := newHVLineEdge(image.Point{n.p.X, n.p.Y - 1}, n.p)
|
||||
_, ok := n.edges[target]
|
||||
return ok
|
||||
}
|
||||
|
||||
// hasLeft determines if this node has an edge to the next node on the left.
|
||||
func (n *hVLineNode) hasLeft() bool {
|
||||
target := newHVLineEdge(image.Point{n.p.X - 1, n.p.Y}, n.p)
|
||||
_, ok := n.edges[target]
|
||||
return ok
|
||||
}
|
||||
|
||||
// hasRight determines if this node has an edge to the next node on the right.
|
||||
func (n *hVLineNode) hasRight() bool {
|
||||
target := newHVLineEdge(n.p, image.Point{n.p.X + 1, n.p.Y})
|
||||
_, ok := n.edges[target]
|
||||
return ok
|
||||
}
|
||||
|
||||
// rune, given the selected line style returns the correct line character to
|
||||
// represent this node.
|
||||
// Only handles nodes with two or more edges, as returned by multiEdgeNodes().
|
||||
func (n *hVLineNode) rune(ls linestyle.LineStyle) (rune, error) {
|
||||
parts, err := lineParts(ls)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
switch len(n.edges) {
|
||||
case 2:
|
||||
switch {
|
||||
case n.hasLeft() && n.hasRight():
|
||||
return parts[hLine], nil
|
||||
case n.hasUp() && n.hasDown():
|
||||
return parts[vLine], nil
|
||||
case n.hasDown() && n.hasRight():
|
||||
return parts[topLeftCorner], nil
|
||||
case n.hasDown() && n.hasLeft():
|
||||
return parts[topRightCorner], nil
|
||||
case n.hasUp() && n.hasRight():
|
||||
return parts[bottomLeftCorner], nil
|
||||
case n.hasUp() && n.hasLeft():
|
||||
return parts[bottomRightCorner], nil
|
||||
default:
|
||||
return -1, fmt.Errorf("unexpected two edges in node representing point %v: %v", n.p, n.edges)
|
||||
}
|
||||
|
||||
case 3:
|
||||
switch {
|
||||
case n.hasUp() && n.hasLeft() && n.hasRight():
|
||||
return parts[hAndUp], nil
|
||||
case n.hasDown() && n.hasLeft() && n.hasRight():
|
||||
return parts[hAndDown], nil
|
||||
case n.hasUp() && n.hasDown() && n.hasRight():
|
||||
return parts[vAndRight], nil
|
||||
case n.hasUp() && n.hasDown() && n.hasLeft():
|
||||
return parts[vAndLeft], nil
|
||||
|
||||
default:
|
||||
return -1, fmt.Errorf("unexpected three edges in node representing point %v: %v", n.p, n.edges)
|
||||
}
|
||||
|
||||
case 4:
|
||||
return parts[vAndH], nil
|
||||
default:
|
||||
return -1, fmt.Errorf("unexpected number of edges(%d) in node representing point %v", len(n.edges), n.p)
|
||||
}
|
||||
}
|
||||
|
||||
// hVLineGraph represents lines on the canvas as a bidirectional graph of
|
||||
// nodes. Helps to determine the characters that should be used where multiple
|
||||
// lines cross.
|
||||
type hVLineGraph struct {
|
||||
nodes map[image.Point]*hVLineNode
|
||||
}
|
||||
|
||||
// newHVLineGraph creates a new hVLineGraph.
|
||||
func newHVLineGraph() *hVLineGraph {
|
||||
return &hVLineGraph{
|
||||
nodes: make(map[image.Point]*hVLineNode),
|
||||
}
|
||||
}
|
||||
|
||||
// getOrCreateNode gets an existing or creates a new node for the point.
|
||||
func (g *hVLineGraph) getOrCreateNode(p image.Point) *hVLineNode {
|
||||
if n, ok := g.nodes[p]; ok {
|
||||
return n
|
||||
}
|
||||
n := newHVLineNode(p)
|
||||
g.nodes[p] = n
|
||||
return n
|
||||
}
|
||||
|
||||
// addLine adds a line to the graph.
|
||||
// This adds edges between all the points on the line.
|
||||
func (g *hVLineGraph) addLine(line *hVLine) {
|
||||
switch {
|
||||
case line.horizontal():
|
||||
for curX := line.start.X; curX < line.end.X; curX++ {
|
||||
from := image.Point{curX, line.start.Y}
|
||||
to := image.Point{curX + 1, line.start.Y}
|
||||
n1 := g.getOrCreateNode(from)
|
||||
n2 := g.getOrCreateNode(to)
|
||||
edge := newHVLineEdge(from, to)
|
||||
n1.edges[edge] = true
|
||||
n2.edges[edge] = true
|
||||
}
|
||||
|
||||
case line.vertical():
|
||||
for curY := line.start.Y; curY < line.end.Y; curY++ {
|
||||
from := image.Point{line.start.X, curY}
|
||||
to := image.Point{line.start.X, curY + 1}
|
||||
n1 := g.getOrCreateNode(from)
|
||||
n2 := g.getOrCreateNode(to)
|
||||
edge := newHVLineEdge(from, to)
|
||||
n1.edges[edge] = true
|
||||
n2.edges[edge] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// multiEdgeNodes returns all nodes that have more than one edge. These are
|
||||
// the nodes where we might need to use different line characters to represent
|
||||
// the crossing of multiple lines.
|
||||
func (g *hVLineGraph) multiEdgeNodes() []*hVLineNode {
|
||||
var nodes []*hVLineNode
|
||||
for _, n := range g.nodes {
|
||||
if len(n.edges) <= 1 {
|
||||
continue
|
||||
}
|
||||
nodes = append(nodes, n)
|
||||
}
|
||||
return nodes
|
||||
}
|
||||
129
examples/go-dashboard/vendor/github.com/mum4k/termdash/private/draw/line_style.go
generated
vendored
129
examples/go-dashboard/vendor/github.com/mum4k/termdash/private/draw/line_style.go
generated
vendored
@@ -1,129 +0,0 @@
|
||||
// Copyright 2018 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package draw
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mum4k/termdash/linestyle"
|
||||
"github.com/mum4k/termdash/private/runewidth"
|
||||
)
|
||||
|
||||
// line_style.go contains the Unicode characters used for drawing lines of
|
||||
// different styles.
|
||||
|
||||
// lineStyleChars maps the line styles to the corresponding component characters.
|
||||
// Source: http://en.wikipedia.org/wiki/Box-drawing_character.
|
||||
var lineStyleChars = map[linestyle.LineStyle]map[linePart]rune{
|
||||
linestyle.Light: {
|
||||
hLine: '─',
|
||||
vLine: '│',
|
||||
topLeftCorner: '┌',
|
||||
topRightCorner: '┐',
|
||||
bottomLeftCorner: '└',
|
||||
bottomRightCorner: '┘',
|
||||
hAndUp: '┴',
|
||||
hAndDown: '┬',
|
||||
vAndLeft: '┤',
|
||||
vAndRight: '├',
|
||||
vAndH: '┼',
|
||||
},
|
||||
linestyle.Double: {
|
||||
hLine: '═',
|
||||
vLine: '║',
|
||||
topLeftCorner: '╔',
|
||||
topRightCorner: '╗',
|
||||
bottomLeftCorner: '╚',
|
||||
bottomRightCorner: '╝',
|
||||
hAndUp: '╩',
|
||||
hAndDown: '╦',
|
||||
vAndLeft: '╣',
|
||||
vAndRight: '╠',
|
||||
vAndH: '╬',
|
||||
},
|
||||
linestyle.Round: {
|
||||
hLine: '─',
|
||||
vLine: '│',
|
||||
topLeftCorner: '╭',
|
||||
topRightCorner: '╮',
|
||||
bottomLeftCorner: '╰',
|
||||
bottomRightCorner: '╯',
|
||||
hAndUp: '┴',
|
||||
hAndDown: '┬',
|
||||
vAndLeft: '┤',
|
||||
vAndRight: '├',
|
||||
vAndH: '┼',
|
||||
},
|
||||
}
|
||||
|
||||
// init verifies that all line parts are half-width runes (occupy only one
|
||||
// cell).
|
||||
func init() {
|
||||
for ls, parts := range lineStyleChars {
|
||||
for part, r := range parts {
|
||||
if got := runewidth.RuneWidth(r); got > 1 {
|
||||
panic(fmt.Errorf("line style %v line part %v is a rune %c with width %v, all parts must be half-width runes (width of one)", ls, part, r, got))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// lineParts returns the line component characters for the provided line style.
|
||||
func lineParts(ls linestyle.LineStyle) (map[linePart]rune, error) {
|
||||
parts, ok := lineStyleChars[ls]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unsupported line style %d", ls)
|
||||
}
|
||||
return parts, nil
|
||||
}
|
||||
|
||||
// linePart identifies individual line parts.
|
||||
type linePart int
|
||||
|
||||
// String implements fmt.Stringer()
|
||||
func (lp linePart) String() string {
|
||||
if n, ok := linePartNames[lp]; ok {
|
||||
return n
|
||||
}
|
||||
return "linePartUnknown"
|
||||
}
|
||||
|
||||
// linePartNames maps linePart values to human readable names.
|
||||
var linePartNames = map[linePart]string{
|
||||
vLine: "linePartVLine",
|
||||
topLeftCorner: "linePartTopLeftCorner",
|
||||
topRightCorner: "linePartTopRightCorner",
|
||||
bottomLeftCorner: "linePartBottomLeftCorner",
|
||||
bottomRightCorner: "linePartBottomRightCorner",
|
||||
hAndUp: "linePartHAndUp",
|
||||
hAndDown: "linePartHAndDown",
|
||||
vAndLeft: "linePartVAndLeft",
|
||||
vAndRight: "linePartVAndRight",
|
||||
vAndH: "linePartVAndH",
|
||||
}
|
||||
|
||||
const (
|
||||
hLine linePart = iota
|
||||
vLine
|
||||
topLeftCorner
|
||||
topRightCorner
|
||||
bottomLeftCorner
|
||||
bottomRightCorner
|
||||
hAndUp
|
||||
hAndDown
|
||||
vAndLeft
|
||||
vAndRight
|
||||
vAndH
|
||||
)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user