Compare commits

...

19 Commits
v138 ... master

Author SHA1 Message Date
e04eb117e5 doc/README: add v157 changelog 2020-05-24 20:01:48 +12:00
2cb08a49e0 doc: move README to top-level markdown file 2020-05-24 20:01:20 +12:00
04b71a48c4 track archived release tarballs for history 2020-05-24 20:00:26 +12:00
72eae517da add .create_dir for the shields_cache directory 2020-05-24 20:00:07 +12:00
847b213fe2 codesite2git: README.md: download URLs link to page only, not raw 2020-05-04 18:08:48 +12:00
5079427678 codesite2git: normalise some image file extensions 2020-05-04 18:08:27 +12:00
bd70d6ae41 codesite2git: clean up debugging output 2020-05-04 18:08:12 +12:00
61c889b7ca codesite2git: README.md: don't titlecase "API" sections 2020-05-04 18:08:04 +12:00
64c5d52afe codesite2git: README.md: add 'Written in' shields.io badge 2020-05-04 18:07:53 +12:00
d9d0451313 codesite2git: README.md: fix special characters 2020-05-04 18:07:39 +12:00
aa96521ca7 util: add space when formatting attachment file sizes 2020-05-04 18:06:49 +12:00
146f08dacd codesite2git: remove temporary variable from short description handling 2020-05-04 18:06:35 +12:00
eeaf1c1dbf codesite2git: add gitea create+push support 2020-05-04 18:05:36 +12:00
ef3f3b6e9b commit all archived files 2013-09-21 00:53:49 +00:00
76341bb409 codesite2git: initial commit 2020-05-03 16:17:49 +12:00
be2354c9b8 separate main rebuilder from bootstrap environment 2020-05-03 16:17:36 +12:00
4379d3f428 ugr: track (migrate from CIM repo) 2020-05-03 14:49:08 +12:00
b4669565e2 hg2git: convert ignores file 2019-01-05 19:22:19 +13:00
5b696229a1 hg-git: remove old .hgtags file 2018-12-30 17:43:03 +13:00
22 changed files with 491 additions and 48 deletions

Binary file not shown.

Binary file not shown.

BIN
.dist-archive/code_v13.zip Normal file

Binary file not shown.

Binary file not shown.

BIN
.dist-archive/code_v39.zip Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
_dist/
sites/*/data/
sites/*/wwwroot/
shields_cache/

View File

@ -1,6 +0,0 @@
mode:regex
^_dist/
^sites/[^/]+/data/
^sites/[^/]+/wwwroot/
^shields_cache/

View File

@ -1,7 +0,0 @@
42a17645b5b21d7fe395767de7fa3e26ee999014 release-r54
0f89ae041c2ee60cc1ea308d047fce816b19c490 release-r64
d4733a95c3428db8722ce0d0350d17bbbabc8720 release-r72
7c92f9e2e4818d74eded59ad516d7d58b4072f8d release-r97
b1426986ff5f265f79d6412c0e81ccc7cae652ff release-r118
2b11aaaad98718025d020c9fbaa451f65d413477 release-r126
36cb5fdcbd0450a32ba9e88f7582a78b0b50db94 release-r132

View File

