Compare commits

..

No commits in common. "main" and "main" have entirely different histories.
main ... main

8 changed files with 205 additions and 589 deletions

16
env.lua
View File

@ -5,7 +5,7 @@ env.app = {
name = "gemwriter", name = "gemwriter",
exec_name = "gemwriter", exec_name = "gemwriter",
version = "0.3", version = "0.3",
last_updated = "2022-08-06", last_updated = "2022-08-05",
} }
env.defaults = { env.defaults = {
@ -18,7 +18,6 @@ env.defaults = {
page = "page.gmi", page = "page.gmi",
post = "post.gmi", post = "post.gmi",
}, },
lang_opts = { "en_US", "fr_FR" },
post_slug = "untitled", post_slug = "untitled",
post_slug_date_format = "%Y-%m-%d", post_slug_date_format = "%Y-%m-%d",
-- Atom feed date format -- Atom feed date format
@ -28,24 +27,13 @@ env.defaults = {
page_slug = os.date("%Y%m%d-%H%M%S"), page_slug = os.date("%Y%m%d-%H%M%S"),
} }
-- Command-line keywords
env.cli_opts = {
config = "config",
page = "page",
post = "post",
index = "index",
publish = "publish",
help = "help",
version = "version",
}
-- Configurable settings -- Configurable settings
env.toml_vars = { env.toml_vars = {
general = "general", general = "general",
capsules = "capsules", capsules = "capsules",
} }
env.general = { env.general = {
app_lang = "en_US", app_lang = "en",
main_capsule = "main", main_capsule = "main",
} }
env.capsules = { env.capsules = {

View File

@ -1,5 +1,5 @@
local env = require("env") local env = require("env")
local lang = require("lang.en_US") local lang = require("lang.en")
local util = require("util") local util = require("util")
@ -8,32 +8,11 @@ writer.docs = {}
writer.conf = {} writer.conf = {}
writer.posts = {} writer.posts = {}
writer.docs.get_lang_opts = [[
Return a table of available language translations for the app interface.
]]
function writer.get_lang_opts()
local lang_opts = {}
for l = 1, #env.defaults.lang_opts do
if util.module_exists("lang." .. env.defaults.lang_opts[l]) then
table.insert(lang_opts, env.defaults.lang_opts[l])
end
end
return lang_opts
end
writer.docs.gen_config = [[ writer.docs.gen_config = [[
Generate a default config directory. Generate a default config directory.
]] ]]
function writer.gen_config(lang_pref) function writer.gen_config()
-- Set lang writer.conf.config_dir = util.replace_shell_vars(env.defaults.config_dir)
if lang_pref ~= nil and util.module_exists("lang." .. lang_pref) then
lang = require("lang." .. lang_pref)
env.defaults_toml = string.gsub(env.defaults_toml, env.general.app_lang,
lang_pref)
end
util.make_dir(writer.conf.config_dir) util.make_dir(writer.conf.config_dir)
for name, file in pairs(env.defaults.config_files) do for name, file in pairs(env.defaults.config_files) do
-- Check each file individually anyway to avoid overwriting existing -- Check each file individually anyway to avoid overwriting existing
@ -127,9 +106,7 @@ function writer.parse_config(config_file)
end end
-- Set lang -- Set lang
if util.module_exists("lang." .. writer.conf.app_lang) then
lang = require("lang." .. writer.conf.app_lang) lang = require("lang." .. writer.conf.app_lang)
end
-- Custom templates override lang defaults (ignore config) -- Custom templates override lang defaults (ignore config)
for name, file in pairs(env.defaults.config_files) do for name, file in pairs(env.defaults.config_files) do
@ -142,9 +119,24 @@ end
writer.docs.load_config = [[ writer.docs.load_config = [[
Load an existing config. Exit if a capsule is not found. Check whether there is an existing config and load it if found. Exit if a
capsule is not found.
]] ]]
function writer.load_config(cap_id) function writer.load_config(cap_id)
writer.conf.config_dir = util.replace_shell_vars(env.defaults.config_dir)
local config_file = util.read_file(writer.conf.config_dir .. "/" ..
env.defaults.config_files.config)
local config_new = false
-- No config found
if (config_file == "") and (not config_new) then
writer.gen_config()
config_new = true
end
-- Config found, read the configuration and load the values to an
-- associative table, writer.conf.
if (config_file ~= "") and (not config_new) then
-- Check capsule id exists, abort if no valid capsule found -- Check capsule id exists, abort if no valid capsule found
if (cap_id == nil) or (string.find(cap_id, " ") ~= nil) then if (cap_id == nil) or (string.find(cap_id, " ") ~= nil) then
writer.conf.cap_id = env.general.main_capsule writer.conf.cap_id = env.general.main_capsule
@ -153,12 +145,14 @@ function writer.load_config(cap_id)
end end
local cap_label = "%[" .. env.toml_vars.capsules .. "%." .. local cap_label = "%[" .. env.toml_vars.capsules .. "%." ..
tostring(cap_id) .. "%]" tostring(cap_id) .. "%]"
if (string.find(writer.conf.config_file, cap_label) == nil) and if (string.find(config_file, cap_label) == nil) and
(writer.conf.cap_id ~= env.general.main_capsule) then (writer.conf.cap_id ~= env.general.main_capsule) then
print(lang.errs.invalid_cap_id) print(lang.errs.invalid_cap_id)
os.exit() os.exit()
end end
writer.parse_config(writer.conf.config_file)
writer.parse_config(config_file)
end
end end
@ -354,58 +348,40 @@ cli.docs.handle_args = [[
any additional arguments to the functions. any additional arguments to the functions.
]] ]]
function cli.handle_args(args) function cli.handle_args(args)
if (args[1] ~= env.cli_opts.help) or (args[1] ~= env.cli_opts.version) then if (args[1] ~= lang.opts.help) and (args[1] ~= lang.opts.version) then
local config_new = false
writer.conf.config_dir = util.replace_shell_vars(env.defaults.config_dir)
writer.conf.config_file = util.read_file(writer.conf.config_dir .. "/" ..
env.defaults.config_files.config)
-- No config found
if (writer.conf.config_file == "") and (not config_new) then
writer.gen_config(args[2])
config_new = true
end
-- Config found, read the configuration and load the values to an
-- associative table, writer.conf.
if (writer.conf.config_file ~= "") and (not config_new) then
writer.load_config(args[2]) writer.load_config(args[2])
end end
end
if (args[1] == env.cli_opts.index) or (args[1] == env.cli_opts.publish) then if (args[1] == lang.opts.index) or (args[1] == lang.opts.publish) then
writer.posts = writer.get_posts_meta() writer.posts = writer.get_posts_meta()
if writer.conf.cap.gen_index_page then writer.gen_index_page() end if writer.conf.cap.gen_index_page then writer.gen_index_page() end
if writer.conf.cap.gen_atom_feed then writer.gen_atom_feed() end if writer.conf.cap.gen_atom_feed then writer.gen_atom_feed() end
end end
if args[1] == env.cli_opts.config then if args[1] == lang.opts.config then
writer.gen_config(args[2]) writer.load_config()
print(string.format(lang.msgs.load_config, writer.conf.config_dir)) print(lang.msgs.load_config .. writer.conf.config_dir)
elseif (args[1] == env.cli_opts.post) or (args[1] == env.cli_opts.page) then elseif (args[1] == lang.opts.post) or (args[1] == lang.opts.page) then
local file_name = "" local file_name = ""
if args[3] ~= nil then if args[3] ~= nil then
file_name = writer.add_gemtext(args[3], args[1]) file_name = writer.add_gemtext(args[3], args[1])
else else
file_name = writer.add_gemtext(args[2], args[1]) file_name = writer.add_gemtext(args[2], args[1])
end end
print(string.format(lang.msgs.add_gemtext, file_name)) print(lang.msgs.add_gemtext .. file_name)
elseif args[1] == env.cli_opts.index then elseif args[1] == lang.opts.index then
print(lang.msgs.index) print(lang.msgs.index)
elseif args[1] == env.cli_opts.publish then elseif args[1] == lang.opts.publish then
writer.publish() writer.publish()
print(lang.msgs.publish) print(lang.msgs.publish)
elseif args[1] == env.cli_opts.help then elseif args[1] == lang.opts.help then
print(string.format(lang.msgs.help_usage, env.app.exec_name) .. print(env.app.exec_name .. lang.msgs.help)
string.format(lang.msgs.help_opts, env.cli_opts.config,
env.cli_opts.page, env.cli_opts.post, env.cli_opts.index,
env.cli_opts.publish, env.cli_opts.help, env.cli_opts.version) ..
string.format(lang.msgs.help_lang_opts,
table.concat(writer.get_lang_opts(), ", ")))
elseif args[1] == env.cli_opts.version then elseif args[1] == lang.opts.version then
print(env.app.name .. " " .. env.app.version .. print(env.app.name .. " " .. env.app.version ..
" (" .. env.app.last_updated .. ")") " (" .. env.app.last_updated .. ")")
end end

