dir = BASEDIR.'data/'.$dirname.'/'; $this->projname = $projname; $matches = []; // Identify resources in folder $ls = scandir($this->dir); $found_real_lastupdate = false; foreach($ls as $file) { if ($file[0] == '.') { continue; } if ($file == 'README.txt') { $this->longdesc = file_get_contents($this->dir.'README.txt'); $this->longdesc = str_replace("\r", "", $this->longdesc); // filter windows CR $this->longdesc = preg_replace("~[\s\t]*$~s", "", $this->longdesc); // filter trailing spaces at line endings // Guess 'last update' time if (preg_match('~\n(\d\d\d\d-\d\d-\d\d)~', $this->longdesc, $matches)) { // Use first date entry (assumed to be a CHANGELOG) $this->lastupdate = strtotime($matches[1]); $found_real_lastupdate = true; } // Find number of releases preg_match_all('~\n(\d\d\d\d-\d\d-\d\d)~', $this->longdesc, $matches, PREG_SET_ORDER); $this->numreleases = count($matches); // Find support lifespan (newest minus youngest) $eldest = time(); $newest = 0; foreach($matches as $match) { $stamp = strtotime($match[1]); $eldest = min($stamp, $eldest); $newest = max($stamp, $newest); } $this->lifespan = floor(max(0, $newest - $eldest) / 3600); // could divide by 86400 but it doesn't matter // Find 'written in' if (preg_match('~Written in ([^\\n]+)~', $this->longdesc, $matches)) { $this->subtag = rtrim($matches[1], ' .'); } // Find `go-get` tags $this->longdesc = preg_replace_callback('~\[go-get\](.+)\[/go-get\]~', function($matches) { $this->go_get_target = $matches[1]; return ''; }, $this->longdesc); // Find tags if (preg_match('~Tags: ([^\\n]+)~', $this->longdesc, $matches)) { $this->tags = array_map('trim', explode(',', $matches[1])); } // Find `entry` tags $this->longdesc = preg_replace( '~\\[entry=([^\\]]+?)\\](.+?)\\[/entry\\]~m', '[url='.BASEURL.'\\1/]\\2[/url]', // hesc still hasn't happened, transform bbcode->bbcode $this->longdesc ); // Strip out any markdown image links // [![](doc/image1.thumb.png)](doc/image1.png) $this->longdesc = preg_replace('~\\[!.+?\\)\\]\\(.+?\\)~m', '', $this->longdesc); // Find "Written in" tags $this->prefix_html = ''; $this->longdesc = preg_replace_callback('~\nWritten in ([^\\n]+)~ms', function($matches) { $this->prefix_html .= ( (SHIELDS_PREFIX ? mkshield('build', 'success', 'brightgreen').' ' : ''). 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 .= ''.mkshield('vcs', 'git', 'yellowgreen', ['logo' => 'git']).''; 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 $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 ); } $this->longdesc = str_replace("\n
", "", $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[] = ''; // TODO include golang `go get` meta if necessary if (strlen($this->go_get_target) > 0) { $extra_head_items[] = ''; // 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(); ?> DOWNLOAD filterLongDescArea(); $longdesc_html = $this->downloads_section_was_replaced ? $this->longdesc : text2html($this->longdesc); ?>

projname))?>

prefix_html)) { ?>

prefix_html?>

downloads_section_was_replaced) { ?> renderDownloadsBlock($this->downloads, true)?>
images)) { ?>
images as $idx => $origname) { ?>