Skip to content

Instantly share code, notes, and snippets.

@Alhadis
Last active April 20, 2024 06:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Alhadis/7ecf06decc7876c8b35007124654a13e to your computer and use it in GitHub Desktop.
Save Alhadis/7ecf06decc7876c8b35007124654a13e to your computer and use it in GitHub Desktop.
More junk
"use strict";
const {TextBuffer, TextEditor, Range, Point} = require("atom");
const ScreenLineBuilder = loadFromCore("text-buffer/lib/screen-line-builder");
const {emitText} = ScreenLineBuilder.prototype;
const exoticNewlines = "\r";
ScreenLineBuilder.prototype.emitText = function(...args){
const {buffer} = this.displayLayer;
const [char] = args;
if(char === buffer.getPreferredLineEnding() && exoticNewlines.includes(char)){
this.currentBuiltInClassNameFlags |= 1 << 6; // LINE_ENDING
return this.emitLineEnding();
}
return emitText.call(this, ...args);
};
const {lineLengthForRow} = TextBuffer.prototype;
TextBuffer.prototype.lineLengthForRow = function(...args){
const eol = this.getPreferredLineEnding();
if(exoticNewlines.includes(eol)){
const [line] = args;
const breaks = this.findAllSync(eol);
const start = Point.fromObject(breaks[line + 0].end);
const end = Point.fromObject(breaks[line + 1].start);
return this.characterIndexForPosition(end) - this.characterIndexForPosition(start);
}
return lineLengthForRow.call(this, ...args);
};
#!/usr/bin/env fontforge
New()
# NOTE: The weird-looking song-and-dance is because FontForge's
# scripting language lacks support for user-defined functions…
chars = ["a", "b", "c", "d"]
case = "lower"
i = 0
while(i++ < 2)
j = 0
while(j < SizeOf(chars))
code = 0x61
if("upper" == case)
code = 0x41
endif
Select(code + j)
Import("glyphs/" + case + "-" + chars[j++] + ".svg", 0, 64)
endloop
case = "upper"
endloop
SelectAll()
DontAutoHint()
# Choose an ugly-looking name unlikely to be used
name = "canvas-html-font-test"
SetTTFName(0x409, 0, "-")
SetTTFName(0x409, 1, name)
SetTTFName(0x409, 3, name)
SetTTFName(0x409, 4, name)
SetTTFName(0x409, 6, name)
SetTTFName(0x409, 5, "Version 1")
SetTTFName(0x409, 2, "Regular")
Generate(name + ".ttf")
/^--- /,/^+++ /d
/^\\ No newline at end of file$/d
s/^-/-/
s/^+/+/
s/^@/@/
/^--- /,/^+++ /d
/^\\ No newline at end of file$/d
#!/usr/bin/env ruby
require "pp"
module Linguist
module Grammars
# Public: Recognised hosting providers for Git repositories.
#
# Hosted repositories are required to have URLs of the form:
# https://[www.]${HOST}/${USERNAME}/${REPOSITORY_NAME}[.git]
HOSTS = %w[
github.com
bitbucket.org
gitlab.com
].freeze
# Internal: Path to Linguist's root directory.
ROOT = File.expand_path("../../..", __FILE__)
# Public: Get the path to the directory containing the language grammar JSON files.
#
# Returns a String.
def self.path
File.expand_path("grammars", ROOT)
end
# Public: Expand a whitelisted hostname into an HTTPS URL.
#
# If the string begins with a protocol component and/or
# redundant subdomain, they're stripped before lookup.
#
# Example
#
# Grammars.expand_hostname "https://www.github.com"
# Grammars.expand_hostname "www.github.com"
# Grammars.expand_hostname "github.com"
# Grammars.expand_hostname "GitHub"
# # => "https://github.com/"
#
# Grammars.expand_hostname "https://github.com/", fqdn: true
# # => "github.com"
#
# name - Hostname, with or without TLD
# fqdn - Return a fully-qualified domain name, rather than an HTTPS URL.
#
# Returns a String, or nil if input wasn't a supported hostname.
def self.expand_hostname(host, fqdn = false)
host = host.downcase.gsub(%r{^[+a-z]*:(?://)?(?:www\.)?|/$}, "")
host = HOSTS.select {|str| str.start_with? host}.first
host = "https://#{host}/" unless fqdn or host.nil
host
end
# Isolate the vendor-name component of a submodule path.
#
# Example
#
# Grammars.resolve_path "./vendor/grammars/language-etc/"
# Grammars.resolve_path "grammars/language-etc"
# Grammars.resolve_path "language-etc"
# # => "vendor/grammars/language-etc"
#
# name - A submodule's name or project-relative path.
# absolute - Resolve the absolute, canonicalised path of a submodule directory.
# By default, paths are resolved relative to Linguist's root.
#
# Returns a String.
def self.resolve_path(name, absolute: false, ignore_missing: false)
name =~ %r{^(?:.*(?:vendor/)?grammars/)?([^/]+)/?$}i
path = "vendor/grammars/#{$1}"
unless ignore_missing or File.exist?("#{ROOT}/" + path)
raise "Submodule '#{path}' does not exist"
end
path
end
# Break a repository URL into its separate components.
#
# Examples
#
# Grammars.resolve_url "https://github.com/Alhadis/language-etc"
# Grammars.resolve_url "git@github.com:Alhadis/language-etc.git"
# Grammars.resolve_url "github.com/Alhadis/language-etc"
# Grammars.resolve_url "github:Alhadis/language-etc"
# Grammars.resolve_url "Alhadis/language-etc"
# # => {host: "github.com", user: "Alhadis", repo: "language-etc"}
#
# Grammars.resolve_url "https://bitbucket.org/bitlang/sublime_cobol"
# Gramamrs.resolve_url "bitbucket:bitlang/sublime_cobol"
# # => {host: "bitbucket.org", user: "bitlang", repo: "sublime_cobol"}
#
# input - A URL referring to a whitelisted host: GitHub
# strict - Raise an exception for an unrecognised URL
#
# Returns a Hash with the following fields:
# :host - A hostname whitelisted by ::HOSTS array (lowercased)
# :user - Username, case-sensitive and `:repo`
# :repo - Repository name, sans trailing `.git` suffix
# :branch - A specific branch name, if one was specified
# :commit - A commit ID (SHA-1 or SHA-256)
# :tag - A tag name, typically a version string
# :url - Original input passed through ::expand_hostname
def self.resolve_url(input, strict: false)
hosts = Regexp.union(HOSTS)
# Initialise fields
url = input
host = nil
user = nil
repo = nil
branch = nil
commit = nil
tag = nil
# HTTPS/HTTP link pointing to a recognised host
if input =~ %r{
^
(?:
# URI scheme
(?<scheme> (?:git\+)?https?|git){0}
(?<protocol>
\g<scheme> : (?!//) | # Tolerate "https:github.com/user/repo"
\g<scheme>? : // | # Tolerate "://github.com/user/repo"
)
# Credentials (ignored since grammar repos must be public)
(?<userinfo>
(?<username> [^\S:@/]+)
(?<password> : [^\S:@/]+)? @
)?
)?
# Hostname
(?:www\.)? # Redundant subdomain
(?<host> #{hosts}) # Whitelisted hostnames
# Path components
/ (?<user> [^/\#@]+) # Username
/ (?<repo> [^/\#@]+) # Repository name
/? (?<extra> (?!/)\S+)? # Superfluous elements
$
}ix
host = $~[:host]
user = $~[:user]
repo = $~[:repo]
# As an extension, allow specific branches and revisions to be specified
if $~.named_captures.has_key?(:extra) and $~[:extra] =~ %r{
^
(?<rev-type>
\# (?<param-sep> :){0} (?!:) | # NPM style syntax: user/repo#rev
@ (?<param-sep> /){0} (?!/) # Git syntax: user/repo@rev
)
(?<rev-value>
\g<rev-explicitly-typed> |
\g<rev-implicitly-typed>
)
$
# Public subroutines
(?<branch> [^\s/]+(?:/[^\s/]+)++ ){0}
(?<commit> \b\h+{4,} ){0}
(?<tag> (?!\S*?\h{40,}$) \S+ ){0}
# Internal subroutines
(?<sep> \b){0}
(?<affix> [-_.]?(?:id|name)? \k'sep'){0}
(?<rev-implicitly-typed> \g<tag> | \g<commit> | \g<branch>){0}
(?<rev-explicitly-typed>
(?<param-name> branch|tree) \g<affix> \g<branch> |
(?<param-name> rev(?:ision)?|commit) \g<affix> \g<commit> |
(?<param-name> tag|release|version|semver) \g<affix> \g<tag>
){0}
}xi
branch = $~[:branch]
commit = $~[:commit]
tag = $~[:tag]
end
repo.sub!(/\.git$/, "")
# git@provider:user/repo.git
elsif input =~ /^git@(#{hosts}):([^\/]+)\/([^\/]+)\.git$/i
host = $1
user = $2
repo = $3
# provider:user/repo
elsif input =~ /^(github|bitbucket|gitlab):\/?([^\/]+)\/([^\/]+)\/?$/i
host = $1
user = $2
repo = $3
# user/repo - Common GitHub shorthand
elsif input =~ /^\/?([^\/]+)\/([^\/]+)\/?$/
host = self.expand_hostname "github", fqdn: true
url = "https://#{host}/#{user}/#{repo}"
user = $1
repo = $2
# Nothing we can reliably identify
else
raise "Unsupported URL: #{input}" if strict
return nil
end
# Clean-up and sanity checks
host = self.expand_hostname(host, true)
if !commit.nil? then branch = tag = nil
elsif !branch.nil? then tag = nil
end
return ({
url: url,
host: host,
user: user,
repo: repo,
branch: branch,
commit: commit,
tag: tag,
}).freeze
end
end
end
if __FILE__ == $0
ARGV.each do |arg|
info = Linguist::Grammars.resolve_url(arg)
p info
end
end
#!/usr/bin/env node
class Irrational extends Number{
#ƒ;
constructor(ƒ = () => Infinity){
super(ƒ());
this.#ƒ = ƒ;
}
valueOf(){
return this.#ƒ();
}
}
// Golden ratio
const ϕ = new Irrational(() => (1 + Math.sqrt(5)) / 2);
console.log(ϕ);
# Terminate with an error message
# - Usage: die [reason] [exit-code=1]
die(){
printf '%s\n' "$1"
[ "$2" ] && exit $2 || exit 1
}
# Verify that an executable is installed and reachable from $PATH
need(){
while [ $# -gt 0 ]; do
command -v "$1" 2>&1 >/dev/null || die "Unable to resolve path to $1" 2
shift
done
}
# Read the entirety of standard input and place it $1
# - Usage: slurp [var-name] < [input]
slurp(){
while IFS= read -r line; do
set -- "$1" "$2`printf '\n%s' "$line"`"
done
set -- "$1" "`printf '%s\n' "$2" | sed -n 's/^[[:blank:]]//; /[^ \t]/,$p;'`"
eval $1=\$2
}
export ELECTRON_RUN_AS_NODE=1
export ELECTRON_ENABLE_LOGGING=true
export NODE_OPTIONS='--experimental-modules --no-warnings --input-type=module'
# Resolve directory for temporary file-storage
[ -d "$EAL_TEMP_DIR" ] \
|| export EAL_TEMP_DIR=`mktemp -d /tmp/Alhadis.EAL.XXXXXX` \
|| die 'Failed to create temporary directory. Aborting.' 2
# Resolve paths to different JavaScript interpreters/environments
need node deno qjs d8
<!DOCTYPE html>
<html lang="en-AU">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="initial-scale=1, minimum-scale=1"/>
<title>Detached stylesheet loading without Shadow DOM</title>
</head>
<body><script>
"use strict";
async function loadCSSFile(path){
path = String(path).replace(/\/$/, "");
if(!path.startsWith("/"))
throw new TypeError("Stylesheet path must be absolute");
const cut = path.lastIndexOf("/");
const dir = path.slice(0, cut);
const name = path.slice(cut + 1);
const url = new URL("file://");
url.pathname = dir;
let init = Promise.withResolvers();
const frame = Object.assign(document.createElement("iframe"), {
src: "about:blank",
width: 100,
height: 100,
sandbox: "allow-same-origin",
onerror: init.reject,
onload: init.resolve,
});
document.currentScript.parentElement.insertBefore(frame, document.currentScript);
await init.promise;
init = Promise.withResolvers();
const doc = frame.contentDocument;
const base = Object.assign(doc.createElement("base"), {href: url});
const link = Object.assign(doc.createElement("link"), {
onerror: init.reject,
onload: init.resolve,
type: "text/css",
rel: "stylesheet",
href: name,
});
doc.head.append(base, link);
await init.promise;
const {sheet} = link;
frame.remove();
link.href = "";
return sheet;
}
loadCSSFile("/Users/Alhadis/Labs/Sandbox/rel.css").then(x => {
console.log(x);
});
</script></body>
</html>
/**
* Merge adjacent strings in an array.
* @param {*} input - An array to operate on; non-arrays (except for symbols) are stringified
* @param {Boolean} [unwrap=false] - Unbox single-element arrays after normalising
* @param {Boolean} [recurse=true] - Merge adjacent strings in subarrays
* @param {WeakSet} [refs] - List of normalised objects used internally to avoid infinite loops
* @return {Array|String}
*/
function normalise(input, unwrap = false, recurse = true, refs = new WeakSet()){
if(!isObj(input))
return "symbol" === typeof input ? input : String(input);
if(refs.has(input)) return;
refs.add(input);
// Merge adjacent strings together
for(let i = input.length - 1, j = 0; i >= -1; --i){
if("string" !== typeof input[i]){
if(j > 1){
const chars = input.splice(i + 1, j, "");
input[i + 1] = chars.join("");
}
j = 0;
// Recurse
if(recurse && Array.isArray(input[i]))
input[i] = normalise(input[i], unwrap, true, refs);
}
else ++j;
}
if(unwrap)
while(Array.isArray(input) && 1 === input.length)
input = input[0];
return input;
}
/**
* Parse the porcelain output of git-status(1).
*
* @example parseGitStatus(execSync("git status --ignored --porcelain=2 -bz"));
* @param {String} input
* @return {Object}
*/
export function parseGitStatus(input){
input = String(input).trim();
const raw = ~input.indexOf("\0");
const status = {};
const changes = [];
const conflicts = [];
const ignored = [];
const untracked = [];
const statusCodes = {
".": "unmodified",
"?": "untracked",
"!": "ignored",
M: "modified",
A: "added",
D: "deleted",
R: "renamed",
C: "copied",
T: "typechange",
X: "unknown",
U: "updated but unmerged",
};
const oct = x => x.map(n => parseInt(n, 8));
const sub = ([S, SC, SM, SU]) => "S" === S && {
commitChanged: "C" === SC,
hasTrackedChanges: "M" === SM,
hasUntrackedChanges: "U" === SU,
};
for(const line of input.split(raw ? "\0" : "\n").filter(Boolean)){
const [prefix, ...entries] = line.split(" ");
switch(prefix){
// Branch header
case "#": {
switch(entries.shift()){
case "branch.oid":
status.currentCommit = "(initial)" === entries[0] ? null : entries[0];
break;
case "branch.head":
status.currentBranch = "(detached)" === entries[0] ? null : entries[0];
break;
case "branch.upstream":
(status.upstream = status.upstream || {}).branch = entries[0];
break;
case "branch.ab":
status.upstream = status.upstream || {};
status.upstream.ahead = Math.abs(parseInt(entries[0]));
status.upstream.behind = Math.abs(parseInt(entries[1]));
break;
}
break;
}
// Changed file
case "1":
case "2": {
1 === +prefix && entries.splice(7, 0, null);
let [XY, s, mH, mI, mW, hH, hI, score, path] = entries;
[mH, mI, mW] = oct([mH, mI, mW]);
path = path.split(raw ? "\0" : "\t");
changes.push({
state: {index: statusCodes[XY[0]], workTree: statusCodes[XY[1]]},
submodule: sub(s),
mode: mH === mI && mI === mW ? mH : {head: mH, index: mI, workTree: mW},
hash: hH === hI ? hH : {head: hH, index: hI},
similarity: score && {action: {R: "renamed", C: "copied"}[score[0]], amount: +score.substring(1)},
path: path[1] ? {old: path[1], new: path[0]} : path[0],
});
break;
}
// Unmerged path (conflict)
case "u": {
let [XY, s, m1, m2, m3, mW, h1, h2, h3, path] = entries;
[m1, m2, m3, mW] = oct([m1, m2, m3, mW]);
conflicts.push({
state: {us: statusCodes[XY[0]], them: statusCodes[XY[1]]},
submodule: sub(s),
mode: {stage1: m1, stage2: m2, stage3: m3, workTree: mW},
hash: {stage1: h1, stage2: h2, stage3: h3},
path,
});
break;
}
// Untracked or ignored
case "?": untracked.push(entries.join(" ")); break;
case "!": ignored .push(entries.join(" ")); break;
}
}
status.isNewRepo = null === status.currentCommit;
status.isDetached = null === status.currentBranch;
status.isClean = !(changes.length + conflicts.length + ignored.length + untracked.length);
if(changes.length) status.changes = changes;
if(conflicts.length) status.conflicts = conflicts;
if(ignored.length) status.ignored = ignored;
if(untracked.length) status.untracked = untracked;
return status;
}
import {isPrimitive, uint} from "./utils/misc.mjs";
export default class Point extends Uint32Array {
static [Symbol.species] = Array;
static get zero(){
return new this(0, 0);
}
static get zenith(){
const max = (this.BYTES_PER_ELEMENT ** 16) - 1;
return new this(max, max);
}
static parse(input){
if(isPrimitive(input))
return [input, 0];
else if("function" === typeof input[Symbol.iterator]){
const [line, column] = input[Symbol.iterator]();
return [line, column];
}
else{
const keys = Object.keys(input);
const line = keys.find(key => /^(?:line|row|x|0)$/i);
const column = keys.find(key => /^(?:column|col|y|1)$/i);
if(line && column)
return [input[line], input[column]];
}
throw new TypeError(`Invalid vector: ${input}`);
}
constructor(...args){
super(2);
if(1 === args.length){
args = Point.parseVector(args);
this[0] = uint(args[0]);
this[1] = uint(args[1]);
}
else{
this[0] = uint(args[0]);
this[1] = uint(args[1]);
}
}
get row(){ return this.line; }
set row(to){ this.line = to; }
get line(){ return this[0]; }
set line(to){
if(!Number.isNaN(to = uint(to)))
this[0] = to;
}
get column(){
return uint(this[1]);
}
set column(to){
if(Number.isNaN(to = uint(to)))
this[1] = to;
}
valueOf(){
return parseFloat(`${this.line}.${this.column}`);
}
toJSON(){
return [this.line, this.column];
}
toString(){
return `[${this.line}, ${this.column}]`;
}
}
# vim: ts=4
def define_effect(name, start_esc, end_esc)
start_esc = "\e[#{start_esc.join(";")}m" if start_esc .is_a? Array
end_esc = "\e[#{end_esc.join(";")}m" if end_esc .is_a? Array
start_esc = "\e[#{start_esc}m" if start_esc .is_a? Integer
end_esc = "\e[#{end_esc}m" if end_esc .is_a? Integer
self.class.define_method(name.to_sym) do |*args|
arg = args.first
case arg
when true, nil then start_esc
when false then end_esc
when arg.is_a?(String) then start_esc + arg + end_esc
end
end
end
# Public: Helpers functions for preparing highlighted terminal output.
module SGR
# Public: Determine if colours should be used when writing output to an IO stream.
#
# Examples
#
# $ ruby sgr-test.rb 2>/dev/null
# SGR.use_colours?(STDOUT) # => true
# SGR.use_colours?(STDERR) # => false
#
# io - An IO stream, STDOUT by default
#
# Returns a Boolean.
def self.use_colours?(io = $>)
return true if forced?
return false if disabled? or dumb?
return io.isatty
end
define_effect :bold, 1, 21
define_effect :fade, 2, 22
define_effect :italic, 3, 23
define_effect :ul, 7, 27
# Simple colour definitions
def self.red (*args); get_colour 1, *args; end
def self.green (*args); get_colour 2, *args; end
def self.yellow (*args); get_colour 3, *args; end
def self.blue (*args); get_colour 4, *args; end
def self.pink (*args); get_colour 5, *args; end
def self.cyan (*args); get_colour 6, *args; end
def self.grey (*args); get_colour 7, *args; end
def self.dark_red (*args); get_colour [1, 88, "80;0;0"], *args; end
def self.dark_green (*args); get_colour [2, 22, "0;80;0"], *args; end
def self.dark_yellow (*args); get_colour [3, 58, "80;80;0"], *args; end
def self.dark_blue (*args); get_colour [4, 55, "0;0;80"], *args; end
def self.dark_pink (*args); get_colour [4, 89, "80;0;80"], *args; end
def self.dark_cyan (*args); get_colour [6, 23, "0;80;80"], *args; end
def self.dark_grey (*args); get_colour [7, 234, "80;80;80"], *args; end
# Internal: Resolve arguments for a method that returns an ANSI escape.
#
# start_esc - Sequence that begins the desired effect
# end_esc - Sequence that ends the desired effect
# enable - Which of the two sequences to use
# io - IO stream, STDOUT by default
#
# Returns a String if coloured output is enabled for io, nil otherwise.
def self.get_ansi(start_esc, end_esc, enable = true, io = $>)
enable ? start_esc : end_esc if use_colours?(io)
end
# Internal: Resolve arguments for a method that returns an ANSI colour sequence.
#
# values - An array of integers or strings corresponding to the colour values
# best supported by the current terminal emulator:
#
# values[0]: 8/16 colours
# values[1]: 256 colours
# values[2]: 16 million colours
#
# A single colour-spec may be passed as a shorthand for setting all
# three colour resolutions. An array with fewer than three entries is
# padded out using the last (best) colour value.
#
# bg - Return an escape for setting background colour, not foreground (text) colour.
#
# Examples
#
# # Single colour
# SGR.get_colour 6 # => "\e[36m"
# SGR.get_colour 51 # => "\e[38;5;51m"
# SGR.get_colour "0;92;197" # => "\e[38;2;0;92;197m"
# SGR.get_colour 6, true # => "\e[46m"
# SGR.get_colour 51, true # => "\e[48;5;51m"
#
# # Resetting colours
# SGR.get_colour nil # => "\e[39m"
#
# # Adaptive colours (varies based on colour support)
# SGR.get_colour [6, 51, "0;92;197"]
#
# Returns a String if coloured output is enabled for io, nil otherwise.
def self.get_colour(values, bg = false)
end
# Retrieve the value of an environment variable.
#
# aliases - List of variable names that are different
# ways to specify the same thing.
#
# Returns a String containing the value of the first alias
# that matches an environment variable. If nothing matched,
# nil is returned instead.
def self.get_var(*aliases)
aliases.each do |name|
return ENV[name] if ENV.include?(name)
end
nil
end
# Return true if colours have been disabled in one's environment.
#
# Behaviour should conform to that of http://no-color.org/.
def self.disabled?
!get_var("NO_COLOR", "NO_COLOUR").nil?
end
# Return true if running inside a TTY without ANSI escape support.
def self.dumb?
term = get_var("TERM")
if term == "dumb" or term == "unknown"
true
elsif (term.nil? or term == "") and not using_windows?
true
else
false
end
end
# Return true if ANSI escape sequences should be output indiscriminately,
# possibly at a specific colour-depth.
#
# This method has greater priority than `disabled?`, and its logic should
# stay consistent with nodejs/node#37477 (upon which this logic is based).
#
# Returns a Boolean.
def self.forced?
case get_var("FORCE_COLOR", "FORCE_COLOUR")
when "", "1", "2", "3", "true", 1, 2, 3, true
true
else
false
end
end
# Return true if user needs a better operating system.
def self.using_windows?
if RUBY_PLATFORM =~ /bccwin|cygwin|emx|mingw|mswin|wince/i
true
elsif get_var("SystemRoot", "SYSTEMROOT") and get_var("ComSpec", "COMSPEC")
true
else
false
end
end
end
<!-- -*- svg -*- -->
<!DOCTYPE html>
<html lang="en-AU">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="initial-scale=1, minimum-scale=1" />
<title></title>
<style>
img{
filter: url('data:image/svg+xml,\
<svg version="1.1" xmlns="http://www.w3.org/2000/svg">\
<filter id="tint">\
<feColorMatrix in="SourceGraphic" type="matrix" values="\
0 0 0 0 0 \
0 1 0 0 0 \
0 0 0 0 0 \
0 0 0 1 0 \
"/>\
</filter>\
</svg>\
#tint');
}
</style>
</head>
<body>
<img src="alpha.png"/>
</body>
</html>
import {readFile} from "./utils/env.mjs";
/**
* A variable-length buffer of plain-text data.
* @public
* @class
*/
export default class TextBuffer extends Array {
static [Symbol.species] = Array;
/**
* Load the contents of a file from disk.
*
* @param {String} path - Pathname of file to load
* @param {String} [encoding="utf8"] - Character encoding of input
* @return {TextBuffer}
* @public
*/
static load(path, encoding = "utf8"){
return new this(readFileSync(path, encoding));
}
/**
* Initialise a new buffer, optionally propagating it with data.
*
* @param {String} [data=""]
* @param {Number} [startIndex=0]
* @constructor
* @public
*/
constructor(data = "", startIndex = 0){
super(...data.split(/(?<=\r?\n|\r(?!\n))(?=^)/m)
.map((s, i) => new Line(s, i + startIndex)));
this.#startIndex = startIndex;
}
/**
* @member {Number} startIndex
* @summary Value to start numbering lines from.
* @desc Setting this property will update the index of every
* {@link Line} currently held by the buffer object.
*/
#startIndex = 0;
get startIndex(){ return this.#startIndex; }
set startIndex(to){
if(Number.isNaN(to = Math.max(+to, 0)) || to === this.#startIndex) return;
this.#startIndex = to;
this.forEach((line, index) => line.index = index + to);
}
/**
* Concatenate each line and return the result.
* @return {String}
*/
join(){
return super.join("");
}
/**
* Convert the buffer to its string representation using {@link TextBuffer#format}.
* @param {FormatOptions} [opts={}]
* @return {String}
* @public
*/
toString(opts = {}){
return this.format(opts);
}
/**
* Extract a range of {@link Char} objects from the buffer.
*
* Unless both arguments are arrays of the form `[line, column]`,
* the method acts similarly to {@link Array.prototype.slice}.
*
* @example thisFile.slice([1,0], [1,13]).join("") == '"use strict";';
* @param {Number|Number[]} from
* @param {Number|Number[]} to
* @return {Char[]}
*/
slice(from, to){
if(Array.isArray(from) && Array.isArray(to)){
if(to[0] < from[0] || to[0] === from[0] && to[1] < from[1])
[from, to] = [to, from];
// Single-line slice
if(from[0] === to[0])
return this[from[0]].slice(from[1], to[1]).flat(9);
// Multi-line slice
const lines = super.slice(from[0], to[0]);
lines[0] = lines[0].slice(from[1]);
lines[lines.length - 1] = lines[lines.length - 1].slice(0, to[1]);
return lines.flat(9);
}
return super.slice(from, to);
}
}
/**
* A row of {@link Cell|Cells} containing character data.
* @public
* @class
*/
export class Line extends Array {
static [Symbol.species] = Array;
terminator = "";
#index = 0;
constructor(data = "", index = 0){
data = [...data];
let terminator = "";
if("\n" === data[data.length - 1]) terminator = data.pop();
if("\r" === data[data.length - 1]) terminator = data.pop() + terminator;
super(...data.map((s, i) => new Cell(s, i)));
this.terminator = terminator;
this.index = index;
}
get index(){
return this.#index;
}
set index(to){
if(Number.isNaN(to = +to)) return;
this.#index = Math.max(to, 0);
}
slice(...args){
const cells = [...this];
if(this.terminator)
cells.push(this.terminator);
return super.slice.apply(cells, args);
}
join(){
return super.join("") + this.terminator;
}
toString(){
return this.join();
}
}
/**
* A single grapheme representing one column of text.
* @public
* @class
*/
export class Cell {
before = "";
after = "";
text = "";
#index = 0;
constructor(text, index = 0){
this.text = String(text || "");
this.index = index;
}
get index(){
return this.#index;
}
set index(to){
if(Number.isNaN(to = +to)) return;
this.#index = Math.max(to, 0);
}
toString(){
return this.before + this.text + this.after;
}
}
atom = string | "*"
scope = atom ("." atom)*
path = "^"? scope (">"? scope)* "$"?
group = "(" selector ")"
filter = ("L:"|"R:"|"B:") (group | path)
expression = "-"? (filter | group | path)
composite = expression ([|&-] expression)*
selector = composite ("," composite)*
(?x)
(?<atom> \g<string> | \*){0}
(?<scope> \g<atom> (\. \g<atom>)*){0}
(?<path> \^? \g<scope> (>? \g<scope>)* \$?){0}
(?<group> \( \g<selector> \)){0}
(?<filter> [LRB]: (\g<group> | \g<path>)){0}
(?<expression> -? (\g<filter> | \g<group> | \g<path>)){0}
(?<composite> \g<expression> ([-\|&] \g<expression>)*){0}
(?<selector> \g<composite> (, \g<composite>)*){0}
\g<selector>
<!DOCTYPE html>
<html lang="en-AU">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="initial-scale=1, minimum-scale=1" />
<title>Crude trigonometry examples</title>
<style>
body{
background: #ccc;
}
#pad{
background: #fff 100px 100px url("data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB3aWR0aD0iMTAwcHgiIGhlaWdodD0iMTAwcHgiIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48c3R5bGU+cGF0aHtvcGFjaXR5Oi4yNTtmaWxsOm5vbmU7c3Ryb2tlOiMwMDA7c3Ryb2tlLW1pdGVybGltaXQ6MTA7fWxpbmV7b3BhY2l0eTouMTtmaWxsOm5vbmU7c3Ryb2tlOiMxODE4MTY7c3Ryb2tlLXdpZHRoOi41O3N0cm9rZS1taXRlcmxpbWl0OjEwO308L3N0eWxlPjxwYXRoIGlkPSJFeHRlbnRzIiBjbGFzcz0ic3QwIiBkPSJNOTkuNSwwdjk5LjVIMCIvPjxsaW5lIGlkPSJYMSIgeDE9IjEwIiB5MT0iMCIgeDI9IjEwIiB5Mj0iOTkiLz48bGluZSBpZD0iWDIiIHgxPSIyMCIgeTE9IjAiIHgyPSIyMCIgeTI9Ijk5Ii8+PGxpbmUgaWQ9IlgzIiB4MT0iMzAiIHkxPSIwIiB4Mj0iMzAiIHkyPSI5OSIvPjxsaW5lIGlkPSJYNCIgeDE9IjQwIiB5MT0iMCIgeDI9IjQwIiB5Mj0iOTkiLz48bGluZSBpZD0iWDUiIHgxPSI1MCIgeTE9IjAiIHgyPSI1MCIgeTI9Ijk5Ii8+PGxpbmUgaWQ9Ilg2IiB4MT0iNjAiIHkxPSIwIiB4Mj0iNjAiIHkyPSI5OSIvPjxsaW5lIGlkPSJYNyIgeDE9IjcwIiB5MT0iMCIgeDI9IjcwIiB5Mj0iOTkiLz48bGluZSBpZD0iWDgiIHgxPSI4MCIgeTE9IjAiIHgyPSI4MCIgeTI9Ijk5Ii8+PGxpbmUgaWQ9Ilg5IiB4MT0iOTAiIHkxPSIwIiB4Mj0iOTAiIHkyPSI5OSIvPjxsaW5lIGlkPSJZMSIgeDE9Ijk5IiB5MT0iOS41IiAgeDI9IjAiIHkyPSI5LjUiLz48bGluZSBpZD0iWTIiIHgxPSI5OSIgeTE9IjE5LjUiIHgyPSIwIiB5Mj0iMTkuNSIvPjxsaW5lIGlkPSJZMyIgeDE9Ijk5IiB5MT0iMjkuNSIgeDI9IjAiIHkyPSIyOS41Ii8+PGxpbmUgaWQ9Ilk0IiB4MT0iOTkiIHkxPSIzOS41IiB4Mj0iMCIgeTI9IjM5LjUiLz48bGluZSBpZD0iWTUiIHgxPSI5OSIgeTE9IjQ5LjUiIHgyPSIwIiB5Mj0iNDkuNSIvPjxsaW5lIGlkPSJZNiIgeDE9Ijk5IiB5MT0iNTkuNSIgeDI9IjAiIHkyPSI1OS41Ii8+PGxpbmUgaWQ9Ilk3IiB4MT0iOTkiIHkxPSI2OS41IiB4Mj0iMCIgeTI9IjY5LjUiLz48bGluZSBpZD0iWTgiIHgxPSI5OSIgeTE9Ijc5LjUiIHgyPSIwIiB5Mj0iNzkuNSIvPjxsaW5lIGlkPSJZOSIgeDE9Ijk5IiB5MT0iODkuNSIgeDI9IjAiIHkyPSI4OS41Ii8+PC9zdmc+");
}
</style>
</head>
<body>
<canvas id="pad" width="1000" height="1000"></canvas>
<script type="module">
const pad = document.getElementById("pad");
const ctx = pad.getContext("2d");
const px = new ImageData(new Uint8ClampedArray([0, 0, 0, 255]), 1, 1);
circle(pad.width / 2, pad.height / 2, 100);
/**
* Draw an ugly circle.
*
* @param {Number} midX - X ordinate of the circle's centre
* @param {Number} midY - Y ordinate of the circle's centre
* @param {Number} radX - Horizontal radius
* @param {Number} [radY=radX] - Vertical radius
* @param {Number} [angleStart=0]
* @param {Number} [angleEnd=360]
*/
function circle(midX, midY, radX, radY = radX, angleStart = 0, angleEnd = 360){
for(let θ = angleStart; θ < angleEnd; ++θ){
const radians = θ * Math.PI / 180;
const x = Math.cos(radians) * radX;
const y = Math.sin(radians) * radY;
ctx.putImageData(px, midX + x, midY + y);
}
}
</script>
</body>
</html>
^
(?:
//
|
(?<protocol>[^/#:?]*) : (?://)?
)?
(?:
(?=$|\s)
|
(?:
(?<auth>
(?<username>[^/:#?@]+)
(?::(?<password>[^@]+))?
@
)?
(?<hostname>[^/:]+)
(?::(?<port>\d+))?
)?
(?<pathname>
/?
(?:[^/#?]+/)*
(?<filename>[^/#?]+)?
/?
)
(?<query>\?[^#]*)?
(?<fragment>#.*)?
$
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment