From b04c834c64930b3ca4773bb6be2776f98576d2a9 Mon Sep 17 00:00:00 2001 From: slonkazoid Date: Fri, 30 Aug 2024 15:24:30 +0300 Subject: [PATCH] remove commit history --- LICENSE | 122 +++++++++++ README.md | 16 ++ bash/arshit/README.md | 66 ++++++ bash/arshit/arshit.sh | 129 ++++++++++++ bash/avt/README.md | 1 + bash/frequented.sh | 79 +++++++ bash/grabber.sh | 21 ++ bash/repo_build.sh | 56 +++++ bash/sshfs_mount_file.sh | 44 ++++ bash/start.sh | 85 ++++++++ c/cecho.c | 13 ++ js/svged.js | 432 +++++++++++++++++++++++++++++++++++++++ 12 files changed, 1064 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 bash/arshit/README.md create mode 100755 bash/arshit/arshit.sh create mode 100644 bash/avt/README.md create mode 100755 bash/frequented.sh create mode 100755 bash/grabber.sh create mode 100755 bash/repo_build.sh create mode 100755 bash/sshfs_mount_file.sh create mode 100755 bash/start.sh create mode 100644 c/cecho.c create mode 100755 js/svged.js diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6ca207e --- /dev/null +++ b/LICENSE @@ -0,0 +1,122 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..472cfe2 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# snippets + +slonked up scripts and little snippets to do stuff + +everything in this repository is licensed under CC0-1.0 unless specified + +# quick navigation + +| C | Bash | JS | +| -------------------- | ----------------------------------------------- | ----------------------- | +| [cecho.c](c/cecho.c) | [arshit](bash/arshit) | [svged.js](js/svged.js) | +| | [sshfs_mount_file.sh](bash/sshfs_mount_file.sh) | | +| | [repo_build.sh](bash/repo_build.sh) | | +| | [grabber.sh](bash/grabber.sh) | | +| | [frequented.sh](bash/frequented.sh) | | +| | [start.sh](bash/start.sh) | | diff --git a/bash/arshit/README.md b/bash/arshit/README.md new file mode 100644 index 0000000..9949709 --- /dev/null +++ b/bash/arshit/README.md @@ -0,0 +1,66 @@ +# Artix ISO modification helper script (ARSHIT) + +A small bash script to modify already built Artix ISOs + +## Dependencies + +- `bsdtar` +- `sponge` +- `perl` +- `xorriso` +- `arch-chroot` or `artix-chroot` (falls back to `chroot`) +- `mksquashfs` +- `unsquashfs` +- `md5sum` +- `bash` (duh) +- `sudo` +- `md5sum` + +## Usage + +``` +Usage: ./arshit.sh extract|unsquashfs|mount|umount|mksquashfs|build|update|cleanup +Usage: ./arshit.sh chroot [program [arguments]] + +Subcommands: + extract - Extract the ISO to a directory + unsquashfs - Extract the filesystem + mount - Mount the filesystem to /tmp/$FS_ROOT + chroot - Chroot into the filesystem + umount - Unmount the filesystem + mksquashfs - Build the filesystem image + build - Build the ISO + update - Does all above while updating the system + cleanup - Removes work directories +``` + +## Configuration + +Configuration is done with environment variables. Export these before using the script or set them each time you use it. + +```sh +# ISO and working directory name (default: "arshit") +export ISO_NAME=arshit +# Where to extract/build the rootfs (default: "$ISO_NAME\_root") +export FS_ROOT=arshit_root +# Volume label for the ISO (default: $(echo ${ISO_NAME^^} | cut -c-12)) +export VOLID=ARSHIT +# Which chroot binary to use (default $(command -v arch-chroot || command -v artix-chroot || command -v chroot)) +# WHEN OVERRIDING WITH CHROOT, USE $(command -v chroot) OR IT WILL NOT MOUNT EVERYTHING +export CHROOT_COMMAND= +# Set to '1' to generate a MD5 hash for the filesystem image (default: unset) +export MD5SUM= +``` + +## Refreshing keys + +From the [wiki](https://wiki.artixlinux.org/Main/Migration#Install_the_Artix_PGP_keyring), but that wont work. + +```sh +pacman-key --init +pacman-key --populate artix +$(curl https://wiki.artixlinux.org/Main/Migration | grep -oP 'pacman-key --lsign-key [0-9A-F]{40}') +pacman -Sy artix-keyring +``` + +**`./arshit.sh update` does this if needed.** \ No newline at end of file diff --git a/bash/arshit/arshit.sh b/bash/arshit/arshit.sh new file mode 100755 index 0000000..f20a0a1 --- /dev/null +++ b/bash/arshit/arshit.sh @@ -0,0 +1,129 @@ +#!/usr/bin/env bash +ISO_NAME=${ISO_NAME:-arshit} +FS_ROOT=${FS_ROOT:-$ISO_NAME\_root} +VOLID=${VOLID:-$(echo "${ISO_NAME^^}" | cut -c-12)} +CHROOT_COMMAND=${CHROOT_COMMAND:-$(command -v arch-chroot || command -v artix-chroot || command -v chroot)} +USES_CHROOT="$( + [[ "$CHROOT_COMMAND" == "$(command -v chroot)" ]] + echo $((-$? + 1)) +)" + +print_help() { + cat << EOF +Usage: $0 extract|unsquashfs|mount|umount|mksquashfs|build|update|cleanup +Usage: $0 chroot [program [arguments]] + +Subcommands: + extract - Extract the ISO to a directory + unsquashfs - Extract the filesystem + mount - Mount the filesystem to /tmp/\$FS_ROOT + chroot - Chroot into the filesystem + umount - Unmount the filesystem + mksquashfs - Build the filesystem image + build - Build the ISO + update - Does all above while updating the system + cleanup - Removes work directories +EOF +} + +case "$1" in +extract) + + mkdir -p "$ISO_NAME" + bsdtar -C "$ISO_NAME" -xf "$ISO_NAME".iso + sudo chmod -R +w "$ISO_NAME" + + ;; +unsquashfs) + + [ -e "$FS_ROOT" ] && sudo rm -rf "$FS_ROOT" + sudo unsquashfs -d "$FS_ROOT" "$ISO_NAME"/LiveOS/rootfs.img + + ;; +mount) + + mkdir -p "/tmp/$FS_ROOT" + sudo mount --bind "$FS_ROOT" "/tmp/$FS_ROOT" + if [[ "$USES_CHROOT" == 1 ]]; then + sudo mount --bind /dev "/tmp/$FS_ROOT"/dev + sudo mount --bind /sys "/tmp/$FS_ROOT"/sys + sudo mount --bind /proc "/tmp/$FS_ROOT"/proc + fi + + ;; +chroot) + + # shellcheck disable=SC2086,SC2068 + sudo $CHROOT_COMMAND "/tmp/$FS_ROOT" ${@:2} + + ;; +umount) + + if [[ "$USES_CHROOT" == 1 ]]; then + sudo umount /tmp/"$FS_ROOT"/dev + sudo umount "/tmp/$FS_ROOT"/sys + sudo umount "/tmp/$FS_ROOT"/proc + fi + sudo umount "/tmp/$FS_ROOT" + rm -r "/tmp/$FS_ROOT" + + ;; +mksquashfs) + + sudo mksquashfs "$FS_ROOT" "$ISO_NAME/LiveOS/rootfs.img" -comp zstd -noappend + [[ "$MD5SUM" == 1 ]] && md5sum "$ISO_NAME"/LiveOS/rootfs.img > \ + "$ISO_NAME"/LiveOS/rootfs.img.md5 + + ;; +build) + + perl -pe 's/(?<=label=)[A-Z0-9_]{0,32}(?= |;)/'"$VOLID"'/' <"$ISO_NAME"/boot/grub/kernels.cfg | + sponge "$ISO_NAME"/boot/grub/kernels.cfg + + sudo xorriso -as mkisofs \ + -volid "$VOLID" \ + --protective-msdos-label -r \ + -graft-points -no-pad \ + --sort-weight 0 / --sort-weight 1 /boot \ + --grub2-mbr "$ISO_NAME"/boot/grub/i386-pc/boot_hybrid.img \ + -b boot/grub/i386-pc/eltorito.img -c boot.catalog \ + -no-emul-boot -boot-load-size 4 \ + -boot-info-table --grub2-boot-info -eltorito-alt-boot \ + -append_partition 2 0xef "$ISO_NAME"/boot/efi.img -no-emul-boot \ + -iso-level 3 \ + -o "$ISO_NAME".iso "$ISO_NAME" + + ;; +cleanup) + + rm -rf "$ISO_NAME" + sudo rm -rf "$FS_ROOT" + + ;; +update) + + $0 extract + $0 unsquashfs + $0 mount + if $0 chroot [ ! -e /etc/pacman.d/gnupg/trustdb.gpg ]; then + $0 chroot pacman-key --init + $0 chroot pacman-key --populate artix + # Fetch key from wiki + # shellcheck disable=SC2046 + $0 chroot $(curl https://wiki.artixlinux.org/Main/Migration | grep -oP 'pacman-key --lsign-key [0-9A-F]{40}') + fi + $0 chroot pacman -Syu --noconfirm + $0 umount + $0 mksquashfs + $0 build + $0 cleanup + + ;; +*) + + print_help + exit 1 + + ;; +esac +exit $? diff --git a/bash/avt/README.md b/bash/avt/README.md new file mode 100644 index 0000000..964cdfc --- /dev/null +++ b/bash/avt/README.md @@ -0,0 +1 @@ +# Moved to [slonkazoid/avt](https://gitlab.com/alifurkany/avt) diff --git a/bash/frequented.sh b/bash/frequented.sh new file mode 100755 index 0000000..09dea2e --- /dev/null +++ b/bash/frequented.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash + +# Usage: ./frequented.sh [number=5] +# Lists your top `number` most frequented channels +# Single threaded, still does 1.22 milliseconds per channel +# (on a 5600X with 2289 channels and 385952 messages) + +# set this to your id to not print your id in dms/group chats +SELF_ID="${SELF_ID:-276363003270791168}" + +n=$((${1:-5})) + +repeat() { + local n=${2?} + for ((i=0; i ${counts[$i]})); then + insert $count "$channel" $i + break + fi + done +done + +echo "total channels: $total_channels" +echo "total messages: $total_messages" +echo "most frequented $n channels:" +for ((i=0; i 2 then + "GC with " + else + "DM with " + end + (.recipients | map(select(. != "'"$SELF_ID"'")) | join(", ")) + ")" + else + if has("name") then + "#" + .name + " (" + .guild.name + ")" + else + .id + " (unknown guild)" + end + end' c"${channels[$i]}"/channel.json) + echo "$((i + 1)). messages: ${counts[$i]}, channel: $channel_text" +done diff --git a/bash/grabber.sh b/bash/grabber.sh new file mode 100755 index 0000000..adb189d --- /dev/null +++ b/bash/grabber.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +set -e + +input="${1:?input not set}" +source="${2:?variable source not set}" + +css="$(cat "$input")" +IFS=$'\n' +for line in $css; do + _match="$(grep -m 1 -oP '(?<=var\()[^)\s]+(?=\))' <<< "$line" || :)" + if [[ ${_match:+x} == x ]]; then for match in $_match; do + color="$(grep -m 1 -oP "(?<=$match: )[^;\s]+(?=;)(?:\s+)?" "$source" | tail -n1 || :)" + if [[ ${color:+x} == x ]]; then + echo "replacing var($match) with $color" 1>&2 + sed -i s/var\("$match"\)/"$color"/g "$input" + else + echo "$match not defined in $source" 1>&2 + fi + done; fi +done diff --git a/bash/repo_build.sh b/bash/repo_build.sh new file mode 100755 index 0000000..4460eee --- /dev/null +++ b/bash/repo_build.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +# repo_build.sh +# Script to create a signed Arch Linux repository +# Usage: ./repo_build.sh [dir] [path-to-db] [key-id] + +cd "$(dirname "$0")" + +TARGET="${1-arch}" +DB_FILE="${2-slonkrepo.db.tar.xz}" +KEY_ID="${3-9C51F3072F8D11A2}" + +echo_stderr() { + echo $@ >&2 +} + +if [[ ! -d "${TARGET}" ]]; then + echo_stderr "Error: Not a directory: ${TARGET}" + exit 1 +fi + +for dir in "${TARGET}" "${TARGET}"/*; do + [[ ! -d "${dir}" ]] &&continue + echo_stderr "Processing '${dir}'" + cd "${dir}" + packages=() + for file in *.pkg*; do + [[ "${file}" == '*.pkg*' ]] && continue + [[ "${file}" =~ .*'.sig' ]] && continue + packages+=("${file}") + [[ -f "${file}.sig" ]] && continue + echo_stderr "Signing '${file}'" + if gpg -u "${KEY_ID}" --output "${file}.sig" --detach-sig "${file}"; then + echo_stderr "Signed '${file}'" + else + code=$? + echo_stderr "gpg failed with exit code ${code}." + exit $code + fi + done + if [[ "${#packages}" == 0 ]]; then + echo_stderr "No packages in '${dir}', ignoring" + cd - >/dev/null + continue + fi + if repo-add -n -s -k "${KEY_ID}" "${DB_FILE}" "${packages[@]}"; then + echo_stderr "Processed '${dir}'" + else + code=$? + echo_stderr "repo-add failed with exit code ${code}." + exit $code + fi + cd - >/dev/null +done + +echo_stderr "All done" diff --git a/bash/sshfs_mount_file.sh b/bash/sshfs_mount_file.sh new file mode 100755 index 0000000..03e857c --- /dev/null +++ b/bash/sshfs_mount_file.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +set -e + +# sshfs_mount_file.sh +# Uses sshfs to mount a single file by mounting the directory to a temporary directory and symlinking the file +# I wrote this to access my Minecraft accounts saved in PrismLauncher without keeping them on the unencrypted drive of my laptop, so that's why the defaults are like that +# Also, you can't have 2 active mounts at the same time. Would be cool if someone implemented that +# MOUNT_PATH has to be the same on both hosts +# Consider creating a ssh config file before using this +# Usage: +# ./sshfs_mount_file.sh mount [MOUNT_PATH] [HOST] +# ./sshfs_mount_file.sh umount [MOUNT_PATH] + +MOUNT_PATH="${2:-/home/afy/.local/share/PrismLauncher/accounts.json}" +MOUNT_DIR="$(dirname "$MOUNT_PATH")" +FILE_NAME="$(basename "$MOUNT_PATH")" +TEMP_DIR="/tmp/sshfs_mount_$UID" +HOST="${3:-remote-pc}" + +mount() { + mkdir -p "$TEMP_DIR" + chmod o-rwx "$TEMP_DIR" + sshfs "$HOST":"$MOUNT_DIR" "$TEMP_DIR" + mv "$MOUNT_PATH"{,.old} + ln -sf "$TEMP_DIR/$FILE_NAME" "$MOUNT_PATH" +} + +umount() { + mv "$MOUNT_PATH"{.old,} + fusermount3 -u "$TEMP_DIR" +} + +if [[ "$1" = "mount" ]]; then + mount +elif [[ "$1" = "umount" ]]; then + umount +elif [[ "$1" = "" ]]; then + echo "You need to specify a command" >&2 + echo "Valid commands: mount, umount" >&2 + exit 1 +else + echo "Invalid command: '$1'" >&2 + exit 1 +fi diff --git a/bash/start.sh b/bash/start.sh new file mode 100755 index 0000000..425c2a6 --- /dev/null +++ b/bash/start.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash + +# Minecraft server start.sh +# for use in slapping in tmux and forgetting +# also rolls logs + +# --- CONFIGURATION + +# you can source an environment file like this: +#. .env + +# auto-restart +AUTO_RESTART=${AUTO_RESTART:-1} # 1 to enable +RESTART_DELAY=${RESTART_DELAY:-3} # seconds +DONT_RESTART_IF=${DONT_RESTART_IF:0} # exit code + +# java options +JAVA_BIN=${JAVA_BIN:-java} # full path to the java binary +#JVM_FLAGS= # Java flags +#NO_DEFAULT_JVM_FLAGS= # 1 to disable default flags +MEMORY_MIN=${MEMORY_MIN-4G} # Xms +MEMORY_MAX=${MEMORY_MAX:-4G} # Xmx +GC=${GC:-ZGC} # ShenandoahGC or ZGC +GC_PAR_THREADS=${GC_PAR_THREADS:-6} # "parallel" (runs while workers are stopped) thread count + # recommended to set to just below total thread count +GC_CONC_THREADS=${GC_CONC_THREADS:-"$(( half=GC_PAR_THREADS / 2, half == 0 ? 1 : half ))"} + # marker thread count, defaults to half of parallel + +# mc options +MC_FLAGS=${MC_FLAGS:nogui} # Minecraft flags +SERVER_JAR=${SERVER_JAR:-server.jar} # server JAR file + +# --- + +gcs=( + [zgc]="-XX:+UseZGC" + [shenandoahgc]="-XX:+UseShenandoahGC" +) + +java_args=() + +[[ "${MEMORY_MIN:+x}" == 'x' ]] && java_args+=(-Xms"${MEMORY_MIN}") +java_args+=(-Xmx"${MEMORY_MAX}") + +(( ! NO_DEFAULT_JVM_FLAGS )) && java_args+=(-XX:+ParallelRefProcEnabled -XX:+DisableExplicitGC -XX:+PerfDisableSharedMem -XX:MaxGCPauseMillis=10) +if [[ "${MEMORY_MIN,,}" == "${MEMORY_MAX,,}" ]]; then + java_args+=(-XX:+AlwaysPreTouch) + [[ "${GC,,}" == 'zgc' ]] && java_args+=(-XX:-ZUncommit) +fi +if (( $( 0 )); then + java_args+=(-XX:+UseLargePages) + [[ $(&1 + + +while true; do + "${JAVA_BIN}" "${java_args[@]}" 2>&1 | tee ./"$(date -I)".log + exitcode=$? + + echo "Process exited with code "$'\033'"[3$(( exitcode == 0 ? 2 : 1 ))m${exitcode}"$'\033'"[0m" >&2 + + #shellcheck disable=SC2076 + (( AUTO_RESTART != 1 || DONT_RESTART_IF == exitcode )) && + exit "${exitcode}" + + echo "Restarting after ${RESTART_DELAY} seconds" >&2 + sleep "${RESTART_DELAY?}" +done diff --git a/c/cecho.c b/c/cecho.c new file mode 100644 index 0000000..47b3f7f --- /dev/null +++ b/c/cecho.c @@ -0,0 +1,13 @@ +#include + +int main(int argc, char **argv) { + for (int i = 1; i < argc; i++) { + printf("\033[%dm%s", i % 2, argv[i]); + if (i != argc - 1) + putchar(' '); + } + puts("\033[0m"); +} + +// Compilation: cc cecho.c -o cecho +// Usage: ./cecho ARG... diff --git a/js/svged.js b/js/svged.js new file mode 100755 index 0000000..465bdc9 --- /dev/null +++ b/js/svged.js @@ -0,0 +1,432 @@ +#!/usr/bin/env node +const { writeFileSync, readFileSync } = require("fs"); + +/** + * Colour class + * Represet the colour object and it's different types (HEX, RGBA, XYZ, LAB) + * This class have the ability to do the following + * 1. Convert HEX to RGBA + * 2. Convert RGB to XYZ + * 3. Convert XYZ to LAB + * 4. Calculate Delta E00 between two LAB colour (Main purpose) + * @author Ahmed Moussa + * @version 2.0 + * @link https://github.com/hamada147/IsThisColourSimilar + * @license Apache-2.0 + */ +class Color { + /** + * Convert HEX to LAB + * @param {[string]} hex hex colour value desired to be converted to LAB + */ + static hex2lab(hex) { + const [r, g, b, a] = Color.hex2rgba(hex); + const [x, y, z] = Color.rgb2xyz(r, g, b, a); + return Color.xyz2lab(x, y, z); // [l, a, b] + } + /** + * Convert RGBA to LAB + * @param {[Number]} r Red value from 0 to 255 + * @param {[Number]} g Green value from 0 to 255 + * @param {[Number]} b Blue value from 0 to 255 + */ + static rgba2lab(r, g, b, a = 1) { + const [x, y, z] = Color.rgb2xyz(r, g, b, a); + return Color.xyz2lab(x, y, z); // [l, a, b] + } + /** + * Convert LAB to RGBA + * @param {[Number]} l + * @param {[Number]} a + * @param {[Number]} b + */ + static lab2rgba(l, a, b) { + const [x, y, z] = Color.lab2xyz(l, a, b); + + return Color.xyz2rgba(x, y, z); // [r, g, b, a] + } + /** + * Convert HEX to RGBA + * @param {[string]} hex hex colour value desired to be converted to RGBA + */ + static hex2rgba(hex) { + let c; + if (hex.charAt(0) === "#") { + c = hex.substring(1).split(""); + } + if (c.length > 6 || c.length < 3) { + throw new Error( + `HEX colour must be 3 or 6 values. You provided it ${c.length}` + ); + } + if (c.length === 3) { + c = [c[0], c[0], c[1], c[1], c[2], c[2]]; + } + c = "0x" + c.join(""); + let r = (c >> 16) & 255; + let g = (c >> 8) & 255; + let b = c & 255; + let a = 1; + return [r, g, b, a]; + } + /** + * Convert RGB to XYZ + * @param {[Number]} r Red value from 0 to 255 + * @param {[Number]} g Green value from 0 to 255 + * @param {[Number]} b Blue value from 0 to 255 + * @param {Number} [a=1] Obacity value from 0 to 1 with a default value of 1 if not sent + */ + static rgb2xyz(r, g, b, a = 1) { + if (r > 255) { + // console.warn("Red value was higher than 255. It has been set to 255."); + r = 255; + } else if (r < 0) { + // console.warn("Red value was smaller than 0. It has been set to 0."); + r = 0; + } + if (g > 255) { + // console.warn("Green value was higher than 255. It has been set to 255."); + g = 255; + } else if (g < 0) { + // console.warn("Green value was smaller than 0. It has been set to 0."); + g = 0; + } + if (b > 255) { + // console.warn("Blue value was higher than 255. It has been set to 255."); + b = 255; + } else if (b < 0) { + // console.warn("Blue value was smaller than 0. It has been set to 0."); + b = 0; + } + if (a > 1) { + // console.warn("Obacity value was higher than 1. It has been set to 1."); + a = 1; + } else if (a < 0) { + // console.warn("Obacity value was smaller than 0. It has been set to 0."); + a = 0; + } + r = r / 255; + g = g / 255; + b = b / 255; + // step 1 + if (r > 0.04045) { + r = Math.pow((r + 0.055) / 1.055, 2.4); + } else { + r = r / 12.92; + } + if (g > 0.04045) { + g = Math.pow((g + 0.055) / 1.055, 2.4); + } else { + g = g / 12.92; + } + if (b > 0.04045) { + b = Math.pow((b + 0.055) / 1.055, 2.4); + } else { + b = b / 12.92; + } + // step 2 + r = r * 100; + g = g * 100; + b = b * 100; + // step 3 + const x = r * 0.4124564 + g * 0.3575761 + b * 0.1804375; + const y = r * 0.2126729 + g * 0.7151522 + b * 0.072175; + const z = r * 0.0193339 + g * 0.119192 + b * 0.9503041; + return [x, y, z]; + } + /** + * Convert XYZ to RGBA + * @param {[Number]} x + * @param {[Number]} y + * @param {[Number]} z + */ + static xyz2rgba(x, y, z) { + let varX = x / 100; + let varY = y / 100; + let varZ = z / 100; + + let varR = varX * 3.2404542 + varY * -1.5371385 + varZ * -0.4985314; + let varG = varX * -0.969266 + varY * 1.8760108 + varZ * 0.041556; + let varB = varX * 0.0556434 + varY * -0.2040259 + varZ * 1.0572252; + + if (varR > 0.0031308) { + varR = 1.055 * Math.pow(varR, 1 / 2.4) - 0.055; + } else { + varR = 12.92 * varR; + } + if (varG > 0.0031308) { + varG = 1.055 * Math.pow(varG, 1 / 2.4) - 0.055; + } else { + varG = 12.92 * varG; + } + if (varB > 0.0031308) { + varB = 1.055 * Math.pow(varB, 1 / 2.4) - 0.055; + } else { + varB = 12.92 * varB; + } + + let r = Math.round(varR * 255); + let g = Math.round(varG * 255); + let b = Math.round(varB * 255); + + return [r, g, b, 1]; + } + /** + * Convert XYZ to LAB + * @param {[Number]} x Value + * @param {[Number]} y Value + * @param {[Number]} z Value + */ + static xyz2lab(x, y, z) { + // using 10o Observer (CIE 1964) + // CIE10_D65 = {94.811f, 100f, 107.304f} => Daylight + const referenceX = 94.811; + const referenceY = 100; + const referenceZ = 107.304; + // step 1 + x = x / referenceX; + y = y / referenceY; + z = z / referenceZ; + // step 2 + if (x > 0.008856) { + x = Math.pow(x, 1 / 3); + } else { + x = 7.787 * x + 16 / 116; + } + if (y > 0.008856) { + y = Math.pow(y, 1 / 3); + } else { + y = 7.787 * y + 16 / 116; + } + if (z > 0.008856) { + z = Math.pow(z, 1 / 3); + } else { + z = 7.787 * z + 16 / 116; + } + // step 3 + const l = 116 * y - 16; + const a = 500 * (x - y); + const b = 200 * (y - z); + return [l, a, b]; + } + /** + * Convert LAB to XYZ + * @param {[Number]} l + * @param {[Number]} a + * @param {[Number]} b + */ + static lab2xyz(l, a, b) { + // using 10o Observer (CIE 1964) + // CIE10_D65 = {94.811f, 100f, 107.304f} => Daylight + const referenceX = 94.811; + const referenceY = 100; + const referenceZ = 107.304; + + let varY = (l + 16) / 116; + let varX = a / 500 + varY; + let varZ = varY - b / 200; + + if (Math.pow(varY, 3) > 0.008856) { + varY = Math.pow(varY, 3); + } else { + varY = (varY - 16 / 116) / 7.787; + } + if (Math.pow(varX, 3) > 0.008856) { + varX = Math.pow(varX, 3); + } else { + varX = (varX - 16 / 116) / 7.787; + } + if (Math.pow(varZ, 3) > 0.008856) { + varZ = Math.pow(varZ, 3); + } else { + varZ = (varZ - 16 / 116) / 7.787; + } + + let x = varX * referenceX; + let y = varY * referenceY; + let z = varZ * referenceZ; + + return [x, y, z]; + } + /** + * The difference between two given colours with respect to the human eye + * @param {[type]} l1 Colour 1 + * @param {[type]} a1 Colour 1 + * @param {[type]} b1 Colour 1 + * @param {[type]} l2 Colour 2 + * @param {[type]} a2 Colour 2 + * @param {[type]} b2 Colour 2 + */ + static deltaE00(l1, a1, b1, l2, a2, b2) { + // Utility functions added to Math Object + Math.rad2deg = function (rad) { + return (360 * rad) / (2 * Math.PI); + }; + Math.deg2rad = function (deg) { + return (2 * Math.PI * deg) / 360; + }; + // Start Equation + // Equation exist on the following URL http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CIE2000.html + const avgL = (l1 + l2) / 2; + const c1 = Math.sqrt(Math.pow(a1, 2) + Math.pow(b1, 2)); + const c2 = Math.sqrt(Math.pow(a2, 2) + Math.pow(b2, 2)); + const avgC = (c1 + c2) / 2; + const g = + (1 - + Math.sqrt( + Math.pow(avgC, 7) / (Math.pow(avgC, 7) + Math.pow(25, 7)) + )) / + 2; + + const a1p = a1 * (1 + g); + const a2p = a2 * (1 + g); + + const c1p = Math.sqrt(Math.pow(a1p, 2) + Math.pow(b1, 2)); + const c2p = Math.sqrt(Math.pow(a2p, 2) + Math.pow(b2, 2)); + + const avgCp = (c1p + c2p) / 2; + + let h1p = Math.rad2deg(Math.atan2(b1, a1p)); + if (h1p < 0) { + h1p = h1p + 360; + } + + let h2p = Math.rad2deg(Math.atan2(b2, a2p)); + if (h2p < 0) { + h2p = h2p + 360; + } + + const avghp = + Math.abs(h1p - h2p) > 180 ? (h1p + h2p + 360) / 2 : (h1p + h2p) / 2; + + const t = + 1 - + 0.17 * Math.cos(Math.deg2rad(avghp - 30)) + + 0.24 * Math.cos(Math.deg2rad(2 * avghp)) + + 0.32 * Math.cos(Math.deg2rad(3 * avghp + 6)) - + 0.2 * Math.cos(Math.deg2rad(4 * avghp - 63)); + + let deltahp = h2p - h1p; + if (Math.abs(deltahp) > 180) { + if (h2p <= h1p) { + deltahp += 360; + } else { + deltahp -= 360; + } + } + + const deltalp = l2 - l1; + const deltacp = c2p - c1p; + + deltahp = + 2 * Math.sqrt(c1p * c2p) * Math.sin(Math.deg2rad(deltahp) / 2); + + const sl = + 1 + + (0.015 * Math.pow(avgL - 50, 2)) / + Math.sqrt(20 + Math.pow(avgL - 50, 2)); + const sc = 1 + 0.045 * avgCp; + const sh = 1 + 0.015 * avgCp * t; + + const deltaro = 30 * Math.exp(-Math.pow((avghp - 275) / 25, 2)); + const rc = + 2 * + Math.sqrt( + Math.pow(avgCp, 7) / (Math.pow(avgCp, 7) + Math.pow(25, 7)) + ); + const rt = -rc * Math.sin(2 * Math.deg2rad(deltaro)); + + const kl = 1; + const kc = 1; + const kh = 1; + + const deltaE = Math.sqrt( + Math.pow(deltalp / (kl * sl), 2) + + Math.pow(deltacp / (kc * sc), 2) + + Math.pow(deltahp / (kh * sh), 2) + + rt * (deltacp / (kc * sc)) * (deltahp / (kh * sh)) + ); + + return deltaE; + } + /** + * Get darker colour of the given colour + * @param {[Number]} r Red value from 0 to 255 + * @param {[Number]} g Green value from 0 to 255 + * @param {[Number]} b Blue value from 0 to 255 + */ + static getDarkerColour(r, g, b, a = 1, darkenPercentage = 0.05) { + let [l1, a1, b1] = Color.rgba2lab(r, g, b, a); + l1 -= l1 * darkenPercentage; + if (l1 < 0) { + l1 = 0; + } + return Color.lab2rgba(l1, a1, b1); // [R, G, B, A] + } + /** + * Get brighter colour of the given colour + * @param {[Number]} r Red value from 0 to 255 + * @param {[Number]} g Green value from 0 to 255 + * @param {[Number]} b Blue value from 0 to 255 + */ + static getBrighterColour(r, g, b, a = 1, brighterPercentage = 0.05) { + let [l1, a1, b1] = Color.rgba2lab(r, g, b, a); + l1 += l1 * brighterPercentage; + if (l1 > 100) { + l1 = 100; + } + return Color.lab2rgba(l1, a1, b1); // [R, G, B, A] + } +} + +const CATPPUCCIN_MOCHA = [ + Color.hex2lab("#f5e0dc"), + Color.hex2lab("#f2cdcd"), + Color.hex2lab("#f5c2e7"), + Color.hex2lab("#cba6f7"), + Color.hex2lab("#f38ba8"), + Color.hex2lab("#eba0ac"), + Color.hex2lab("#fab387"), + Color.hex2lab("#f9e2af"), + Color.hex2lab("#a6e3a1"), + Color.hex2lab("#94e2d5"), + Color.hex2lab("#89dceb"), + Color.hex2lab("#74c7ec"), + Color.hex2lab("#89b4fa"), + Color.hex2lab("#b4befe"), + Color.hex2lab("#cdd6f4"), + Color.hex2lab("#bac2de"), + Color.hex2lab("#a6adc8"), + Color.hex2lab("#9399b2"), + Color.hex2lab("#7f849c"), + Color.hex2lab("#6c7086"), + Color.hex2lab("#585b70"), + Color.hex2lab("#45475a"), + Color.hex2lab("#313244"), + Color.hex2lab("#1e1e2e"), + Color.hex2lab("#181825"), + Color.hex2lab("#11111b"), +]; + +let file = process.argv[2]; +if (!file) process.exit(1); + +let palette = [...CATPPUCCIN_MOCHA]; + +writeFileSync( + file, + readFileSync(file, "utf8").replace(/#[0-9a-f]{6}/g, (match) => { + let color = Color.hex2lab(match); + let newColor = palette.sort( + (a, b) => + Color.deltaE00(...color, ...a) - Color.deltaE00(...color, ...b) + )[0]; + let [r, g, b] = Color.lab2rgba(...newColor); + let hex = + "#" + + r.toString(16).padStart(2, "0") + + g.toString(16).padStart(2, "0") + + b.toString(16).padStart(2, "0"); + return hex; + }) +);