@ -1,10 +1,12 @@
# codesite
A static site generator for a portfolio website.
This script is currently in use to generate the code.ivysaur.me website.
This script was used to generate the https://code.ivysaur.me/ website between 2013 and 2020, when it was replaced by a [teafolio](https://code.ivysaur.me/teafolio) instance.
Written in PHP, Bash
=FEATURES=
## Features
- Generates static website, minimising server load and decreasing attack surface compared to dynamic server-side PHP
- Automatic thumbnailing and sprite sheet generation
@ -12,12 +14,30 @@ Written in PHP, Bash
- One-click rebuild, one-click deploy
- Parallel generation
=CHANGELOG=
## Changelog
2020-05-24: v157
- Sunset release
- Rewrite `codesite` Git repository history for full public release
- Feature: Add support for autogenerating codesite projects from a Git repository (`update_git_remote.sh`)
- Feature: Support converting a codesite project into a readonly, archive Git repository (`codesite2git`)
- Feature: Support bulk pushing `codesite2git`-converted repositories to a Gitea instance
2018-12-30: v138
- (Private release only)
- Feature: Support `[git-repository]` tags to link to a custom VCS repository
- Feature: Support markdown link syntax
- Feature: Support markdown multiline comments that include code syntax highlight tag
- Enhancement: Support Go subpackages under `[go-get]` tags
- Enhancement: Strip markdown image urls
- Enhancement: Trim leading blank lines
- Fix a cosmetic issue with favicons and document rendering mode in some versions of IE
2017-10-28: v132
- Fix an issue with not applying project rename redirections in the new URL format
- Fix a cosmetic issue with classifying downloads named with numeric strings
- Fix a cosmetic issue with truncated sheilds caused by ID collisions
- [⬇️ code_v132.tar.xz](.dist-archive/code_v132.tar.xz) *(106.49 KiB)*
2017-04-23: v126
- Breaking: BASEURL is now a mandatory field
@ -26,6 +46,7 @@ Written in PHP, Bash
- Feature: Use extension-less paths by default (old .html paths still supported via redirect)
- Feature: Support `[url]` tags
- Enhancement: Cache-busting parameters on homepage thumbnails
- [⬇️ code_v126.tar.xz](.dist-archive/code_v126.tar.xz) *(106.39 KiB)*
2016-04-19: v118
- Feature: Classify download artefacts by matching CHANGELOG entry
@ -33,6 +54,7 @@ Written in PHP, Bash
- Feature: Separate site generation code from site data repositories
- Enhancement: Update supplied CSS normalize script
- Enhancement: Read parallelism from number of CPUs
- [⬇️ code_v118.tar.xz](.dist-archive/code_v118.tar.xz) *(103.18 KiB)*
2015-11-08: v97
- Feature: Support BBCode b/i/spoiler/entry tags
@ -41,15 +63,18 @@ Written in PHP, Bash
- Feature: Optional blurbs=off, article_header={string}, shields_prefix=true configuration directives
- Fix an issue with project update-time detection
- Fix a cosmetic issue with whitespace
- [⬇️ code_v97.tar.xz](.dist-archive/code_v97.tar.xz) *(107.54 KiB)*
2015-04-05: v72
- Feature: Support redirecting old project names
- Feature: Add file hash in download URLs to prevent filename collisions
- Fix an issue generating spritesheets even if no project images are present
- Don't include `ctime` when estimating project update time
- [⬇️ code_v72.tar.xz](.dist-archive/code_v72.tar.xz) *(106.79 KiB)*
2015-04-05: v64
- Feature: Support sorting projects
- [⬇️ code_v64.tar.xz](.dist-archive/code_v64.tar.xz) *(106.45 KiB)*
2015-04-04: v54
- Feature: Support multiple code sites
@ -57,6 +82,7 @@ Written in PHP, Bash
- Fix an issue corrupting URL links with multiple parameters
- Fix a cosmetic issue with page overflow
- Fix a cosmetic issue with whitespace on code elements
- [⬇️ code_v54.tar.xz](.dist-archive/code_v54.tar.xz) *(105.79 KiB)*
2014-07-02: v39
- Feature: Tags
@ -66,9 +92,11 @@ Written in PHP, Bash
- Fix a cosmetic issue with image thumbnail backgrounds
- Fix a cosmetic issue with download sort order
- Fix a cosmetic issue with page layout
- [⬇️ code_v39.zip](.dist-archive/code_v39.zip) *(13.11 KiB)*
2013-09-28: v13
- Initial public source code release
- [⬇️ code_v13.zip](.dist-archive/code_v13.zip) *(9.02 KiB)*
2013-09-21: v3
- Initial deployment

27
build-worker Executable file
View File

@ -0,0 +1,27 @@
#!/usr/bin/php
<?php
require __DIR__ . '/lib/bootstrap.php';
function main($args) {
$config = setup_vars();
$total = $args[0];
$pos = $args[1];
// Perform build tasks
if ($pos == 0) {
buildcommon();
if (array_key_exists('redirect', $config)) {
buildredirects( $config['redirect'] );
}
if (array_key_exists('golang-subpackages', $config)) {
buildgosubpackages( $config['golang-subpackages'] );
}
} else {
buildprojects($pos, array_decimate(listprojects(), $total, $pos));
}
}
main(array_slice($_SERVER['argv'], 1));

