Compare commits

..

5 Commits
master ... v39

Author SHA1 Message Date
f08081a129 remove some more ivysaur.me references 2
--HG--
branch : sanitise-paths
2014-07-03 22:01:04 +12:00
8c4754ae8d remove some more ivysaur.me references
--HG--
branch : sanitise-paths
2014-07-03 21:59:42 +12:00
fccaa6a326 Merge with default
--HG--
branch : sanitise-paths
2014-07-03 21:58:05 +12:00
3073231d38 remove homepage blurb for publication
--HG--
branch : sanitise-paths
2013-09-28 13:35:43 +12:00
0d522e1cd1 remove all branding for publication
--HG--
branch : sanitise-paths
2013-09-28 13:33:47 +12:00
39 changed files with 751 additions and 1833 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

4
.gitignore vendored
View File

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

3
.hgignore Normal file
View File

@ -0,0 +1,3 @@
wwwroot/*
nbproject/*
data/*

102
README.md
View File

@ -1,102 +0,0 @@
# codesite
A static site generator for a portfolio 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
- Generates static website, minimising server load and decreasing attack surface compared to dynamic server-side PHP
- Automatic thumbnailing and sprite sheet generation
- Download file attachments per-project
- One-click rebuild, one-click deploy
- Parallel generation
## 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
- Feature: Support `[go-get]` tags
- Feature: Canonical paths for SEO
- 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
- Feature: Allow sorting by lifespan, artefacts, release entries
- 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
- Feature: Add more CSS styles to allow per-site customistaion
- Feature: Use http://shields.io images where appropriate
- 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
- Fix an issue with parallel builds on some versions of windows
- 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
- Feature: Generate pages in parallel
- Enhancement: Support raw HTML sections in page content
- Fix an issue with URLs containing spaces
- 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

View File

@ -1,8 +0,0 @@
- Merge "written in" and "tags"
- RSS for recent changes
- RSS for all projects
- Switchable CSS (reddit theme, 4chan theme, HN theme)

View File

@ -1,27 +0,0 @@
#!/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));

View File

@ -1,334 +0,0 @@
#!/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']);

3
deploy.cmd Normal file
View File

@ -0,0 +1,3 @@
@echo off
start C:\path\to\cygwin\bin\mintty.exe /bin/bash -l -c "rsync -avz -e ""ssh -i /cygdrive/c/path/to/deploy_key"" --progress /cygdrive/c/path/to/code/wwwroot www-data@your-web-server:/var/www/path/to/code-site/"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

8
homepage_blurb.htm Normal file
View File

@ -0,0 +1,8 @@
<p>
Homepage blurb goes here.
</p>
<p>
<strong>PROJECTS</strong>
</p>

View File

@ -1,408 +0,0 @@
<?php
class CProject {
protected $dir;
/**
* @var string $projname
*/
public $projname;
/**
* @var string $shortdesc
*/
public $shortdesc = '(no description)';
public $subtag = '';
public $lastupdate = 0;
public $numreleases = 0;
protected $longdesc = '';
protected $prefix_html = '';
protected $images = array();
protected $downloads = array();
protected $downloads_hashes = array();
public $downloads_section_was_replaced = false;
public $lifespan = 0;
public $tags = array();
public $homeimage = null;
protected $go_get_target = '';
protected $git_repo = '';
protected $allowText2Html = true;
public function __construct($dirname, $projname) {
$this->dir = BASEDIR.'data/'.$dirname.'/';
$this->projname = $projname;
$matches = [];
// Identify resources in folder
$ls = scandir($this->dir);
$found_real_lastupdate = false;
foreach($ls as $file) {
if ($file[0] == '.') {
continue;
}
if ($file == 'README.txt') {
$this->longdesc = file_get_contents($this->dir.'README.txt');
$this->longdesc = str_replace("\r", "", $this->longdesc); // filter windows CR
$this->longdesc = preg_replace("~[\s\t]*$~s", "", $this->longdesc); // filter trailing spaces at line endings
// Guess 'last update' time
if (preg_match('~\n(\d\d\d\d-\d\d-\d\d)~', $this->longdesc, $matches)) {
// Use first date entry (assumed to be a CHANGELOG)
$this->lastupdate = strtotime($matches[1]);
$found_real_lastupdate = true;
}
// Find number of releases
preg_match_all('~\n(\d\d\d\d-\d\d-\d\d)~', $this->longdesc, $matches, PREG_SET_ORDER);
$this->numreleases = count($matches);
// Find support lifespan (newest minus youngest)
$eldest = time();
$newest = 0;
foreach($matches as $match) {
$stamp = strtotime($match[1]);
$eldest = min($stamp, $eldest);
$newest = max($stamp, $newest);
}
$this->lifespan = floor(max(0, $newest - $eldest) / 3600); // could divide by 86400 but it doesn't matter
// Find 'written in'
if (preg_match('~Written in ([^\\n]+)~', $this->longdesc, $matches)) {
$this->subtag = rtrim($matches[1], ' .');
}
// Find `go-get` tags
$this->longdesc = preg_replace_callback('~\[go-get\](.+)\[/go-get\]~', function($matches) {
$this->go_get_target = $matches[1];
return '';
}, $this->longdesc);
// Find tags
if (preg_match('~Tags: ([^\\n]+)~', $this->longdesc, $matches)) {
$this->tags = array_map('trim', explode(',', $matches[1]));
}
// Find `entry` tags
$this->longdesc = preg_replace(
'~\\[entry=([^\\]]+?)\\](.+?)\\[/entry\\]~m',
'[url='.BASEURL.'\\1/]\\2[/url]', // hesc still hasn't happened, transform bbcode->bbcode
$this->longdesc
);
// Strip out any markdown image links
// [![](doc/image1.thumb.png)](doc/image1.png)
$this->longdesc = preg_replace('~\\[!.+?\\)\\]\\(.+?\\)~m', '', $this->longdesc);
// Find "Written in" tags
$this->prefix_html = '';
$this->longdesc = preg_replace_callback('~\nWritten in ([^\\n]+)~ms', function($matches) {
$this->prefix_html .= (
(SHIELDS_PREFIX ? mkshield('build', 'success', 'brightgreen').'&nbsp;' : '').
mkshield('written in', rtrim($matches[1], '.'), 'blue')
);
return '';
}, $this->longdesc);
// Find 'git-repository' tags
$this->longdesc = preg_replace_callback('~\[git\](.+)\[/git\]~', function($matches) {
$this->git_repo = $matches[1];
if (strlen($this->prefix_html) > 0) {
$this->prefix_html .= '&nbsp';
}
$this->prefix_html .= '<a href="'.hesc($this->git_repo).'">'.mkshield('vcs', 'git', 'yellowgreen', ['logo' => 'git']).'</a>';
return '';
}, $this->longdesc);
// Collapse multiple blank lines
$this->longdesc = ltrim($this->longdesc, "\n");
while(strpos($this->longdesc, "\n\n\n") !== false) {
$this->longdesc = str_replace("\n\n\n", "\n\n", $this->longdesc);
}
$this->longdesc = rtrim($this->longdesc, "\n")."\n";
// Extract short description (last)
$parts = explode("\n", $this->longdesc);
$this->shortdesc = array_shift($parts);
$this->shortdesc[0] = strtolower($this->shortdesc[0]); // cosmetic lowercase
continue;
}
if (! $found_real_lastupdate) {
$this->lastupdate = max(
$this->lastupdate,
// filectime($this->dir.$file),
($file == 'README.txt' ? filectime($this->dir.$file) : filemtime($this->dir.$file)) // Don't count README updates
);
}
if (is_image($file)) {
$this->images[] = $file;
} else {
$this->downloads[] = $file;
}
}
natcasesort($this->downloads);
$this->downloads = array_reverse($this->downloads);
for($i = 0, $e = count($this->downloads); $i !== $e; ++$i) {
$this->downloads_hashes[] = (
sha1_file($this->dir.$this->downloads[$i])
);
}
}
public function genHomeImage() {
if (count($this->images)) {
$this->homeimage = mkthumbnail(
$this->dir.$this->images[0],
null, // raw handle
INDEX_THUMB_W, INDEX_THUMB_H
);
}
}
protected function filterLongDescArea() {
// If *all* downloads can be assigned to a changelog release entry,
// then move the files
// A release entry is marked by any string following the date field.
// Add one more NL than we really want, for regex reasons
$this->longdesc .= "\n";
preg_match_all('~^(\d\d\d\d-\d\d-\d\d)\s?:? (.+?)\n\n~ms', $this->longdesc, $matches, PREG_SET_ORDER);
do {
// Ensure changelog exists
if (! count($matches)) {
break;
}
// Find all tags
$known_tags = [];
foreach($matches as $i => $match) {
$tag = trim(explode("\n", $match[2])[0]);
$known_tags[$tag] = $i;
}
// Ensure all release entries have tags
if (count($matches) != count($known_tags)) {
error_log("[".$this->projname."] not all release entries have tags\n");
break;
}
// Ensure all downloads can be assigned to tags.
// In the event of a download matching multiple tags, it'll
// be assigned to the newest (topmost) entry
$found_idx = [];
$render_per_tag = [];
foreach(array_keys($known_tags) as $tagname) {
$render_per_tag[$tagname] = [];
}
foreach($this->downloads as $idx => $filename) {
foreach(array_keys($known_tags) as $tagname) {
if (stripos($filename, (string)$tagname) !== false) {
$found_idx[$idx] = $tagname;
$render_per_tag[$tagname][$idx] = $filename;
break; // next file
}
}
}
if (count($found_idx) != count($this->downloads)) {
error_log("[".$this->projname."] not all downloads have matching tags");
break;
}
// Make HTML modifications
foreach($known_tags as $tag_name => $tag_idx) {
$find = rtrim($matches[$tag_idx][0]);
$this->longdesc = str_replace(
$find,
$find.'${{TAG_'.$tag_idx.'}}',
$this->longdesc
);
}
$this->longdesc = substr($this->longdesc, 0, strlen($this->longdesc)-1); // Strip the extra NL we added
if ($this->allowText2Html) {
$this->longdesc = text2html($this->longdesc);
}
foreach($known_tags as $tag_name => $tag_idx) {
$this->longdesc = str_replace(
'${{TAG_'.$tag_idx.'}}',
$this->renderDownloadsBlock($render_per_tag[$tag_name], false),
$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
$this->downloads_section_was_replaced = true;
// Successful upgrade
} while(false);
if (! $this->downloads_section_was_replaced) {
$this->longdesc = substr($this->longdesc, 0, strlen($this->longdesc)-1); // Strip the extra NL we added
}
}
public function numDownloads() {
return count($this->downloads);
}
public function write() {
// Generate image thumbnails
foreach($this->images as $idx => $image) {
$outfile = BASEDIR.'wwwroot/img/'.$this->projname.'_'.$idx;
copy($this->dir.$image, $outfile.'.'.str_ext($image));
mkthumbnail($outfile.'.'.str_ext($image), $outfile.'_thumb.jpg', PAGE_THUMB_W, PAGE_THUMB_H);
}
// Copy downloads to wwwroot
foreach($this->downloads as $idx => $filename) {
$cmkdir = @mkdir( BASEDIR.'wwwroot/srv/'.$this->downloads_hashes[$idx] );
if (! $cmkdir) {
fputs(
STDOUT,
"WARNING: Couldn't create directory ".$this->downloads_hashes[$idx].
" for file '${filename}'".
" in project '".$this->projname."'!\n"
);
}
copy(
$this->dir.$filename,
BASEDIR.'wwwroot/srv/'.$this->downloads_hashes[$idx].'/'.$filename
);
}
// Generate index page
ob_start();
$this->index();
$extra_head_items = [];
$extra_head_items[] = '<link rel="canonical" href="'.hesc(BASEURL.$this->projname).'">'; // TODO include golang `go get` meta if necessary
if (strlen($this->go_get_target) > 0) {
$extra_head_items[] = '<meta name="go-import" content="'.hesc($this->go_get_target).'">'; // TODO include golang `go get` meta if necessary
}
$idxfile = template($this->projname.' | '.SITE_TITLE, ob_get_clean(), implode("\n", $extra_head_items));
mkdir(BASEDIR.'wwwroot/'.$this->projname);
file_put_contents(BASEDIR.'wwwroot/'.$this->projname.'/index.html', $idxfile); // new URL format
file_put_contents(BASEDIR.'wwwroot/'.$this->projname.'.html', redirecthtml(BASEURL.$this->projname)); // old URL format
}
public function getClassAttr() {
if (count($this->tags)) {
return 'taggedWith-'.implode(' taggedWith-', $this->tags);
} else {
return '';
}
}
public function renderDownloadsBlock($render_downloads, $include_header=false) {
if (! count($render_downloads)) {
return;
}
ob_start();
?>
<?php if ($include_header) { ?>
<strong>DOWNLOAD</strong>
<?php } ?>
<ul class="<?=$include_header ? 'downloads-large' : 'downloads-small' ?>">
<?php foreach($render_downloads as $idx => $filename) { ?>
<li>
<a href="<?=BASEURL?>srv/<?=hesc($this->downloads_hashes[$idx])?>/<?=hesc(rawurlencode($filename))?>"><?=hesc($filename)?></a>
<small>
<?=hesc(fbytes(filesize(BASEDIR.'wwwroot/srv/'.$this->downloads_hashes[$idx].'/'.$filename)))?>
</small>
</li>
<?php } ?>
</ul>
<?php
return ob_get_clean();
}
public function index() {
$this->filterLongDescArea();
$longdesc_html = $this->downloads_section_was_replaced ? $this->longdesc : text2html($this->longdesc);
?>
<h2><?=hesc(str_replace('_', ' ', $this->projname))?></h2>
<div class="projinfo">
<div class="projbody projbody_<?=(count($this->images) ? 'half' : 'full')?>w">
<?php if (strlen($this->prefix_html)) { ?>
<p style="margin-top:0;"><?=$this->prefix_html?></p>
<?php } ?>
<strong><?=hesc(strtoupper(ARTICLE_HEADER))?></strong>
<div class="content-paragraph">
<?=$longdesc_html?>
</div>
<?=file_get_contents(BASEDIR.'/footer.htm')?>
<?php if (! $this->downloads_section_was_replaced) { ?>
<?=$this->renderDownloadsBlock($this->downloads, true)?>
<?php } ?>
</div>
<?php if (count($this->images)) { ?>
<div class="projimg">
<?php foreach($this->images as $idx => $origname) { ?>
<a href="<?=BASEURL?>img/<?=hesc(urlencode($this->projname))?>_<?=$idx?>.<?=str_ext($origname)?>"><img src="<?=BASEURL?>img/<?=hesc(urlencode($this->projname))?>_<?=$idx?>_thumb.jpg" class="thumbimage"></a>
<?php } ?>
</div>
<div style="clear:both;"></div>
<?php } ?>
</div>
<?php
}
}

View File

@ -1,47 +0,0 @@
<?php
ini_set('display_errors', 'On');
date_default_timezone_set('Etc/UTC');
error_reporting(E_ALL);
require __DIR__.'/util.php';
require __DIR__.'/template.php';
require __DIR__.'/CProject.php';
define('SHIELDS_CACHE_DIR', __DIR__.'/../shields_cache/');
/**
* 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,
INI_SCANNER_RAW
);
if ($config === false) {
die("[FATAL] Couldn't load '${basedir}config.ini'!\n");
}
define('BASEDIR', $basedir);
define('BASEURL', trim($config['codesite']['baseurl']));
define('SITE_TITLE', trim($config['codesite']['title']));
define('PAGE_THUMB_W', intval($config['codesite']['page_thumb_w']));
define('PAGE_THUMB_H', intval($config['codesite']['page_thumb_h']));
define('INDEX_THUMB_W', intval($config['codesite']['index_thumb_w']));
define('INDEX_THUMB_H', intval($config['codesite']['index_thumb_h']));
define('SHOW_BLURBS', !(isset($config['codesite']['blurbs']) && $config['codesite']['blurbs'] === 'off') );
define('ARTICLE_HEADER', (isset($config['codesite']['article_header']) ? $config['codesite']['article_header'] : 'ABOUT') );
define('SHIELDS_PREFIX', isset($config['codesite']['shields_prefix']));
return $config;
}

View File

@ -1,214 +0,0 @@
<?php
function template($title, $content, $extra_head='') {
ob_start();
?>
<!DOCTYPE html>
<html>
<head>
<title><?=hesc($title)?></title>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta charset="UTF-8">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" >
<meta name="viewport" content="width=960" >
<?=$extra_head?>
<link rel="icon" href="<?=BASEURL?>static/favicon.ico" type="image/x-icon">
<link type="text/css" rel="stylesheet" href="<?=BASEURL?>static/normalize.css">
<link type="text/css" rel="stylesheet" href="<?=BASEURL?>static/style.css">
<script type="text/javascript" src="<?=BASEURL?>static/site.js"></script>
</head>
<body>
<div id="container">
<div id="content">
<?=file_get_contents(BASEDIR.'/header.htm')?>
<?=$content?>
</div>
</div>
</body>
</html>
<?php
return ob_get_clean();
}
function listprojects() {
// List projects
$ls = scandir(BASEDIR.'data');
rsort($ls);
$projects = array();
foreach($ls as $dirname) {
if ($dirname[0] == '.') continue;
if (! is_dir(BASEDIR.'data/'.$dirname)) continue;
$matches = array();
if (preg_match('~(?:\d+-)?(.+)~', $dirname, $matches)) {
$projects[$dirname] = $matches[1];
}
}
return $projects;
}
function buildprojects($id, $projects) {
$count = 0;
foreach($projects as $dirname => $projectname) {
echo sprintf("@%1d [%3d/%3d] ".$projectname."...\n", $id, ++$count, count($projects));
$pr = new CProject($dirname, $projectname);
$pr->write();
}
}
function buildcommon() {
echo "@0 [ 0/ ?] Common files...\n";
$projects = listprojects();
// Build all projects
$plist = array();
$handles = array();
$handle_lookup = array();
$alphasort = [];
foreach($projects as $dirname => $projectname) {
$pr = new CProject($dirname, $projectname);
$pr->genHomeImage(); // thumbnail
$plist[] = $pr;
if (is_null($pr->homeimage)) {
$handle_lookup[$projectname] = null;
} else {
$handle_lookup[$projectname] = count($handles);
$handles[] = $pr->homeimage;
}
$alphasort[] = [$pr->projname, count($plist)-1];
}
usort($alphasort, function($a, $b) {
return strcasecmp($a[0], $b[0]);
});
$alphaidx = [];
foreach($alphasort as $a) {
$alphaidx[ $a[1] ] = count($alphaidx);
}
// Build homepage spritesheet
if (count($handles)) {
mkspritesheet($handles, BASEDIR.'wwwroot/logos.jpg', INDEX_THUMB_W, INDEX_THUMB_H);
array_map('imagedestroy', $handles); // free
}
// Cache-busting stylesheet
$style = '.homeimage-sprite { background-image: url("logos.jpg?'.md5_file(BASEDIR.'wwwroot/logos.jpg').'"); }';
// Build index page
ob_start();
?>
<?php if (file_exists(BASEDIR.'homepage_blurb.htm')) { ?>
<!-- homepage blurb {{ -->
<?=file_get_contents(BASEDIR.'homepage_blurb.htm')?>
<!-- }} -->
<?php } ?>
<style type="text/css">
<?php echo $style; ?>
</style>
<table id="projtable-main" class="projtable">
<?php foreach ($plist as $i => $pr) { ?>
<tr class="<?=$pr->getClassAttr()?>"
data-sort-mt="-<?=$pr->lastupdate?>"
data-sort-ct="<?=$i?>"
data-sort-al="<?=$alphaidx[$i]?>"
data-sort-nr="-<?=$pr->numreleases?>"
data-sort-nf="-<?=$pr->numDownloads()?>"
data-sort-ls="-<?=$pr->lifespan?>"
>
<td>
<a href="<?=hesc(BASEURL.urlencode($pr->projname))?>/"><?=(is_null($handle_lookup[$pr->projname]) ? '<div class="no-image"></div>' : '<div class="homeimage homeimage-sprite" style="background-position:0 -'.($handle_lookup[$pr->projname]*INDEX_THUMB_H).'px"></div>')?></a>
</td>
<td>
<strong><?=hesc(str_replace('_', ' ', $pr->projname))?></strong><?php if (SHOW_BLURBS) { ?>,
<?=hesc($pr->shortdesc)?>
<?php } ?>
<a href="<?=hesc(BASEURL.urlencode($pr->projname))?>/" class="article-read-more">more...</a>
<?php if (strlen($pr->subtag) || count($pr->tags)) { ?>
<br>
<small>
<?=hesc($pr->subtag)?>
<?php if (strlen($pr->subtag) && count($pr->tags)) { ?>
::
<?php } ?>
<?php foreach($pr->tags as $tag) { ?>
<a class="tag tag-link" data-tag="<?=hesc($tag)?>"><?=hesc($tag)?></a>
<?php } ?>
</small>
<?php } ?>
</td>
</tr>
<?php } ?>
</table>
<?php
$extra_head = '<link rel="canonical" href="'.hesc(BASEURL).'">';
$index = template(SITE_TITLE, ob_get_clean(), $extra_head);
file_put_contents(BASEDIR.'wwwroot/index.html', $index);
// Done
}
function redirecthtml($target) {
ob_start();
?>
<meta http-equiv="refresh" content="0; url=<?=hesc($target)?>">
<a href="<?=hesc($target)?>">Moved &raquo;</a>
<?php
return ob_get_clean();
}
function buildredirects($redirects) {
foreach($redirects as $oldname => $newname) {
$page = redirecthtml(BASEURL.$newname.'/');
// old format
file_put_contents(BASEDIR.'wwwroot/'.$oldname.'.html', $page);
// new format
mkdir(BASEDIR.'wwwroot/'.$oldname);
file_put_contents(BASEDIR.'wwwroot/'.$oldname.'/index.html', $page);
}
}
function buildgosubpackages($packages) {
foreach($packages as $path => $goGetStr) {
$page = (
'<meta name="go-import" content="'.hesc($goGetStr).'">'.
"\n".
redirecthtml(BASEURL)
);
// new directory format only
mkdir_all(BASEDIR.'wwwroot/'.$path);
file_put_contents(BASEDIR.'wwwroot/'.$path.'/index.html', $page);
}
}

View File

@ -1,191 +0,0 @@
<?php
function mkshield($left_str, $right_str, $color_str, $params=[]) {
$filename = sprintf(
"%s-%s-%s.svg",
rawurlencode(str_replace('-', '--', $left_str)),
rawurlencode(str_replace('-', '--', $right_str)),
rawurlencode($color_str)
);
if (count($params) > 0) {
$filename .= '?' . http_build_query($params);
}
$cache_path = SHIELDS_CACHE_DIR.urlencode($filename);
if (file_exists($cache_path)) {
return file_get_contents($cache_path);
} else {
$retn = file_get_contents('https://img.shields.io/badge/'.$filename);
// We need unique IDs
$prefix = substr(sha1($filename), 8).'-';
$retn = str_replace('id="', 'id="'.$prefix, $retn);
$retn = str_replace('url(#', 'url(#'.$prefix, $retn);
file_put_contents($cache_path, $retn);
return $retn;
}
}
/**
* Create a thumbnail of an image. It overscales, centers, and crops to fit the
* target dimensions.
*
* @param string $src_file
* @param string $dest_file Null to return an image handle
* @param int $width
* @param int $height
* @return boolean
*/
function mkthumbnail($src_file, $dest_file, $width, $height) {
list($src_width, $src_height) = getimagesize($src_file);
$im = imagecreatefromstring(file_get_contents($src_file));
$dest = imagecreatetruecolor($width, $height);
imagefilledrectangle($dest, 0, 0, $width, $height, imagecolorallocate($dest, 0xFF, 0xFF, 0xFF));
$scale = max( $width/$src_width, $height/$src_height ); // overscale + crop
$box_w = $width/$scale;
$box_h = $height/$scale;
$box_xoff = floor(($src_width - $box_w)/2);
$box_yoff = floor(($src_height - $box_h)/2);
imagecopyresampled(
$dest, $im,
0, 0,
$box_xoff, $box_yoff,
$width, $height, $box_w, $box_h
);
imagedestroy($im);
if (is_null($dest_file)) {
return $dest;
} else {
return imagejpeg($dest, $dest_file);
}
}
function mkspritesheet(array $handles, $dest_file, $width, $height) {
$im = imagecreatetruecolor($width, $height * count($handles));
for($i = 0, $e = count($handles); $i != $e; ++$i) {
imagecopy($im, $handles[$i], 0, $i * $height, 0, 0, $width, $height);
}
if (is_null($dest_file)) {
return $dest_file;
} else {
return imagejpeg($im, $dest_file);
}
}
function fbytes($size, $suffixes='B|KiB|MiB|GiB|TiB') {
$sxlist = explode('|', $suffixes);
if ($size < 1024) {
return $size.$sxlist[0];
}
while ($size > 1024 && count($sxlist) >= 2) {
array_shift($sxlist);
$size /= 1024;
}
return number_format($size, 2).' '.array_shift($sxlist);
}
function str_ext($sz) {
$dpos = strrpos($sz, '.');
return substr($sz, $dpos+1);
}
function is_image($sz) {
return in_array(strtolower(str_ext($sz)), ['jpg', 'png', 'jpeg']);
}
function hesc($sz) {
return @htmlentities($sz, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
function mkdir_all($path) {
$epath = escapeshellarg($path);
`mkdir -p ${epath}`;
}
function text2html($sz) {
$identity = function($sz) {
return $sz;
};
$splitInside = function($begin, $end, $sz) {
$parts = explode($begin, $sz);
if (count($parts) == 1) return [$sz];
$ret = [$parts[0]];
for($i = 1, $e = count($parts); $i !== $e; ++$i) {
$inner = explode($end, $parts[$i], 2);
$ret = array_merge($ret, $inner);
}
return $ret;
};
$oddEven = function(array $parts, $odd, $even, $join='') {
$ret = [];
for($i = 0, $e = count($parts); $i != $e; ++$i) {
$ret[] = ($i % 2) ? $odd($parts[$i]) : $even($parts[$i]);
}
return implode($join, $ret);
};
$sectionFmt = function($sz) use($oddEven, $identity) {
$base = hesc($sz);
$base = preg_replace('~^=+(.+)=+~m', '<strong>\\1</strong>', $base);
$base = preg_replace('~\\[url=([^\\]]+?)\\](.+?)\\[/url\\]~m', '<a href="\\1">\\2</a>', $base);
$base = preg_replace('~\\[([^\\]]+?)\\]\\((https?://.+?)\\)~m', '<a href="\\2">\\1</a>', $base); // Support markdown-style URLs
$base = preg_replace('~([^="])(https?://[^ \\r\\n\\t]+)~i', '\\1<a href="\\2">\\2</a>', $base); // Support standalone URLs
$base = preg_replace('~\\[b\\](.+?)\\[/b\\]~m', '<strong>\\1</strong>', $base);
$base = preg_replace('~\\[i\\](.+?)\\[/i\\]~m', '<i>\\1</i>', $base);
$base = preg_replace('~\\[spoiler\\](.+?)\\[/spoiler\\]~m', '<span class="spoiler">\\1</span>', $base);
$base = preg_replace('~\n- ~ms', "\n&bull; ", $base);
$base = preg_replace('~^```.+$~m', '`', $base); // Convert ```html to single `{}` element
// TODO support markdown tables
$btparts = explode('`', $base);
if (count($btparts) > 1 && (count($btparts) % 2)) {
for ($i = 1, $e = count($btparts); $i < $e; $i += 2) {
$class = 'code';
if (strpos($btparts[$i], "\n") !== false) {
$class .= ' code-multiline';
}
$btparts[$i] = '<span class="'.$class.'">'.ltrim($btparts[$i], "\n").'</span>';
}
}
return $oddEven($btparts, $identity, 'nl2br');
};
$htmlSections = $splitInside('<html>', '</html>', $sz);
return $oddEven($htmlSections, $identity, $sectionFmt);
}
function array_decimate($array, $total, $partno) {
$ct = 0;
$ret = [];
foreach($array as $k => $v) {
if (++$ct % $total == ($partno - 1)) {
$ret[$k] = $v;
}
}
return $ret;
}

View File

@ -1,14 +0,0 @@
#!/bin/bash
set -eu
tar cJvf "codesite-$(date +%s).tar.xz" \
--exclude='sites/codesite.example.com/wwwroot' \
rebuild.sh \
sites/codesite.example.com \
static \
lib \
--owner=0 --group=0
read -p "Press any key to continue..."

3
rebuild.cmd Normal file
View File

@ -0,0 +1,3 @@
@echo off
C:\path\to\php_54\php.exe rebuild.php
pause

453
rebuild.php Normal file
View File

@ -0,0 +1,453 @@
<?php
// Code-hosting website
// ````````````````````
// CONFIGURATION
// `````````````
define('BASEDIR', __DIR__.'\\');
define('SITE_TITLE', 'codesite');
define('PAGE_THUMB_W', 60);
define('PAGE_THUMB_H', 60);
define('INDEX_THUMB_W', 90);
define('INDEX_THUMB_H', 32); // recommend a multiple of the jpg iDCT block size
/**
* Create a thumbnail of an image. It overscales, centers, and crops to fit the
* target dimensions.
*
* @param string $src_file
* @param string $dest_file Null to return an image handle
* @param int $width
* @param int $height
* @return boolean
*/
function mkthumbnail($src_file, $dest_file, $width, $height) {
list($src_width, $src_height) = getimagesize($src_file);
$im = imagecreatefromstring(file_get_contents($src_file));
$dest = imagecreatetruecolor($width, $height);
imagefilledrectangle($dest, 0, 0, $width, $height, imagecolorallocate($dest, 0xFF, 0xFF, 0xFF));
$scale = max( $width/$src_width, $height/$src_height ); // overscale + crop
$box_w = $width/$scale;
$box_h = $height/$scale;
$box_xoff = floor(($src_width - $box_w)/2);
$box_yoff = floor(($src_height - $box_h)/2);
imagecopyresampled(
$dest, $im,
0, 0,
$box_xoff, $box_yoff,
$width, $height, $box_w, $box_h
);
imagedestroy($im);
if (is_null($dest_file)) {
return $dest;
} else {
return imagejpeg($dest, $dest_file);
}
}
function mkspritesheet(array $handles, $dest_file, $width, $height) {
$im = imagecreatetruecolor($width, $height * count($handles));
for($i = 0, $e = count($handles); $i != $e; ++$i) {
imagecopy($im, $handles[$i], 0, $i * $height, 0, 0, $width, $height);
}
if (is_null($dest_file)) {
return $dest_file;
} else {
return imagejpeg($im, $dest_file);
}
}
function fbytes($size, $suffixes='B|KiB|MiB|GiB|TiB') {
$sxlist = explode('|', $suffixes);
if ($size < 1024) {
return $size.$sxlist[0];
}
while ($size > 1024 && count($sxlist) >= 2) {
array_shift($sxlist);
$size /= 1024;
}
return number_format($size, 2).array_shift($sxlist);
}
function str_ext($sz) {
$dpos = strrpos($sz, '.');
return substr($sz, $dpos+1);
}
function is_image($sz) {
return in_array(strtolower(str_ext($sz)), ['jpg', 'png', 'jpeg']);
}
function hesc($sz) {
return @htmlentities($sz, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
function text2html($sz) {
$sectionFmt = function($sz) {
$base = hesc($sz);
$base = preg_replace('~=+(.+)=+~', '<strong>\\1</strong>', $base);
$base = preg_replace('~(https?://[^ \\r\\n\\t]+)~i', '<a href="\\1">\\1</a>', $base);
$btparts = explode('`', $base);
if (count($btparts) > 1 && (count($btparts) % 2)) {
for ($i = 1, $e = count($btparts); $i < $e; $i += 2) {
$btparts[$i] = '<span class="code">'.$btparts[$i].'</span>';
}
$base = implode('', $btparts);
}
return nl2br($base);
};
$identity = function($sz) {
return $sz;
};
$splitInside = function($begin, $end, $sz) {
$parts = explode($begin, $sz);
if (count($parts) == 1) return [$sz];
$ret = [$parts[0]];
for($i = 1, $e = count($parts); $i !== $e; ++$i) {
$inner = explode($end, $parts[$i], 2);
$ret = array_merge($ret, $inner);
}
return $ret;
};
$oddEven = function(array $parts, $odd, $even, $join='') {
$ret = [];
for($i = 0, $e = count($parts); $i != $e; ++$i) {
$ret[] = ($i % 2) ? $odd($parts[$i]) : $even($parts[$i]);
}
return implode($join, $ret);
};
$htmlSections = $splitInside('<html>', '</html>', $sz);
return $oddEven($htmlSections, $identity, $sectionFmt);
}
function array_decimate($array, $total, $partno) {
$ct = 0;
$ret = [];
foreach($array as $k => $v) {
if (++$ct % $total == ($partno - 1)) {
$ret[$k] = $v;
}
}
return $ret;
}
/**
*
*/
class CProject {
private $dir;
public $projname;
public $shortdesc = '(no description)';
public $subtag = '';
private $longdesc = '';
private $images = array();
private $downloads = array();
public $tags = array();
public $homeimage = null;
public function __construct($dirname, $projname) {
$this->dir = BASEDIR.'data/'.$dirname.'/';
$this->projname = $projname;
// Identify resources in folder
$ls = scandir($this->dir);
foreach($ls as $file) {
if ($file[0] == '.') continue;
if ($file == 'README.txt') {
$this->longdesc = file_get_contents($this->dir.'README.txt');
$matches = array();
if (preg_match('~Written in ([^\\r\\n]+)~', $this->longdesc, $matches)) {
$this->subtag = rtrim($matches[1], ' .');
}
if (preg_match('~Tags: ([^\\r\\n]+)~', $this->longdesc, $matches)) {
$this->tags = array_map('trim', explode(',', $matches[1]));
}
$parts = explode("\n", $this->longdesc);
$this->shortdesc = array_shift($parts);
$this->shortdesc[0] = strtolower($this->shortdesc[0]); // cosmetic lowercase
continue;
}
if (is_image($file)) {
$this->images[] = $file;
} else {
$this->downloads[] = $file;
}
}
natcasesort($this->downloads);
$this->downloads = array_reverse($this->downloads);
}
public function genHomeImage() {
if (count($this->images)) {
$this->homeimage = mkthumbnail(
$this->dir.$this->images[0], //BASEDIR.'wwwroot/srv/'.$this->projname.'_0.'.str_ext($this->images[0]),
null, // raw handle
INDEX_THUMB_W, INDEX_THUMB_H
);
}
}
public function write() {
// Generate image thumbnails
foreach($this->images as $idx => $image) {
$outfile = BASEDIR.'wwwroot/srv/'.$this->projname.'_'.$idx;
copy($this->dir.$image, $outfile.'.'.str_ext($image));
mkthumbnail($outfile.'.'.str_ext($image), $outfile.'_thumb.jpg', PAGE_THUMB_W, PAGE_THUMB_H);
}
// Copy downloads to wwwroot
foreach($this->downloads as $idx => $filename) {
copy($this->dir.$filename, BASEDIR.'wwwroot/srv/'.$filename);
}
// Generate index page
ob_start();
$this->index();
$idxfile = template($this->projname.' | '.SITE_TITLE, ob_get_clean());
file_put_contents(BASEDIR.'wwwroot/'.$this->projname.'.html', $idxfile);
}
public function getClassAttr() {
if (count($this->tags)) {
return 'taggedWith-'.implode(' taggedWith-', $this->tags);
} else {
return '';
}
}
public function index() {
?>
<h2><?=hesc($this->projname)?></h2>
<div class="projinfo">
<div class="projbody projbody_<?=(count($this->images) ? 'half' : 'full')?>w">
<strong>ABOUT</strong>
<p><?=text2html($this->longdesc)?></p>
<?=file_get_contents(__DIR__.'/footer.htm')?>
<?php if (count($this->downloads)) { ?>
<strong>DOWNLOAD</strong>
<ul>
<?php foreach($this->downloads as $filename) { ?>
<li>
<a href="srv/<?=hesc(rawurlencode($filename))?>"><?=hesc($filename)?></a>
<small>
<?=hesc(fbytes(filesize(BASEDIR.'wwwroot/srv/'.$filename)))?>
</small>
</li>
<?php } ?>
</ul>
<?php } ?>
</div>
<?php if (count($this->images)) { ?>
<div class="projimg">
<?php foreach($this->images as $idx => $origname) { ?>
<a href="srv/<?=hesc(urlencode($this->projname))?>_<?=$idx?>.<?=str_ext($origname)?>"><img src="srv/<?=hesc(urlencode($this->projname))?>_<?=$idx?>_thumb.jpg" class="thumbimage"></a>
<?php } ?>
</div>
<?php } ?>
</div>
<?php
}
}
function template($title, $content) {
ob_start();
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" >
<meta name="viewport" content="width=768px" >
<link type="text/css" rel="stylesheet" href="normalize.css">
<link type="text/css" rel="stylesheet" href="style.css">
<script type="text/javascript" src="site.js"></script>
<title><?=hesc($title)?></title>
</head>
<body>
<div id="container">
<div id="content">
<?=file_get_contents(__DIR__.'/header.htm')?>
<?=$content?>
</div>
</div>
</body>
</html>
<?php
return ob_get_clean();
}
function listprojects() {
// List projects
$ls = scandir(BASEDIR.'data');
rsort($ls);
$projects = array();
foreach($ls as $dirname) {
if ($dirname[0] == '.') continue;
$matches = array();
if (preg_match('~(?:\d+-)?(.+)~', $dirname, $matches)) {
$projects[$dirname] = $matches[1];
}
}
return $projects;
}
function buildprojects($id, $projects) {
$count = 0;
foreach($projects as $dirname => $projectname) {
echo sprintf("@%1d [%3d/%3d] ".$projectname."...\n", $id, ++$count, count($projects));
$pr = new CProject($dirname, $projectname);
$pr->write();
}
}
function buildcommon() {
echo "@0 [ 0/ ?] Common files...\n";
$projects = listprojects();
// Build all projects
$plist = array();
$handles = array();
$handle_lookup = array();
foreach($projects as $dirname => $projectname) {
$pr = new CProject($dirname, $projectname);
$pr->genHomeImage(); // thumbnail
$plist[] = $pr;
if (is_null($pr->homeimage)) {
$handle_lookup[$projectname] = null;
} else {
$handle_lookup[$projectname] = count($handles);
$handles[] = $pr->homeimage;
}
}
// Build homepage spritesheet
mkspritesheet($handles, BASEDIR.'wwwroot/logos.jpg', INDEX_THUMB_W, INDEX_THUMB_H);
array_map('imagedestroy', $handles); // free
// Build index page
ob_start();
?>
<?php if (file_exists(BASEDIR.'homepage_blurb.htm')) { ?>
<!-- homepage blurb {{ -->
<?=file_get_contents(BASEDIR.'homepage_blurb.htm')?>
<!-- }} -->
<?php } ?>
<table class="projtable">
<?php foreach ($plist as $pr) { ?>
<tr class="<?=$pr->getClassAttr()?>">
<td>
<a href="<?=hesc(urlencode($pr->projname))?>.html"><?=(is_null($handle_lookup[$pr->projname]) ? '<div class="no-image"></div>' : '<div class="homeimage homeimage-sprite" style="background-position:0 -'.($handle_lookup[$pr->projname]*INDEX_THUMB_H).'px"></div>')?></a>
</td>
<td>
<strong><?=hesc($pr->projname)?></strong>,
<?=hesc($pr->shortdesc)?>
<a href="<?=hesc(urlencode($pr->projname))?>.html">more...</a>
<?php if (strlen($pr->subtag) || count($pr->tags)) { ?>
<br>
<small>
<?=hesc($pr->subtag)?>
<?php if (strlen($pr->subtag) && count($pr->tags)) { ?>
::
<?php } ?>
<?php foreach($pr->tags as $tag) { ?>
<a class="tag tag-link" data-tag="<?=hesc($tag)?>"><?=hesc($tag)?></a>
<?php } ?>
</small>
<?php } ?>
</td>
</tr>
<?php } ?>
</table>
<?php
$index = template(SITE_TITLE, ob_get_clean());
file_put_contents(BASEDIR.'wwwroot/index.html', $index);
// Done
}
function main($args) {
$total = $args[0];
$pos = $args[1];
if ($pos == 0) {
buildcommon();
} else {
buildprojects($pos, array_decimate(listprojects(), $total, $pos));
}
}
// Entry point
//
ini_set('display_errors', 'On');
error_reporting(E_ALL);
main(array_slice($_SERVER['argv'], 1));

View File

@ -1,66 +0,0 @@
#!/bin/bash
set -eu
APP_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
numcpus() {
cat /proc/cpuinfo | grep '^processor' | wc -l
}
buildsite() {
local site="$1"
echo "Site: ${site}"
(
cd "$site"
echo "Cleaning wwwroot directory..."
if [[ -d wwwroot ]] ; then
rm -r wwwroot
fi
mkdir -p wwwroot/{img,srv,static}
echo "Copying static resources..."
if [[ ! -d static ]] ; then
mkdir static
fi
cp "${APP_DIR}/static/"* wwwroot/static || true
cp static/* wwwroot/static || true
for htm in footer header homepage_blurb ; do
if [[ ! -f "${htm}.htm" ]] ; then
touch "${htm}.htm"
fi
done
echo "Building pages..."
local threadcount=$(numcpus)
for i in $(seq 0 "$threadcount") ; do
"${APP_DIR}/build-worker" "$threadcount" "$i" &
done
wait
echo "Site: ${site} finished."
echo ""
)
}
usage() {
echo "USAGE: ./rebuild.sh path-to-siteroot"
exit 1
}
main() {
if [[ $# -ne 1 ]] ; then
usage
fi
buildsite "$1"
}
main "$@"

View File

@ -1,13 +0,0 @@
[codesite]
title=codesite.example.com
page_thumb_w=60
page_thumb_h=60
index_thumb_w=90
index_thumb_h=32
; n.b. Recommend a multiple of the JPEG iDCT block size for index_thumb_h
[redirect]
; old project name = new project name
old-project-name=example-project

View File

@ -1,14 +0,0 @@
<p>
Homepage blurb goes here.
</p>
<p>
<select id="sortorder" style="float:right;">
<option value="a">Youngest project first</option>
<option value="b">Recent updates first</option>
<option value="c">Alphabetical</option>
</select>
<strong>PROJECTS</strong>
</p>

BIN
static/header.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 436 B

After

Width:  |  Height:  |  Size: 436 B

535
static/normalize.css vendored
View File

@ -1,8 +1,66 @@
/*! normalize.css v4.1.1 | MIT License | github.com/necolas/normalize.css */ /*! normalize.css v2.1.3 | MIT License | git.io/normalize */
/* ==========================================================================
HTML5 display definitions
========================================================================== */
/** /**
* 1. Change the default font family in all browsers (opinionated). * Correct `block` display not defined in IE 8/9.
* 2. Prevent adjustments of font size after orientation changes in IE and iOS. */
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
main,
nav,
section,
summary {
display: block;
}
/**
* Correct `inline-block` display not defined in IE 8/9.
*/
audio,
canvas,
video {
display: inline-block;
}
/**
* Prevent modern browsers from displaying `audio` without controls.
* Remove excess height in iOS 5 devices.
*/
audio:not([controls]) {
display: none;
height: 0;
}
/**
* Address `[hidden]` styling not present in IE 8/9.
* Hide the `template` element in IE, Safari, and Firefox < 22.
*/
[hidden],
template {
display: none;
}
/* ==========================================================================
Base
========================================================================== */
/**
* 1. Set default font family to sans-serif.
* 2. Prevent iOS text size adjust after orientation change, without disabling
* user zoom.
*/ */
html { html {
@ -12,141 +70,49 @@ html {
} }
/** /**
* Remove the margin in all browsers (opinionated). * Remove default margin.
*/ */
body { body {
margin: 0; margin: 0;
} }
/* HTML5 display definitions /* ==========================================================================
Links
========================================================================== */ ========================================================================== */
/** /**
* Add the correct display in IE 9-. * Remove the gray background color from active links in IE 10.
* 1. Add the correct display in Edge, IE, and Firefox.
* 2. Add the correct display in IE.
*/
article,
aside,
details, /* 1 */
figcaption,
figure,
footer,
header,
main, /* 2 */
menu,
nav,
section,
summary { /* 1 */
display: block;
}
/**
* Add the correct display in IE 9-.
*/
audio,
canvas,
progress,
video {
display: inline-block;
}
/**
* Add the correct display in iOS 4-7.
*/
audio:not([controls]) {
display: none;
height: 0;
}
/**
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
*/
progress {
vertical-align: baseline;
}
/**
* Add the correct display in IE 10-.
* 1. Add the correct display in IE.
*/
template, /* 1 */
[hidden] {
display: none;
}
/* Links
========================================================================== */
/**
* 1. Remove the gray background on active links in IE 10.
* 2. Remove gaps in links underline in iOS 8+ and Safari 8+.
*/ */
a { a {
background-color: transparent; /* 1 */ background: transparent;
-webkit-text-decoration-skip: objects; /* 2 */
} }
/** /**
* Remove the outline on focused links when they are also active or hovered * Address `outline` inconsistency between Chrome and other browsers.
* in all browsers (opinionated). */
a:focus {
outline: thin dotted;
}
/**
* Improve readability when focused and also mouse hovered in all browsers.
*/ */
a:active, a:active,
a:hover { a:hover {
outline-width: 0; outline: 0;
} }
/* Text-level semantics /* ==========================================================================
Typography
========================================================================== */ ========================================================================== */
/** /**
* 1. Remove the bottom border in Firefox 39-. * Address variable `h1` font-size and margin within `section` and `article`
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. * contexts in Firefox 4+, Safari 5, and Chrome.
*/
abbr[title] {
border-bottom: none; /* 1 */
text-decoration: underline; /* 2 */
text-decoration: underline dotted; /* 2 */
}
/**
* Prevent the duplicate application of `bolder` by the next rule in Safari 6.
*/
b,
strong {
font-weight: inherit;
}
/**
* Add the correct font weight in Chrome, Edge, and Safari.
*/
b,
strong {
font-weight: bolder;
}
/**
* Add the correct font style in Android 4.3-.
*/
dfn {
font-style: italic;
}
/**
* Correct the font size and margin on `h1` elements within `section` and
* `article` contexts in Chrome, Firefox, and Safari.
*/ */
h1 { h1 {
@ -155,16 +121,79 @@ h1 {
} }
/** /**
* Add the correct background and color in IE 9-. * Address styling not present in IE 8/9, Safari 5, and Chrome.
*/
abbr[title] {
border-bottom: 1px dotted;
}
/**
* Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome.
*/
b,
strong {
font-weight: bold;
}
/**
* Address styling not present in Safari 5 and Chrome.
*/
dfn {
font-style: italic;
}
/**
* Address differences between Firefox and other browsers.
*/
hr {
-moz-box-sizing: content-box;
box-sizing: content-box;
height: 0;
}
/**
* Address styling not present in IE 8/9.
*/ */
mark { mark {
background-color: #ff0; background: #ff0;
color: #000; color: #000;
} }
/** /**
* Add the correct font size in all browsers. * Correct font family set oddly in Safari 5 and Chrome.
*/
code,
kbd,
pre,
samp {
font-family: monospace, serif;
font-size: 1em;
}
/**
* Improve readability of pre-formatted text in all browsers.
*/
pre {
white-space: pre-wrap;
}
/**
* Set consistent quote types.
*/
q {
quotes: "\201C" "\201D" "\2018" "\2019";
}
/**
* Address inconsistent and variable font size in all browsers.
*/ */
small { small {
@ -172,8 +201,7 @@ small {
} }
/** /**
* Prevent `sub` and `sup` elements from affecting the line height in * Prevent `sub` and `sup` affecting `line-height` in all browsers.
* all browsers.
*/ */
sub, sub,
@ -184,150 +212,52 @@ sup {
vertical-align: baseline; vertical-align: baseline;
} }
sub {
bottom: -0.25em;
}
sup { sup {
top: -0.5em; top: -0.5em;
} }
/* Embedded content sub {
bottom: -0.25em;
}
/* ==========================================================================
Embedded content
========================================================================== */ ========================================================================== */
/** /**
* Remove the border on images inside links in IE 10-. * Remove border when inside `a` element in IE 8/9.
*/ */
img { img {
border-style: none; border: 0;
} }
/** /**
* Hide the overflow in IE. * Correct overflow displayed oddly in IE 9.
*/ */
svg:not(:root) { svg:not(:root) {
overflow: hidden; overflow: hidden;
} }
/* Grouping content /* ==========================================================================
Figures
========================================================================== */ ========================================================================== */
/** /**
* 1. Correct the inheritance and scaling of font size in all browsers. * Address margin not present in IE 8/9 and Safari 5.
* 2. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
pre,
samp {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/**
* Add the correct margin in IE 8.
*/ */
figure { figure {
margin: 1em 40px; margin: 0;
} }
/** /* ==========================================================================
* 1. Add the correct box sizing in Firefox. Forms
* 2. Show the overflow in Edge and IE.
*/
hr {
box-sizing: content-box; /* 1 */
height: 0; /* 1 */
overflow: visible; /* 2 */
}
/* Forms
========================================================================== */ ========================================================================== */
/** /**
* 1. Change font properties to `inherit` in all browsers (opinionated). * Define consistent border, margin, and padding.
* 2. Remove the margin in Firefox and Safari.
*/
button,
input,
select,
textarea {
font: inherit; /* 1 */
margin: 0; /* 2 */
}
/**
* Restore the font weight unset by the previous rule.
*/
optgroup {
font-weight: bold;
}
/**
* Show the overflow in IE.
* 1. Show the overflow in Edge.
*/
button,
input { /* 1 */
overflow: visible;
}
/**
* Remove the inheritance of text transform in Edge, Firefox, and IE.
* 1. Remove the inheritance of text transform in Firefox.
*/
button,
select { /* 1 */
text-transform: none;
}
/**
* 1. Prevent a WebKit bug where (2) destroys native `audio` and `video`
* controls in Android 4.
* 2. Correct the inability to style clickable types in iOS and Safari.
*/
button,
html [type="button"], /* 1 */
[type="reset"],
[type="submit"] {
-webkit-appearance: button; /* 2 */
}
/**
* Remove the inner border and padding in Firefox.
*/
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
border-style: none;
padding: 0;
}
/**
* Restore the focus styles unset by the previous rule.
*/
button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
outline: 1px dotted ButtonText;
}
/**
* Change the border, margin, and padding in all browsers (opinionated).
*/ */
fieldset { fieldset {
@ -337,83 +267,140 @@ fieldset {
} }
/** /**
* 1. Correct the text wrapping in Edge and IE. * 1. Correct `color` not being inherited in IE 8/9.
* 2. Correct the color inheritance from `fieldset` elements in IE. * 2. Remove padding so people aren't caught out if they zero out fieldsets.
* 3. Remove the padding so developers are not caught out when they zero out
* `fieldset` elements in all browsers.
*/ */
legend { legend {
box-sizing: border-box; /* 1 */ border: 0; /* 1 */
color: inherit; /* 2 */ padding: 0; /* 2 */
display: table; /* 1 */
max-width: 100%; /* 1 */
padding: 0; /* 3 */
white-space: normal; /* 1 */
} }
/** /**
* Remove the default vertical scrollbar in IE. * 1. Correct font family not being inherited in all browsers.
* 2. Correct font size not being inherited in all browsers.
* 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome.
*/ */
button,
input,
select,
textarea { textarea {
overflow: auto; font-family: inherit; /* 1 */
font-size: 100%; /* 2 */
margin: 0; /* 3 */
} }
/** /**
* 1. Add the correct box sizing in IE 10-. * Address Firefox 4+ setting `line-height` on `input` using `!important` in
* 2. Remove the padding in IE 10-. * the UA stylesheet.
*/ */
[type="checkbox"], button,
[type="radio"] { input {
line-height: normal;
}
/**
* Address inconsistent `text-transform` inheritance for `button` and `select`.
* All other form control elements do not inherit `text-transform` values.
* Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+.
* Correct `select` style inheritance in Firefox 4+ and Opera.
*/
button,
select {
text-transform: none;
}
/**
* 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
* and `video` controls.
* 2. Correct inability to style clickable `input` types in iOS.
* 3. Improve usability and consistency of cursor style between image-type
* `input` and others.
*/
button,
html input[type="button"], /* 1 */
input[type="reset"],
input[type="submit"] {
-webkit-appearance: button; /* 2 */
cursor: pointer; /* 3 */
}
/**
* Re-set default cursor for disabled elements.
*/
button[disabled],
html input[disabled] {
cursor: default;
}
/**
* 1. Address box sizing set to `content-box` in IE 8/9/10.
* 2. Remove excess padding in IE 8/9/10.
*/
input[type="checkbox"],
input[type="radio"] {
box-sizing: border-box; /* 1 */ box-sizing: border-box; /* 1 */
padding: 0; /* 2 */ padding: 0; /* 2 */
} }
/** /**
* Correct the cursor style of increment and decrement buttons in Chrome. * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
* 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
* (include `-moz` to future-proof).
*/ */
[type="number"]::-webkit-inner-spin-button, input[type="search"] {
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Correct the odd appearance in Chrome and Safari.
* 2. Correct the outline style in Safari.
*/
[type="search"] {
-webkit-appearance: textfield; /* 1 */ -webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */ -moz-box-sizing: content-box;
-webkit-box-sizing: content-box; /* 2 */
box-sizing: content-box;
} }
/** /**
* Remove the inner padding and cancel buttons in Chrome and Safari on OS X. * Remove inner padding and search cancel button in Safari 5 and Chrome
* on OS X.
*/ */
[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-cancel-button,
[type="search"]::-webkit-search-decoration { input[type="search"]::-webkit-search-decoration {
-webkit-appearance: none; -webkit-appearance: none;
} }
/** /**
* Correct the text style of placeholders in Chrome, Edge, and Safari. * Remove inner padding and border in Firefox 4+.
*/ */
::-webkit-input-placeholder { button::-moz-focus-inner,
color: inherit; input::-moz-focus-inner {
opacity: 0.54; border: 0;
padding: 0;
} }
/** /**
* 1. Correct the inability to style clickable types in iOS and Safari. * 1. Remove default vertical scrollbar in IE 8/9.
* 2. Change font properties to `inherit` in Safari. * 2. Improve readability and alignment in all browsers.
*/ */
::-webkit-file-upload-button { textarea {
-webkit-appearance: button; /* 1 */ overflow: auto; /* 1 */
font: inherit; /* 2 */ vertical-align: top; /* 2 */
}
/* ==========================================================================
Tables
========================================================================== */
/**
* Remove most spacing between table cells.
*/
table {
border-collapse: collapse;
border-spacing: 0;
} }

View File

Before

Width:  |  Height:  |  Size: 233 B

After

Width:  |  Height:  |  Size: 233 B

View File

Before

Width:  |  Height:  |  Size: 264 B

After

Width:  |  Height:  |  Size: 264 B

View File

@ -1,10 +1,6 @@
(function() { (function() {
"use strict"; "use strict";
//
// Tag support
//
var show_all = function() { var show_all = function() {
var tr = document.querySelectorAll(".projtable tr"); var tr = document.querySelectorAll(".projtable tr");
for (var i = 0, e = tr.length; i !== e; ++i) { for (var i = 0, e = tr.length; i !== e; ++i) {
@ -37,10 +33,7 @@
}; };
var get_show_tag = function(tag) { var get_show_tag = function(tag) {
return function() { return function() { show_tag(tag); return false; };
show_tag(tag);
return false;
};
}; };
window.addEventListener('load', function() { window.addEventListener('load', function() {
@ -50,34 +43,4 @@
taglinks[i].addEventListener('click', get_show_tag(tag)); taglinks[i].addEventListener('click', get_show_tag(tag));
} }
}); });
//
// Sort support (theme opt-in)
//
var sort_rows = function(cb) {
var tr = document.querySelectorAll(".projtable tr");
var items = [];
for (var i = 0, e = tr.length; i !== e; ++i) {
items.push([i, cb(tr[i])]);
}
items.sort(function(a, b) {
return (a[1] - b[1]);
});
for (var i = 0, e = items.length; i !== e; ++i) {
var el = tr[items[i][0]];
var parent = el.parentElement;
parent.removeChild(el);
parent.appendChild(el);
}
};
var sort_update = function(sort_by) {
sort_rows(function(el) {
return el.getAttribute(sort_by);
});
};
window.sortUpdate = sort_update;
})(); })();

View File

@ -1,60 +0,0 @@
#!/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 "$@"