#### libremiliacr
#### Copyright(C) 2020-2024 Remilia Scarlet <remilia@posteo.jp>
####
#### This program is free software: you can redistribute it and/or modify
#### it under the terms of the GNU General Public License as published
#### the Free Software Foundation, either version 3 of the License, or
#### (at your option) any later version.
####
#### This program is distributed in the hope that it will be useful,
#### but WITHOUT ANY WARRANTY; without even the implied warranty of
#### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
#### GNU General Public License for more details.
####
#### You should have received a copy of the GNU General Public License
#### along with this program.If not, see<http:####www.gnu.org/licenses/.>
require "./remilib/*"
require "./remilib/math/*"
require "./remilib/args/*"
require "./remilib/console/*"
require "./remilib/compression/bzip/*"
require "./remilib/digest/*"
require "./remilib/config/*"
lib LibC
fun remi_mkdtemp = "mkdtemp"(template : Pointer(LibC::Char)) : Pointer(LibC::Char)
end
# libremiliacr is a small library that provides utility functions and some extra
# batteries to [Crystal](https://crystal-lang.org/). It's similar in nature
# (and design) to my
# [cl-sdm](https://chiselapp.com/user/MistressRemilia/repository/cl-sdm/)
# library for Common Lisp.
module RemiLib
# The version of the library.
VERSION = "0.92.5"
# Raised when `#assert` fails.
class AssertionError < Exception
end
# Unless `-Dremilib_no_assertions` is passed to the compiler, this evalutes
# `expr`. If it evalutes to `true`, then an `AssertionError` is raised,
# otherwise this does nothing.
#
# If `-Dremilib_no_assertions` is **NOT** passed to the compiler, then this is
# effectively a no-op.
macro assert(expr)
{% unless flag?(:remilib_no_assertions) %}
unless {{expr}}
raise ::RemiLib::AssertionError.new("Assertion failure: #{ {{expr.stringify}} }")
end
{% end %}
end
# Unless `-Dremilib_no_assertions` is passed to the compiler, this evalutes
# `expr`. If it evalutes to `true`, then an `AssertionError` is raised with
# `msg` as a custom error message, otherwise this does nothing.
#
# If `-Dremilib_no_assertions` is **NOT** passed to the compiler, then this is
# effectively a no-op.
macro assert(msg, expr)
{% unless flag?(:remilib_no_assertions) %}
unless {{expr}}
raise ::RemiLib::AssertionError.new({{msg}})
end
{% end %}
end
# Defines raise-on-nil and nilable getter methods for each of the given
# arguments, as well as a setter method that can only be called if the current
# value is `nil`.
macro setonce!(*names)
{% for name in names %}
{% if name.is_a?(TypeDeclaration) %}
@{{name}}?
def {{name.var.id}}? : {{name.type}}?
@{{name.var.id}}
end
def {{name.var.id}} : {{name.type}}
if (value = @{{name.var.id}}).nil?
::raise NilAssertionError.new("#{{{@type}}}#{'#'}{{name.var.id}} cannot be nil")
else
value
end
end
def {{name.var.id}}=(newThing : {{name.type}})
unless @{{name.var.id}}.nil?
raise "Cannot set {{@type}}#{'#'}{{name.id}} twice"
else
@{{name.var.id}} = newThing
end
end
{% else %}
def {{name.id}}?
@{{name.id}}
end
def {{name.id}}
if (value = @{{name.id}}).nil?
::raise NilAssertionError.new("{{@type}}#{'#'}{{name.id}} cannot be nil")
else
value
end
end
def {{name.var.id}}=(newThing : {{name.type}})
unless @{{name.var.id}}.nil?
raise "Cannot set {{@type}}#{'#'}{{name.id}} twice"
else
@{{name.var.id}} = newThing
end
end
{% end %}
{% end %}
end
# Defines raise-on-nil and nilable getter methods for each of the given
# arguments, as well as a setter method that can only be called if the current
# value is `nil`.
macro classSetonce!(*names)
{% for name in names %}
{% if name.is_a?(TypeDeclaration) %}
@@{{name}}?
def self.{{name.var.id}}? : {{name.type}}?
@@{{name.var.id}}
end
def self.{{name.var.id}} : {{name.type}}
if (value = @@{{name.var.id}}).nil?
::raise NilAssertionError.new("#{{{@type}}}#{'#'}{{name.var.id}} cannot be nil")
else
value
end
end
def self.{{name.var.id}}=(newThing : {{name.type}})
unless @@{{name.var.id}}.nil?
raise "Cannot set {{@type}}#{'#'}{{name.id}} twice"
else
@@{{name.var.id}} = newThing
end
end
{% else %}
def self.{{name.id}}?
@@{{name.id}}
end
def self.{{name.id}}
if (value = @@{{name.id}}).nil?
::raise NilAssertionError.new("{{@type}}#{'#'}{{name.id}} cannot be nil")
else
value
end
end
def self.{{name.var.id}}=(newThing : {{name.type}})
unless @@{{name.var.id}}.nil?
raise "Cannot set {{@type}}#{'#'}{{name.id}} twice"
else
@@{{name.var.id}} = newThing
end
end
{% end %}
{% end %}
end
# Creates a "displaced array", which is a `Slice` that has no storage
# of its own, but instead points into a subsequence within
# `displacedTo`. Changes to the yielded slice will change
# `displacedTo`.
#
# The yielded `Slice`'s index 0 will correspond to
# `displacedTo[displacedIndexOffset]`, while `size` is the number of
# elements in `Slice`. The total size of the yielded `Slice` must
# be less than or equal to the size of `displacedTo`.
#
# http://www.lispworks.com/documentation/HyperSpec/Body/26_glo_d.htm#displaced_array
def self.withDisplacedArray(displacedTo : Array(T)|Slice(T), displacedIndexOffset : Int, size : Int, &) forall T
if displacedIndexOffset + size > displacedTo.size
raise "Displaced array is too small"
end
displaced = Slice.new(displacedTo.to_unsafe + displacedIndexOffset, size)
yield displaced
end
class MkdtempError < Exception
getter errno : Errno?
def initialize(message : String)
super(message, nil)
end
def initialize(message : String, cause : Exception|Nil)
super(message, cause)
end
def initialize(message : String|Nil, cause : Exception|Nil, @errno : Errno|Nil)
super(message, cause)
end
end
# Creates a temporary directory using `mkdtemp()` from C's standard lib. The
# directory is created with its permissions set to 0700. On failure, this
# will raise a `MkdtempError` exception.
def self.mkdtemp(prefix : String = "") : String
template = String.build do |str|
str << prefix
str << "XXXXXX"
end
path = Path[Dir.tempdir, template]
templatePtr = path.to_s.bytes.dup # dup may not be needed here
Errno.value = Errno::NONE
str = LibC.remi_mkdtemp(templatePtr.to_unsafe)
if str.null?
raise MkdtempError.new("Could not create temporary directory")
elsif Errno.value.einval?
raise MkdtempError.new("Bad mkdtemp template", nil, Errno.value)
elsif !Errno.value.none?
raise MkdtempError.new("Could not create temporary directory", nil, errno: Errno.value)
end
String.new(str)
end
end