334
codesite2git Executable file
View File

@ -0,0 +1,334 @@
#!/usr/bin/php
<?php
require __DIR__.'/lib/bootstrap.php';
class CProjectGitExporter extends CProject {
function __construct(string $projdirname, string $projname) {
CProject::__construct($projdirname, $projname);
$this->allowText2Html = false; // Don't produce HTML when processing the download block
}
function ExportAsGit(string $dest) {
// Create destination directory
if (is_dir($dest)) {
throw new Exception("Destination path '{$dest}' already exists");
}
mkdir($dest);
shell_exec('cd '. escapeshellarg($dest).' && git init');
// Parse download information blocks
$this->filterLongDescArea();
// Prepare to start modifying the generated README.md file
$this->longdesc = preg_replace('~\\[url=([^\\]]+?)\\](.+?)\\[/url\\]~m', '[\\2](\\1)', $this->longdesc);
$this->longdesc = str_replace("\xEF\xBB\xBF", "", $this->longdesc); // Remove interior UTF-8 BOM markers
$this->longdesc = str_replace("\r", "", $this->longdesc); // dos2unix
// Add main header and "written in" badge
$lines = explode("\n", $this->longdesc);
$header = ["# ".$this->projname, ""];
if (strlen($this->subtag) > 0) {
$header[] = "![](https://img.shields.io/badge/written%20in-" . rawurlencode($this->subtag) . "-blue)";
$header[] = "";
}
$lines = array_merge($header, $lines);
// Modify some lines to standard markdown format:
foreach($lines as $i => $line) {
// Convert ==HEADERS== to ## Headers
if (strlen($line) > 0 && $line[0] == '=') {
$tmp = rtrim($line, '=');
$indent = substr_count($tmp, '=');
// Normalise the capitalisation for header text
$header_text = substr($tmp, $indent);
if ($header_text != "TODO" && $header_text != "FIXME" && $header_text != "WARNING" && $header_text != "API") {
$header_text = ucwords(strtolower($header_text));
}
// raise markdown-equivalent header level by 1 to compensate for new header
$lines[$i] = $line = str_repeat('#', $indent + 1).' '.$header_text;
}
// Convert multiline single-backtick to multi-backtick
// Heuristic: if there is only a single backtick in the line
if (substr_count($line, '`') == 1) {
$lines[$i] = $line = str_replace('`', "\n```\n", $line);
}
// If the previous line started with a hyphen, and this line didn't, we need an
// extra line separator
if ($i > 0 && strlen($line) > 0 &&
$line[0] != '-' && strlen($lines[$i-1]) > 0 && $lines[$i-1][0] == '-'
) {
$lines[$i] = $line = "\n".$line;
}
}
// Copy all images to a doc/ subdirectory
if (count($this->images) > 0) {
mkdir($dest.'/doc');
foreach($this->images as $img) {
// Normalise some legacy file extension variants while we're here
copy($this->dir.$img, $dest.'/doc/'.str_replace(['.jpeg', '.JPG', '.JPEG'], ['.jpg', '.jpg', '.jpg'], $img));
}
}
// Copy all downloads to a dist-archive/ subdirectory
if (count($this->downloads) > 0) {
mkdir($dest.'/dist-archive');
foreach($this->downloads as $file) {
copy($this->dir.$file, $dest.'/dist-archive/'.$file);
}
if (! $this->downloads_section_was_replaced) {
// Add our own downloads section
$lines[] = $this->renderDownloadsBlock($this->downloads, true);
}
}
// Save final README
file_put_contents($dest.'/README.md', implode("\n", $lines));
// Extra properties file
$ctime = $this->lastupdate - ($this->lifespan * 3600); // Lifespans are measured in hours
if (strlen($this->shortdesc) > 0) {
$this->shortdesc = rtrim($this->shortdesc, '.').'.';
$this->shortdesc[0] = strtoupper($this->shortdesc[0]);
}
file_put_contents($dest.'/.legacy-codesite.toml', "# Converted with codesite2git
project_name=".json_encode($this->projname)."
short_description=".json_encode($this->shortdesc)."
written_in_lang=".json_encode($this->subtag)."
topics=".json_encode($this->tags)."
ctime=".$ctime."
mtime=".$this->lastupdate."
");
// Git commit everything
// Once for the meta with ctime; once for all files with the mtime
$command = (
'cd '. escapeshellarg($dest).' && '.
'git add .legacy-codesite.toml ; '.
'GIT_COMMITTER_DATE="'.date(DATE_ISO8601, $ctime).'" git commit -m "initial meta commit" --date '.escapeshellarg(date(DATE_ISO8601, $ctime)).' ; '.
'git add -A ; '.
'GIT_COMMITTER_DATE="'.date(DATE_ISO8601, $this->lastupdate).'" git commit -m "commit all archived files" --date '.escapeshellarg(date(DATE_ISO8601, $this->lastupdate))
);
//echo $command."\n";
shell_exec($command);
}
public function renderDownloadsBlock($render_downloads, $include_header=false) { // override
if (! count($render_downloads)) {
return;
}
$ret = "\n";
if ($include_header) {
$ret .= "## Download\n\n";
}
foreach($render_downloads as $filename) {
// It's possible to make a general URL that links directly to the raw download endpoint
// But because our markdown is rendered in context of the repo, we can't properly make a relative link
// We could make an absolute link, but that's probably a bad idea w.r.t. any future Git migrations
// Just link to the file in-repo although it won't be a RAW download link
$ret .= "- [⬇️ {$filename}](dist-archive/".rawurlencode($filename).") "; // raw/branch/master/dist-archive/{$filename}
$ret .= "*(".fbytes(filesize($this->dir.$filename)).")*\n";
}
return $ret;
}
}
class Gitea {
protected $url;
protected $org;
protected $token;
public function __construct(string $url, string $org, string $token) {
$this->url = $url;
$this->org = $org;
$this->token = $token;
}
public function repoExists(string $repo): bool {
$resp = @file_get_contents($this->url.'api/v1/repos/'.$this->org.'/'.$repo);
if ($resp === false) {
return false; // not conclusive but it will suffice
}
if (strpos($resp, 'does not exist')) {
return false;
}
$inf = json_decode($resp, true);
if (is_array($inf) && $inf['name'] == $repo) {
return true; // definitely yes
}
throw new \Exception("Couldn't decide whether repo {$repo} exists\n\n".var_export($resp, true));
}
public function createRepo(string $repo) {
$command = ('curl -sS -X POST ' .
escapeshellarg($this->url.'api/v1/admin/users/'.$this->org.'/repos?access_token='.urlencode($this->token)) . ' '.
'-H "accept: application/json" -H "Content-Type: application/json" -d ' . escapeshellarg(json_encode(['name' => $repo]))
);
$code = 0;
$output = [];
exec($command, $output, $code);
if ($code == 0) {
return; // success
}
throw new \Exception("failed to create repository {$repo} (exit code {$code})\n\n".var_export($output, true));
}
public function updateRepoProperties(string $repo, string $description) {
$json_update = [
'archived' => true,
'description' => $description,
// SPECIAL https://code.ivysaur.me/ - don't leave in commit {{{
'has_wiki' => false,
'website' => 'https://code.ivysaur.me/'.$repo,
// }}}
];
$command = ('curl -sS -X PATCH ' .
escapeshellarg($this->url.'api/v1/repos/'.$this->org.'/'.$repo.'?access_token='.urlencode($this->token)) . ' '.
'-H "accept: application/json" -H "Content-Type: application/json" -d ' . escapeshellarg(json_encode($json_update))
);
$code = 0;
$output = [];
exec($command, $output, $code);
if ($code == 0) {
return; // success
}
throw new \Exception("failed to update repository {$repo} properties: (exit code {$code})\n\n".var_export($output, true));
}
public function setRepoTopics(string $repo, array $topics) {
$command = ('curl -sS -X PUT ' .
escapeshellarg($this->url.'api/v1/repos/'.$this->org.'/'.$repo.'/topics?access_token='.urlencode($this->token)) . ' '.
'-H "accept: application/json" -H "Content-Type: application/json" -d ' . escapeshellarg(json_encode(['topics' => $topics]))
);
$code = 0;
$output = [];
exec($command, $output, $code);
if ($code == 0) {
return; // success
}
throw new \Exception("failed to update repository {$repo} topics: (exit code {$code})\n\n".var_export($output, true));
}
public function gitRemoteUrlFor(string $repo): string {
return $this->url.$this->org.'/'.$repo.'.git';
}
}
function codesite2git(string $projdirname, string $projname, string $dest): CProjectGitExporter {
// Parse existing project
$c = new CProjectGitExporter($projdirname, $projname);
// var_dump($c);
$c->ExportAsGit($dest);
return $c;
}
function usage() {
die("Usage:
codesite2git [FLAGS...]
Options:
--single CODESITE_ROOT_PATH PROJECT_DIR_NAME PROJECT_REAL_NAME DEST_DIR
--all CODESITE_ROOT_PATH TEMP_DIR GITEA_URL GITEA_ORG GITEA_AUTH_TOKEN
");
die(1);
}
function main(array $argv) {
if (count($argv) < 2) {
usage();
}
if ($argv[1] == '--single') {
if (count($argv) != 6) {
usage();
}
$config = setup_vars($argv[2]);
codesite2git($argv[3], $argv[4], $argv[5]);
} else if ($argv[1] == "--all") {
if (count($argv) != 7) {
usage();
}
$config = setup_vars($argv[2]);
$repos = glob(BASEDIR.'data/*');
$temp_dir = $argv[3];
$gitea = new Gitea($argv[4], $argv[5], $argv[6]);
foreach($repos as $i => $path) {
$projdir = basename($path);
$reponame = explode('-', $projdir, 2)[1];
echo sprintf("[%3d/%3d] Processing '%s'... ", $i+1, count($repos), $reponame);
// Convert to git
$git_repo_dir = $temp_dir.'/'.$projdir.'-archive.git';
$c = codesite2git($projdir, $reponame, $git_repo_dir);
// Check if Gitea repo already exists
if ($gitea->repoExists($reponame)) {
throw new \Exception("Abandoning process since repo {$reponame} already exists in Gitea - move it out of the source directory first");
}
// Create new Gitea repo
$gitea->createRepo($reponame);
if (! $gitea->repoExists($reponame)) {
throw new \Exception("Created repo {$reponame} successfully but it doesn't seem to exist?");
}
// Add git remote and push
shell_exec(
'cd '.escapeshellarg($git_repo_dir) .' && '.
'git remote add origin '.escapeshellarg($gitea->gitRemoteUrlFor($reponame)).' && '.
'git push origin master'
);
// Set Gitea topics + description
$gitea->updateRepoProperties($reponame, $c->shortdesc);
$gitea->setRepoTopics($reponame, $c->tags);
echo " Done\n";
}
} else {
usage();
}
}
main($_SERVER['argv']);

BIN
doc/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

View File

@ -2,19 +2,26 @@
class CProject {
private $dir;
protected $dir;
/**
* @var string $projname
*/
public $projname;
/**
* @var string $shortdesc
*/
public $shortdesc = '(no description)';
public $subtag = '';
public $lastupdate = 0;
public $numreleases = 0;
private $longdesc = '';
private $prefix_html = '';
private $images = array();
private $downloads = array();
private $downloads_hashes = array();
protected $longdesc = '';
protected $prefix_html = '';
protected $images = array();
protected $downloads = array();
protected $downloads_hashes = array();
public $downloads_section_was_replaced = false;
@ -26,6 +33,8 @@ class CProject {
protected $go_get_target = '';
protected $git_repo = '';
protected $allowText2Html = true;
public function __construct($dirname, $projname) {
$this->dir = BASEDIR.'data/'.$dirname.'/';
@ -235,7 +244,9 @@ class CProject {
$this->longdesc = substr($this->longdesc, 0, strlen($this->longdesc)-1); // Strip the extra NL we added
$this->longdesc = text2html($this->longdesc);
if ($this->allowText2Html) {
$this->longdesc = text2html($this->longdesc);
}
foreach($known_tags as $tag_name => $tag_idx) {
$this->longdesc = str_replace(
'${{TAG_'.$tag_idx.'}}',
@ -244,7 +255,9 @@ class CProject {
);
}
$this->longdesc = str_replace("</ul>\n<br />", "</ul>", $this->longdesc);
if ($this->allowText2Html) {
$this->longdesc = str_replace("</ul>\n<br />", "</ul>", $this->longdesc);
}
// Skip displaying the global downloads area
// This flag also indicates that the content has been pre-HTMLified

View File

@ -1,4 +1,3 @@
#!/usr/bin/php
<?php
ini_set('display_errors', 'On');
@ -11,13 +10,18 @@ require __DIR__.'/CProject.php';
define('SHIELDS_CACHE_DIR', __DIR__.'/../shields_cache/');
function main($args) {
$basedir = './';
$total = $args[0];
$pos = $args[1];
/**
* Set up global defines for a given codesite project
* Should be called once we have the root path for a codesite project.
*
*/
function setup_vars(string $basedir="./"): array {
// Parse configuration
if (! is_file($basedir.'config.ini')) {
die("[FATAL] Non-file '${basedir}config.ini'!\n");
}
$config = @parse_ini_file(
$basedir . 'config.ini',
true,
@ -25,7 +29,7 @@ function main($args) {
);
if ($config === false) {
die("[FATAL] Couldn't load '${basedir}/config.ini'!\n");
die("[FATAL] Couldn't load '${basedir}config.ini'!\n");
}
define('BASEDIR', $basedir);
@ -39,19 +43,5 @@ function main($args) {
define('ARTICLE_HEADER', (isset($config['codesite']['article_header']) ? $config['codesite']['article_header'] : 'ABOUT') );
define('SHIELDS_PREFIX', isset($config['codesite']['shields_prefix']));
// Perform build tasks
if ($pos == 0) {
buildcommon();
if (array_key_exists('redirect', $config)) {
buildredirects( $config['redirect'] );
}
if (array_key_exists('golang-subpackages', $config)) {
buildgosubpackages( $config['golang-subpackages'] );
}
} else {
buildprojects($pos, array_decimate(listprojects(), $total, $pos));
}
return $config;
}
main(array_slice($_SERVER['argv'], 1));

View File

@ -96,7 +96,7 @@ function fbytes($size, $suffixes='B|KiB|MiB|GiB|TiB') {
array_shift($sxlist);
$size /= 1024;
}
return number_format($size, 2).array_shift($sxlist);
return number_format($size, 2).' '.array_shift($sxlist);
}
function str_ext($sz) {

View File

@ -40,7 +40,7 @@ buildsite() {
local threadcount=$(numcpus)
for i in $(seq 0 "$threadcount") ; do
"${APP_DIR}/lib/bootstrap.php" "$threadcount" "$i" &
"${APP_DIR}/build-worker" "$threadcount" "$i" &
done
wait

View File

60
update_git_remote.sh Normal file
View File

@ -0,0 +1,60 @@
#!/bin/bash
#
# This script updates/rebuilds a codesite directory from a Git repository.
#
set -eu
main() {
if [[ $# -ne 3 ]] ; then
echo "Usage: update_git_remote DATA_DIR GIT_REMOTE_URI EXTRA_CONTENT" >&2
exit 1
fi
local datadir="$1"
local gitremote="$2"
local extra="${3:-}"
#
echo "Updating ${datadir}..."
if [[ ! -d $datadir ]] ; then
mkdir $datadir
fi
local clone=$(mktemp -d)
(
cd "${clone}"
git clone "${gitremote}" .
)
cp "${clone}/README.md" "${datadir}/README.txt"
rm -fr "${clone}"
# Ensure LF endings
sed -i 's/\x0D$//' "${datadir}/README.txt"
# Remove all shields (codesite inserts its own)
sed -i /shields.io/d "${datadir}/README.txt"
# Remove top-level header (codesite inserts its own)
sed -i -re '/^#([^#])/d' "${datadir}/README.txt"
# Remove leading blank lines (since we removed the top-level header)
sed -i '/./,$!d' "${datadir}/README.txt"
# Convert headings from github-flavored markdown to codesite style (\U for Uppercase)
sed -i -re 's/^## (.+)/=\U\1=/' "${datadir}/README.txt"
# Add extra metadata so that codesite generator can link the repository
echo "" >> "${datadir}/README.txt"
echo "[git]${gitremote}[/git]" >> "${datadir}/README.txt"
# Add any extra per-repository metadata, on a line after the description
if [[ -n "${extra}" ]] ; then
sed -i 3i"${extra}" "${datadir}/README.txt"
fi
}
main "$@"