From fbd9be8a8ba3b2b9f5af73c3ff1901bbe89fed9f Mon Sep 17 00:00:00 2001
From: Corey Williamson Some wrappers/conveniences around working with ansi escape codes. For example getting the length of a string that contains escape codes shouldnt include themTable of Contents
+
diff --git a/docs/index.html b/docs/index.html
index 22f17d2..ec892be 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -2,6 +2,12 @@
@@ -217,279 +255,434 @@ cyan.cli
cyan.colorstring
ColorString:copy(): ColorString
-Make a deep copy of a ColorString
-ColorString:len(): integer
-Get the length of a given ColorString
, not counting any escape sequences.
Note that ColorString:len() >= ColorString:tostring():len()
ColorString:surround(col: {integer})
+method ColorString:copy
surrounds a string with a color
-ColorString:to_raw(): string
+function
ColorString:copy(): ColorString
Make a deep copy of a ColorString
+method ColorString:len
Converts a Colorstring
to a regular string, stripping any ANSI escapes
ColorString:tostring(): string
+function
ColorString:len(): integer
Get the length of a given ColorString
, not counting any escape sequences.
Note that ColorString:len() >= ColorString:tostring():len()
method ColorString:surround
Converts a Colorstring
to a regular string with the correct ANSI escapes
type ColorString
+function
ColorString:surround(
+ col
: {integer}
+)
surrounds a string with a color
+method ColorString:to_raw
record ColorString-
content: {string | {integer}}
len: function(ColorString): integer
tostring: function(ColorString): string
metamethod __len: function(ColorString): integer
metamethod __concat: function(ColorString | string, ColorString | string): ColorString
end
The main object that this library consumes and produces. It basically implements the 'string'
interface and can be used wherever a string is.
Colors are described as arrays of numbers that directly correspond to ANSI escape sequences
-colorstring.copy(str: string | ColorString): string | ColorString
+function
ColorString:to_raw(): string
Converts a Colorstring
to a regular string, stripping any ANSI escapes
method ColorString:tostring
If input is a colorstring, return a deep copy of it, otherwise just return the input
-colorstring.highlight(hl: {integer}, str: string): ColorString
+function
ColorString:tostring(): string
Converts a Colorstring
to a regular string with the correct ANSI escapes
type ColorString
Create a Colorstring
by surrounding a string with the given ANSI color and an ANSI reset
colorstring.new(...: string | {integer}): ColorString
-The Colorstring
constructor
colorstring.rgb_bg(r: integer, g: integer, b: integer): {integer, integer, integer, integer, integer}
-The ansi escape for an arbitrary RGB background color
-colorstring.rgb_fg(r: integer, g: integer, b: integer): {integer, integer, integer, integer, integer}
-The ansi escape for an arbitrary RGB foreground color
-record
ColorString
+ content: {string | {integer}}
+ len: function(ColorString): integer
+ tostring: function(ColorString): string
+
+ metamethod
__len: function(ColorString): integer
+ metamethod
__concat: function(ColorString | string, ColorString | string): ColorString
+end
The main object that this library consumes and produces. It basically implements the 'string'
interface and can be used wherever a string is.
Colors are described as arrays of numbers that directly correspond to ANSI escape sequences
+function colorstring.copy
+function
colorstring.copy(
+ str
: string | ColorString
+): string | ColorString
If input is a colorstring, return a deep copy of it, otherwise just return the input
+function colorstring.highlight
+function
colorstring.highlight(
+ hl
: {integer}
,
+ str
: string
+): ColorString
Create a Colorstring
by surrounding a string with the given ANSI color and an ANSI reset
function colorstring.new
+function
colorstring.new(
+ ...
: string | {integer}
+): ColorString
The Colorstring
constructor
function colorstring.rgb_bg
+function
colorstring.rgb_bg(
+ r
: integer
,
+ g
: integer
,
+ b
: integer
+): {integer, integer, integer, integer, integer}
The ansi escape for an arbitrary RGB background color
+function colorstring.rgb_fg
+function
colorstring.rgb_fg(
+ r
: integer
,
+ g
: integer
,
+ b
: integer
+): {integer, integer, integer, integer, integer}
The ansi escape for an arbitrary RGB foreground color
+The common interface for commands to implement
-type Command
-record Command-
name: string
description: string
argparse: function(argparse.Command)
script_hooks: {string}
exec: CommandFn
end
The interface
-command.get(name: string): Command
-Get a command that was created with command.new
Works whether or not command.register_all
was called
command.merge_args_into_config(cfg: config.Config, args: Args)
-Merge the relevant entries of the provided command arguments into the provided config table
-command.new(cmd: Command)
-Create a new command
This is stored in an internal cache and will do nothing unless command.register_all
is called afterwards
command.register_all(p: argparse.Parser)
-Install all commands created with command.new
into the given parser
type Command
+record
Command
+ name: string
+ description: string
+ argparse: function(argparse.Command)
+ script_hooks: {string}
+ exec: CommandFn
+end
The interface
+function command.get
+function
command.get(
+ name
: string
+): Command
Get a command that was created with command.new
Works whether or not command.register_all
was called
function command.merge_args_into_config
+function
command.merge_args_into_config(
+ cfg
: config.Config
,
+ args
: Args
+)
Merge the relevant entries of the provided command arguments into the provided config table
+function command.new
+function
command.new(
+ cmd
: Command
+)
Create a new command
This is stored in an internal cache and will do nothing unless command.register_all
is called afterwards
function command.register_all
+function
command.register_all(
+ p
: argparse.Parser
+)
Install all commands created with command.new
into the given parser
Config loading API
-type Config
-record Config-
loaded_from: fs.Path
build_dir: string
source_dir: string
include: {string}
exclude: {string}
global_env_def: string
include_dir: {string}
module_name: string
dont_prune: {string}
scripts: {string:{string:string|{string}}}
gen_compat: tl.CompatMode
gen_target: tl.TargetMode
disable_warnings: {tl.WarningKind}
warning_error: {tl.WarningKind}
externals: {string:any}
end
The config data
-config.find(): fs.Path
-Find config.filename
in the current or parent directories
config.is_config(c: any): Config, {string}, {string}
-Check if c
conforms to the Config
type and return any errors and warnings generated from checking
config.load(): Config, {string}, {string}
-Try to load tlconfig.lua
in the current directory
type Config
+record
Config
+ loaded_from: fs.Path
+ build_dir: string
+ source_dir: string
+ include: {string}
+ exclude: {string}
+ global_env_def: string
+ include_dir: {string}
+ module_name: string
+ dont_prune: {string}
+ scripts: {string:{string:string|{string}}}
+ gen_compat: tl.CompatMode
+ gen_target: tl.TargetMode
+ disable_warnings: {tl.WarningKind}
+ warning_error: {tl.WarningKind}
+ externals: {string:any}
+end
The config data
+function config.find
+function
config.find(): fs.Path
Find config.filename
in the current or parent directories
function config.is_config
+function
config.is_config(
+ c
: any
+): Config
, {string}
, {string}
Check if c
conforms to the Config
type and return any errors and warnings generated from checking
function config.load
+function
config.load(): Config
, {string}
, {string}
Try to load tlconfig.lua
in the current directory
Object oriented lexical path management
-Path.eq(a: Path | string, b: Path | string, use_os_sep: boolean): boolean
+function Path.eq
Check if two paths are equal
This function is used for the __eq metamethod with use_os_sep as false
-Path:ancestors(): function(): Path
+function
Path.eq(
+ a
: Path | string
,
+ b
: Path | string
,
+ use_os_sep
: boolean
+): boolean
Check if two paths are equal
This function is used for the __eq metamethod with use_os_sep as false
+method Path:ancestors
Iterate over the leading folders in a path
ex: path.new(
will construct "foo/bar/baz/bat"
):ancestors()Path
objects from "foo"
, "foo/bar"
, "foo/bar/baz"
Path:append(other: string | Path)
+function
Path:ancestors(): function(): Path
Iterate over the leading folders in a path
ex: path.new(
will construct "foo/bar/baz/bat"
):ancestors()Path
objects from "foo"
, "foo/bar"
, "foo/bar/baz"
method Path:append
Mutate the given Path
by appending another path to it
Traversals in the other
path will be normalized
local p = path.new-"foo/bar"
p:append"../baz"
assert(p == path.new"foo/baz"
)
Path:copy(): Path
+function
Path:append(
+ other
: string | Path
+)
Mutate the given Path
by appending another path to it
Traversals in the other
path will be normalized
local p = path.new+"foo/bar"
p:append"../baz"
assert(p == path.new"foo/baz"
)
method Path:copy
Create a copy of the given path
-Path:exists(): boolean
+function
Path:copy(): Path
Create a copy of the given path
+method Path:exists
Check if the path exists
-Path:is_absolute(): boolean
+function
Path:exists(): boolean
Check if the path exists
+method Path:is_absolute
Returns whether the path is absolute
On windows, checks for paths like "C:\..."
, elsewhere looks for "/..."
Path:is_directory(): boolean
+function
Path:is_absolute(): boolean
Returns whether the path is absolute
On windows, checks for paths like "C:\..."
, elsewhere looks for "/..."
method Path:is_directory
Get whether the "mode"
attribute of the given path is set to "directory"
Path:is_file(): boolean
+function
Path:is_directory(): boolean
Get whether the "mode"
attribute of the given path is set to "directory"
method Path:is_file
Get whether the "mode"
attribute of the given path is set to "file"
Path:is_in(dirname: string | Path, use_os_sep: boolean): boolean
+function
Path:is_file(): boolean
Get whether the "mode"
attribute of the given path is set to "file"
method Path:is_in
returns true if the path is inside the given directory
If relative and absolute paths are mixed, the relative path is assumed to be in the current working directory (as determined by lfs.currentdir()
)
If dirname is a string
, a path will be constructed using path.new
with use_os_sep
Path:match(patt: string): boolean
+function
Path:is_in(
+ dirname
: string | Path
,
+ use_os_sep
: boolean
+): boolean
returns true if the path is inside the given directory
If relative and absolute paths are mixed, the relative path is assumed to be in the current working directory (as determined by lfs.currentdir()
)
If dirname is a string
, a path will be constructed using path.new
with use_os_sep
method Path:match
See if the given path matches the pattern
Path separators in patterns are always represented with '/'
.
*
characters represent any number of non-path-separator characters
**/
represent any number of directories
Path:match_any(patts: {string}): integer, string
+function
Path:match(
+ patt
: string
+): boolean
See if the given path matches the pattern
Path separators in patterns are always represented with '/'
.
*
characters represent any number of non-path-separator characters
**/
represent any number of directories
method Path:match_any
See if the given path matches any of the given patterns
-Path:mk_parent_dirs(): boolean, string
+function
Path:match_any(
+ patts
: {string}
+): integer
, string
See if the given path matches any of the given patterns
+method Path:mk_parent_dirs
Attempt to create the leading directories of a given path
-Path:mkdir(): boolean, string
+function
Path:mk_parent_dirs(): boolean
, string
Attempt to create the leading directories of a given path
+method Path:mkdir
Attempt to create a directory at the given path, creating the parent directories if needed. Can be seen as an equivalent to mkdir -p
Path:mod_time(): integer
+function
Path:mkdir(): boolean
, string
Attempt to create a directory at the given path, creating the parent directories if needed. Can be seen as an equivalent to mkdir -p
method Path:mod_time
Get the "modification"
attribute of a file
Path:normalize()
+function
Path:mod_time(): integer
Get the "modification"
attribute of a file
method Path:normalize
Modify path in place to remove any traversals that it can
local p = path.new"foo/bar/../baz"
p:normalize()
assert(p == path.new"foo/baz"
)
if a traversal can't be removed it will remain
local p = path.new-"../baz"
p:normalize()
assert(p == path.new"../baz"
)
Path:prepend(other: string | Path)
+function
Path:normalize()
Modify path in place to remove any traversals that it can
local p = path.new"foo/bar/../baz"
p:normalize()
assert(p == path.new"foo/baz"
)
if a traversal can't be removed it will remain
local p = path.new+"../baz"
p:normalize()
assert(p == path.new"../baz"
)
method Path:prepend
Mutate the given Path
by prepending another path to it
Path:relative_to(other: Path): Path
+function
Path:prepend(
+ other
: string | Path
+)
Mutate the given Path
by prepending another path to it
method Path:relative_to
Expresses a path in terms of another path. If any relative paths are given, they are treated as though they are in the current directory
for example: path.new(
"/foo/bar/baz"
):relative_to(path.new("/foo/bat"
)) == path.new "../bar/baz"
Path:remove_leading(p: string | Path)
+function
Path:relative_to(
+ other
: Path
+): Path
Expresses a path in terms of another path. If any relative paths are given, they are treated as though they are in the current directory
for example: path.new(
"/foo/bar/baz"
):relative_to(path.new("/foo/bat"
)) == path.new "../bar/baz"
method Path:remove_leading
Mutate the given path by removing the leading parts from the given path
Will error if you attempt to mix absolute and non-absolute paths
-Path:to_absolute()
+function
Path:remove_leading(
+ p
: string | Path
+)
Mutate the given path by removing the leading parts from the given path
Will error if you attempt to mix absolute and non-absolute paths
+method Path:to_absolute
Modify a path in place to become an absolute path
When the path is already absolute, does nothing
Otherwise, prepends the current directory
-Path:to_real_path(): string
+function
Path:to_absolute()
Modify a path in place to become an absolute path
When the path is already absolute, does nothing
Otherwise, prepends the current directory
+method Path:to_real_path
Convert a Path
to a string describing a real path
Path:tostring(): string
+function
Path:to_real_path(): string
Convert a Path
to a string describing a real path
method Path:tostring
Convert a path to a string. Always uses '/'
as a path separator. Intended for displaying purposes. For an actual path in the filesystem, use Path:to_real_path()
Used for the __tostring metamethod
-type Path
+function
Path:tostring(): string
Convert a path to a string. Always uses '/'
as a path separator. Intended for displaying purposes. For an actual path in the filesystem, use Path:to_real_path()
Used for the __tostring metamethod
+type Path
record Path-
{string}
metamethod __concat: function(Path | string, Path | string): Path
metamethod __eq: function(Path | string, Path | string): boolean
end
The main path object. Basically just an array of strings with some methods and metamethods to interact with other paths
-path.ensure(s: string | Path, use_os_sep: boolean): Path
-Ensures s
is a Path.
If s is a string, parse it into a path otherwise return s unmodified
-path.new(s: string, use_os_sep: boolean): Path
-The Path
constructor
By default uses '/'
as a path separator
record
Path
+ {string}
+
+ metamethod
__concat: function(Path | string, Path | string): Path
+ metamethod
__eq: function(Path | string, Path | string): boolean
+end
The main path object. Basically just an array of strings with some methods and metamethods to interact with other paths
+function path.ensure
+function
path.ensure(
+ s
: string | Path
,
+ use_os_sep
: boolean
+): Path
Ensures s
is a Path.
If s is a string, parse it into a path otherwise return s unmodified
+function path.new
+function
path.new(
+ s
: string
,
+ use_os_sep
: boolean
+): Path
The Path
constructor
By default uses '/'
as a path separator
Filesystem and path management
-fs.chdir(p: string | Path): boolean, string
-Change the current directory to p
fs.copy(source: string | Path, dest: string | Path): boolean, string
-Copy a file
uses fs.read
internally to get and cache the contents of source
fs.cwd(): Path
-Get the current working directory as an fs.Path
fs.dir(dir: string | Path, include_dotfiles: boolean): function(): Path
-Iterate over the given directory, returning fs.Path
objects
By default, will not include paths that start with '.'
fs.extension_split(p: Path | string, ndots: integer): string, string
-Split a path on its extension. Forces the extension to be lowercase.
the ndots
argument lets you specify the upper limit of how many dots the extension can have
ex:
fs.extension_split(-"foo.d.tl"
) =>"foo.d"
,".tl"
fs.extension_split("foo.d.tl"
, 2) =>"foo"
,".d.tl"
fs.extension_split("foo.D.TL"
, 2) =>"foo"
,".d.tl"
fs.get_line(p: string, n: integer): string, string
-Gets line n
of a file.
if the file is less than n
lines, returns nil
if there was an error opening the file, returns nil
, err
Uses fs.read()
internally, which caches reads
fs.path_concat(a: string, b: string): string
-Concatenate two strings using the os path separator
-fs.read(p: string): string, string
-Open a file, read it, close the file, return the contents or nil
and an error if it couldn't be opened
Additionally caches results so multiple locations can read the same file for minimal cost. There is currently no way to clear out this cache.
-fs.scan_dir(dir: string | Path, include: {string}, exclude: {string}, include_directories: boolean): function(): Path
-Recursively iterate over the files in a directory, following the provided include
and exclude
patterns
For information on path patterns, see the Path:match()
method
fs.search_parent_dirs(start_path: string | Path, fname: string): Path
-Search for a file in the parent directories of the given path. Returns the path of the file found.
e.g. if file.txt
is in /foo/bar
, then fs.search_parent_dirs(
"/foo/bar/baz"
, "file.txt"
) == path.new "/foo/bar/file.txt"
function fs.chdir
+function
fs.chdir(
+ p
: string | Path
+): boolean
, string
Change the current directory to p
function fs.copy
+function
fs.copy(
+ source
: string | Path
,
+ dest
: string | Path
+): boolean
, string
Copy a file
uses fs.read
internally to get and cache the contents of source
function fs.cwd
+function
fs.cwd(): Path
Get the current working directory as an fs.Path
function fs.dir
+function
fs.dir(
+ dir
: string | Path
,
+ include_dotfiles
: boolean
+): function(): Path
Iterate over the given directory, returning fs.Path
objects
By default, will not include paths that start with '.'
function fs.extension_split
+function
fs.extension_split(
+ p
: Path | string
,
+ ndots
: integer
+): string
, string
Split a path on its extension. Forces the extension to be lowercase.
the ndots
argument lets you specify the upper limit of how many dots the extension can have
ex:
fs.extension_split(+"foo.d.tl"
) =>"foo.d"
,".tl"
fs.extension_split("foo.d.tl"
, 2) =>"foo"
,".d.tl"
fs.extension_split("foo.D.TL"
, 2) =>"foo"
,".d.tl"
function fs.get_line
+function
fs.get_line(
+ p
: string
,
+ n
: integer
+): string
, string
Gets line n
of a file.
if the file is less than n
lines, returns nil
if there was an error opening the file, returns nil
, err
Uses fs.read()
internally, which caches reads
function fs.path_concat
+function
fs.path_concat(
+ a
: string
,
+ b
: string
+): string
Concatenate two strings using the os path separator
+function fs.read
+function
fs.read(
+ p
: string
+): string
, string
Open a file, read it, close the file, return the contents or nil
and an error if it couldn't be opened
Additionally caches results so multiple locations can read the same file for minimal cost. There is currently no way to clear out this cache.
+function fs.scan_dir
+function
fs.scan_dir(
+ dir
: string | Path
,
+ include
: {string}
,
+ exclude
: {string}
,
+ include_directories
: boolean
+): function(): Path
Recursively iterate over the files in a directory, following the provided include
and exclude
patterns
For information on path patterns, see the Path:match()
method
function fs.search_parent_dirs
+function
fs.search_parent_dirs(
+ start_path
: string | Path
,
+ fname
: string
+): Path
Search for a file in the parent directories of the given path. Returns the path of the file found.
e.g. if file.txt
is in /foo/bar
, then fs.search_parent_dirs(
"/foo/bar/baz"
, "file.txt"
) == path.new "/foo/bar/file.txt"
A utility for building directed acyclic graphs of Teal source files
This is the main driver behind the build
command
Dag:find(fstr: string | fs.Path): Node
-Find a node in the graph with the given path name
-Dag:insert_file(fstr: string | fs.Path, in_dir: string | fs.Path): boolean, {string}
-Inserts a file and its dependencies into a graph
Ignores absolute paths and non .tl
or .lua
files
If in_dir is provided, dependencies of the given file will not be added to the graph unless they are inside of the given dir
Returns false
if inserting the file introduced a circular dependency along with a list of the filenames in the cycle
Dag:mark_each(predicate: function(fs.Path): boolean)
-For each node in the graph, if predicate
returns true for that input path, the node is marked for compilation, and that node's children are marked for type checking
Dag:marked_nodes(): function(): Node
-Iterate over every node that has been marked, no matter what the mark is
-Dag:nodes(): function(): Node
-Iterate over nodes in order of dependents
If two nodes have the same number of dependent nodes, the order of iteration between those two nodes is not guaranteed
-type Dag
-record Dag-
-- private fields
_nodes_by_filename: {string:Node}
end
The graph object
-type Node
-record Node-
input: fs.Path
output: fs.Path
modules: {string:fs.Path}
mark: Mark
dependents: {Node:boolean}
end
The nodes that are stored in the graph
-graph.empty(): Dag
-Initializes an empty graph
-graph.scan_dir(dir: string | fs.Path, include: {string}, exclude: {string}): Dag, {string}
-Recursively scan a directory (using fs.scan_dir
) and build up a graph, respecting the given include
and exclude
patterns
Returns nil
if a circular dependency was found, along with a list of the filenames in the cycle
method Dag:find
+function
Dag:find(
+ fstr
: string | fs.Path
+): Node
Find a node in the graph with the given path name
+method Dag:insert_file
+function
Dag:insert_file(
+ fstr
: string | fs.Path
,
+ in_dir
: string | fs.Path
+): boolean
, {string}
Inserts a file and its dependencies into a graph
Ignores absolute paths and non .tl
or .lua
files
If in_dir is provided, dependencies of the given file will not be added to the graph unless they are inside of the given dir
Returns false
if inserting the file introduced a circular dependency along with a list of the filenames in the cycle
method Dag:mark_each
+function
Dag:mark_each(
+ predicate
: function(fs.Path): boolean
+)
For each node in the graph, if predicate
returns true for that input path, the node is marked for compilation, and that node's children are marked for type checking
method Dag:marked_nodes
+function
Dag:marked_nodes(): function(): Node
Iterate over every node that has been marked, no matter what the mark is
+method Dag:nodes
+function
Dag:nodes(): function(): Node
Iterate over nodes in order of dependents
If two nodes have the same number of dependent nodes, the order of iteration between those two nodes is not guaranteed
+type Dag
+record
Dag
+ -- private fields (click to show)
+ _nodes_by_filename: {string:Node}
+end
The graph object
+type Node
+record
Node
+ input: fs.Path
+ output: fs.Path
+ modules: {string:fs.Path}
+ mark: Mark
+ dependents: {Node:boolean}
+end
The nodes that are stored in the graph
+function graph.empty
+function
graph.empty(): Dag
Initializes an empty graph
+function graph.scan_dir
+function
graph.scan_dir(
+ dir
: string | fs.Path
,
+ include
: {string}
,
+ exclude
: {string}
+): Dag
, {string}
Recursively scan a directory (using fs.scan_dir
) and build up a graph, respecting the given include
and exclude
patterns
Returns nil
if a circular dependency was found, along with a list of the filenames in the cycle
Module for handling when input from the user is needed
-interaction.yes_no_prompt(
- prompt: string | cs.ColorString,
- logger: log.Logger,
- default: boolean,
- affirm: {string},
- deny: {string}
-): boolean
-Ask the user to affirm or deny a given prompt. The user input will be compared against the given affirm
and deny
lists (case-insensitive), with defaults used if they are not provided.
The given logger will be used to print the prompt, and log.info
if none is provided.
function interaction.yes_no_prompt
+function
interaction.yes_no_prompt(
+ prompt
: string | cs.ColorString
,
+ logger
: log.Logger
,
+ default
: boolean
,
+ affirm
: {string}
,
+ deny
: {string}
+): boolean
Ask the user to affirm or deny a given prompt. The user input will be compared against the given affirm
and deny
lists (case-insensitive), with defaults used if they are not provided.
The given logger will be used to print the prompt, and log.info
if none is provided.
Console logging utils, not to be confused with log files
Each logger object has the same __call
signature of function(...: any)
, and by default the following are provided:
Name | Stream | Description |
---|---|---|
info | stdout | General info, should be seen as the default, silenced by --quiet |
extra | stdout | Extra info that isn't strictly necessary, enabled via -v extra, silenced by --quiet |
warn | stderr | Used to display warnings, silenced by --quiet |
err | stderr | Used to display errors |
debug | stderr | Used for debugging, uses the inspect module (if it is found) to print its arguments, enabled by -v debug |
You may notice that these are nicely padded and after the first line the prefix is replaced by a '...'
. Another function is provided, create_logger
,
create_logger: function(
stream: FILE,
verbosity_threshold: Verbosity,
prefix: string | ColorString,
cont: string | ColorString,
inspector: function(any): string
): Logger
to automatically generate formatted output. cont
defaults to
and "..."
inspector
defaults to tostring
. Prefixes will be padded to 10 characters wide by default, so your logging may look off from the default if your prefix is longer.
Additionally, loggers will try to detect whether or not to display colors. This is only handled with the ColorString
type to avoid the many pitfalls of trying to parse ANSI escape sequences. If a regular string contains any escape sequences or an inspector produces them (outside of a ColorString
) it will not be handled.
Logger:cont(...: any)
-Log only using the continuation prefix.
-Logger:cont_nonl(...: any)
-Log only using the continuation prefix, but don't put a newline at the end.
-Logger:copy(
- new_prefix: string | cs.ColorString,
- new_continuation: string | cs.ColorString
-): Logger
-Create a copy of a logger, deep copying relevant data
-Logger:format(fmt: string, ...: any)
-Call string.format
with the given arguments and log that.
Logger:format_nonl(fmt: string, ...: any)
-Call string.format
with the given arguments and log that, without a new line.
Logger:nonl(...: any)
-Same as calling the logger, but don't put a newline at the end
-Logger:should_log(): boolean
-Returns whether the current verbosity is less than or equal to this loggers verbosity threshold.
-type Logger
-record Logger-
stream: FILE
verbosity_threshold: Verbosity
prefix: string | cs.ColorString
continuation: string | cs.ColorString
inspector: function(any): string
metamethod __call: function(...: any)
end
The data needed for a logger to do its job.
-type Verbosity
-enum Verbosity-
"quiet"
"normal"
"extra"
"debug"
end
The thresholds for loggers to actually write their output
-create_logger(
- stream: FILE,
- verbosity_threshold: Verbosity,
- prefix: string | cs.ColorString,
- cont: string | cs.ColorString,
- inspector: function(any): string
-): Logger
-Creates a Logger as described above
-log.set_prefix_padding(padding: integer)
-Globally set the padding of the prefixes of loggers.
-log.set_verbosity(level: Verbosity)
-Globally set the verbosity of the logging module.
-method Logger:cont
+function
Logger:cont(
+ ...
: any
+)
Log only using the continuation prefix.
+method Logger:cont_nonl
+function
Logger:cont_nonl(
+ ...
: any
+)
Log only using the continuation prefix, but don't put a newline at the end.
+method Logger:copy
+function
Logger:copy(
+ new_prefix
: string | cs.ColorString
,
+ new_continuation
: string | cs.ColorString
+): Logger
Create a copy of a logger, deep copying relevant data
+method Logger:format
+function
Logger:format(
+ fmt
: string
,
+ ...
: any
+)
Call string.format
with the given arguments and log that.
method Logger:format_nonl
+function
Logger:format_nonl(
+ fmt
: string
,
+ ...
: any
+)
Call string.format
with the given arguments and log that, without a new line.
method Logger:nonl
+function
Logger:nonl(
+ ...
: any
+)
Same as calling the logger, but don't put a newline at the end
+method Logger:should_log
+function
Logger:should_log(): boolean
Returns whether the current verbosity is less than or equal to this loggers verbosity threshold.
+type Logger
+record
Logger
+ stream: FILE
+ verbosity_threshold: Verbosity
+ prefix: string | cs.ColorString
+ continuation: string | cs.ColorString
+ inspector: function(any): string
+
+ metamethod
__call: function(...: any)
+end
The data needed for a logger to do its job.
+type Verbosity
+enum
Verbosity
+ "quiet"
+ "normal"
+ "extra"
+ "debug"
+end
The thresholds for loggers to actually write their output
+function create_logger
+function
create_logger(
+ stream
: FILE
,
+ verbosity_threshold
: Verbosity
,
+ prefix
: string | cs.ColorString
,
+ cont
: string | cs.ColorString
,
+ inspector
: function(any): string
+): Logger
Creates a Logger as described above
+function log.set_prefix_padding
+function
log.set_prefix_padding(
+ padding
: integer
+)
Globally set the padding of the prefixes of loggers.
+function log.set_verbosity
+function
log.set_verbosity(
+ level
: Verbosity
+)
Globally set the verbosity of the logging module.
+Meta information about Cyan itself
The script loading api
-script.disable()
-Make everything in this library a no-op, there is currently no way to re-enable this
-script.emit_hook(name: string, ...: any): boolean, string
-Iterates through each loaded script and runs any with the given hook, logging each script that it ran, and stopping early if any error
-script.emitter(name: string, ...: any): function(): fs.Path, boolean, string
-Emit a hook to load and run all registered scripts that run on the given hook.
This function will assert that ensure_loaded_for_command
was called before.
Returns an iterator that will run the next script when called and returns the path to the script, whether the script was loaded and ran with no errors, and an error message if it didn't
-script.ensure_loaded_for_command(name: string): boolean, string | tl.Result
-Attempts to load each script that the given command may need
-script.register(path: string, command_name: string, hooks: string | {string})
-Registers a file path as a lua/teal script to execute for the given hook(s) when script.emit_hook
is called
This is called by the cli driver to register the scripts found in the config file with the relevant hooks
Note: this function does not attempt to actually load the file. Scripts are loaded all at once via ensure_loaded_for_command
function script.disable
+function
script.disable()
Make everything in this library a no-op, there is currently no way to re-enable this
+function script.emit_hook
+function
script.emit_hook(
+ name
: string
,
+ ...
: any
+): boolean
, string
Iterates through each loaded script and runs any with the given hook, logging each script that it ran, and stopping early if any error
+function script.emitter
+function
script.emitter(
+ name
: string
,
+ ...
: any
+): function(): fs.Path, boolean, string
Emit a hook to load and run all registered scripts that run on the given hook.
This function will assert that ensure_loaded_for_command
was called before.
Returns an iterator that will run the next script when called and returns the path to the script, whether the script was loaded and ran with no errors, and an error message if it didn't
+function script.ensure_loaded_for_command
+function
script.ensure_loaded_for_command(
+ name
: string
+): boolean
, string | tl.Result
Attempts to load each script that the given command may need
+function script.register
+function
script.register(
+ path
: string
,
+ command_name
: string
,
+ hooks
: string | {string}
+)
Registers a file path as a lua/teal script to execute for the given hook(s) when script.emit_hook
is called
This is called by the cli driver to register the scripts found in the config file with the relevant hooks
Note: this function does not attempt to actually load the file. Scripts are loaded all at once via ensure_loaded_for_command
Common things needed by most commands in addition to wrappers around the tl api, since it isn't super stable
-type ParseResult
-record ParseResult-
tks: {Token}
ast: Node
reqs: {string}
errs: {tl.Error}
end
The result from parsing source code including the tokens, ast, calls to require, and errors
-common.init_env_from_config(cfg: config.Config): tl.Env, string
-Initialize a strict Teal environment, using the relevant entries of the config to modify that environment
may return nil
and an error message if something could not be applied to the environment
common.init_teal_env(gen_compat: boolean | tl.CompatMode, gen_target: tl.TargetMode, env_def: string): tl.Env, string
-Initialize a strict Teal environment
-common.lex_file(path: string): {Token}, {tl.Error}, string
-reads a file, calls tl.lex
on its contents, caches and returns the results
common.make_error_header(file: string, num_errors: integer, category: string): cs.ColorString
-Creates a nicely colored header to log errors
For example make_error_header(
would produce something like "foo.tl"
, 10, "foo error"
)10 foo errors in foo.tl
with 10
and foo.tl
highlighted
common.parse_file(path: string): ParseResult, string
-calls lex_file
, parses the token stream, caches and returns the results
common.prepend_to_lua_path(path_str: string)
-Prepend the given string to package.path and package.cpath.
Correctly adds ?.lua and ?/init.lua to the path
-common.report_config_errors(errs: {string}, warnings: {string}): boolean
-use log.warn
and log.err
to report errors and warnings from config.load
common.report_env_results(env: tl.Env, cfg: config.Config): boolean
-Report all errors from a tl.Env
Returns false when errors were reported
-common.report_errors(logger: log.Logger, errs: {tl.Error}, file: string, category: string)
-Logs an array of errors with nice colors and a header generated by make_error_header
common.report_result(r: tl.Result, c: config.Config): boolean
-Logs all the syntax errors, warnings, type errors, etc. from a tl.Result
with proper colors
Returns false if there were any errors. This includs warnings that were promoted to errors and doesn't include warnings that were not promoted to errors.
-common.result_has_errors(r: tl.Result, c: config.Config): boolean
-Returns whether or not the result has errors. Doesn't print/log anything
-common.search_module(name: string, search_dtl: boolean): fs.Path
-A wrapper around tl.search_module
but, returns an fs.Path
and will cache results
common.syntax_highlight(s: string): cs.ColorString
-Takes Teal or Lua code and returns a ColorString highlighting things like keywords, operators, and more.
-common.type_check_ast(ast: Node, opts: tl.TypeCheckOptions): tl.Result, string
-Just type checks an ast
-type ParseResult
+record
ParseResult
+ tks: {Token}
+ ast: Node
+ reqs: {string}
+ errs: {tl.Error}
+end
The result from parsing source code including the tokens, ast, calls to require, and errors
+function common.init_env_from_config
+function
common.init_env_from_config(
+ cfg
: config.Config
+): tl.Env
, string
Initialize a strict Teal environment, using the relevant entries of the config to modify that environment
may return nil
and an error message if something could not be applied to the environment
function common.init_teal_env
+function
common.init_teal_env(
+ gen_compat
: boolean | tl.CompatMode
,
+ gen_target
: tl.TargetMode
,
+ env_def
: string
+): tl.Env
, string
Initialize a strict Teal environment
+function common.lex_file
+function
common.lex_file(
+ path
: string
+): {Token}
, {tl.Error}
, string
reads a file, calls tl.lex
on its contents, caches and returns the results
function common.make_error_header
+function
common.make_error_header(
+ file
: string
,
+ num_errors
: integer
,
+ category
: string
+): cs.ColorString
Creates a nicely colored header to log errors
For example make_error_header(
would produce something like "foo.tl"
, 10, "foo error"
)10 foo errors in foo.tl
with 10
and foo.tl
highlighted
function common.parse_file
+function
common.parse_file(
+ path
: string
+): ParseResult
, string
calls lex_file
, parses the token stream, caches and returns the results
function common.prepend_to_lua_path
+function
common.prepend_to_lua_path(
+ path_str
: string
+)
Prepend the given string to package.path and package.cpath.
Correctly adds ?.lua and ?/init.lua to the path
+function common.report_config_errors
+function
common.report_config_errors(
+ errs
: {string}
,
+ warnings
: {string}
+): boolean
use log.warn
and log.err
to report errors and warnings from config.load
function common.report_env_results
+function
common.report_env_results(
+ env
: tl.Env
,
+ cfg
: config.Config
+): boolean
Report all errors from a tl.Env
Returns false when errors were reported
+function common.report_errors
+function
common.report_errors(
+ logger
: log.Logger
,
+ errs
: {tl.Error}
,
+ file
: string
,
+ category
: string
+)
Logs an array of errors with nice colors and a header generated by make_error_header
function common.report_result
+function
common.report_result(
+ r
: tl.Result
,
+ c
: config.Config
+): boolean
Logs all the syntax errors, warnings, type errors, etc. from a tl.Result
with proper colors
Returns false if there were any errors. This includs warnings that were promoted to errors and doesn't include warnings that were not promoted to errors.
+function common.result_has_errors
+function
common.result_has_errors(
+ r
: tl.Result
,
+ c
: config.Config
+): boolean
Returns whether or not the result has errors. Doesn't print/log anything
+function common.search_module
+function
common.search_module(
+ name
: string
,
+ search_dtl
: boolean
+): fs.Path
A wrapper around tl.search_module
but, returns an fs.Path
and will cache results
function common.syntax_highlight
+function
common.syntax_highlight(
+ s
: string
+): cs.ColorString
Takes Teal or Lua code and returns a ColorString highlighting things like keywords, operators, and more.
+function common.type_check_ast
+function
common.type_check_ast(
+ ast
: Node
,
+ opts
: tl.TypeCheckOptions
+): tl.Result
, string
Just type checks an ast
+Basically some extensions of the std lib. Currently these lean towards a more functional style
This is split into two main modules, str
and tab
. For string and table utilities respectively.
peek<Value>(iter: function(...: any): (Value), ...: any): function(): Value, Value
-Takes an iterator and turns it into an iterator that returns pairs of values
For example if some iterator f
returns the sequence 1, 2, 3 peek(f)
would return the pairs (1, 2), (2, 3), (3, nil)
str.esc(s: string, sub: string | function(string): string | {string:string}): string, integer
-escape any special characters in a string
use sub
to control how the characters are substituted, by default a special character x
will be replaced with %x
returns the new string and the number of characters replaced
-str.pad_left(s: string, n: integer): string
-Prefix s
with spaces so the resulting string is at least n
characters long
str.split(s: string, del: string, no_patt: boolean): function(): string
-Split a string by del
, returning the substring that was matched
Will error if the delimiter matches the empty string
-str.split_find(s: string, del: string, no_patt: boolean): function(): integer, integer
-Split a string by del
, returning the indexes of the match
Will error if the delimiter matches the empty string
-tab.contains<Value>(t: {Value}, val: Value): boolean
-Report if an array contains an element (as determined by the ==
operator)
tab.ensure_scalar_array<Value>(source: Value | {Value}): {Value}
-If source
is not an array, create an array with source
as its only element
Note: Due to teal's current generic limitations, this only works if Value
is a NON-table type
tab.filter<Value>(t: {Value}, pred: function(Value): boolean): {Value}, {Value}
-Create two new lists from t
: the values that return true
from pred
and the values that return false
tab.from<Value>(fn: function(...: any): (Value), ...: any): {Value}
-Collect all the values of an iterator in a list
-tab.intersperse<Value>(t: {Value}, val: Value): {Value}
-produce a new list by inserting val
after each element
tab.ivalues<Value>(t: {any:Value}): function(): Value
-Iterate over the integer indexed values of a map
-tab.keys<Key>(t: {Key:any}): function(): Key
-Iterate over the keys of a map
-tab.map<Key, Value, MappedValue>(t: {Key:Value}, fn: function(Value): MappedValue): {Key:MappedValue}
-Create a new map from t
by passing each value through fn
tab.map_ipairs<Value, MappedValue>(t: {Value}, fn: function(Value): MappedValue): function(): integer, MappedValue
-iterate over a list like ipairs does, but filter the values through fn
tab.merge_list<Value>(a: {Value}, b: {Value}): {Value}
-Create a new list by shallow copying the contents of a
and b
tab.set<Value>(lst: {Value}): {Value:boolean}
-Create a Set from a list
-tab.sort_in_place<Value>(t: {Value}, fn: function(Value, Value): boolean): {Value}
-Sort a table (in place) and return that table
-tab.values<Key, Value>(t: {Key:Value}): function(): Value
-Iterate over the values of a map
+function peek
+function
peek<Value
>(
+ iter
: function(...: any): (Value)
,
+ ...
: any
+): function(): Value
, Value
Takes an iterator and turns it into an iterator that returns pairs of values
For example if some iterator f
returns the sequence 1, 2, 3 peek(f)
would return the pairs (1, 2), (2, 3), (3, nil)
function str.esc
+function
str.esc(
+ s
: string
,
+ sub
: string | function(string): string | {string:string}
+): string
, integer
escape any special characters in a string
use sub
to control how the characters are substituted, by default a special character x
will be replaced with %x
returns the new string and the number of characters replaced
+function str.pad_left
+function
str.pad_left(
+ s
: string
,
+ n
: integer
+): string
Prefix s
with spaces so the resulting string is at least n
characters long
function str.split
+function
str.split(
+ s
: string
,
+ del
: string
,
+ no_patt
: boolean
+): function(): string
Split a string by del
, returning the substring that was matched
Will error if the delimiter matches the empty string
+function str.split_find
+function
str.split_find(
+ s
: string
,
+ del
: string
,
+ no_patt
: boolean
+): function(): integer
, integer
Split a string by del
, returning the indexes of the match
Will error if the delimiter matches the empty string
+function tab.contains
+function
tab.contains<Value
>(
+ t
: {Value}
,
+ val
: Value
+): boolean
Report if an array contains an element (as determined by the ==
operator)
function tab.ensure_scalar_array
+function
tab.ensure_scalar_array<Value
>(
+ source
: Value | {Value}
+): {Value}
If source
is not an array, create an array with source
as its only element
Note: Due to teal's current generic limitations, this only works if Value
is a NON-table type
function tab.filter
+function
tab.filter<Value
>(
+ t
: {Value}
,
+ pred
: function(Value): boolean
+): {Value}
, {Value}
Create two new lists from t
: the values that return true
from pred
and the values that return false
function tab.from
+function
tab.from<Value
>(
+ fn
: function(...: any): (Value)
,
+ ...
: any
+): {Value}
Collect all the values of an iterator in a list
+function tab.intersperse
+function
tab.intersperse<Value
>(
+ t
: {Value}
,
+ val
: Value
+): {Value}
produce a new list by inserting val
after each element
function tab.ivalues
+function
tab.ivalues<Value
>(
+ t
: {any:Value}
+): function(): Value
Iterate over the integer indexed values of a map
+function tab.keys
+function
tab.keys<Key
>(
+ t
: {Key:any}
+): function(): Key
Iterate over the keys of a map
+function tab.map
+function
tab.map<Key
, Value
, MappedValue
>(
+ t
: {Key:Value}
,
+ fn
: function(Value): MappedValue
+): {Key:MappedValue}
Create a new map from t
by passing each value through fn
function tab.map_ipairs
+function
tab.map_ipairs<Value
, MappedValue
>(
+ t
: {Value}
,
+ fn
: function(Value): MappedValue
+): function(): integer
, MappedValue
iterate over a list like ipairs does, but filter the values through fn
function tab.merge_list
+function
tab.merge_list<Value
>(
+ a
: {Value}
,
+ b
: {Value}
+): {Value}
Create a new list by shallow copying the contents of a
and b
function tab.set
+function
tab.set<Value
>(
+ lst
: {Value}
+): {Value:boolean}
Create a Set from a list
+function tab.sort_in_place
+function
tab.sort_in_place<Value
>(
+ t
: {Value}
,
+ fn
: function(Value, Value): boolean
+): {Value}
Sort a table (in place) and return that table
+function tab.values
+function
tab.values<Key
, Value
>(
+ t
: {Key:Value}
+): function(): Value
Iterate over the values of a map
+", content, "
" }, { name = name }))
+local function bullet_header(content: string | TagTree, name: string): TagTree
+ return h3(a({ "", content, "
" }, { name = name }))
end
local emit: {string:Emitter} = setmetatable({}, {
@@ -117,22 +117,94 @@ local function escape_html_chars(str: string): string, integer
return str:gsub("[<>'\"&]", html_escape_map)
end
+local function begin_signature_and_description(name: string, keyword: string): {string}
+ local data = {
+ "",
+ "", assert(keyword), "
", assert(name),
+ }
+
+ return data
+end
+
+local function end_signature_and_description(
+ name: string,
+ link_name: string,
+ data: {string},
+ content: string | TagTree
+): string
+ return html {
+ bullet_header(name, link_name),
+ data,
+ "
",
+ content,
+ "")
+ table.insert(data, child:source())
+ table.insert(data, "
")
+ first = false
+ end
+ table.insert(data, (escape_html_chars ">"))
+ end
+
+ table.insert(data, "(")
+
+ first = true
+ for child in sig:child_by_field_name("arguments"):named_children() do
+ local parameter_name ")
+ table.insert(data, parameter_name and parameter_name:source() or "...")
+ table.insert(data, "
: ")
+ table.insert(data, ty)
+ table.insert(data, "
")
+
+ first = false
+ end
+
+ table.insert(data, first and ")" or "\n)")
+
+ if ret then
+ table.insert(data, ": ")
+ first = true
+ for t in ret:named_children() do
+ if not first then
+ table.insert(data, ", ")
+ end
+ table.insert(data, "" .. t:source() .. "
")
+ first = false
+ end
+ end
+
table.insert(
out,
- html {
- doc_header({ name,
- typeargs and escape_html_chars(typeargs:source()) or "",
- sig:child_by_field_name("arguments"):source(),
- (ret and ": " .. ret:source() or "") },
- name),
+ end_signature_and_description(
+ (name:match(":") and "method " or "function ") .. name,
+ name,
+ data,
p { escape(table.concat(prefix)) }
- }
+ )
)
return name
end
@@ -142,34 +214,30 @@ emit["enum_declaration"] = function(prefix: {string}, n: ts.Node, out: {string})
assertf(body, "enum_body is nil for %s", tostring(n));
local name " .. child:source() .. "
\n")
end
+ table.insert(data, "end
")
table.insert(
out,
- html {
- doc_header ({"type ", name}, name),
- pre { "enum ", name, br, " ",
- table.concat(entries, br .. " "),
- br,
- "end " },
- p { escape(table.concat(prefix)) }
- }
+ end_signature_and_description("type " .. name, name, data, p { escape(table.concat(prefix)) })
)
return name
end
emit["record_declaration"] = function(prefix: {string}, n: ts.Node, out: {string}): string
- local fields " .. t:source() .. "
"
)
elseif c:type() == "metamethod" then
table.insert(
meta,
- c:source()
+ "metamethod
"
+ .. c:child_by_field_name("name"):source()
+ .. ": "
+ .. c:child_by_field_name("type"):source()
+ .. "
"
)
elseif c:type() == "record_array_type" then
table.insert(
fields,
1,
- "{" .. c:child(0):source() .. "}"
+ "{" .. c:child(0):source() .. "}
"
)
end
end
local obj_name end
")
table.insert(
out,
- html {
- doc_header ({"type ", obj_name}, obj_name),
- pre (pre_contents),
- p { escape(table.concat(prefix)) }
- }
+ end_signature_and_description("type " .. obj_name, obj_name, data, p { escape(table.concat(prefix)) })
)
return obj_name
end