This documentation is auto-generated and may be incomplete.
The command line driver
This is not a module to be used and requiring it will attempt to start the cli and call os.exit
The common interface for commands to implement
type Command
record Command
name: string
description: string
argparse: function(argparse.Command)
script_hooks: {string}
exec: CommandFn
endThe interface
function command.get
function command.get(
name: string
): CommandGet 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: lexical_path.Path
build_dir: lexical_path.Path
source_dir: lexical_path.Path
include: {lexical_path.Pattern}
exclude: {lexical_path.Pattern}
global_env_def: string
include_dir: {lexical_path.Path}
dont_prune: {string}
scripts: {string:{string:{lexical_path.Path}}}
feat_arity: tl.Feat
gen_compat: tl.GenCompat
gen_target: tl.GenTarget
disable_warnings: {tl.WarningKind}
warning_error: {tl.WarningKind}
externals: {string:any}
endThe config data
function config.find
function config.find(): lexical_path.PathFind config.filename in the current or parent directories
function config.is_config
function config.is_config(
c_in: 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
A replacement for colorstring that doesn't specifically rely on ANSI terminal escapes
type Color
record Color
red: integer
green: integer
blue: integer
endA color described by rgb values in [0, 255]
method Decorated:copy
function Decorated:copy(
delta: Decoration
): DecoratedDeeply copy the decoration of a decorated string
type Decorated
record Decorated
plain_content: string
decoration: Decoration
endA string and a decoration
type Decoration
record Decoration
bold: boolean
italic: boolean
monospace: boolean
linked_uri: string
color: Color
background_color: Color
ansi_color: integer
ansi_background_color: integer
endVarious visual properties that could apply to some text
type Renderer
type Rendererfunction(out_buffer: {string}, plain_content: string, Decoration)Render the decorated contents into out_buffer
Renderers are assumed to be stateless, and results may be cached and rearranged in the actual output
type SchemeEntry
enum SchemeEntry
"black"
"red"
"green"
"yellow"
"blue"
"magenta"
"cyan"
"white"
"gray"
"bright_red"
"bright_green"
"bright_yellow"
"bright_blue"
"bright_magenta"
"bright_cyan"
"bright_white"
"teal"
"emphasis"
"error"
"error_number"
"file"
"keyword"
"number"
"operator"
"string"
"warn"
"affirmative"
"negative"
endPredefined decorations used for logging, syntax highlighting errors, etc.
function decoration.color_copy
function decoration.color_copy(
c: Color
): ColorDeep copy a Color
function decoration.copy
function decoration.copy(
to_be_copied: Decoration,
delta: Decoration
): DecorationDeep copy a Decoration, but copy values from delta when present
function decoration.decorate
function decoration.decorate(
plain: string,
decor: Decoration
): DecoratedCreate a decorated string
function decoration.file_name
function decoration.file_name(
path: string | lexical_path.Path
): DecoratedDecorate using the default decoration for file names and a uri for the path
function decoration.render_ansi
function decoration.render_ansi(
buf: {string},
content: string,
decor: Decoration
)Render decorations as ANSI escape sequences
function decoration.render_plain
function decoration.render_plain(
buf: {string},
content: string,
_decoration: Decoration
)Render text by discarding all decorations
function decoration.render_to_string
function decoration.render_to_string(
render: Renderer,
plain_content: string,
decor: Decoration
): stringA convenience function to render to a buffer, then concatenate that buffer
function decoration.rgb
function decoration.rgb(
red: integer,
green: integer,
blue: integer
): ColorPositional constructor for Color
Filesystem and path management
function fs.change_directory
function fs.change_directory(
p: lexical_path.Path
): boolean, stringAttempt to change the current directory to the directory referred to by the given path. On success returns true. On failure returns nil and an error message.
function fs.copy
function fs.copy(
source_path: lexical_path.Path,
dest_path: lexical_path.Path
): boolean, stringCopy a file
uses fs.read internally to get and cache the contents of source
function fs.current_directory
function fs.current_directory(): lexical_path.Path, stringOn success returns the current working directory as a lexical path. On error returns nil and an error message
function fs.exists
function fs.exists(
p: lexical_path.Path
): booleanAttempt to see if the path referred to by p exists.
function fs.get_line
function fs.get_line(
p: string,
n: integer
): string, stringGets 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.is_directory
function fs.is_directory(
p: lexical_path.Path
): boolean, stringReturn if the file referred to by the given path is a directory.
On failure, returns nil and an error message
function fs.is_file
function fs.is_file(
p: lexical_path.Path
): boolean, stringReturn if the file referred to by the given path is a regular file
function fs.iterate_directory
function fs.iterate_directory(
dir: lexical_path.Path,
include_dotfiles: boolean
): function(): lexical_path.PathIterate over the given directory, returning lexical_path.Path objects
By default, will not include paths that start with '.'
function fs.make_directory
function fs.make_directory(
path: lexical_path.Path
): boolean, stringCreate a directory with the given path, creating any ancestor directories
In unixy terms, this is mkdir -p
function fs.make_parent_directories
function fs.make_parent_directories(
of: lexical_path.Path
): boolean, stringFor each ancestor of the given path, mkdir that path
In unixy terms, this is roughly mkdir -p path/..
function fs.match_any
function fs.match_any(
path: lexical_path.Path,
patterns: {string | lexical_path.Pattern}
): integerIf the given path matches any of the patterns in patterns, returns the index of the matched pattern otherwise returns nil
function fs.mod_time
function fs.mod_time(
of: lexical_path.Path
): integer, stringReturns the modification time of the file referred to by the given path
In case of an error, returns nil, and an error message
function fs.normalize
function fs.normalize(
p: string
): stringNormalizes a given path via the lexical_path library
function fs.read
function fs.read(
p: string
): string, stringOpen 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_directory
function fs.scan_directory(
dir: lexical_path.Path,
include: {string | lexical_path.Pattern},
exclude: {string | lexical_path.Pattern},
include_directories: boolean
): function(): lexical_path.PathRecursively iterate over the files in a directory, following the provided include and exclude patterns
For information on path patterns, see the lexical-path module's documentation
function fs.search_parent_dirs
function fs.search_parent_dirs(
start_path: lexical_path.Path,
fname: string
): lexical_path.PathSearch for a file in the parent directories of the given path. Returns the path of the file found.
Always returns an absolute path
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.write
function fs.write(
p: lexical_path.Path,
content: string
): boolean, stringOpen a file, overwrite its contents, then close the file
A utility for building directed acyclic graphs of Teal source files
This is the main driver behind the build command
method Dag:find
function Dag:find(
f: lexical_path.Path
): NodeFind a node in the graph with the given path name
method Dag:insert_file
function Dag:insert_file(
f: lexical_path.Path,
in_dir: lexical_path.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(lexical_path.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(): NodeIterate over every node that has been marked, no matter what the mark is
method Dag:nodes
function Dag:nodes(): function(): NodeIterate over nodes in order of dependents
If two nodes have the same number of dependent nodes, they are sorted by input filename lexicographically
type Dag
record Dag
-- private fields (click to show)
_nodes_by_filename: {string:Node}
endThe graph object
type Node
record Node
input: lexical_path.Path
output: lexical_path.Path
modules: {string:lexical_path.Path}
mark: Mark
dependents: {Node:boolean}
endThe nodes that are stored in the graph
function graph.empty
function graph.empty(): DagInitializes an empty graph
function graph.scan_directory
function graph.scan_directory(
dir: lexical_path.Path,
include: {string | lexical_path.Pattern},
exclude: {string | lexical_path.Pattern}
): Dag, {string}Recursively scan a directory (using fs.scan_directory) 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
function interaction.yes_no_prompt
function interaction.yes_no_prompt(
prompt: string | decoration.Decorated,
logger: log.Logger,
default: boolean,
affirm: {string},
deny: {string}
): booleanAsk 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.
An invocation context contains what would be global variables that are concerned with how cyan was invoked. Things like the initial directory are kept here so that paths may be displayed relative to it despite some commands changing the directory of the process.
type InvocationContext
record InvocationContext
initial_directory: lexical_path.Path
project_root_directory: lexical_path.Path
endThe context itself.
function invocation_context.new
function invocation_context.new(
initial_directory: lexical_path.Path,
project_root_directory: lexical_path.Path
): InvocationContextCreate a new context, asserting any invariants.
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 | decoration.Decorated,
cont: string | decoration.Decorated,
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 decoration.Decorated 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 decoration.Decorated) it will not be handled.
type ColorMode
enum ColorMode
"always"
"never"
"auto"
endWhether to apply color to the output
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 | decoration.Decorated,
new_continuation: string | decoration.Decorated
): LoggerCreate 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(): booleanReturns 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 | decoration.Decorated
continuation: string | decoration.Decorated
inspector: function(any): string
metamethod __call: function(...: any)
endThe data needed for a logger to do its job.
type Verbosity
enum Verbosity
"quiet"
"normal"
"extra"
"debug"
endThe thresholds for loggers to actually write their output
function create_logger
function create_logger(
stream: FILE,
verbosity_threshold: Verbosity,
prefix: string | decoration.Decorated,
cont: string | decoration.Decorated,
inspector: function(any): string
): LoggerCreates a Logger as described above
function log.set_color_mode
function log.set_color_mode(
mode: ColorMode
)Globally set the color behavior of the logging module.
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
Super simplistic sandboxing In the future maybe could be integrated with some very simple debugging utilities for now, we just offer the convenience of exiting gracefully if someone puts while true do end in their config for some reason
The script loading api
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(
context: invocation_context.InvocationContext,
name: string,
...: any
): boolean, stringIterates 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(): lexical_path.Path, boolean, stringEmit 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.ResultAttempts to load each script that the given command may need
function script.register
function script.register(
path: lexical_path.Path,
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
path MUST be an absolute path
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: {tl.Token}
ast: tl.Node
reqs: {string}
errs: {tl.Error}
endThe 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, stringInitialize 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.GenCompat,
gen_target: tl.GenTarget,
env_def: string
): tl.Env, stringInitialize a strict Teal environment
function common.lex_file
function common.lex_file(
path: string
): {tl.Token}, {tl.Error}, stringreads 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
): {string | decoration.Decorated}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, stringcalls 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}
): booleanuse 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
): booleanReport 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
): booleanLogs 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
): booleanReturns 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
): lexical_path.PathA wrapper around tl.search_module but, returns an lexical_path.Path and will cache results
function common.syntax_highlight
function common.syntax_highlight(
s: string
): {decoration.Decorated}Takes Teal or Lua code and returns a decorated string highlighting things like keywords, operators, and more.
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.
function peek
function peek<Value>(
iter: function(...: any): (Value),
...: any
): function(): Value, ValueTakes 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, integerescape 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
): stringPrefix 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(): stringSplit 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, integerSplit 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
): booleanReport 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(): ValueIterate over the integer indexed values of a map
function tab.keys
function tab.keys<Key>(
t: {Key:any}
): function(): KeyIterate 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, MappedValueiterate 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(): ValueIterate over the values of a map