145
lang/en.lua 100644
View File

@ -0,0 +1,145 @@
local en = {}
-- Templates
en.tpl_vars = {
post = {
author = "{{ post_author }}",
content = "{{ post_content }}",
date = "{{ post_date }}",
summary = "{{ post_summary }}",
tags = "{{ post_tags }}",
title = "{{ post_title }}",
url = "{{ post_url }}",
},
feed = {
date = "{{ feed_date }}",
url = "{{ feed_url }}",
},
index = {
posts = "{{ posts }}",
},
log = {
author = "{{ log_author }}",
subtitle = "{{ log_subtitle }}",
title = "{{ log_title }}",
log_url = "{{ log_url }}",
},
}
en.atom_header = [[<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>{{ log_title }}</title>
<subtitle>{{ log_subtitle }}</subtitle>
<updated>{{ feed_date }}</updated>
<author>
<name>{{ log_author }}</name>
</author>
<id>{{ feed_url }}</id>
<link href="{{ log_url }}" rel="alternate"/>
<link href="{{ feed_url }}" rel="self" type="application/atom+xml"/>
]]
en.atom_entry = [[
<entry>
<id>{{ post_url }}</id>
<title>
<![CDATA[{{ post_title }}]] .. "]]>" .. [[
</title>
<updated>{{ post_date }}</updated>
<author>
<name>{{ post_author }}</name>
</author>
<link href="{{ post_url }}" rel="alternate"/>
<summary>
<![CDATA[{{ post_summary }}]] .. "]]>" .. [[
</summary>
<content>
<![CDATA[{{ post_content }}]] .. "]]>" .. [[
</content>
</entry>
]]
en.atom_footer = [[</feed>]]
en.post = [[---
date: {{ post_date }}
---
# {{ post_title }}
## Links
=> gemini:// link
=> gemini:// link (img)
=> https:// link (https)]]
en.index = [[
# {{ log_title }}
{{ posts }}
]]
en.page = [[
# {{ post_title }}
## Heading 2
### Heading 3
List:
*
*
*
```
Preformatted text
```
## Links
=> gemini:// link
=> gemini:// link (img)
=> https:// link (https)]]
-- App command options and messages output
en.opts = {
config = "config",
page = "page",
post = "post",
index = "index",
publish = "publish",
help = "help",
version = "version",
}
en.msgs = {
add_gemtext = "Created ",
help = [[ [options] [capsule] [title]
Options:
config Generate a config directory
page [capsule] [title] Add a new page with the given title
post [capsule] [title] Add a new gemlog post with the given title
index Generate an index page and feed of posts
publish Index and copy posts remotely using scp
help Show this help message
version Print version info]],
index = "Created index page and feed.",
load_config = [[Created config files. Please edit them with the correct
details before proceeding. The config files can be found at:
]],
publish = "Published capsule.",
}
en.errs = {
invalid_cap_id = "Error: unknown capsule id.",
}
return en

View File

@ -1,157 +0,0 @@
local en = {}
-- Templates
en.tpl_vars = {
post = {
author = "{{ author }}",
content = "{{ content }}",
date = "{{ date }}",
summary = "{{ summary }}",
-- This is not used in the templates yet.
tags = "{{ tags }}",
title = "{{ title }}",
url = "{{ url }}",
},
feed = {
date = "{{ date }}",
url = "{{ feed_url }}",
},
index = {
posts = "{{ posts }}",
},
log = {
author = "{{ author }}",
subtitle = "{{ subtitle }}",
title = "{{ title }}",
url = "{{ log_url }}",
},
}
en.atom_header = [[<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>{{ title }}</title>
<subtitle>{{ subtitle }}</subtitle>
<updated>{{ date }}</updated>
<author>
<name>{{ author }}</name>
</author>
<id>{{ feed_url }}</id>
<link href="{{ log_url }}" rel="alternate"/>
<link href="{{ feed_url }}" rel="self" type="application/atom+xml"/>
]]
en.atom_entry = [[
<entry>
<id>{{ url }}</id>
<title>
<![CDATA[{{ title }}]] .. "]]>" .. [[
</title>
<updated>{{ date }}</updated>
<author>
<name>{{ author }}</name>
</author>
<link href="{{ url }}" rel="alternate"/>
<summary>
<![CDATA[{{ summary }}]] .. "]]>" .. [[
</summary>
<content>
<![CDATA[{{ content }}]] .. "]]>" .. [[
</content>
</entry>
]]
en.atom_footer = [[</feed>]]
en.post = [[---
date: {{ date }}
---
# {{ title }}
## Links
=> gemini:// link
=> gemini:// link (img)
=> https:// link (https)]]
en.index = [[
# {{ title }}
{{ posts }}
]]
en.page = [[
# {{ title }}
## Heading 2
### Heading 3
List:
*
*
*
```
Preformatted text
```
## Links
=> gemini:// link
=> gemini:// link (img)
=> https:// link (https)]]
en.msgs = {}
-- %s: gemtext filename
en.msgs.add_gemtext = "Created %s."
-- %s: app executable name
en.msgs.help_usage = [[%s [options] [capsule] [title]
]]
-- %s: command-line options
-- (see env.lua env.cli_opts for the full list)
en.msgs.help_opts = [[
Options:
%s [lang] Generate a config directory
%s [capsule] [title] Add a new page with the given title
%s [capsule] [title] Add a new gemlog post with the given title
%s Generate an index page and feed of posts
%s Index and copy posts remotely using scp
%s Show this help message
%s Print version info
]]
-- %s: list of language codes
en.msgs.help_lang_opts = [[
Config language options:
%s]]
en.msgs.index = "Created index page and feed."
-- %s: config directory path
en.msgs.load_config = [[Created config files. Please edit them with the correct
details before proceeding. The config files can be found at:
%s]]
en.msgs.publish = "Published capsule."
en.errs = {
invalid_cap_id = "Error: unknown capsule id.",
}
return en

View File

@ -1,159 +0,0 @@
local eo = {}
-- Templates
eo.tpl_vars = {
post = {
author = "{{ aŭtoro }}",
content = "{{ entenaĵo }}",
date = "{{ dato }}",
summary = "{{ resumo }}",
-- This is not used in the templates yet.
tags = "{{ markoj }}",
title = "{{ titolo }}",
-- unuforma risurca lokindiko
url = "{{ url }}",
},
feed = {
date = "{{ dato }}",
url = "{{ XML-fluo_url }}",
},
index = {
posts = "{{ afiŝejo }}",
},
log = {
author = "{{ aŭtoro }}",
subtitle = "{{ subtitolo }}",
title = "{{ titolo }}",
url = "{{ protokolo_url }}",
},
}
eo.atom_header = [[<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>{{ titolo }}</title>
<subtitle>{{ subtitolo }}</subtitle>
<updated>{{ dato }}</updated>
<author>
<name>{{ aŭtoro }}</name>
</author>
<id>{{ XML-fluo_url }}</id>
<link href="{{ protokolo_url }}" rel="alternate"/>
<link href="{{ XML-fluo_url }}" rel="self" type="application/atom+xml"/>
]]
eo.atom_entry = [[
<entry>
<id>{{ url }}</id>
<title>
<![CDATA[{{ titolo }}]] .. "]]>" .. [[
</title>
<updated>{{ dato }}</updated>
<author>
<name>{{ aŭtoro }}</name>
</author>
<link href="{{ url }}" rel="alternate"/>
<summary>
<![CDATA[{{ resumo }}]] .. "]]>" .. [[
</summary>
<content>
<![CDATA[{{ entenaĵo }}]] .. "]]>" .. [[
</content>
</entry>
]]
eo.atom_footer = [[</feed>]]
eo.post = [[---
date: {{ dato }}
---
# {{ titolo }}
## Ligiloj
=> gemini:// ligilo
=> gemini:// ligilo (bildo)
=> https:// ligilo (https)]]
eo.index = [[
# {{ titolo }}
{{ afiŝejo }}
]]
eo.page = [[
# {{ titolo }}
## supra paĝotitolo 2
### supra paĝotitolo 3
Tabelo:
*
*
*
```
antaŭformatita teksto
```
## Ligiloj
=> gemini:// ligilo
=> gemini:// ligilo (bildo)
=> https:// ligilo (https)]]
eo.msgs = {}
-- %s: gemtext filename
eo.msgs.add_gemtext = "Kreita %s."
-- %s: app executable name
eo.msgs.help_usage = [[%s [opcioj] [kapsulo] [titolo]
]]
-- %s: command-line options
-- (see env.lua env.cli_opts for the full list)
eo.msgs.help_opts = [[
Opcioj:
%s [lang] Generi agordan dosierujon
%s [kapsulo] [titolo] Aldonu novan paĝon kun la donita titolo
%s [kapsulo] [titolo] Aldonu novan gemlog-afiŝon kun la donita titolo
%s Generu indeksan paĝon kaj fonton de afiŝoj
%s Indeksu kaj kopiu afiŝojn malproksime uzante scp
%s Helpema helpo estas helpema
%s Presaĵversio-informoj
]]
-- %s: list of language codes
eo.msgs.help_lang_opts = [[
Agordu lingvo-opciojn:
%s]]
eo.msgs.index = "Kreita indeksa paĝo kaj feed"
-- %s: config directory path
eo.msgs.load_config = [[Kreis agordosierojn. Bonvolu redakti ilin per la ĝustaj
detaloj antaŭ ol daŭrigi. La agordosieroj troveblas ĉe:
%s]]
eo.msgs.publish = "Eldonita kapsulo."
eo.errs = {
invalid_cap_id = "Eraro: nekonata kapsula id.",
}
return eo

View File

@ -1,160 +0,0 @@
local fr = {}
-- Templates
fr.tpl_vars = {
post = {
author = "{{ auteur }}",
content = "{{ contenu }}",
date = "{{ date }}",
summary = "{{ résumé }}",
-- This is not used in the templates yet.
tags = "{{ tags }}",
title = "{{ titre }}",
url = "{{ url }}",
},
feed = {
date = "{{ date }}",
url = "{{ url_flux }}",
},
index = {
posts = "{{ articles }}",
},
log = {
-- The concept of a "gemlog" does not have a French translation,
-- the closest term after some discussion might be "journal".
author = "{{ auteur }}",
subtitle = "{{ sous_titre }}",
title = "{{ titre }}",
log_url = "{{ url_journal }}",
},
}
fr.atom_header = [[<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>{{ titre }}</title>
<subtitle>{{ sous_titre }}</subtitle>
<updated>{{ date }}</updated>
<author>
<name>{{ auteur }}</name>
</author>
<id>{{ url_flux }}</id>
<link href="{{ url_journal }}" rel="alternate"/>
<link href="{{ url_flux }}" rel="self" type="application/atom+xml"/>
]]
fr.atom_entry = [[
<entry>
<id>{{ url }}</id>
<title>
<![CDATA[{{ titre }}]] .. "]]>" .. [[
</title>
<updated>{{ date }}</updated>
<author>
<name>{{ auteur }}</name>
</author>
<link href="{{ url }}" rel="alternate"/>
<summary>
<![CDATA[{{ résumé }}]] .. "]]>" .. [[
</summary>
<content>
<![CDATA[{{ contenu }}]] .. "]]>" .. [[
</content>
</entry>
]]
fr.atom_footer = [[</feed>]]
fr.post = [[---
date: {{ date }}
---
# {{ titre }}
## Liens
=> gemini:// lien
=> gemini:// lien (image)
=> https:// lien (https)]]
fr.index = [[
# {{ titre }}
{{ articles }}
]]
fr.page = [[
# {{ titre }}
## Titre de niveau 2
### Titre de niveau 3
Liste :
*
*
*
```
Texte préformaté
```
## Liens
=> gemini:// lien
=> gemini:// lien (image)
=> https:// lien (https)]]
fr.msgs = {}
-- %s: gemtext filename
fr.msgs.add_gemtext = "%s créé."
-- %s: app executable name
fr.msgs.help_usage = [[%s [options] [capsule] [titre]
]]
-- %s: command-line options
-- (see env.lua env.cli_opts for the full list)
fr.msgs.help_opts = [[
Options :
%s [lang] Générer un dossier de configuration
%s [capsule] [titre] Ajouter une nouvelle page avec le titre fourni
%s [capsule] [titre] Ajouter un nouvel article avec le titre fourni
%s Générer une page d'accueil et un flux d'articles
%s Générer les pages puis les publier via scp
%s Afficher ce message d'aide
%s Afficher les informations de version
]]
-- %s: list of language codes
fr.msgs.help_lang_opts = [[
Langues disponibles :
%s]]
fr.msgs.index = "Flux et page d'accueil créés."
-- %s: config directory path
fr.msgs.load_config = [[Les fichiers de configuration ont été créés. Veuillez y
inscrire les valeurs correctes avant de continuer.
Les fichiers peuvent être trouvés dans :
%s]]
fr.msgs.publish = "Capsule publiée."
fr.errs = {
invalid_cap_id = "Erreur: Identifiant de capsule inconnu.",
}
return fr

View File

@ -6,7 +6,7 @@ A little command-line helper for publishing [Gemini] sites or "capsules".
``` ```
Options: Options:
config [lang] Generate a config directory config Generate a config directory
page [capsule] [title] Add a new page with the given title page [capsule] [title] Add a new page with the given title
post [capsule] [title] Add a new gemlog post with the given title post [capsule] [title] Add a new gemlog post with the given title
index Generate an index page and feed of posts index Generate an index page and feed of posts
@ -25,20 +25,17 @@ version Print version info
## Build ## Build
- Install Lua (including development libraries and headers) and [luastatic]. - Install Lua and [luastatic].
- Clone this repository and change into the directory. Run: - Clone this repository and change into the directory. Run:
``` ```
luastatic gemwriter.lua env.lua util.lua lang/*.lua \ luastatic gemwriter.lua env.lua util.lua lang/en.lua \
/usr/lib/[arch]/liblua[5.x].a -I/usr/include/lua[5.x] -static \ /usr/lib/liblua.so -I/usr/include -o gemwriter
-o gemwriter
``` ```
Replace `[arch]` with the OS architecture, e.g. `x86_64-linux-gnu`, and The paths to `liblua.so` and the development headers (i.e.
`[5.x]` with the Lua version. The paths to `liblua.a` and the development `/usr/include/lua.h`) may need to be adjusted for your distribution.
headers (i.e. `/usr/include/lua[5.x]/lua.h`) may need to be adjusted for
your distribution.
- Move the `gemwriter` executable to a location in your `$PATH`. - Move the `gemwriter` executable to a location in your `$PATH`.
@ -55,14 +52,6 @@ version Print version info
4. Publish your capsule: `gemwriter publish` 4. Publish your capsule: `gemwriter publish`
## Credits
Special thanks to the following contributors:
- [lucidiot](https://tilde.town/~lucidiot/) — French translation
- [wsinatra](http://lambdacreate.com/) — Esperanto translation
## License ## License
BSD-3-Clause BSD-3-Clause

14
util.lua 100755 → 100644
View File

@ -28,8 +28,12 @@ end
function util.extract_str(full_str, find_str, end_str) function util.extract_str(full_str, find_str, end_str)
local fi1, fi2 = string.find(full_str, find_str, 1) local fi1, fi2 = string.find(full_str, find_str, 1)
local ei1, ei2 = string.find(full_str, end_str, fi2) local ei1, ei2 = string.find(full_str, end_str, fi2)
if end_str == "\n" then
return string.sub(full_str, fi2 + 1, ei1 - 2)
else
return string.sub(full_str, fi2 + 1, ei1 - 1) return string.sub(full_str, fi2 + 1, ei1 - 1)
end end
end
function util.to_bool(str) function util.to_bool(str)
@ -135,16 +139,6 @@ function util.extract_file_date(dir, file)
end end
function util.module_exists(mod)
local check, err = pcall(require, mod)
if check then do
return true end
else
return false
end
end
function util.read_file(file) function util.read_file(file)
local fh = io.open(file, "r") local fh = io.open(file, "r")
local text = "" local text = ""