remove commit history

This commit is contained in:
slonkazoid 2024-08-30 15:24:30 +03:00
commit b04c834c64
Signed by: slonk
SSH key fingerprint: SHA256:tbZfJX4IOvZ0LGWOWu5Ijo8jfMPi78TU7x1VoEeCIjM
12 changed files with 1064 additions and 0 deletions

122
LICENSE Normal file
View file

@ -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.

16
README.md Normal file
View file

@ -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) | |

66
bash/arshit/README.md Normal file
View file

@ -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=<system dependent>
# 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.**

129
bash/arshit/arshit.sh Executable file
View file

@ -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 $?

1
bash/avt/README.md Normal file
View file

@ -0,0 +1 @@
# Moved to [slonkazoid/avt](https://gitlab.com/alifurkany/avt)

79
bash/frequented.sh Executable file
View file

@ -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<n; i++)); do echo "${1?}"; done
}
# shellcheck disable=SC2207
counts=($(repeat '0' $n))
# shellcheck disable=SC2207
channels=($(repeat 'none' $n))
# insert $1 and $2 into index $3 and drop last element
insert() {
local value1=${1?}
local value2=${2?}
local idx=${3:-0}
counts=("${counts[@]:0:$idx}" "$value1" "${counts[@]:$idx:$((n - idx - 1))}")
channels=("${channels[@]:0:$idx}" "$value2" "${channels[@]:$idx:$((n - idx - 1))}")
}
total_messages=0
total_channels=0
word_count() {
local total=0
while read -r; do
total=$((total + 1))
done
echo $total
}
time for dir in c*; do
channel=${dir#c}
count=$(("$(word_count < "$dir"/messages.csv)" - 1))
total_messages=$((total_messages + count))
total_channels=$((total_channels + 1))
for ((i=0; i<n; i++)); do
if ((count > ${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<n; i++)); do
# 1 and 3 are DM and GROUP_DM respectively
# https://discord.com/developers/docs/resources/channel#channel-object-channel-types
# if you want to understand the following `jq` filter,
# start reading here: https://jqlang.github.io/jq/manual/#basic-filters
channel_text=$(jq 'if .type == 1 // .type == 3 then
.id + " (" + if (.recipients | length) > 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

21
bash/grabber.sh Executable file
View file

@ -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

56
bash/repo_build.sh Executable file
View file

@ -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"

44
bash/sshfs_mount_file.sh Executable file
View file

@ -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

85
bash/start.sh Executable file
View file

@ -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 (( $(</proc/sys/vm/nr_hugepages) > 0 )); then
java_args+=(-XX:+UseLargePages)
[[ $(</sys/kernel/mm/transparent_hugepage/enabled) != 'never' ]] && java_args+=(-XX:+UseTransparentHugePages)
fi
java_args+=("${gcs[${GC,,}]}")
[[ "${GC_CONC_THREADS:+x}" == 'x' ]] && java_args+=(-XX:ConcGCThreads="${GC_CONC_THREADS}")
java_args+=(-XX:ParallelGCThreads="${GC_PAR_THREADS}")
# shellcheck disable=SC2206
java_args+=(${JVM_FLAGS})
# shellcheck disable=SC2206
java_args+=(-jar "${SERVER_JAR}" $MC_FLAGS)
{
echo "Using settings:"
echo "Java binary: ${JAVA_BIN?}"
# shellcheck disable=SC2145
echo "Options: ${java_args[@]@Q} ${}"
echo "Auto restart: $( (( AUTO_RESTART )) && echo 'enabled' || echo 'disabled' )"
} 2>&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

13
c/cecho.c Normal file
View file

@ -0,0 +1,13 @@
#include <stdio.h>
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...

432
js/svged.js Executable file
View file

@ -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 <moussa.ahmed95@gmail.com>
* @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;
})
);