diff --git a/lib/CProject.php b/lib/CProject.php
new file mode 100644
index 0000000..0c0730a
--- /dev/null
+++ b/lib/CProject.php
@@ -0,0 +1,213 @@
+dir = BASEDIR.'data/'.$dirname.'/';
+ $this->projname = $projname;
+
+ // Identify resources in folder
+
+ $ls = scandir($this->dir);
+ $found_real_lastupdate = false;
+ foreach($ls as $file) {
+ if ($file[0] == '.') continue;
+
+ if ($file == 'README.txt') {
+
+ // Guess 'last update' time
+ $matches = [];
+ 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;
+
+ }
+
+ $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
+
+ // Filter longdesc
+ $this->longdesc = str_replace("\r", "", $this->longdesc); // filter windows CR
+
+ $prefix_html = '';
+ $this->longdesc = preg_replace_callback('~\r?\nWritten in ([^\\n]+)~ms', function($matches) use (&$prefix_html) {
+ $prefix_html .= (
+ (SHIELDS_PREFIX ? mkshield('build', 'success', 'brightgreen').' ' : '').
+ mkshield('written in', rtrim($matches[1], '.'), 'blue')
+ );
+ return '';
+ }, $this->longdesc);
+
+ 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");
+
+ $this->prefix_html = $prefix_html;
+
+ 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
+ );
+ }
+
+ }
+
+ 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) {
+ $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();
+ $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() {
+?>
+
=hesc(str_replace('_', ' ', $this->projname))?>
+
+
+
+
+
+prefix_html)) { ?>
+
=$this->prefix_html?>
+
+
+
=hesc(strtoupper(ARTICLE_HEADER))?>
+
+
=text2html($this->longdesc)?>
+
+ =file_get_contents(BASEDIR.'/footer.htm')?>
+
+downloads)) { ?>
+
+
DOWNLOAD
+
+
+ downloads as $idx => $filename) { ?>
+ -
+ =hesc($filename)?>
+
+ =hesc(fbytes(filesize(BASEDIR.'wwwroot/srv/'.$this->downloads_hashes[$idx].'/'.$filename)))?>
+
+
+
+
+
+
+
+images)) { ?>
+
+ images as $idx => $origname) { ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ =hesc($title)?>
+
+
+
+
+=file_get_contents(BASEDIR.'/header.htm')?>
+=$content?>
+
+
+
+
+ $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
+ }
+
+ // Build index page
+
+ ob_start();
+?>
+
+
+
+ =file_get_contents(BASEDIR.'homepage_blurb.htm')?>
+
+
+
+
+ $pr) { ?>
+
+
+ =(is_null($handle_lookup[$pr->projname]) ? '' : '')?>
+ |
+
+ =hesc(str_replace('_', ' ', $pr->projname))?>,
+ =hesc($pr->shortdesc)?>
+
+ more...
+ subtag) || count($pr->tags)) { ?>
+
+
+ =hesc($pr->subtag)?>
+ subtag) && count($pr->tags)) { ?>
+ ::
+
+ tags as $tag) { ?>
+ =hesc($tag)?>
+
+
+
+ |
+
+
+
+ $newname) {
+ ob_start();
+?>
+
+Moved »
+ 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) {
+
+ $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', '\\1', $base);
+ $base = preg_replace('~(https?://[^ \\r\\n\\t]+)~i', '\\1', $base);
+ $base = preg_replace('~\\[b\\](.+?)\\[/b\\]~m', '\\1', $base);
+ $base = preg_replace('~\\[i\\](.+?)\\[/i\\]~m', '\\1', $base);
+ $base = preg_replace('~\\[spoiler\\](.+?)\\[/spoiler\\]~m', '\\1', $base);
+ $base = preg_replace('~\\[entry=([^\\]]+?)\\](.+?)\\[/entry\\]~m', '\\2', $base);
+ $base = preg_replace('~\n- ~ms', "\n• ", $base);
+
+ $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] = ''.$btparts[$i].'';
+ }
+ }
+
+ return $oddEven($btparts, $identity, 'nl2br');
+ };
+
+ $htmlSections = $splitInside('', '', $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;
+}
diff --git a/rebuild.php b/rebuild.php
deleted file mode 100644
index e2418b1..0000000
--- a/rebuild.php
+++ /dev/null
@@ -1,600 +0,0 @@
-#!/usr/bin/php
- 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) {
-
- $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', '\\1', $base);
- $base = preg_replace('~(https?://[^ \\r\\n\\t]+)~i', '\\1', $base);
- $base = preg_replace('~\\[b\\](.+?)\\[/b\\]~m', '\\1', $base);
- $base = preg_replace('~\\[i\\](.+?)\\[/i\\]~m', '\\1', $base);
- $base = preg_replace('~\\[spoiler\\](.+?)\\[/spoiler\\]~m', '\\1', $base);
- $base = preg_replace('~\\[entry=([^\\]]+?)\\](.+?)\\[/entry\\]~m', '\\2', $base);
- $base = preg_replace('~\n- ~ms', "\n• ", $base);
-
- $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] = ''.$btparts[$i].'';
- }
- }
-
- return $oddEven($btparts, $identity, 'nl2br');
- };
-
- $htmlSections = $splitInside('', '', $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 = '';
- public $lastupdate = 0;
- private $longdesc = '';
- private $prefix_html = '';
- private $images = array();
- private $downloads = array();
- private $downloads_hashes = 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);
- $found_real_lastupdate = false;
- foreach($ls as $file) {
- if ($file[0] == '.') continue;
-
- if ($file == 'README.txt') {
-
- // Guess 'last update' time
- $matches = [];
- 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;
-
- }
-
- $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
-
- // Filter longdesc
- $this->longdesc = str_replace("\r", "", $this->longdesc); // filter windows CR
-
- $prefix_html = '';
- $this->longdesc = preg_replace_callback('~\r?\nWritten in ([^\\n]+)~ms', function($matches) use (&$prefix_html) {
- $prefix_html .= (
- (SHIELDS_PREFIX ? mkshield('build', 'success', 'brightgreen').' ' : '').
- mkshield('written in', rtrim($matches[1], '.'), 'blue')
- );
- return '';
- }, $this->longdesc);
-
- 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");
-
- $this->prefix_html = $prefix_html;
-
- 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
- );
- }
-
- }
-
- 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) {
- $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();
- $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() {
-?>
-=hesc(str_replace('_', ' ', $this->projname))?>
-
-
-
-
-
-prefix_html)) { ?>
-
=$this->prefix_html?>
-
-
-
=hesc(strtoupper(ARTICLE_HEADER))?>
-
-
=text2html($this->longdesc)?>
-
- =file_get_contents(BASEDIR.'/footer.htm')?>
-
-downloads)) { ?>
-
-
DOWNLOAD
-
-
- downloads as $idx => $filename) { ?>
- -
- =hesc($filename)?>
-
- =hesc(fbytes(filesize(BASEDIR.'wwwroot/srv/'.$this->downloads_hashes[$idx].'/'.$filename)))?>
-
-
-
-
-
-
-
-images)) { ?>
-
- images as $idx => $origname) { ?>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- =hesc($title)?>
-
-
-
-
-=file_get_contents(BASEDIR.'/header.htm')?>
-=$content?>
-
-
-
-
- $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
- }
-
- // Build index page
-
- ob_start();
-?>
-
-
-
- =file_get_contents(BASEDIR.'homepage_blurb.htm')?>
-
-
-
-
- $pr) { ?>
-
-
- =(is_null($handle_lookup[$pr->projname]) ? '' : '')?>
- |
-
- =hesc(str_replace('_', ' ', $pr->projname))?>,
- =hesc($pr->shortdesc)?>
-
- more...
- subtag) || count($pr->tags)) { ?>
-
-
- =hesc($pr->subtag)?>
- subtag) && count($pr->tags)) { ?>
- ::
-
- tags as $tag) { ?>
- =hesc($tag)?>
-
-
-
- |
-
-
-
- $newname) {
- ob_start();
-?>
-
-Moved »
-