Cyan API Documentation

This documentation is auto-generated and may be incomplete.

Table of Contents

cyan.cli

cyan.command
Command
command.get
command.merge_args_into_config
command.new
command.register_all

cyan.config
Config
config.find
config.is_config
config.load

cyan.decoration
Color
Decorated:copy
Decorated
Decoration
Renderer
SchemeEntry
decoration.color_copy
decoration.copy
decoration.decorate
decoration.file_name
decoration.render_ansi
decoration.render_plain
decoration.render_to_string
decoration.rgb

cyan.fs
fs.change_directory
fs.copy
fs.current_directory
fs.exists
fs.get_line
fs.is_directory
fs.is_file
fs.iterate_directory
fs.make_directory
fs.make_parent_directories
fs.match_any
fs.mod_time
fs.normalize
fs.read
fs.scan_directory
fs.search_parent_dirs
fs.write

cyan.graph
Dag:find
Dag:insert_file
Dag:mark_each
Dag:marked_nodes
Dag:nodes
Dag
Node
graph.empty
graph.scan_directory

cyan.interaction
interaction.yes_no_prompt

cyan.invocation-context
InvocationContext
invocation_context.new

cyan.log
ColorMode
Logger:cont
Logger:cont_nonl
Logger:copy
Logger:format
Logger:format_nonl
Logger:nonl
Logger:should_log
Logger
Verbosity
create_logger
log.set_color_mode
log.set_prefix_padding
log.set_verbosity

cyan.meta

cyan.sandbox

cyan.script
script.disable
script.emit_hook
script.emitter
script.ensure_loaded_for_command
script.register

cyan.tlcommon
ParseResult
common.init_env_from_config
common.init_teal_env
common.lex_file
common.make_error_header
common.parse_file
common.prepend_to_lua_path
common.report_config_errors
common.report_env_results
common.report_errors
common.report_result
common.result_has_errors
common.search_module
common.syntax_highlight

cyan.util
peek
str.esc
str.pad_left
str.split
str.split_find
tab.contains
tab.ensure_scalar_array
tab.filter
tab.from
tab.intersperse
tab.ivalues
tab.keys
tab.map
tab.map_ipairs
tab.merge_list
tab.set
tab.sort_in_place
tab.values

cyan.cli

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

cyan.command

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

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

cyan.config

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}
end

The config data

function config.find

function config.find(): lexical_path.Path

Find 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

cyan.decoration

A replacement for colorstring that doesn't specifically rely on ANSI terminal escapes

type Color

record Color
	red: integer
	green: integer
	blue: integer
end

A color described by rgb values in [0, 255]

method Decorated:copy

function Decorated:copy(
	delta: Decoration
): Decorated

Deeply copy the decoration of a decorated string

type Decorated

record Decorated
	plain_content: string
	decoration: Decoration
end

A 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
end

Various 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"
end

Predefined decorations used for logging, syntax highlighting errors, etc.

function decoration.color_copy

function decoration.color_copy(
	c: Color
): Color

Deep copy a Color

function decoration.copy

function decoration.copy(
	to_be_copied: Decoration,
	delta: Decoration
): Decoration

Deep copy a Decoration, but copy values from delta when present

function decoration.decorate

function decoration.decorate(
	plain: string,
	decor: Decoration
): Decorated

Create a decorated string

function decoration.file_name

function decoration.file_name(
	path: string | lexical_path.Path
): Decorated

Decorate 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
): string

A convenience function to render to a buffer, then concatenate that buffer

function decoration.rgb

function decoration.rgb(
	red: integer,
	green: integer,
	blue: integer
): Color

Positional constructor for Color

cyan.fs

Filesystem and path management

function fs.change_directory

function fs.change_directory(
	p: lexical_path.Path
): boolean, string

Attempt 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, string

Copy 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, string

On 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
): boolean

Attempt to see if the path referred to by p exists.

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

function fs.is_directory(
	p: lexical_path.Path
): boolean, string

Return 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, string

Return 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.Path

Iterate 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, string

Create 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, string

For 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}
): integer

If 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, string

Returns 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
): string

Normalizes a given path via the lexical_path library

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_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.Path

Recursively 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.Path

Search 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, string

Open a file, overwrite its contents, then close the file

cyan.graph

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
): Node

Find 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(): 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, they are sorted by input filename lexicographically

type Dag

record Dag
-- private fields (click to show) _nodes_by_filename: {string:Node}
end

The graph object

type Node

record Node
	input: lexical_path.Path
	output: lexical_path.Path
	modules: {string:lexical_path.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_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

cyan.interaction

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}
): 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.

cyan.invocation-context

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
end

The context itself.

function invocation_context.new

function invocation_context.new(
	initial_directory: lexical_path.Path,
	project_root_directory: lexical_path.Path
): InvocationContext

Create a new context, asserting any invariants.

cyan.log

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

Whether 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
): 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 | decoration.Decorated
	continuation: string | decoration.Decorated
	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 | decoration.Decorated,
	cont: string | decoration.Decorated,
	inspector: function(any): string
): Logger

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

cyan.meta

Meta information about Cyan itself

cyan.sandbox

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

cyan.script

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, 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(): lexical_path.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: 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

cyan.tlcommon

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}
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.GenCompat,
	gen_target: tl.GenTarget,
	env_def: string
): tl.Env, string

Initialize a strict Teal environment

function common.lex_file

function common.lex_file(
	path: string
): {tl.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
): {string | decoration.Decorated}

Creates a nicely colored header to log errors

For example make_error_header("foo.tl", 10, "foo error") would produce something like 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
): lexical_path.Path

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

cyan.util

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