Help:Dottyweb

From Esolang
Jump to navigation Jump to search

Dottyweb is a system for literate programming that can be implemented using MediaWiki templates. Code sections are lines with a space at the beginning, and you must use a prefix line indicating a chunk name. You can also use <xmp> to delimit code sections, instead, in which case no templates or anything else inside is interpreted, and it is passed verbatim (this mode will not work on this wiki, it requires a MediaWiki software that supports the <xmp> tag).

Templates usable are:

  • {{.def|name}} Define a code chunk that can be included elsewhere.
  • {{.file|name}} Make this code chunk output to named file.
  • {{.get|name|URL}} Make a file that is downloaded from a URL.
  • {{.incl|name}} Read additional chunks and other things from another wiki page.
  • {{.hex|name}} Make this code chunk output to named file, converting hex data in text into a binary file.
  • {{.reg|command}} Make a regular expression rule.
  • {{.use|name}} Include contents of other code chunk here.

Program to retrieve files

dottyweb.php =

<?php
// DottyWeb v0.2
// Public domain

// --- Configuration division ---
  $config_URLs=array(
    'esolang'=>'http://esolangs.org/w/index.php',
    'local'=>0,
  );
  $config_line_end="\r\n";
// --- End of configuration ---

function get_page($wiki,$name) {
  global $config_URLs;
  $title=urlencode(strtr($name,' ','_'));
  if($config_URLs[$wiki]==0) {
    $t=file($name);
  } else {
    $t=file($config_URLs[$wiki].'?action=raw&ctype=text/css&smaxage=0&title='.$title);
  }
  if(!$t) die("Error: nonexistent file: $wiki/$name\n");
  $o=array();
  $s=false;
  $n=false;
  foreach($t as $v) {
    $v=trim($v,"\0\r\n");
    if($n) {
      if($v=='</x'.'mp>' || $v=='</nowiki></pre>') {
        $n=false;
      } else {
        $o[]=$v;
      }
    } else {
      $v=preg_replace('/\{\{\.([a-z]+)\}\}/',"\x01\$1\x02\x03",$v);
      $v=preg_replace('/\{\{\.([a-z]+)\|([^{}]+)\}\}/',"\x01\$1\x02\$2\x03",$v);
      $v=preg_replace('/\&\#(x?[0-9A-Fa-f]+);/',"\x01chr\x02\$1\x03",$v);
      $v=strtr($v,array(
        '&amp;'=>'&',
        '&lt;'=>'<',
        '&gt;'=>'>',
        '&quot;'=>'"',
      ));
      if(preg_match('/\x01incl\x02(.*?)\x03/',$v,$m)) {
        $o=array_merge($o,get_page($wiki,$m[1]));
        $s=false;
      } else if(preg_match('/\x01reg\x02(.*?)\x03/',$v)) {
        regex_line($v);
      } else if($v[0]=="\x01") {
        $s=true;
      } else if(strlen(trim($v)) && $v[0]!=" ") {
        $s=false;
      } else if($v=='<xmp>' || $v=='<pre><nowiki>') {
        $s=false; $n=true;
      }
      if($s) {
        if($v[0]==" ") $v=substr($v,1);
        $o[]=$v;
      }
    }
  }
  return $o;
}

$regex_all=array();
$regex=array();
$regex_current=array();

function regex_line($text) {
  global $regex_all;
  preg_match('/^ ?\x01reg\x02(.*?)\x03(.*?)\x01z\x02\x03(.*)$/',$text,$m);
  $q=explode(' ',$m[1]);
  if($q[0][0]=="'") {
    $i=substr($q[0],1);
    $q=array_slice($q,1);
  } else {
    $i=0; while(isset($regex_all[$i])) ++$i;
  }
  $regex_all[$i]=array($q,$m[2],$m[3]);
  return $i;
}

function regex_callback($mat) {
  global $chunks,$regex_all,$regex,$regex_current;
  $ru=$regex_current[0];
  $data=$regex_current[2];
  $off=0;
  while(preg_match('/\x01use\x02(.*?)\x03/',$data,$m,PREG_OFFSET_CAPTURE,$off)) {
    if(ctype_digit($m[1][0])) {
      $repl=$mat[(int)($m[1][0])];
    } else if($m[1][0][0]=="'") {
      $repl="\x01use\x02".substr($m[1][0],1).chr(3);
    } else if(isset($chunks[$m[1][0]])) {
      $repl=chunk_tangle($chunks[$m[1][0]]);
    } else {
      $repl="";
    }
    $data=substr($data,0,$m[0][1]).$repl.substr($data,$m[0][1]+strlen($m[0][0]));
    $off=$m[0][1]+strlen($repl);
  }
  $out=$mat[0];
  foreach($ru as $k=>$v) {
    while(preg_match('/\$([0-9])/',$v,$m,PREG_OFFSET_CAPTURE)) {
      $repl=$mat[(int)($m[1][0])];
      $ru[$k]=substr($v,0,$m[0][1]).$repl.substr($v,$m[0][1]+strlen($m[0][0]));
    }
  }
  for($i=0;$i<count($ru);$i++) {
    switch($ru[$i]) {
      case 'append':
        $x=$ru[++$i];
        $chunks[$x][]=$data;
        break;
      case 'change':
        $out=$data;
        break;
      case 'delete':
        unset($regex[$data]);
        break;
      case 'erase':
        $out="";
        break;
      case 'global':
        regex_line($data);
        break;
      case 'local':
        $q=$regex_all;
        $x=regex_line($data);
        $regex[$x]=$regex_all[$x];
        $regex_all=$q;
        break;
      case 'overwrite':
        $x=$ru[++$i];
        $chunks[$x]=array($data);
        break;
    }
  }
  return $out;
}

