ADDED dev/fossil-json-plugin-repo.php Index: dev/fossil-json-plugin-repo.php ================================================================== --- dev/fossil-json-plugin-repo.php +++ dev/fossil-json-plugin-repo.php @@ -0,0 +1,192 @@ +[\w-]+) # fossil basename + /(? + (?: + [/\w.-]+ # basedir prefix + [*]? + ){0,3} # up to 3 *-glob segments + ) + $~x", + $_SERVER["PATH_INFO"], $groups + ); + return $groups; +} + + +/** + * Handler + * [MAIN] + * + * · invoked with $repo="projectname" and $glob="lib/*.src" + * · scans files, converts into repo.json, and outputs that + * + */ +function gen($kwargs) { + extract($kwargs); + header("Content-Type: json/common-repo; x-ver=0"); + exit( + json_encode( + common_repo( + query_files("/fossil.d/$repo.fossil", $glob), + $repo + ), + JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT + ) + ); +} + + + +/** + * Read glob-specified files from repository + * [EXEC] + * + * · scans fossil repos for given filespec, e.g. "lib/plugins/*.py" + * · returns just first 2K from content + * + */ +function query_files($R, $glob) { + $r = []; + if (!file_exists($R)) { + return $r; + } + + // loop through files + $glob = escapeshellarg(preg_replace("~[^/\w.*-]~", "", $glob)); + $sql = escapeshellarg(" + SELECT name AS name, uuid, SUBSTR(HEX(CONTENT(uuid)),1,2048) AS content + FROM (SELECT filename.name, bf.uuid, filename.fnid + FROM filename JOIN mlink ON mlink.fnid=filename.fnid JOIN blob bf ON bf.rid=mlink.fid + JOIN event ON event.objid=mlink.mid + WHERE (mlink.fnid NOT IN (SELECT fnid FROM mlink WHERE fid=0)) + AND filename.name GLOB $glob + GROUP BY filename.name + ORDER BY event.mtime DESC + ) + "); + + // Just retrieve as CSV list from fossil directly, + // instead of using PDO handle and `fossil artifact` on each UUID + $pipe = popen("fossil sqlite -R $R \".mode csv\" $sql", "rb"); + while ($row = fgetcsv($pipe, 0, ",", '"', '"')) { + + // skip emtpy rows + if (count($row) != 3) { + continue; + } + // add file + $r[$row[0]] = hex2bin($row[2]); + } + + return $r; +} + + +/** + * Convert attributes into list + * [TRANSFORM] + * + * · from $fn=>$meta dict to [$pkg, $pkg] list + * + */ +function common_repo($files, $R) { + + // extend each PMD key:value list + $repo = meta_extract($files); + foreach ($repo as $fn => $meta) { + + // basename, extension + $id = strtok(basename($fn), "."); + $ext = pathinfo($fn, PATHINFO_EXTENSION); + $dir = basename(dirname($fn)); + + // add some stub fields + $meta += [ + "type" => "unknown", + "api" => "$R", + "title" => null, + "description" => null, + "version" => null, + ]; + + // common repo fields carry a `$` sigil + $repo[$fn] = array_merge( + [ + "\$name" => $id, # package basename + "\$type" => "x-$ext", # e.g. "deb", "rpm" or "x-src" + "\$dist" => "app/$R/$dir", # e.g. "trusty/main" or "app:pkg:part" + "\$file" => "http://fossil.include-once.org/$R/cat/$fn", # resource locator + ], + $meta + ); + } + return array_values($repo); +} + + +/** + * Extract plugin meta data + * [REGEX] + * + * · really just looks for scalar key:value lines + * · comment/docs are not extracted + * + */ +function meta_extract($files) { + foreach ($files as $fn=>$cnt) { + preg_match_all("~\s*(?:#|//|[*])\s*(\w+):\h*(.+)~m", $cnt, $fields); + $meta = array_combine($fields[1], $fields[2]); + $meta = array_change_key_case($meta); + $files[$fn] = $meta; + } + return $files; +} + + +