Compare commits
118 Commits
Author | SHA1 | Date | |
---|---|---|---|
e04eb117e5 | |||
2cb08a49e0 | |||
04b71a48c4 | |||
72eae517da | |||
847b213fe2 | |||
5079427678 | |||
bd70d6ae41 | |||
61c889b7ca | |||
64c5d52afe | |||
d9d0451313 | |||
aa96521ca7 | |||
146f08dacd | |||
eeaf1c1dbf | |||
ef3f3b6e9b | |||
76341bb409 | |||
be2354c9b8 | |||
4379d3f428 | |||
b4669565e2 | |||
5b696229a1 | |||
52f90c5673 | |||
428303c9fb | |||
4811fcc29e | |||
ff70556d8e | |||
fb20358834 | |||
8a43a8181e | |||
2e9666521b | |||
b75ec5aba8 | |||
657d5ccd31 | |||
5801030e31 | |||
8adadb0be1 | |||
9af6849a82 | |||
650664a95d | |||
6c21f36d77 | |||
378dbec981 | |||
b10e5696e2 | |||
f05f429728 | |||
d9b52860b6 | |||
242f69c731 | |||
1a0191a716 | |||
17f5355557 | |||
20d9ea66e5 | |||
0f6cabe3ee | |||
d3739c2f81 | |||
cfdeee6a46 | |||
4f6ce8695c | |||
39789220a8 | |||
12eb8a2111 | |||
da4cf9b528 | |||
0e561b38f7 | |||
0240db512f | |||
85ef555bfd | |||
11336d3451 | |||
1a350aab89 | |||
e6df007875 | |||
a4d6e7a0b2 | |||
a0c64ad5b2 | |||
08892bd0b8 | |||
5b98cd36cd | |||
662496c214 | |||
1d8ccdb5ad | |||
138a3c3342 | |||
fc28e8c5c8 | |||
d67c7fc926 | |||
8a9b36d3db | |||
deea763923 | |||
8f34df0cbd | |||
87f6da2957 | |||
766fee12d5 | |||
6488bed03c | |||
ea7092ebef | |||
ac79051db4 | |||
1194223ddd | |||
ad76f6fb7a | |||
f39a05ba59 | |||
67f20f4379 | |||
2c857250c0 | |||
931b6e0208 | |||
8ea9ca2b2f | |||
9def31abeb | |||
1878d023bf | |||
fef327ec61 | |||
5ddd86e4aa | |||
87dd96adbd | |||
9de0095c8d | |||
7480da7568 | |||
81c65b3cce | |||
441c05f096 | |||
671573bc2e | |||
17b0bea213 | |||
0ad96058d5 | |||
092efca34d | |||
0303019641 | |||
8efff11ac2 | |||
4a6f8d90d8 | |||
b572d957fc | |||
0f70fc7bf0 | |||
a38b0dbea5 | |||
42f4bca8ba | |||
2f254fd355 | |||
f54c388b1a | |||
ee66a10fc3 | |||
d52f58ecae | |||
376745b825 | |||
1c432afdbf | |||
df731a8171 | |||
503bdbd527 | |||
8b153119ea | |||
de3e1f1210 | |||
e693abc079 | |||
72bf8d9410 | |||
8736cc97b4 | |||
2eac27a439 | |||
bbbfba2e8f | |||
97c06514eb | |||
4c3e285547 | |||
40a86b41c7 | |||
301d81a637 | |||
dc260a27d6 |
BIN
.dist-archive/code_v118.tar.xz
Normal file
BIN
.dist-archive/code_v126.tar.xz
Normal file
BIN
.dist-archive/code_v13.zip
Normal file
BIN
.dist-archive/code_v132.tar.xz
Normal file
BIN
.dist-archive/code_v39.zip
Normal file
BIN
.dist-archive/code_v54.tar.xz
Normal file
BIN
.dist-archive/code_v64.tar.xz
Normal file
BIN
.dist-archive/code_v72.tar.xz
Normal file
BIN
.dist-archive/code_v97.tar.xz
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
_dist/
|
||||
sites/*/data/
|
||||
sites/*/wwwroot/
|
||||
shields_cache/
|
102
README.md
Normal file
@ -0,0 +1,102 @@
|
||||
# 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
|
8
TODO.txt
Normal file
@ -0,0 +1,8 @@
|
||||
|
||||
- Merge "written in" and "tags"
|
||||
|
||||
- RSS for recent changes
|
||||
|
||||
- RSS for all projects
|
||||
|
||||
- Switchable CSS (reddit theme, 4chan theme, HN theme)
|
27
build-worker
Executable 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
@ -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[] = " . "-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']);
|
@ -1,3 +0,0 @@
|
||||
@echo off
|
||||
|
||||
start C:\bin\cygwin\bin\mintty.exe /bin/bash -l -c "rsync --delete -avz -e ""ssh -i /cygdrive/c/www/ms1_deploy_key -p 2222"" --progress /cygdrive/c/www/m6/code/wwwroot www-data@ms1.ivysaur.me:~/code.ivysaur.me/"
|
BIN
doc/screenshot.png
Normal file
After Width: | Height: | Size: 115 KiB |
11
footer.htm
@ -1,11 +0,0 @@
|
||||
<p>
|
||||
<strong>CONTACT</strong>
|
||||
</p>
|
||||
<p>
|
||||
For bug reports, feature requests, or if you need any help, please
|
||||
<a
|
||||
href="http://www.google.com/recaptcha/mailhide/d?k=01GuAWzMc9JjSdooo-2KCMQA==&c=kgR3dBrP39yhPIy8FvLFbuBLmWqorQBDc_Zjbw6NAmU="
|
||||
onclick="window.open('http://www.google.com/recaptcha/mailhide/d?k\07501GuAWzMc9JjSdooo-2KCMQA\75\75\46c\75kgR3dBrP39yhPIy8FvLFbuBLmWqorQBDc_Zjbw6NAmU\075', '', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=500,height=300'); return false;"
|
||||
title="Reveal this e-mail address"
|
||||
>click here</a> to email me.
|
||||
</p>
|
@ -1 +0,0 @@
|
||||
<h1><a href="index.html"><div id="ivylogo"></div>code.ivysaur.me</a></h1>
|
@ -1,20 +0,0 @@
|
||||
|
||||
<p>
|
||||
It's said that if you're not disgusted by code you wrote six months ago, you've stopped learning.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>OVERVIEW</strong>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
This page contains several assorted spare-time projects, which no longer have any relation. They are listed in roughly chronological order (newest first). <strong>Unless specified otherwise</strong>, you may feel free to use and modify both the binaries and any source code, for any purpose, on the general condition you do not misrepresent who the author is (BSD license).
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Some of these projects formerly appeared on Google Code <a href="https://code.google.com/p/mappy/">here</a>, but were moved following the discontinuation of the Google Code binary download system.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>PROJECTS</strong>
|
||||
</p>
|
408
lib/CProject.php
Normal file
@ -0,0 +1,408 @@
|
||||
<?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.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').' ' : '').
|
||||
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 .= ' ';
|
||||
}
|
||||
$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
|
||||
}
|
||||
|
||||
}
|
47
lib/bootstrap.php
Executable file
@ -0,0 +1,47 @@
|
||||
<?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;
|
||||
}
|
214
lib/template.php
Normal file
@ -0,0 +1,214 @@
|
||||
<?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 »</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);
|
||||
|
||||
}
|
||||
}
|
191
lib/util.php
Normal file
@ -0,0 +1,191 @@
|
||||
<?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• ", $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;
|
||||
}
|
14
mkdist.sh
Executable file
@ -0,0 +1,14 @@
|
||||
#!/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..."
|
||||
|
20
rebuild.cmd
@ -1,20 +0,0 @@
|
||||
@echo off
|
||||
|
||||
set PHP=C:\bin\php54\php.exe
|
||||
|
||||
echo Cleaning target directory...
|
||||
echo.
|
||||
rmdir /s /q wwwroot
|
||||
mkdir wwwroot
|
||||
mkdir wwwroot\srv
|
||||
copy static\* wwwroot
|
||||
|
||||
echo Building pages...
|
||||
echo.
|
||||
start /b %PHP% rebuild.php 4 0
|
||||
start /b %PHP% rebuild.php 4 1
|
||||
start /b %PHP% rebuild.php 4 2
|
||||
start /b %PHP% rebuild.php 4 3
|
||||
start /b %PHP% rebuild.php 4 4
|
||||
|
||||
pause
|
453
rebuild.php
@ -1,453 +0,0 @@
|
||||
<?php
|
||||
|
||||
// Code-hosting website
|
||||
// ````````````````````
|
||||
|
||||
// CONFIGURATION
|
||||
// `````````````
|
||||
|
||||
define('BASEDIR', __DIR__.'\\');
|
||||
define('SITE_TITLE', 'code.ivysaur.me');
|
||||
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));
|
66
rebuild.sh
Executable file
@ -0,0 +1,66 @@
|
||||
#!/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 "$@"
|
0
shields_cache/.create_dir
Normal file
13
sites/codesite.example.com/config.ini
Normal file
@ -0,0 +1,13 @@
|
||||
[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
|
||||
|
6
sites/codesite.example.com/footer.htm
Normal file
@ -0,0 +1,6 @@
|
||||
<p>
|
||||
<strong>FOOTER</strong>
|
||||
</p>
|
||||
<p>
|
||||
Content here.
|
||||
</p>
|
1
sites/codesite.example.com/header.htm
Normal file
@ -0,0 +1 @@
|
||||
<h1>HEADER</h1>
|
14
sites/codesite.example.com/homepage_blurb.htm
Normal file
@ -0,0 +1,14 @@
|
||||
|
||||
<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>
|
Before Width: | Height: | Size: 436 B After Width: | Height: | Size: 436 B |
Before Width: | Height: | Size: 233 B After Width: | Height: | Size: 233 B |
Before Width: | Height: | Size: 264 B After Width: | Height: | Size: 264 B |
@ -133,20 +133,6 @@ html, body {
|
||||
|
||||
/* */
|
||||
|
||||
#ivylogo {
|
||||
background:transparent url('ivysaur24.png') no-repeat 0 0;
|
||||
width:24px;
|
||||
height:24px;
|
||||
display:inline-block;
|
||||
*display:block;
|
||||
*zoom:1;
|
||||
margin-right:4px;
|
||||
position:relative;
|
||||
top:4px;
|
||||
}
|
||||
|
||||
/* */
|
||||
|
||||
.homeimage {
|
||||
width:90px;
|
||||
height:32px;
|
Before Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 1.4 KiB |
541
static/normalize.css
vendored
@ -1,406 +1,419 @@
|
||||
/*! normalize.css v2.1.3 | MIT License | git.io/normalize */
|
||||
/*! normalize.css v4.1.1 | MIT License | github.com/necolas/normalize.css */
|
||||
|
||||
/* ==========================================================================
|
||||
HTML5 display definitions
|
||||
/**
|
||||
* 1. Change the default font family in all browsers (opinionated).
|
||||
* 2. Prevent adjustments of font size after orientation changes in IE and iOS.
|
||||
*/
|
||||
|
||||
html {
|
||||
font-family: sans-serif; /* 1 */
|
||||
-ms-text-size-adjust: 100%; /* 2 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the margin in all browsers (opinionated).
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* HTML5 display definitions
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Correct `block` display not defined in IE 8/9.
|
||||
* Add the correct display in IE 9-.
|
||||
* 1. Add the correct display in Edge, IE, and Firefox.
|
||||
* 2. Add the correct display in IE.
|
||||
*/
|
||||
|
||||
article,
|
||||
aside,
|
||||
details,
|
||||
details, /* 1 */
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
main,
|
||||
main, /* 2 */
|
||||
menu,
|
||||
nav,
|
||||
section,
|
||||
summary {
|
||||
display: block;
|
||||
summary { /* 1 */
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct `inline-block` display not defined in IE 8/9.
|
||||
* Add the correct display in IE 9-.
|
||||
*/
|
||||
|
||||
audio,
|
||||
canvas,
|
||||
progress,
|
||||
video {
|
||||
display: inline-block;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent modern browsers from displaying `audio` without controls.
|
||||
* Remove excess height in iOS 5 devices.
|
||||
* Add the correct display in iOS 4-7.
|
||||
*/
|
||||
|
||||
audio:not([controls]) {
|
||||
display: none;
|
||||
height: 0;
|
||||
display: none;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address `[hidden]` styling not present in IE 8/9.
|
||||
* Hide the `template` element in IE, Safari, and Firefox < 22.
|
||||
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
|
||||
*/
|
||||
|
||||
[hidden],
|
||||
template {
|
||||
display: none;
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Base
|
||||
/**
|
||||
* Add the correct display in IE 10-.
|
||||
* 1. Add the correct display in IE.
|
||||
*/
|
||||
|
||||
template, /* 1 */
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Links
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Set default font family to sans-serif.
|
||||
* 2. Prevent iOS text size adjust after orientation change, without disabling
|
||||
* user zoom.
|
||||
*/
|
||||
|
||||
html {
|
||||
font-family: sans-serif; /* 1 */
|
||||
-ms-text-size-adjust: 100%; /* 2 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove default margin.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Links
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the gray background color from active links in IE 10.
|
||||
* 1. Remove the gray background on active links in IE 10.
|
||||
* 2. Remove gaps in links underline in iOS 8+ and Safari 8+.
|
||||
*/
|
||||
|
||||
a {
|
||||
background: transparent;
|
||||
background-color: transparent; /* 1 */
|
||||
-webkit-text-decoration-skip: objects; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Address `outline` inconsistency between Chrome and other browsers.
|
||||
*/
|
||||
|
||||
a:focus {
|
||||
outline: thin dotted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Improve readability when focused and also mouse hovered in all browsers.
|
||||
* Remove the outline on focused links when they are also active or hovered
|
||||
* in all browsers (opinionated).
|
||||
*/
|
||||
|
||||
a:active,
|
||||
a:hover {
|
||||
outline: 0;
|
||||
outline-width: 0;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Typography
|
||||
/* Text-level semantics
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Address variable `h1` font-size and margin within `section` and `article`
|
||||
* contexts in Firefox 4+, Safari 5, and Chrome.
|
||||
*/
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address styling not present in IE 8/9, Safari 5, and Chrome.
|
||||
* 1. Remove the bottom border in Firefox 39-.
|
||||
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
|
||||
*/
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: 1px dotted;
|
||||
border-bottom: none; /* 1 */
|
||||
text-decoration: underline; /* 2 */
|
||||
text-decoration: underline dotted; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome.
|
||||
* Prevent the duplicate application of `bolder` by the next rule in Safari 6.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bold;
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address styling not present in Safari 5 and Chrome.
|
||||
* 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;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address differences between Firefox and other browsers.
|
||||
* Correct the font size and margin on `h1` elements within `section` and
|
||||
* `article` contexts in Chrome, Firefox, and Safari.
|
||||
*/
|
||||
|
||||
hr {
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
height: 0;
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address styling not present in IE 8/9.
|
||||
* Add the correct background and color in IE 9-.
|
||||
*/
|
||||
|
||||
mark {
|
||||
background: #ff0;
|
||||
color: #000;
|
||||
background-color: #ff0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct font family set oddly in Safari 5 and Chrome.
|
||||
* Add the correct font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent `sub` and `sup` elements from affecting the line height in
|
||||
* all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
/* Embedded content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the border on images inside links in IE 10-.
|
||||
*/
|
||||
|
||||
img {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the overflow in IE.
|
||||
*/
|
||||
|
||||
svg:not(:root) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Grouping content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
pre,
|
||||
samp {
|
||||
font-family: monospace, serif;
|
||||
font-size: 1em;
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent `sub` and `sup` affecting `line-height` in all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Embedded content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove border when inside `a` element in IE 8/9.
|
||||
*/
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct overflow displayed oddly in IE 9.
|
||||
*/
|
||||
|
||||
svg:not(:root) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Figures
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Address margin not present in IE 8/9 and Safari 5.
|
||||
* Add the correct margin in IE 8.
|
||||
*/
|
||||
|
||||
figure {
|
||||
margin: 0;
|
||||
margin: 1em 40px;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Forms
|
||||
/**
|
||||
* 1. Add the correct box sizing in Firefox.
|
||||
* 2. Show the overflow in Edge and IE.
|
||||
*/
|
||||
|
||||
hr {
|
||||
box-sizing: content-box; /* 1 */
|
||||
height: 0; /* 1 */
|
||||
overflow: visible; /* 2 */
|
||||
}
|
||||
|
||||
/* Forms
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Define consistent border, margin, and padding.
|
||||
*/
|
||||
|
||||
fieldset {
|
||||
border: 1px solid #c0c0c0;
|
||||
margin: 0 2px;
|
||||
padding: 0.35em 0.625em 0.75em;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct `color` not being inherited in IE 8/9.
|
||||
* 2. Remove padding so people aren't caught out if they zero out fieldsets.
|
||||
*/
|
||||
|
||||
legend {
|
||||
border: 0; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 1. Change font properties to `inherit` in all browsers (opinionated).
|
||||
* 2. Remove the margin in Firefox and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
font-family: inherit; /* 1 */
|
||||
font-size: 100%; /* 2 */
|
||||
margin: 0; /* 3 */
|
||||
font: inherit; /* 1 */
|
||||
margin: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Address Firefox 4+ setting `line-height` on `input` using `!important` in
|
||||
* the UA stylesheet.
|
||||
* 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 {
|
||||
line-height: normal;
|
||||
input { /* 1 */
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Remove the inheritance of text transform in Edge, Firefox, and IE.
|
||||
* 1. Remove the inheritance of text transform in Firefox.
|
||||
*/
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
select { /* 1 */
|
||||
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.
|
||||
* 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 input[type="button"], /* 1 */
|
||||
input[type="reset"],
|
||||
input[type="submit"] {
|
||||
-webkit-appearance: button; /* 2 */
|
||||
cursor: pointer; /* 3 */
|
||||
html [type="button"], /* 1 */
|
||||
[type="reset"],
|
||||
[type="submit"] {
|
||||
-webkit-appearance: button; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 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).
|
||||
*/
|
||||
|
||||
input[type="search"] {
|
||||
-webkit-appearance: textfield; /* 1 */
|
||||
-moz-box-sizing: content-box;
|
||||
-webkit-box-sizing: content-box; /* 2 */
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove inner padding and search cancel button in Safari 5 and Chrome
|
||||
* on OS X.
|
||||
*/
|
||||
|
||||
input[type="search"]::-webkit-search-cancel-button,
|
||||
input[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove inner padding and border in Firefox 4+.
|
||||
* Remove the inner border and padding in Firefox.
|
||||
*/
|
||||
|
||||
button::-moz-focus-inner,
|
||||
input::-moz-focus-inner {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
[type="button"]::-moz-focus-inner,
|
||||
[type="reset"]::-moz-focus-inner,
|
||||
[type="submit"]::-moz-focus-inner {
|
||||
border-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Remove default vertical scrollbar in IE 8/9.
|
||||
* 2. Improve readability and alignment in all browsers.
|
||||
* 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 {
|
||||
border: 1px solid #c0c0c0;
|
||||
margin: 0 2px;
|
||||
padding: 0.35em 0.625em 0.75em;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the text wrapping in Edge and IE.
|
||||
* 2. Correct the color inheritance from `fieldset` elements in IE.
|
||||
* 3. Remove the padding so developers are not caught out when they zero out
|
||||
* `fieldset` elements in all browsers.
|
||||
*/
|
||||
|
||||
legend {
|
||||
box-sizing: border-box; /* 1 */
|
||||
color: inherit; /* 2 */
|
||||
display: table; /* 1 */
|
||||
max-width: 100%; /* 1 */
|
||||
padding: 0; /* 3 */
|
||||
white-space: normal; /* 1 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the default vertical scrollbar in IE.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
overflow: auto; /* 1 */
|
||||
vertical-align: top; /* 2 */
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Tables
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove most spacing between table cells.
|
||||
* 1. Add the correct box sizing in IE 10-.
|
||||
* 2. Remove the padding in IE 10-.
|
||||
*/
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
[type="checkbox"],
|
||||
[type="radio"] {
|
||||
box-sizing: border-box; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the cursor style of increment and decrement buttons in Chrome.
|
||||
*/
|
||||
|
||||
[type="number"]::-webkit-inner-spin-button,
|
||||
[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 */
|
||||
outline-offset: -2px; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner padding and cancel buttons in Chrome and Safari on OS X.
|
||||
*/
|
||||
|
||||
[type="search"]::-webkit-search-cancel-button,
|
||||
[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the text style of placeholders in Chrome, Edge, and Safari.
|
||||
*/
|
||||
|
||||
::-webkit-input-placeholder {
|
||||
color: inherit;
|
||||
opacity: 0.54;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inability to style clickable types in iOS and Safari.
|
||||
* 2. Change font properties to `inherit` in Safari.
|
||||
*/
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
-webkit-appearance: button; /* 1 */
|
||||
font: inherit; /* 2 */
|
||||
}
|
@ -1,6 +1,10 @@
|
||||
(function() {
|
||||
"use strict";
|
||||
|
||||
//
|
||||
// Tag support
|
||||
//
|
||||
|
||||
var show_all = function() {
|
||||
var tr = document.querySelectorAll(".projtable tr");
|
||||
for (var i = 0, e = tr.length; i !== e; ++i) {
|
||||
@ -33,7 +37,10 @@
|
||||
};
|
||||
|
||||
var get_show_tag = function(tag) {
|
||||
return function() { show_tag(tag); return false; };
|
||||
return function() {
|
||||
show_tag(tag);
|
||||
return false;
|
||||
};
|
||||
};
|
||||
|
||||
window.addEventListener('load', function() {
|
||||
@ -43,4 +50,34 @@
|
||||
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;
|
||||
|
||||
})();
|
60
update_git_remote.sh
Normal 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 "$@"
|