function regex_select($n) {
  global $regex_all,$regex;
  $regex=array();
  foreach($regex_all as $k=>$v) {
    $q=strtr(preg_quote($v[0][0],'/'),array('\*'=>'.*','\?'=>'.?'));
    $v[0]=array_slice($v[0],1);
    if(preg_match('/^'.$q.'$/',$n)) $regex[$k]=$v;
  }
}

function regex_do($t) {
  global $regex,$regex_current;
  $n=true;
  while($n) {
    $n=false;
    foreach($regex as $d) {
      $regex_current=$d;
      $n|=$t!=($t=preg_replace_callback(chr(7).$d[1].chr(7),regex_callback,$t));
    }
  }
  return $t;
}

function check_filename($x) {
  if(!strlen(trim($x))) die("Bad filename: $x\n");
  if($x[0]=='/' || $x[0]=='.') die("Bad filename: $x\n");
  if(strpos($x,'//')) die("Bad filename: $x\n");
  if(strpos($x,'/.')) die("Bad filename: $x\n");
  if(!preg_match('|^[A-Za-z_0-9./-]+$|',$x)) die("Bad filename: $x\n");
}

function chunk_tangle($data) {
  global $chunks,$config_line_end;
  if(is_array($data)) $data=implode($config_line_end,$data);
  $off=0;
  while(preg_match('/\x01([a-z]+)\x02(.*?)\x03/',$data,$m,PREG_OFFSET_CAPTURE,$off)) {
    $repl="";
    switch($m[1][0]) {
      case 'chr':
        if($m[2][0][0]=='x') {
          $repl=chr(hexdec(substr($m[2][0],1)));
        } else {
          $repl=chr((int)($m[2][0]));
        }
        break;
      case 'use':
        $repl=chunk_tangle($chunks[$m[2][0]]);
        break;
    }
    $data=substr($data,0,$m[0][1]).$repl.substr($data,$m[0][1]+strlen($m[0][0]));
    $off=$m[0][1]+strlen($repl);
  }
  return regex_do($data);
}

function do_download_files($data) {
  global $chunks,$files,$filetypes,$filelist;
  $chunks=array();
  $files=array();
  $filetypes=array();
  $cur=array();
  foreach($data as $v) {
    if(!preg_match('/^\x01(.*?)\x02(.*?)\x03 *$/',$v,$m)) $m=array(,,);
    switch($m[1]) {
      case 'def':
        if(!isset($chunks[$m[2]])) $chunks[$m[2]]=array();
        $cur=&$chunks[$m[2]];
        break;
      case 'file':
        check_filename($m[2]);
        $filetypes[$m[2]]=0;
        if(!isset($files[$m[2]])) $files[$m[2]]=array();
        $cur=&$files[$m[2]];
        break;
      case 'get':
        $x=explode('|',$m[2]);
        check_filename($x[0]);
        copy($x[1],$x[0]);
        break;
      case 'hex':
        check_filename($m[2]);
        $filetypes[$m[2]]=1;
        if(!isset($files[$m[2]])) $files[$m[2]]=array();
        $cur=&$files[$m[2]];
        break;
      default:
        $cur[]=$v;
    }
  }
  if(!count($filelist)) $filelist=array_keys($files);
  foreach($filelist as $n) {
    $fp=fopen($n,"w");
    regex_select($n);
    $d=chunk_tangle($files[$n]);
    if($filetypes[$n]==0) {
      fwrite($fp,$d,strlen($d));
    } else if($filetypes[$n]==1) {
      fwrite($fp,pack('H',preg_replace('/[^0-9A-Fa-f]/',,$d)));
    }
    fclose($fp);
  }
}

if($argc<3) {
  die("usage: dottyweb <mode> <wiki>/<page> [<file...>]\n"
     ."modes: 'l'=list files, 'x'=receive files\n"
     ."alt. usage: dottyweb c <src> <dest>\n");
}

if($argv[1]=='c') {
  $schemes=array(
    'ftp','gopher','http','https','irc','mailto','news',
  );
  $data=file($argv[2]);
  $fp=fopen($argv[3],"w");
  fwrite($fp,'{{.file|'.$argv[2].'}}'.$config_line_end);
  foreach($data as $x) {
    $x=trim($x,"\0\r\n");
    $x=strtr($x,array(
      '&'=>'&amp;',
      '<'=>'&lt;',
      '>'=>'&gt;',
      '[['=>'[&#x5B;',
      ']]'=>']&#x5D;',
      '{{'=>'&#x7B;{',
      '}}'=>'&#x7D;}',
    ));
    $x=preg_replace('/('.implode('|',$schemes).')\:/','$1&#x3A;',$x);
    fwrite($fp,' '.$x.$config_line_end);
  }
  fclose($fp);
  die();
}

$i=(int)strpos($argv[2],'/');
$wiki=substr($argv[2],0,$i);
$name=substr($argv[2],$i+1);
$data=get_page($wiki,$name);
$filelist=array_slice($argv,3);

switch($argv[1]) {
  case 'l':
    foreach($data as $v) {
      if(substr($v,0,6)=="\x01file\x02") echo trim(substr($v,6),"\x03")."\n";
    }
    break;
  case 'x':
    do_download_files($data);
    break;
  default:
    die("Invalid mode\n");
    break;
}