pw
ruby
HTTP.rb
# coding: utf-8
class WebResource
  module HTTP
    include URIs
    def self.call env
      return [405,{},[]] unless %w{HEAD GET}.member? env['REQUEST_METHOD']
      rawpath = env['REQUEST_PATH'].utf8.gsub /[\/]+/, '/'
      path = Pathname.new(rawpath).expand_path.to_s        # evaluate path
      path += '/' if path[-1] != '/' && rawpath[-1] == '/' # preserve trailing-slash
      path.R.environment(env).send env['REQUEST_METHOD']
    rescue Exception => x
      [500,{'Content-Type'=>'text/plain'},[[x.class,x.message,x.backtrace].join("\n")]]
    end
    def environment env=nil; env ? (@r = env; self) : @r end
    def HEAD; self.GET.do{|s,h,b|[s,h,[]]} end
    def GET
      @r[:Response] = {}
      @r[:Links] = {}
      parts = path[1..-1].split '/'
      firstPart = parts[0] || ''
      return fileResponse if node.file?
      return (chronoDir parts) if firstPart.match(/^(y(ear)?|m(onth)?|d(ay)?|h(our)?)$/i)
      return [302,{'Location' => path+'/'+qs},[]] if node.directory? && path[-1] != '/'
      dp = []
      dp.push parts.shift.to_i while parts[0] && parts[0].match(/^[0-9]+$/)
      n = nil; p = nil
      case dp.length
      when 1 # Y
        year = dp[0]
        n = '/' + (year + 1).to_s
        p = '/' + (year - 1).to_s
      when 2 # Y-m
        year = dp[0]
        m = dp[1]
        n = m >= 12 ? "/#{year + 1}/#{01}" : "/#{year}/#{'%02d' % (m + 1)}"
        p = m <=  1 ? "/#{year - 1}/#{12}" : "/#{year}/#{'%02d' % (m - 1)}"
      when 3 # Y-m-d
        day = ::Date.parse "#{dp[0]}-#{dp[1]}-#{dp[2]}" rescue nil
        if day
          p = (day-1).strftime('/%Y/%m/%d')
          n = (day+1).strftime('/%Y/%m/%d')
        end
      when 4 # Y-m-d-H
        day = ::Date.parse "#{dp[0]}-#{dp[1]}-#{dp[2]}" rescue nil
        if day
          hour = dp[3]
          p = hour <=  0 ? (day - 1).strftime('/%Y/%m/%d/23') : (day.strftime('/%Y/%m/%d/')+('%02d' % (hour-1)))
          n = hour >= 23 ? (day + 1).strftime('/%Y/%m/%d/00') : (day.strftime('/%Y/%m/%d/')+('%02d' % (hour+1)))
        end
      end
      sl = parts.empty? ? '' : (path[-1] == '/' ? '/' : '')

      @r[:Links][:prev] = p + '/' + parts.join('/') + sl + qs + '#prev' if p && R[p].e
      @r[:Links][:next] = n + '/' + parts.join('/') + sl + qs + '#next' if n && R[n].e
      @r[:Links][:up] = dirname + (dirname == '/' ? '' : '/') + HTTP.qs(q.merge({'head' => ''})) + '#r' + path.sha2 unless path=='/'
      if q.has_key? 'head'
        qq = q.dup; qq.delete 'head'
        @r[:Links][:down] = path + (HTTP.qs qq)
      end

      set = selectNodes
      return notfound if !set || set.empty?
      format = selectMIME

      @r[:Response].update({'Link' => @r[:Links].map{|type,uri|"<#{uri}>; rel=#{type}"}.intersperse(', ').join}) unless @r[:Links].empty?
      @r[:Response].update({'Content-Type' => %w{text/html text/turtle}.member?(format) ? (format+'; charset=utf-8') : format,
                            'ETag' => [set.sort.map{|r|[r,r.m]}, format].join.sha2})
      entity @r, ->{
        if set.size == 1 && set[0].mime == format
          set[0] # static entity
        else # dynamic
          if format == 'text/html'
            htmlDocument load set
          elsif format == 'application/atom+xml'
            renderFeed load set
          else
            g = RDF::Graph.new
            set.map{|n|
              g.load n.toRDF.localPath,
                     :base_uri => n.stripDoc }
            g.dump (RDF::Writer.for :content_type => format).to_sym, :base_uri => self, :standard_prefixes => true
          end
        end}
    end

    # file(s) -> graph-in-tree
    def load set
      graph = RDF::Graph.new # graph
      g = {}                 # tree

      # static resources
      rdf,json = set.partition &:isRDF
      # RDF files
      rdf.map{|n|
        graph.load n.localPath, :base_uri => n}
      graph.each_triple{|s,p,o| # each triple
        s = s.to_s; p = p.to_s # subject, predicate
        o = [RDF::Node, RDF::URI, WebResource].member?(o.class) ? o.R : o.value # object
        g[s] ||= {'uri'=>s}
        g[s][p] ||= []
        g[s][p].push o unless g[s][p].member? o} # tree insert
      # optimization. skip RDF::Reader for native JSON format
      json.map{|n|
        n.transcode.do{|transcode|
          ::JSON.parse(transcode.readFile).map{|s,re| # subject
            re.map{|p,o| # predicate object(s)
              o.justArray.map{|o| # each triple
                o = o.R if o.class==Hash
                g[s] ||= {'uri'=>s}
                g[s][p] ||= []
                g[s][p].push o unless g[s][p].member? o} unless p == 'uri' }}}} # tree insert
      # storage size
      if q.has_key?('du') && path != '/'
        set.select{|d|d.node.directory?}.-([self]).map{|node|
          g[node.path+'/']||={}
          g[node.path+'/'][Size] = node.du}
      elsif (q.has_key?('f')||q.has_key?('q')||@r[:glob]) && path!='/' # search-result counts
        set.map{|r|
          bin = r.dirname + '/'
          g[bin] ||= {'uri' => bin, Type => Container}
          g[bin][Size] = 0 if !g[bin][Size] || g[bin][Size].class==Array
          g[bin][Size] += 1}
      end

      g
    end

    def chronoDir ps
      time = Time.now
      loc = time.strftime(case ps[0][0].downcase
                          when 'y'
                            '%Y'
                          when 'm'
                            '%Y/%m'
                          when 'd'
                            '%Y/%m/%d'
                          when 'h'
                            '%Y/%m/%d/%H'
                          else
                          end)
      [303,@r[:Response].update({'Location' => '/' + loc + '/' + ps[1..-1].join('/') + (qs.empty? ? '?head' : qs)}),[]]
    end

    def fileResponse
      @r[:Response].update({'Content-Type' => %w{text/html text/turtle}.member?(mime) ? (mime+'; charset=utf-8') : mime,
                            'ETag' => [m,size].join.sha2})
      @r[:Response].update({'Cache-Control' => 'no-transform'}) if mime.match /^(audio|image|video)/
      if q.has_key?('preview') && ext.match(/(mp4|mkv|png|jpg)/i)
        filePreview
      else
        entity @r
      end
    end

    def entity env, body = nil
      etags = env['HTTP_IF_NONE_MATCH'].do{|m| m.strip.split /\s*,\s*/ }
      if etags && (etags.include? env[:Response]['ETag'])
        [304, {}, []]
      else
        body = body ? body.call : self
        if body.class == WebResource # use Rack handler for static resource
          (Rack::File.new nil).serving((Rack::Request.new env),body.localPath).do{|s,h,b|
            [s,h.update(env[:Response]),b]}
        else
          [(env[:Status]||200), env[:Response], [body]]
        end
      end
    end

    def notfound; [404,{'Content-Type' => 'text/html'},[htmlDocument]] end

    # querystring -> Hash
    def q fromEnv = true
      @q ||=
        (if q = (fromEnv && @r && @r['QUERY_STRING'] || query)
         h = {}
         q.split(/&/).map{|e|
           k, v = e.split(/=/,2).map{|x|CGI.unescape x}
           h[(k||'').downcase] = v}
         h
        else
          {}
         end)
    end

    def inDoc; path == @r['REQUEST_PATH'] end

    # env -> ?querystring
    def qs; @qs ||= (@r['QUERY_STRING'] && !@r['QUERY_STRING'].empty? && ('?' + @r['QUERY_STRING']) || '') end

    # Hash -> ?querystring
    def HTTP.qs h; '?'+h.map{|k,v|k.to_s + '=' + (v ? (CGI.escape [*v][0].to_s) : '')}.intersperse("&").join('') end

  end
  include HTTP
end
2018-01-18T10:48:40+00:007452
Table.rb
# coding: utf-8
class WebResource
  module HTML

    def htmlTable graph
      (1..10).map{|i|@r[:label]["quote"+i.to_s] = true} # labels
      [:links,:images].map{|p|@r[p] = []} # link & image lists
      p = q['sort'] || Date
      direction = q.has_key?('ascending') ? :id : :reverse
      datatype = [R::Size,R::Stat+'mtime'].member?(p) ? :to_i : :to_s
      keys = graph.values.map(&:keys).flatten.uniq - InlineMeta
      keys -= VerboseMeta unless q.has_key? 'full'
      [{_: :table,
        c: [{_: :tbody,
             c: graph.values.sort_by{|s|((p=='uri' ? (s[Title]||s[Label]||s.uri) : s[p]).justArray[0]||0).send datatype}.
               send(direction).map{|r|
               (r.R.environment(@r).tableRow p,direction,keys)}},
            {_: :tr, c: [From,Type,'uri',*keys,DC+'cache',Date,Size].map{|k| # header row
               selection = p == k
               q_ = q.merge({'sort' => k})
               if direction == :id # direction toggle
                 q_.delete 'ascending'
               else
                 q_['ascending'] = ''
               end
               href = CGI.escapeHTML HTTP.qs q_
               {_: :th, property: k, class: selection ? 'selected' : '',
                c: [{_: :a,href: href,class: Icons[k]||'',c: Icons[k] ? '' : (k.R.fragment||k.R.basename)},
                    (selection ? {_: :link, rel: :sort, href: href} : '')]}}}]},
       {_: :style, c: "[property=\"#{p}\"] {border-color:#444;border-style: solid; border-width: 0 0 .08em 0}"}]
    end

    def tableCellTitle
      self[Title].compact.map{|t|
        meta = {id: inDoc ? fragment : 'r'+sha2,
                class: :title,
                label: CGI.escapeHTML(t.to_s)}
        name = if a SIOC+'Tweet'
                 parts[1]
               elsif a SIOC+'ChatLog'
                 CGI.unescape(basename).split('#')[0]
               elsif inDoc && !fragment
                 'this'
               elsif uri[-1] == '/'
                 'dirname'
               end
        if name
          meta.update({name: name})
          @r[:label][name] = true
        end
        link = uri.R
        link += '?head' if a Container
        link.data meta}.intersperse(' ')
    end

    def tableCellLabels
      self[Label].compact.map{|v|
        {_: :a, class: :label, href: uri,
         c: (CGI.escapeHTML (v.respond_to?(:uri) ? (v.R.fragment || v.R.basename) : v))}}.intersperse(' ')
    end

    def tableCellAbstract
      [self[Abstract],
       self[DC+'note'].map{|n|
         {_: :a, href: uri, class: :notes, c: CGI.escapeHTML(n.to_s)}}.intersperse(' ')]
    end

    def tableCellContent
      self[Content].map{|c|
        if (a SIOC+'SourceCode') || (a SIOC+'MailMessage')
          {_: :pre, c: c}
        elsif a SIOC+'InstantMessage'
          {_: :span, class: :monospace, c: c}
        else
          c
        end
      }.intersperse(' ') unless q.has_key?('head')
    end

    def tableCellBody
      [tableCellLabels,
       tableCellTitle,
       tableCellAbstract,
       tableCellLinks,
       tableCellContent,
       tableCellPhoto,
       tableCellVideo]
    end

    def tableCellTypes
      self[Type].uniq.select{|t|t.respond_to? :uri}.map{|t|
        {_: :a, href: uri, c: Icons[t.uri] ? '' : (t.R.fragment||t.R.basename), class: Icons[t.uri]}}
    end

    def tableCellLinks
      links = LinkPred.map{|p|self[p]}.flatten.compact.map(&:R).select{|l|!@r[:links].member? l}.sort_by &:tld
      {_: :table, class: :links,
       c: links.group_by(&:host).map{|host,links|
         tld = links[0] && links[0].tld || 'none'
         traverse = links.size <= 16
         @r[:label][tld] = true
         {_: :tr,
          c: [{_: :td, class: :path, colspan: host ? 1 : 2,
               c: links.map{|link|
                 @r[:links].push link
                 [link.data((traverse ? {id: 'link'+SecureRandom.hex(8), name: tld} : {})),' ']}},
              ({_: :td, class: :host, c: R['//'+host]} if host)
             ]}}} unless links.empty?
    end

    def tableCellPhoto
      # scan RDF for not-yet-shown resourcs
      images = []
      images.push self if types.member?(Image) # subject of triple
      self[Image].do{|i|images.concat i}        # object of triple
      images.map(&:R).select{|i|!@r[:images].member? i}.map{|img| # unvisited
        @r[:images].push img                     # visit
        {_: :a, class: :thumb, href: uri,       # render
         c: [{_: :img, class: :thumb, src: if !img.host || img.host == @r['HTTP_HOST'] # thumbnail if locally-hosted
               img.path + '?preview'
             else
               img.uri
              end},'<br>',
             {_: :span, class: :host, c: img.host},
             {_: :span, class: :notes, c: (CGI.escapeHTML img.path)}]}}
    end

    def tableCellVideo
      self[Video].map(&:R).map{|video|
        if video.match /youtu/
          id = video.q(false)['v'] || video.parts[-1]
          {_: :iframe, width: 560, height: 315, src: "https://www.youtube.com/embed/#{id}", frameborder: 0, gesture: "media", allow: "encrypted-media", allowfullscreen: :true}
        else
          {class: :video,
           c: [{_: :video, src: video.uri, controls: :true}, '<br>',
               {_: :span, class: :notes, c: video.basename}]}
        end}
    end

    def tableRow sort,direction,keys
      hidden = q.has_key?('head') && self[Title].empty? && self[Abstract].empty?
      date = self[Date].sort[0]
      datePath = '/' + date[0..13].gsub(/[-T:]/,'/') if date
      fromTo = -> {
        [[Creator,SIOC+'addressed_to'].map{|edge|
               self[edge].map{|v|
                 if v.respond_to?(:uri)
                   v = v.R
                   id = SecureRandom.hex 8
                   if a SIOC+'MailMessage' # messages*address*month
                     @r[:label][v.basename] = true
                     R[v.path + '?head#r' + sha2].data({id: 'address_'+id, label: v.basename, name: v.basename}) if v.path
                   elsif a SIOC+'Tweet'
                     if edge == Creator  # tweets*author*day
                       @r[:label][v.basename] = true
                       R[datePath[0..-4] + '*/*twitter.com.'+v.basename+'*#r' + sha2].data({name: v.basename, label: v.basename})
                     else # tweets*hour
                       R[datePath + '*twitter*#r' + sha2].data({label: '&#x1F425;'})
                     end
                   elsif a SIOC+'InstantMessage'
                     if edge==From
                       nick = v.fragment
                       name = nick.gsub(/[_\-#@]+/,'')
                       @r[:label][name] = true
                       ((dir||self)+'?q='+nick).data({name: name, label: nick})
                     elsif edge==To
                       v.data({label: CGI.unescape(basename).split('#')[-1]})
                     end
                   elsif (a SIOC+'BlogPost') && edge==To
                     name = 'blog_'+(v.host||'').gsub('.','')
                     @r[:label][name] = true
                     R[datePath ? (datePath[0..-4] + '*/*' + (v.host||'') + '*#r' + sha2) : ('//'+host)].data({id: 'post'+id, label: v.host, name: name})
                   elsif (a SIOC+'ChatLog') && edge==To
                     name = v.basename[0..-2]
                     @r[:label][name] = true
                     v.data({name: name})
                   else
                     v
                   end
                 else
                   {_: :span, c: (CGI.escapeHTML v.to_s)}
                 end}.intersperse(' ')}.map{|a|a.empty? ? nil : a}.compact.intersperse('&rarr;'),
             self[SIOC+'user_agent'].map{|a|['<br>',{_: :span, class: :notes, c: a}]}]}

      cacheLink = -> {
        self[DC+'cache'].map{|c|
          {_: :a, id: 'c'+sha2, href: c.uri, class: :chain}}.intersperse(' ')}

      timeStamp = -> {{_: :a, class: :date, href: datePath + '#r' + sha2, c: date} if datePath}

      size = -> {
        sum = 0
        self[Size].map{|v|sum += v.to_i}
        sum == 0 ? '' : sum}

      hidden ? '' : [{_: :tr,
                      c: [{_: :td, class: :fromTo, c: fromTo[]},
                          {_: :td, class: :typeTag, c: tableCellTypes},
                          {_: :td, c: tableCellBody},
                          keys.map{|k|
                            {_: :td, property: k,
                             c: self[k].map{|v|
                               v.respond_to?(:uri) ? v.R : CGI.escapeHTML(v.to_s)}.intersperse(' ')}},
                          [cacheLink, timeStamp, size].map{|cell| {_: :td, c: cell[]}}]},"\n"]
    end
  end
  module Webize
    def triplrCSV d
      ns    = W3 + 'ns/csv#'
      lines = CSV.read localPath
      lines[0].do{|fields| # header-row
        yield uri, Type, R[ns+'Table']
        yield uri, ns+'rowCount', lines.size
        lines[1..-1].each_with_index{|row,line|
          row.each_with_index{|field,i|
            id = uri + '#row:' + line.to_s
            yield id, fields[i], field
            yield id, Type, R[ns+'Row']}}}
    end
  end
end
2018-01-18T10:48:40+00:009036
ruby2018-01-18T10:48:40+00:0020
POSIX.rb
class Pathname
  def R; R::POSIX.path to_s.utf8 end
end
class WebResource
  module POSIX

    # link fs-nodes
    def ln n
      #puts "ln #{path} #{n.path}"
      FileUtils.ln   node.expand_path, n.node.expand_path
    end
    #TODO relative_path_from so db (fs tree) can support multiple concurrent mountpoints
    # symlink fs-nodes
    def ln_s n
      #puts "ln -s #{path} #{n.path}"
      FileUtils.ln_s node.expand_path, n.node.expand_path
    end

    def link n
      send LinkMethod, n unless n.exist?
    rescue Exception => e
      puts e,e.class,e.message
    end

    # read file at location of POSIX path-map
    def readFile; File.open(localPath).read end

    # write file at location of POSIX path-map
    def writeFile o; dir.mkdir; File.open(localPath,'w'){|f|f << o}; self end

    # touch mapped node
    def touch
      dir.mkdir
      FileUtils.touch localPath
    end

    # erase mapped node
    def delete
      node.delete
    end

    # contained children excepting hidden nodes
    def children; node.children.delete_if{|f|f.basename.to_s.index('.')==0}.map &:R end

    # dirname of path component, mapped to WebResource
    def dir; dirname.R if path end

    # dirname of path component
    def dirname; File.dirname path if path end

    # storage-space usage
    def du; `du -s #{sh}| cut -f 1`.chomp.to_i end

    # FIND on path component
    def find p
      (p && !p.empty?) ? `find #{sh} -ipath #{('*'+p+'*').sh} | head -n 2048`.lines.map{|pth|POSIX.path pth.chomp} : []
    end

    # GLOB on path component
    def glob; (Pathname.glob localPath).map &:R end

    # existence check on mapped fs-node
    def exist?; node.exist? end
    def symlink?; node.symlink? end
    alias_method :e, :exist?

    # create container
    def mkdir; FileUtils.mkdir_p localPath unless exist?; self end

    # size of mapped node
    def size; node.size rescue 0 end

    # mtime of mapped node
    def mtime; node.stat.mtime end
    alias_method :m, :mtime

    # storage path -> URI
    def self.path p; p.sub(/^\./,'').gsub(' ','%20').gsub('#','%23').R end

    # URI -> storage path
    def localPath; @path ||= (URI.unescape(path[0]=='/' ? '.' + path : path)) end

    # Pathname object
    def node; @node ||= (Pathname.new localPath) end

    # shell-escaped path
    def shellPath; localPath.utf8.sh end
    alias_method :sh, :shellPath

    # '/'-separated parts of path component
    def parts; path ? path.split('/') : [] end

    # basename of path component
    def basename; File.basename (path||'') end

    # strip extension of native document formats
    def stripDoc; R[uri.sub /\.(bu|e|html|json|log|md|msg|ttl|txt|u)$/,''] end

    # name-extension of path component
    def ext; (File.extname uri)[1..-1] || '' end

    # TLD of host component
    def tld; host && host.split('.')[-1] || '' end

    # SHA2 hash of URI as string
    def sha2; to_s.sha2 end

    # env -> file(s)
    def selectNodes
      (if node.directory?
       if q.has_key?('f') && path!='/' # FIND
         found = find q['f']
         q['head'] = true if found.size > 127
         found
       elsif q.has_key?('q') && path!='/' # GREP
         grep q['q']
       else # LS
         if uri[-1] == '/' # inside container
           index = (self+'index.html').glob # static index
           !index.empty? && qs.empty? && index || [self, children]
         else # outside container
           @r[:Links][:down] = path + '/' + qs
           self
         end
       end
      else # GLOB
        @r[:glob] = match /[\*\{\[]/
        [self,
         (@r[:glob] ? self : (self+'.*')).glob,
         join('index.ttl').R]
       end).justArray.flatten.compact.uniq.select &:exist?
    end

    # pattern -> file(s)
    def grep q
      args = q.shellsplit
      case args.size
      when 0
        return []
      when 2
        cmd = "grep -rilZ #{args[0].sh} #{sh} | xargs -0 grep -il #{args[1].sh}"
      when 3
        cmd = "grep -rilZ #{args[0].sh} #{sh} | xargs -0 grep -ilZ #{args[1].sh} | xargs -0 grep -il #{args[2].sh}"
      when 4
        cmd = "grep -rilZ #{args[0].sh} #{sh} | xargs -0 grep -ilZ #{args[1].sh} | xargs -0 grep -ilZ #{args[2].sh} | xargs -0 grep -il #{args[3].sh}"
      else
        pattern = args.join '.*'
        cmd = "grep -ril #{pattern.sh} #{sh}"
      end
      `#{cmd} | head -n 1024`.lines.map{|path| POSIX.path path.chomp}
    end
  end
  module Webize

    # emit RDF of file-metadata
    def triplrFile
      s = path
      size.do{|sz|yield s, Size, sz}
      yield s, Title, basename
      mtime.do{|mt|
        yield s, Mtime, mt.to_i
        yield s, Date, mt.iso8601}
    end

    # emit RDF of container-metadata
    def triplrContainer
      s = path
      s = s + '/' unless s[-1] == '/'
      yield s, Type, R[Container]
      yield s, Size, children.size
      yield s, Title, basename
      mtime.do{|mt|
        yield s, Mtime, mt.to_i
        yield s, Date, mt.iso8601}
    end

  end

  include POSIX

  module POSIX
    LinkMethod = begin
                   file = '.cache/link'.R
                   link = '.cache/link_'.R
                   file.touch unless file.exist?
                   link.delete if link.exist?
                   file.ln link
                   :ln
                 rescue Exception => e
                   :ln_s
                 end
  end
  module HTML
    # TODO change multi-arg handling (|| to &&)
    def htmlGrep graph, q
      wordIndex = {}
      args = q.shellsplit
      args.each_with_index{|arg,i| wordIndex[arg] = i }
      pattern = /(#{args.join '|'})/i
      # find matches
      graph.map{|u,r|
        keep = r.to_s.match(pattern) || r[Type] == Container
        graph.delete u unless keep}
      # highlight matches
      graph.values.map{|r|
        (r[Content]||r[Abstract]).justArray.map(&:lines).flatten.grep(pattern).do{|lines|
          r[Abstract] = lines[0..5].map{|l|
            l.gsub(/<[^>]+>/,'')[0..512].gsub(pattern){|g| # capture match
              HTML.render({_: :span, class: "w#{wordIndex[g.downcase]}", c: g}) # wrap match
            }} if lines.size > 0 }}
      # highlighting CSS
      graph['#abstracts'] = {Abstract => {_: :style, c: wordIndex.values.map{|i|".w#{i} {background-color: #{'#%06x' % (rand 16777216)}; color: white}\n"}}}
    end
  end
end
2018-01-18T09:37:06+00:006298
Tree.rb
class WebResource
  module HTML
    # container tree-structure in HTML
    def htmlTree graph
      q = qs.empty? ? '?head' : qs

      # construct tree
      tree = {}
      graph.keys.select{|k|!k.R.host && k[-1]=='/'}.map{|uri| # local containers
        c = tree # start at root
        uri.R.parts.map{|name| # path instructions
          c = c[name] ||= {}}} # create node and jump cursor

      # renderer
      render = -> t,path='' {
        nodes = t.keys.sort - %w{msg}
        label = 'p'+path.sha2 if nodes.size > 1
        @r[:label][label] = true if label
        tabled = nodes.size < 36
        sizes = []

        # scale nodes
        nodes.map{|name|
          uri = path + name + '/'
          graph[uri].do{|r|
            r[Size].justArray.map{|sz|
              sizes.push sz}}} if label
        maxSize = sizes.max.to_f

        # output
        {_: tabled ? :table : :div, class: :tree, c: [
           {_: tabled ? :tr : :div, class: :nodes, c: nodes.map{|name| # nodes
              this = path + name + '/' # path
              s = graph[this].do{|r|r[Size].justArray[0]} # size
              named = !name.empty?
              scaled = sizes.size > 0 && s && tabled
              height = scaled && (s / maxSize) # scale
              {_: tabled ? :td : :div, style: scaled ? 'height: 8em' : '',
               c: named ? {_: :a, id: 't'+this.gsub(/[^a-zA-Z0-9]/,'_'), href: this + q, name: label, style: scaled ? "height:#{height * 100.0}%" : '',
                           c: CGI.escapeHTML(URI.unescape(name)[0..24])} : ''}}.intersperse("\n")},"\n",
           ({_: tabled ? :tr : :div, c: nodes.map{|k| # children
              {_: tabled ? :td : :div, c: (render[t[k], path+k+'/'] if t[k].size > 0)}}.intersperse("\n")} unless !nodes.find{|n|t[n].size > 0})]}}

      # render
      render[tree]
    end
  end
end
2018-01-18T09:12:35+00:001849
ww.rb
# coding: utf-8
%w{cgi csv date digest/sha2 dimensions fileutils icalendar json linkeddata mail nokogiri open-uri pathname rack rdf redcarpet shellwords}.map{|r|require r}
%w{URI MIME HTTP HTML POSIX Feed JSON Text Mail Table Tree Calendar Chat Icons Image}.map{|i|require_relative i}
R = WebResource
class Array
  def justArray; self end
  def intersperse i; inject([]){|a,b|a << b << i}[0..-2] end
end
class FalseClass
  def do; self end
end
class NilClass
  def justArray; [] end
  def do; self end
end
class Object
  def justArray; [self] end
  def id; self end
  def do; yield self end
  def to_time; [Time, DateTime].member?(self.class) ? self : Time.parse(self) end
end
2018-01-18T09:07:45+00:00677
Image.rb
class WebResource
  module Webize
    #TODO popular image host translation to RDF
    def triplrImage &f
      yield uri, Type, R[Image]
      w,h = Dimensions.dimensions localPath
      yield uri, Stat+'width', w
      yield uri, Stat+'height', h
      triplrFile &f
    end
    def ig
      open(localPath).readlines.map(&:chomp).map{|ig|
        R[Instagram+ig].indexInstagram}
    end
  end
end
2018-01-18T06:53:22+00:00399
Feed.rb
# coding: utf-8
class WebResource
  module Feed
    include URIs

    def feeds; puts (nokogiri.css 'link[rel=alternate]').map{|u|join u.attr :href} end

    def fetchFeed
      head = {} # request header
      cache = R['/.cache/'+uri.sha2+'/'] # storage
      etag = cache + 'etag'      # cache etag URI
      priorEtag = nil            # cache etag value
      mtime = cache + 'mtime'    # cache mtime URI
      priorMtime = nil           # cache mtime value
      body = cache + 'body.atom' # cache body URI
      if etag.e
        priorEtag = etag.readFile
        head["If-None-Match"] = priorEtag unless priorEtag.empty?
      elsif mtime.e
        priorMtime = mtime.readFile.to_time
        head["If-Modified-Since"] = priorMtime.httpdate
      end
      begin # conditional GET
        open(uri, head) do |response|
          curEtag = response.meta['etag']
          curMtime = response.last_modified || Time.now rescue Time.now
          etag.writeFile curEtag if curEtag && !curEtag.empty? && curEtag != priorEtag # new ETag value
          mtime.writeFile curMtime.iso8601 if curMtime != priorMtime # new Last-Modified value
          resp = response.read
          unless body.e && body.readFile == resp
            body.writeFile resp # new cached body
            ('file:'+body.localPath).R.indexFeed :format => :feed, :base_uri => uri # run indexer
          end
        end
      rescue OpenURI::HTTPError => error
        msg = error.message
        puts [uri,msg].join("\t") unless msg.match(/304/)
      end
    rescue Exception => e
      puts uri, e.class, e.message
    end
    def fetchFeeds; open(localPath).readlines.map(&:chomp).map(&:R).map(&:fetchFeed) end

    alias_method :getFeed, :fetchFeed

    def indexFeed options = {}
      # TODO runtime-alternate storage locations for (all comments on post in subdir, lkml in hourdir instead of defaultmonth-dir)
      g = RDF::Repository.load self, options
      g.each_graph.map{|graph|
        graph.query(RDF::Query::Pattern.new(:s,R[R::Date],:o)).first_value.do{|t| # find timestamp
          time = t.gsub(/[-T]/,'/').sub(':','/').sub /(.00.00|Z)$/, ''
          slug = (graph.name.to_s.sub(/https?:\/\//,'.').gsub(/[\W_]/,'..').sub(/\d{12,}/,'')+'.').gsub(/\.+/,'.')[0..127].sub(/\.$/,'')
          doc =  R["/#{time}#{slug}.ttl"]
          unless doc.e
            doc.dir.mkdir
            cacheBase = doc.stripDoc
            graph << RDF::Statement.new(graph.name, R[DC+'cache'], cacheBase)
            RDF::Writer.open(doc.localPath){|f|f << graph}
            puts cacheBase
          end
          true}}
      self
    rescue Exception => e
      puts uri, e.class, e.message
    end

    class Format < RDF::Format
      content_type     'application/atom+xml', :extension => :atom
      content_encoding 'utf-8'
      reader { WebResource::Feed::Reader }
    end

    class Reader < RDF::Reader
      include URIs
      format Format

      def initialize(input = $stdin, options = {}, &block)
        @doc = (input.respond_to?(:read) ? input : StringIO.new(input.to_s)).read.to_utf8
        @base = (options[:base_uri] || '/').R
        @host = @base.host
        if block_given?
          case block.arity
          when 0 then instance_eval(&block)
          else block.call(self)
          end
        end
        nil
      end

      def each_triple &block; each_statement{|s| block.call *s.to_triple} end

      def each_statement &fn # triples flow (left ← right)
        scanContent(:normalizeDates, :normalizePredicates,:rawTriples){|s,p,o|
          fn.call RDF::Statement.new(s.R, p.R,
                                     (o.class == WebResource || o.class == RDF::URI) ? o : (l = RDF::Literal (if p == Content
                                                                                                    R::HTML.strip o
                                                                                                   else
                                                                                                     o.gsub(/<[^>]*>/,' ')
                                                                                                    end)
                                                                                  l.datatype=RDF.XMLLiteral if p == Content
                                                                                  l), :graph_name => s.R)}
      end

      def scanContent *f
        send(*f){|s,p,o|
          if p==Content && o.class==String
            content = Nokogiri::HTML.fragment o

            content.css('a').map{|a|
              (a.attr 'href').do{|href|
                link = s.R.join href
                re = link.R
                a.set_attribute 'href', link
                if %w{gif jpeg jpg png webp}.member? re.ext.downcase
                  yield s, Image, re
                elsif re.host && re.host.match(/youtu/)
                  yield s, Video, re
                elsif s.R != re
                  yield s, DC+'link', re
                end
              }}

            content.css('img').map{|i|
              (i.attr 'src').do{|src|
                yield s, Image, src.R}}

            content.css('iframe').map{|i|
              (i.attr 'src').do{|src|
                src = src.R
                if src.host && src.host.match(/youtu/)
                  id = src.parts[-1]
                  yield s, Video, R['https://www.youtube.com/watch?v='+id]
                end}}

            yield s, p, content.to_xhtml
          else
            yield s, p, o
          end}
      end

      def normalizePredicates *f
        send(*f){|s,p,o|
          yield s,
                {Atom+'content' => Content,
                 Atom+'displaycategories' => Label,
                 Atom+'enclosure' => SIOC+'attachment',
                 Atom+'link' => DC+'link',
                 Atom+'summary' => Abstract,
                 Atom+'title' => Title,
                 DCe+'subject' => Title,
                 DCe+'type' => Type,
                 Media+'title' => Title,
                 Media+'description' => Abstract,
                 Media+'community' => Content,
                 Podcast+'author' => Creator,
                 Podcast+'keywords' => Label,
                 Podcast+'subtitle' => Title,
                 YouTube+'videoId' => Label,
                 YouTube+'channelId' => SIOC+'user_agent',
                 RSS+'category' => Label,
                 RSS+'description' => Content,
                 RSS+'encoded' => Content,
                 RSS+'modules/content/encoded' => Content,
                 RSS+'modules/slash/comments' => SIOC+'num_replies',
                 RSS+'source' => DC+'source',
                 RSS+'title' => Title,
                }[p]||p, o }
      end

      def normalizeDates *f
        send(*f){|s,p,o|
          yield *({'CreationDate' => true,
                   'Date' => true,
                   RSS+'pubDate' => true,
                   Date => true,
                   DCe+'date' => true,
                   Atom+'published' => true,
                   Atom+'updated' => true
                  }[p] ?
                    [s,Date,Time.parse(o).utc.iso8601] : [s,p,o])}
      end

      def rawTriples

        # identifiers
        reRDF = /about=["']?([^'">\s]+)/              # RDF @about
        reLink = /<link>([^<]+)/                      # <link> element
        reLinkCData = /<link><\!\[CDATA\[([^\]]+)/    # <link> CDATA block
        reLinkHref = /<link[^>]+rel=["']?alternate["']?[^>]+href=["']?([^'">\s]+)/ # <link> @href @rel=alternate
        reLinkRel = /<link[^>]+href=["']?([^'">\s]+)/ # <link> @href
        reId = /<(?:gu)?id[^>]*>([^<]+)/              # <id> element
        reURL = /\A(\/|http)[\S]+\Z/                  # HTTP URI

        # elements
        reHead = /<(rdf|rss|feed)([^>]+)/i
        reXMLns = /xmlns:?([a-z0-9]+)?=["']?([^'">\s]+)/
        reItem = %r{<(?<ns>rss:|atom:)?(?<tag>item|entry)(?<attrs>[\s][^>]*)?>(?<inner>.*?)</\k<ns>?\k<tag>>}mi
        reElement = %r{<([a-z0-9]+:)?([a-z]+)([\s][^>]*)?>(.*?)</\1?\2>}mi
        reGroup = /<\/?media:group>/i
        reMedia = %r{<(link|enclosure|media)([^>]+)>}mi
        reSrc = /(href|url|src)=['"]?([^'">\s]+)/
        reRel = /rel=['"]?([^'">\s]+)/

        # XML name-space
        x = {}
        head = @doc.match(reHead)
        head && head[2] && head[2].scan(reXMLns){|m|
          prefix = m[0]
          base = m[1]
          base = base + '#' unless %w{/ #}.member? base [-1]
          x[prefix] = base}

        # scan items
        @doc.scan(reItem){|m|
          attrs = m[2]
          inner = m[3]
          # identifier search. prioritize resolvable URIs
          u = (attrs.do{|a|a.match(reRDF)} || inner.match(reLink) || inner.match(reLinkCData) || inner.match(reLinkHref) || inner.match(reLinkRel) || inner.match(reId)).do{|s|s[1]}
          if u # identifier match
            u = @base.join(u).to_s unless u.match /^http/
            resource = u.R

            yield u, Type, R[SIOC+'BlogPost']
            blogs = [resource.join('/')]
            blogs.push @base.join('/') if @host && @host != resource.host
            blogs.map{|blog|
              yield u, R::To, blog}

            inner.scan(reMedia){|e|
              e[1].match(reSrc).do{|url|
                rel = e[1].match reRel
                rel = rel ? rel[1] : 'link'
                o = (@base.join url[2]).R
                p = case o.ext.downcase
                    when 'jpg'
                      R::Image
                    when 'jpeg'
                      R::Image
                    when 'png'
                      R::Image
                    else
                      R::Atom + rel
                    end
                yield u,p,o unless resource == o}}

            inner.gsub(reGroup,'').scan(reElement){|e|
              p = (x[e[0] && e[0].chop]||R::RSS) + e[1] # namespaced attribute-names
              if [Atom+'id',RSS+'link',RSS+'guid',Atom+'link'].member? p
               # element used in subject-URI search
              elsif [Atom+'author', RSS+'author', RSS+'creator', DCe+'creator'].member? p
                crs = [] # creators
                uri = e[3].match /<uri>([^<]+)</
                crs.push uri[1].R if uri
                name = e[3].match /<name>([^<]+)</
                crs.push name[1] if name
                unless name || uri
                  crs.push e[3].do{|o|
                    o.match(reURL) ? o.R : o }
                end
                crs.map{|cr|
                  yield u, Creator, cr
                }
              else # basic element
                yield u,p,e[3].do{|o|
                  case o
                  when /^\s*<\!\[CDATA/m
                    o.sub /^\s*<\!\[CDATA\[(.*?)\]\]>\s*$/m,'\1'
                  when /</m
                    o
                  else
                    CGI.unescapeHTML o
                  end
                }.do{|o|o.match(/\A(\/|http)[\S]+\Z/) ? o.R : o }
              end
            }
          end}
      end
    end

    def renderFeed graph
      HTML.render ['<?xml version="1.0" encoding="utf-8"?>',
                   {_: :feed,xmlns: 'http://www.w3.org/2005/Atom',
                    c: [{_: :id, c: uri},
                        {_: :title, c: uri},
                        {_: :link, rel: :self, href: uri},
                        {_: :updated, c: Time.now.iso8601},
                        graph.map{|u,d|
                          {_: :entry,
                           c: [{_: :id, c: u}, {_: :link, href: u},
                               d[Date].do{|d|   {_: :updated, c: d[0]}},
                               d[Title].do{|t|  {_: :title,   c: t}},
                               d[Creator].do{|c|{_: :author,  c: c[0]}},
                               {_: :content, type: :xhtml,
                                c: {xmlns:"http://www.w3.org/1999/xhtml",
                                    c: d[Content]}}]}}]}]
    end
  end
  include Feed
  module Webize
    def triplrOPML
      Nokogiri::HTML.fragment(readFile).css('outline[type="rss"]').map{|t|
        s = t.attr 'xmlurl'
        yield s, Type, R[SIOC+'Feed']
        yield s, Title, t.attr('title')
      }
    end
  end
end
2018-01-18T06:53:22+00:0012236
Calendar.rb
class WebResource
  module Webize
# TODO ICal
    def triplrCalendar
      cal_file = File.open localPath
      cals = Icalendar::Calendar.parse(cal_file)
      cal = cals.first
      puts cal
      event = cal.events.first
      puts event
    end
  end
end
2018-01-18T06:53:22+00:00259
Text.rb
# coding: utf-8
class WebResource
  module Webize

    def triplrArchive &f; yield uri, Type, R[Stat+'Archive']; triplrFile &f end
    def triplrAudio &f;   yield uri, Type, R[Sound]; triplrFile &f end
    def triplrDataFile &f; yield uri, Type, R[Stat+'DataFile']; triplrFile &f end

    def triplrBat &f; yield uri, Type, R[SIOC+'SourceCode']; yield uri, Content, `pygmentize -l batch -f html #{sh}`; triplrFile &f end
    def triplrDocker &f; yield uri, Type, R[SIOC+'SourceCode']; yield uri, Content, `pygmentize -l docker -f html #{sh}`; triplrFile &f end
    def triplrIni &f; yield uri, Type, R[SIOC+'SourceCode']; yield uri, Content, `pygmentize -l ini -f html #{sh}`; triplrFile &f end
    def triplrMakefile &f; yield uri, Type, R[SIOC+'SourceCode']; yield uri, Content, `pygmentize -l make -f html #{sh}`; triplrFile &f end
    def triplrLisp &f; yield uri, Type, R[SIOC+'SourceCode']; yield uri, Content, `pygmentize -l lisp -f html #{sh}`; triplrFile &f end
    def triplrRuby &f; yield uri, Type, R[SIOC+'SourceCode']; yield uri, Content, `pygmentize -l ruby -f html #{sh}`; triplrFile &f end
    def triplrShellScript &f; yield uri, Type, R[SIOC+'SourceCode']; yield uri, Content, `pygmentize -l sh -f html #{sh}`; triplrFile &f end
    def triplrSourceCode &f; yield uri, Type, R[SIOC+'SourceCode']; yield uri, Content, `pygmentize -f html #{sh}`; triplrFile &f end
    def triplrTeX;        yield stripDoc.uri, Content, `cat #{sh} | tth -r` end

    def triplrRTF          &f; triplrWord :catdoc,        &f end
    def triplrWordDoc      &f; triplrWord :antiword,      &f end
    def triplrWordXML      &f; triplrWord :docx2txt, '-', &f end
    def triplrOpenDocument &f; triplrWord :odt2txt,       &f end
    def triplrWord conv, argB='', &f
      yield uri, Type, R[Stat+'WordDocument']
      yield uri, Content, '<pre>' +
                          `#{conv} #{sh} #{argB}` +
                          '</pre>'
      triplrFile &f
    end

    def triplrText enc=nil, &f
      doc = stripDoc.uri
      yield doc, Type, R[Stat+'TextFile']
      yield doc, Title, stripDoc.basename
      mtime.do{|mt|
        yield doc, Date, mt.iso8601}
      yield doc, DC+'hasFormat', self
      yield doc, Content,
            HTML.render({_: :pre, style: 'white-space: pre-wrap',
               c: readFile.do{|r| enc ? r.force_encoding(enc).to_utf8 : r}.hrefs})
    rescue Exception => e
      puts uri, e.class, e.message
    end
    
    def triplrMarkdown
      doc = stripDoc.uri
      attr = stripDoc.basename == 'README' ? Abstract : Content
      yield doc, Type, R[Stat+'MarkdownFile']
      yield doc, Title, stripDoc.basename
      yield doc, attr, ::Redcarpet::Markdown.new(::Redcarpet::Render::Pygment, fenced_code_blocks: true).render(readFile)
      mtime.do{|mt|yield doc, Date, mt.iso8601}
    end
  end
end

class String
  def sha2; Digest::SHA2.hexdigest self end
  def to_utf8; encode('UTF-8', undef: :replace, invalid: :replace, replace: '?') end
  def utf8; force_encoding 'UTF-8' end
  def sh; Shellwords.escape self end
end
2018-01-14T13:41:52+00:003053
HTML.rb
# coding: utf-8
class WebResource
  module HTML
    def self.render x
      case x
      when String
        x
      when Hash
        void = [:img, :input, :link, :meta].member? x[:_]
        '<' + (x[:_] || 'div').to_s +                        # element name
          (x.keys - [:_,:c]).map{|a|                         # attribute name
          ' ' + a.to_s + '=' + "'" + x[a].to_s.chars.map{|c| # attribute value
            {"'"=>'%27', '>'=>'%3E',
             '<'=>'%3C'}[c]||c}.join + "'"}.join +
          (void ? '/' : '') + '>' + (render x[:c]) +              # children
          (void ? '' : ('</'+(x[:_]||'div').to_s+'>'))       # element closer
      when Array
        x.map{|n|render n}.join
      when R
        render({_: :a, href: x.uri,
                c: x[:label][0] || URI.unescape(x.fragment || (x.path && x.basename != '/' && x.basename) || x.host || '&#x279f;')}.
                 update(x[:id][0] ? {id: x[:id][0]} : {}).
                 update(x[:style][0] ? {style: x[:style][0]} : {}).
                 update(x[:class][0] ? {class: x[:class][0]} : {}).
                 update(x[:name][0] ? {name: x[:name][0]} : {}))
      when NilClass
        ''
      when FalseClass
        ''
      else
        CGI.escapeHTML x.to_s
      end
    end
    include URIs
    InlineMeta = ['uri', Title, Image, Video, Abstract, From, To, Size, Date, Type, Content, Label, DC+'cache', DC+'link', DC+'note', Atom+'link', RSS+'link', RSS+'guid', DC+'hasFormat', SIOC+'channel', SIOC+'attachment', SIOC+'user_agent', Stat+'contains']
    VerboseMeta = [DC+'identifier', DC+'source', DCe+'rights', DCe+'publisher',RSS+'comments', RSS+'em', RSS+'category', Atom+'edit', Atom+'self', Atom+'replies', Atom+'alternate', SIOC+'has_discussion', SIOC+'reply_of', SIOC+'num_replies', Mtime, Podcast+'explicit', Podcast+'summary', Comments,"http://rssnamespace.org/feedburner/ext/1.0#origLink","http://purl.org/syndication/thread/1.0#total","http://search.yahoo.com/mrss/content"]
    LinkPred = [SIOC+'attachment',Stat+'contains',Atom+'link',RSS+'link',DC+'link']

    def self.strip body, loseTags=%w{iframe script style}, keepAttr=%w{alt href rel src title type}
      html = Nokogiri::HTML.fragment body
      loseTags.map{|tag| html.css(tag).remove} if loseTags
      html.traverse{|e|
        e.attribute_nodes.map{|a|
          a.unlink unless keepAttr.member? a.name}} if keepAttr
      html.to_xhtml(:indent => 0)
    end

    def htmlDocument graph = {}
      empty = graph.empty?
      @r ||= {}
      @r[:title] ||= graph[path+'#this'].do{|r|r[Title].justArray[0]}
      @r[:label] ||= {}
      @r[:Links] ||= {}
      htmlGrep graph, q['q'] if q['q']
      if q.has_key?('1')
        resources  = graph.values
        resources.map{|re|
          re.map{|p,o|
            one = graph[''] ||= {'uri' => ''}
            unless p=='uri'
              one[p] ||= []
              o.justArray.map{|o|
                one[p].push o unless one[p].member?(o)}
            end}
          graph.delete re.uri unless re.uri == '' }
      end
      query = q['q'] || q['f']
      title = @r[:title] || [*path.split('/'), query].join(' ')
      useGrep = path.split('/').size > 3 # search-provider suggestion
      link = -> name,icon {@r[:Links][name].do{|l| l.R.data({id: name, label: icon})}}
      css = %w{icons site}
      css.push 'code' if graph.values.find{|r|r.R.a SIOC+'SourceCode'}

      HTML.render ["<!DOCTYPE html>\n",
                   {_: :html,
                    c: [{_: :head,
                         c: [{_: :meta, charset: 'utf-8'}, {_: :title, c: title}, {_: :link, rel: :icon, href: '/.conf/icon.png'},
                             css.map{|s|
                               {_: :style, c: ".conf/#{s}.css".R.readFile}},
                             @r[:Links].do{|links|
                               links.map{|type,uri|
                                 {_: :link, rel: type, href: CGI.escapeHTML(uri.to_s)}}},
                             {_: :script, c: '.conf/site.js'.R.readFile}]},
                        {_: :body,
                         c: [link[:up, '&#9650;'], link[:prev, '&#9664;'], link[:next, '&#9654;'],
                             {class: :scroll, c: (htmlTree graph)},
                             !empty && (htmlTable graph),
                             path!='/' && {class: :search,
                                           c: {_: :form,
                                               c: [{_: :a, id: :query, class: :find, href: (query ? '?head' : '') + '#searchbox' },
                                                   {_: :input, id: :searchbox, name: useGrep ? 'q' : 'f',
                                                    placeholder: useGrep ? :grep : :find
                                                   }.update(query ? {value: query} : {})]}},
                             {_: :style,
                              c: [q.has_key?('bright') ? "body {background-color: #fff; color: #000}\n" : "body {background-color: #000; color: #fff}\n",
                                  @r[:label].map{|name,_|
                                    color = '#%06x' % (rand 16777216)
                                    "[name=\"#{name}\"] {background-color: #{color}}\n"}]},
                             !empty && link[:down, '&#9660;'],
                             empty && [{_: :a, id: :nope, style: "color:#{'#%06x' % (rand 16777216)}", c: 404, href: dirname},
                                       {_: :table, class: :env, c: @r.map{|k,vs|
                                          {_: :tr,
                                           c: [{_: :td, c: k},
                                               {_: :td, c: vs.justArray.map{|v|CGI.escapeHTML v.to_s}.intersperse(' ')}]}}}]]}]}]
    end

    def nokogiri
      Nokogiri::HTML.parse (open uri).read
    end

  end
  include HTML
  module Webize
    def triplrHTML &f
      triplrFile &f
      yield uri, Type, R[Stat+'HTMLFile']
      n = Nokogiri::HTML.parse readFile
      n.css('title').map{|title| yield uri, Title, title.inner_text }
      n.css('meta[property="og:image"]').map{|m| yield uri, Image, m.attr("content").R }
    end
  end
end

class String
  # text -> HTML. also a triplr yielding RDF to supplied block
  # '(' required to capture ')', <> and ()-wrapping stripped, comma or period after URI not captured
  def hrefs &blk
    pre, link, post = self.partition(/(https?:\/\/(\([^)>\s]*\)|[,.]\S|[^\s),.”\'\"<>\]])+)/)
    pre.gsub('&','&amp;').gsub('<','&lt;').gsub('>','&gt;') +    # escaped pre-match
      (link.empty? && '' ||
       '<a class="link" href="' + link.gsub('&','&amp;').gsub('<','&lt;').gsub('>','&gt;') + '">' + # HTML link
       (yield(link.match(/(gif|jpg|jpeg|jpg:large|png|webp)$/i) ? R::Image : (link.match(/(youtube.com|(mkv|mp4|webm)$)/i) ? R::Video : R::Link), link.R) if blk; '') + # RDF link
       '</a>') +
      (post.empty? && '' || post.hrefs(&blk)) # again on tail
  end
end

module Redcarpet
  module Render
    class Pygment < HTML
      def block_code(code, lang)
        if lang
          IO.popen("pygmentize -l #{lang.downcase.sh} -f html",'r+'){|p|
            p.puts code
            p.close_write
            p.read
          }
        else
          code
        end
      end
    end
  end
end
2018-01-14T13:14:36+00:007281
URI.rb
class RDF::Node
  def R; WebResource.new to_s end
end
class RDF::URI
  def R; WebResource.new to_s end
end
class String
  def R; WebResource.new self end
end
class WebResource < RDF::URI
  def R; self end
  def self.[] u; WebResource.new u end
  alias_method :uri, :to_s
  module URIs
    def + u; R[to_s + u.to_s] end
    def match p; to_s.match p end

    # short names for URIs
    W3 = 'http://www.w3.org/'
    OA = 'https://www.w3.org/ns/oa#'
    Purl = 'http://purl.org/'
    DC   = Purl + 'dc/terms/'
    DCe  = Purl + 'dc/elements/1.1/'
    SIOC = 'http://rdfs.org/sioc/ns#'
    Link = DC + 'link'
    Schema = 'http://schema.org/'
    Media = 'http://search.yahoo.com/mrss/'
    Podcast = 'http://www.itunes.com/dtds/podcast-1.0.dtd#'
    Comments = 'http://wellformedweb.org/CommentAPI/commentRss'
    Sound    = Purl + 'ontology/mo/Sound'
    Image    = DC + 'Image'
    Video    = DC + 'Video'
    RSS      = Purl + 'rss/1.0/'
    Date     = DC   + 'date'
    Title    = DC   + 'title'
    Abstract = DC   + 'abstract'
    Post     = SIOC + 'Post'
    To       = SIOC + 'addressed_to'
    From     = SIOC + 'has_creator'
    Creator  = SIOC + 'has_creator'
    Content  = SIOC + 'content'
    Stat     = W3   + 'ns/posix/stat#'
    Atom     = W3   + '2005/Atom#'
    Type     = W3 + '1999/02/22-rdf-syntax-ns#type'
    Label    = W3 + '2000/01/rdf-schema#label'
    Size     = Stat + 'size'
    Mtime    = Stat + 'mtime'
    Container = W3  + 'ns/ldp#Container'
    Twitter = 'https://twitter.com'
    Instagram = 'https://www.instagram.com/'
    YouTube = 'http://www.youtube.com/xml/schemas/2015#'
  end
  module Webize
    def triplrUriList based=nil
      lines = open(localPath).readlines
      doc = stripDoc.uri
      base = stripDoc.basename
      yield doc, Type, R[Stat+'UriList']
      yield doc, Title, base
      yield doc, Size, lines.size
      yield doc, Date, mtime.iso8601
      prefix = based ? "https://#{base}/" : ''
      lines.map{|line|
        t = line.chomp.split ' '
        uri = prefix + t[0]
        title = t[1..-1].join ' ' if t.size > 1
        yield uri, Type, R[W3+'2000/01/rdf-schema#Resource']
        yield uri, Title, title ? title : uri
      }
    end
  end
end
2018-01-12T21:16:53+00:002215
MIME.rb
# coding: utf-8
class WebResource
  module MIME
    include URIs

    # name prefix -> MIME
    MIMEprefix = {
      'authors' => 'text/plain',
      'changelog' => 'text/plain',
      'contributors' => 'text/plain',
      'copying' => 'text/plain',
      'dockerfile' => 'text/x-docker',
      'gemfile' => 'text/x-ruby',
      'license' => 'text/plain',
      'makefile' => 'text/x-makefile',
      'todo' => 'text/plain',
      'unlicense' => 'text/plain',
      'msg' => 'message/rfc822',
    }

    # name suffix -> MIME
    MIMEsuffix = {
      'asc' => 'text/plain',
      'atom' => 'application/atom+xml',
      'bat' => 'text/x-batch',
      'bu' => 'text/based-uri-list',
      'cfg' => 'text/ini',
      'chk' => 'text/plain',
      'conf' => 'application/config',
      'dat' => 'application/octet-stream',
      'db' => 'application/octet-stream',
      'desktop' => 'application/config',
      'doc' => 'application/msword',
      'docx' => 'application/msword+xml',
      'e' => 'application/json',
      'eot' => 'application/font',
      'go' => 'application/go',
      'haml' => 'text/plain',
      'hs' => 'application/haskell',
      'in' => 'text/x-makefile',
      'ini' => 'text/ini',
      'ino' => 'application/ino',
      'lisp' => 'text/x-lisp',
      'list' => 'text/plain',
      'log' => 'text/chatlog',
      'md' => 'text/markdown',
      'msg' => 'message/rfc822',
      'opml' => 'text/xml+opml',
      'rb' => 'text/x-ruby',
      'rst' => 'text/restructured',
      'ru' => 'text/x-ruby',
      'sample' => 'application/config',
      'sh' => 'text/x-shellscript',
      'terminfo' => 'application/config',
      'tmp' => 'application/octet-stream',
      'ttl' => 'text/turtle',
      'u' => 'text/uri-list',
      'woff' => 'application/font',
      'yaml' => 'text/plain',
    }

    # MIME -> RDF-yielding function
    Triplr = {
      'application/config'   => [:triplrDataFile],
      'application/font'      => [:triplrFile],
      'application/go'   => [:triplrSourceCode],
      'application/haskell'   => [:triplrSourceCode],
      'application/javascript' => [:triplrSourceCode],
      'application/ino'      => [:triplrSourceCode],
      'application/json'      => [:triplrDataFile],
      'application/octet-stream' => [:triplrFile],
      'application/org'      => [:triplrOrg],
      'application/pdf'      => [:triplrFile],
      'application/msword'   => [:triplrWordDoc],
      'application/msword+xml' => [:triplrWordXML],
      'application/pkcs7-signature' => [:triplrFile],
      'application/rtf'      => [:triplrRTF],
      'application/ruby'     => [:triplrSourceCode],
      'application/sh'      => [:triplrSourceCode],
      'application/x-sh'     => [:triplrSourceCode],
      'application/xml'     => [:triplrDataFile],
      'application/x-executable' => [:triplrFile],
      'application/x-gzip'   => [:triplrArchive],
      'application/zip'   => [:triplrArchive],
      'application/vnd.oasis.opendocument.text' => [:triplrOpenDocument],
      'audio/mpeg'           => [:triplrAudio],
      'audio/x-wav'          => [:triplrAudio],
      'audio/3gpp'           => [:triplrAudio],
      'image/bmp'            => [:triplrImage],
      'image/gif'            => [:triplrImage],
      'image/png'            => [:triplrImage],
      'image/svg+xml'        => [:triplrImage],
      'image/tiff'           => [:triplrImage],
      'image/jpeg'           => [:triplrImage],
      'inode/directory'      => [:triplrContainer],
      'message/rfc822'       => [:triplrMail],
      'text/cache-manifest'  => [:triplrText],
      'text/calendar'        => [:triplrCalendar],
      'text/chatlog'         => [:triplrChatLog],
      'text/css'             => [:triplrSourceCode],
      'text/csv'             => [:triplrCSV,/,/],
      'text/html'            => [:triplrHTML],
      'text/man'             => [:triplrMan],
      'text/xml+opml'        => [:triplrOPML],
      'text/x-batch'         => [:triplrBat],
      'text/x-c'             => [:triplrSourceCode],
      'text/x-asm'           => [:triplrSourceCode],
      'text/x-lisp'          => [:triplrLisp],
      'text/x-docker'        => [:triplrDocker],
      'text/ini'             => [:triplrIni],
      'text/x-makefile'      => [:triplrMakefile],
      'text/x-java-source'   => [:triplrSourceCode],
      'text/x-ruby'          => [:triplrRuby],
      'text/x-php'           => [:triplrSourceCode],
      'text/x-python'        => [:triplrSourceCode],
      'text/x-script.ruby'   => [:triplrSourceCode],
      'text/x-script.python' => [:triplrSourceCode],
      'text/x-shellscript'   => [:triplrShellScript],
      'text/markdown'        => [:triplrMarkdown],
      'text/nfo'             => [:triplrText,'cp437'],
      'text/plain'           => [:triplrText],
      'text/restructured'    => [:triplrSourceCode],
      'text/rtf'             => [:triplrRTF],
      'text/semicolon-separated-values' => [:triplrCSV,/;/],
      'text/tab-separated-values' => [:triplrCSV,/\t/],
      'text/uri-list'        => [:triplrUriList],
      'text/based-uri-list'        => [:triplrUriList,true],
      'text/x-tex'           => [:triplrTeX],
    }

    # file -> MIME
    def mime
      @mime ||= # memoize
        (name = path || ''
         prefix = ((File.basename name).split('.')[0]||'').downcase
         suffix = ((File.extname name)[1..-1]||'').downcase
         if node.directory? # container
           'inode/directory'
         elsif MIMEprefix[prefix] # prefix mapping
           MIMEprefix[prefix]
         elsif MIMEsuffix[suffix] # suffix mapping
           MIMEsuffix[suffix]
         elsif Rack::Mime::MIME_TYPES['.'+suffix] # suffix mapping (Rack fallback)
           Rack::Mime::MIME_TYPES['.'+suffix]
         else
           puts "#{localPath} unmapped MIME, sniffing content (SLOW)"
           `file --mime-type -b #{Shellwords.escape localPath.to_s}`.chomp
         end)
    end

    # file -> boolean
    def isRDF; %w{atom n3 owl rdf ttl}.member? ext end

    # file -> RDF file
    def toRDF; isRDF ? self : transcode end
    def transcode
      return self if ext == 'e'
      hash = node.stat.ino.to_s.sha2
      doc = R['/.cache/'+hash[0..2]+'/'+hash[3..-1]+'.e']
      unless doc.e && doc.m > m
        tree = {}
        triplr = Triplr[mime]
        unless triplr
          puts "WARNING missing #{mime} triplr for #{uri}"
          triplr = :triplrFile
        end
        send(*triplr){|s,p,o|
          tree[s] ||= {'uri' => s}
          tree[s][p] ||= []
          tree[s][p].push o}
        doc.writeFile tree.to_json
      end
      doc
    end

    # file -> preview file
    def filePreview
      p = join('.' + basename + '.jpg').R
      if !p.e
        if mime.match(/^video/)
          `ffmpegthumbnailer -s 256 -i #{sh} -o #{p.sh}`
        else
          `gm convert #{sh} -thumbnail "256x256" #{p.sh}`
        end
      end
      p.e && p.entity(@r) || notfound
    end

    # env -> MIME
    def selectMIME
      return 'application/atom+xml' if q.has_key?('feed')
      index = {}
      @r['HTTP_ACCEPT'].do{|k|
        (k.split /,/).map{|e| # (MIME,q) pairs
          format, q = e.split /;/      # pair
          i = q && q.split(/=/)[1].to_f || 1.0 # q-value with default
          index[i] ||= []
          index[i].push format.strip}} # indexed q-vals
       index.sort.reverse.map{|q,formats| # order index
        formats.map{|mime| # formats tied at q-val. return first serializable
          return mime if RDF::Writer.for(:content_type => mime) || %w{application/atom+xml text/html}.member?(mime)}} # serializable
      'text/html' # default
    end

  end
  module Webize
    include URIs
  end
  include MIME
  include Webize
end
2018-01-08T00:03:43+00:007714
R
#!/usr/bin/env ruby
require 'ww'
ARGV[0].R.send *ARGV[1..-1]
2018-01-07T08:28:22+00:0061
JSON.rb
class Hash
  def R; WebResource.new(self["uri"]).data self end # preserve Hash/JSON data via reference
  def uri;     self["uri"] end
end
class WebResource
  module JSON
    include URIs
    def [] p; (@data||{})[p].justArray end
    def data d; @data = (@data||{}).merge(d); self end
    def types; @types ||= self[Type].select{|t|t.respond_to? :uri}.map(&:uri) end
    def a type; types.member? type end
    def to_json *a; {'uri' => uri}.to_json *a end

    class Format < RDF::Format
      content_type     'application/json+rdf', :extension => :e
      content_encoding 'utf-8'
      reader { WebResource::JSON::Reader }
    end

    # JSON -> RDF
    class Reader < RDF::Reader
      format Format
      def initialize(input = $stdin, options = {}, &block)
        @graph = ::JSON.parse (input.respond_to?(:read) ? input : StringIO.new(input.to_s)).read
        @base = options[:base_uri]
        if block_given?
          case block.arity
          when 0 then instance_eval(&block)
          else block.call(self)
          end
        end
        nil
      end
      def each_statement &fn
        @graph.map{|s,r|
          r.map{|p,o|
            o.justArray.map{|o|
              fn.call RDF::Statement.new(@base.join(s), RDF::URI(p),
                                         o.class==Hash ? @base.join(o['uri']) : (l = RDF::Literal o
                                                                                 l.datatype=RDF.XMLLiteral if p == 'http://rdfs.org/sioc/ns#content'
                                                                                 l))} unless p=='uri'}}
      end
      def each_triple &block; each_statement{|s| block.call *s.to_triple} end
    end
  end
  include JSON
end
2018-01-07T06:36:58+00:001721
Chat.rb
# coding: utf-8
class WebResource
  module Webize

    def twitter
      open(localPath).readlines.map(&:chomp).shuffle.each_slice(16){|s|
        readURI = Twitter + '/search?f=tweets&vertical=default&q=' + s.map{|u|'from:'+u.chomp}.intersperse('+OR+').join
        readURI.R.indexTweets}
    end

    def fetchTweets
      nokogiri.css('div.tweet > div.content').map{|t|
        s = Twitter + t.css('.js-permalink').attr('href')
        authorName = t.css('.username b')[0].inner_text
        author = R[Twitter + '/' + authorName]
        ts = Time.at(t.css('[data-time]')[0].attr('data-time').to_i).iso8601
        yield s, Type, R[SIOC+'Tweet']
        yield s, Date, ts
        yield s, Creator, author
        yield s, To, (Twitter + '/#twitter').R
        yield s, Title, '▶'
        content = t.css('.tweet-text')[0]
        content.css('a').map{|a|
          a.set_attribute('href', Twitter + (a.attr 'href')) if (a.attr 'href').match /^\//
          yield s, DC+'link', R[a.attr 'href']}
        yield s, Abstract, HTML.strip(content.inner_html).gsub(/<\/?span[^>]*>/,'').gsub(/\n/,'').gsub(/\s+/,' ')}
    end

    def indexTweets
      graph = {}
      # build graph
      fetchTweets{|s,p,o|
        graph[s] ||= {'uri'=>s}
        graph[s][p] ||= []
        graph[s][p].push o}
      # serialize tweets to file(s)
      graph.map{|u,r|
        r[Date].do{|t|
          slug = (u.sub(/https?/,'.').gsub(/\W/,'.')).gsub /\.+/,'.'
          time = t[0].to_s.gsub(/[-T]/,'/').sub(':','/').sub /(.00.00|Z)$/, ''
          doc = "/#{time}#{slug}.e".R
          unless doc.e
            puts u
            doc.writeFile({u => r}.to_json)
          end}}
    end

    def triplrChatLog &f
      linenum = -1
      base = stripDoc
      dir = base.dir
      log = base.uri
      basename = base.basename
      channel = dir + '/' + basename
      network = dir + '/' + basename.split('%23')[0] + '*'
      day = dir.uri.match(/\/(\d{4}\/\d{2}\/\d{2})/).do{|d|d[1].gsub('/','-')}
      readFile.lines.map{|l|
        l.scan(/(\d\d)(\d\d)(\d\d)[\s+@]*([^\(\s]+)[\S]* (.*)/){|m|
          s = base + '#l' + (linenum += 1).to_s
          yield s, Type, R[SIOC+'InstantMessage']
          yield s, Creator, R['#'+m[3]]
          yield s, To, channel
          yield s, Content, m[4].hrefs{|p,o|
            yield s, Title, '▶' if p==Image
            yield s, p, o
          }
          yield s, Date, day+'T'+m[0]+':'+m[1]+':'+m[2] if day}}
      if linenum > 0 # summarize at log-URI
        yield log, Type, R[SIOC+'ChatLog']
        yield log, Date, mtime.iso8601
        yield log, Creator, channel
        yield log, To, network
        yield log, Title, basename.split('%23')[-1] # channel
        yield log, Size, linenum
      end
    end
  end
end
2018-01-07T06:19:42+00:002763
Mail.rb
class WebResource
  module Webize
    def triplrMail &b
      m = Mail.read node; return unless m
      id = m.message_id || m.resent_message_id || rand.to_s.sha2 # Message-ID
      puts " MID #{id}" if @verbose
      msgURI = -> id { h=id.sha2; ['', 'msg', h[0], h[1], h[2], id.gsub(/[^a-zA-Z0-9]+/,'.')[0..96], '#this'].join('/').R}
      resource = msgURI[id]; e = resource.uri                # Message URI
      puts " URI #{resource}" if @verbose
      srcDir = resource.path.R; srcDir.mkdir # container
      srcFile = srcDir + 'this.msg'          # pathname
      unless srcFile.e
        link srcFile # link canonical-location
        puts "LINK #{srcFile}" if @verbose
      end
      yield e, DC+'identifier', id    # Message-ID as RDF
      yield e, DC+'cache', resource
      yield e, Type, R[SIOC+'MailMessage'] # RDF type

      # HTML body
      htmlFiles, parts = m.all_parts.push(m).partition{|p|p.mime_type=='text/html'}
      htmlCount = 0
      htmlFiles.map{|p| # HTML file
        html = srcDir + "#{htmlCount}.html"  # file location
        yield e, DC+'hasFormat', html        # file pointer
        unless html.e
          html.writeFile p.decoded  # store HTML email
          puts "HTML #{html}" if @verbose
        end
        htmlCount += 1 } # increment count

      # text/plain body
      parts.select{|p|
        (!p.mime_type || p.mime_type == 'text/plain') && # text parts
          Mail::Encodings.defined?(p.body.encoding)      # decodable?
      }.map{|p|
        yield e, Content, (HTML.render p.decoded.to_utf8.lines.to_a.map{|l| # split lines
                             l = l.chomp # strip any remaining [\n\r]
                             if qp = l.match(/^((\s*[>|]\s*)+)(.*)/) # quoted line
                               depth = (qp[1].scan /[>|]/).size # > count
                               if qp[3].empty? # drop blank quotes
                                 nil
                               else # wrap quotes in <span>
                                 indent = "<span name='quote#{depth}'>&gt;</span>"
                                 {_: :span, class: :quote,
                                  c: [indent * depth,' ',
                                      {_: :span, class: :quoted, c: qp[3].gsub('@','').hrefs{|p,o|yield e, p, o}}]}
                               end
                             else # fresh line
                               [l.gsub(/(\w+)@(\w+)/,'\2\1').hrefs{|p,o|yield e, p, o}]
                             end}.compact.intersperse("\n"))} # join lines

      # recursive messages, digests, forwards, archives..
      parts.select{|p|p.mime_type=='message/rfc822'}.map{|m|
        content = m.body.decoded                   # decode message-part
        f = srcDir + content.sha2 + '.inlined.msg' # message location
        f.writeFile content if !f.e                # store message
        f.triplrMail &b} # triplr on contained message

      # From
      from = []
      m.from.do{|f|
        f.justArray.compact.map{|f|
          noms = f.split ' '
          if noms.size > 2 && noms[1] == 'at'
            f = "#{noms[0]}@#{noms[2]}"
          end
          puts "FROM #{f}" if @verbose 
          from.push f.to_utf8.downcase}} # queue address for indexing + triple-emitting
      m[:from].do{|fr|
        fr.addrs.map{|a|
          name = a.display_name || a.name # human-readable name
          yield e, Creator, name
          puts "NAME #{name}" if @verbose
        } if fr.respond_to? :addrs}
      m['X-Mailer'].do{|m|
        yield e, SIOC+'user_agent', m.to_s
        puts " MLR #{m}" if @verbose
      }

      # To
      to = []
      %w{to cc bcc resent_to}.map{|p|      # recipient fields
        m.send(p).justArray.map{|r|        # recipient
          puts "  TO #{r}" if @verbose
          to.push r.to_utf8.downcase }}    # queue for indexing
      m['X-BeenThere'].justArray.map{|r|to.push r.to_s} # anti-loop recipient
      m['List-Id'].do{|name|yield e, To, name.decoded.sub(/<[^>]+>/,'').gsub(/[<>&]/,'')} # mailinglist name

      # Subject
      subject = nil
      m.subject.do{|s|
        subject = s.to_utf8.gsub(/\[[^\]]+\]/){|l| yield e, Label, l[1..-2] ; nil }
        yield e, Title, subject}

      # Date
      date = m.date || Time.now rescue Time.now
      date = date.to_time.utc
      dstr = date.iso8601
      yield e, Date, dstr
      dpath = '/' + dstr[0..6].gsub('-','/') + '/msg/' # month
      puts "DATE #{date}\nSUBJ #{subject}" if @verbose && subject

      # index addresses
      [*from,*to].map{|addr|
        user, domain = addr.split '@'
        if user && domain
          apath = dpath + domain + '/' + user # address
          yield e, (from.member? addr) ? Creator : To, R[apath+'#'+user] # To/From triple
          if subject
            slug = subject.scan(/[\w]+/).map(&:downcase).uniq.join('.')[0..63]
            mpath = apath + '.' + dstr[8..-1].gsub(/[^0-9]+/,'.') + slug # time & subject
            mpath = mpath + (mpath[-1] == '.' ? '' : '.')  + 'msg' # file-type extension
            mdir = '../.mail/' + domain + '/' # maildir
            %w{cur new tmp}.map{|c| R[mdir + c].mkdir} # maildir container
            mloc = R[mdir + 'cur/' + id.sha2 + '.msg'] # maildir entry
            iloc = mpath.R # index entry
            [iloc,mloc].map{|loc| loc.dir.mkdir # container
              unless loc.e
                link loc
                puts "LINK #{loc}" if @verbose
              end
            }
          end
        end
      }

      # index bidirectional refs
      %w{in_reply_to references}.map{|ref|
        m.send(ref).do{|rs|
          rs.justArray.map{|r|
            dest = msgURI[r]
            yield e, SIOC+'reply_of', dest
            destDir = dest.path.R; destDir.mkdir; destFile = destDir+'this.msg'
            # bidirectional reference link
            rev = destDir + id.sha2 + '.msg'
            rel = srcDir + r.sha2 + '.msg'
            if !rel.e # link missing
              if destFile.e # link
                destFile.link rel
              else # symlink. it may appear
                destFile.ln_s rel unless rel.symlink?
              end
            end
            srcFile.link rev if !rev.e}}}

      # attachments
      m.attachments.select{|p|Mail::Encodings.defined?(p.body.encoding)}.map{|p| # decodability check
        name = p.filename.do{|f|f.to_utf8.do{|f|!f.empty? && f}} ||                           # explicit name
               (rand.to_s.sha2 + (Rack::Mime::MIME_TYPES.invert[p.mime_type] || '.bin').to_s) # generated name
        file = srcDir + name                     # file location
        unless file.e
          file.writeFile p.body.decoded # store
          puts "FILE #{file}" if @verbose
        end
        yield e, SIOC+'attachment', file         # file pointer
        if p.main_type=='image'                  # image attachments
          yield e, Image, file                   # image link represented in RDF
          yield e, Content,                      # image link represented in HTML
                HTML.render({_: :a, href: file.uri, c: [{_: :img, src: file.uri}, p.filename]}) # render HTML
        end }
    end
    def indexMail
      triples = 0
      triplrMail{|s,p,o|triples += 1}
      puts "    #{triples} triples"
    rescue Exception => e
      puts uri, e.class, e.message
    end

    def indexMails; glob.map &:indexMail end
  end
end
2017-12-27T10:46:03+00:007391
Icons.rb
class WebResource
  module HTML
    Icons = {
      'uri' => :id,
      Comments => :comments,
      Container => :dir,
      Content => :pencil,
      DC+'cache' => :chain,
      DC+'hasFormat' => :file,
      DC+'link' => :chain,
      Date => :date,
      Image => :img,
      Label => :tag,
      Mtime => :time,
      RSS+'comments' => :comments,
      SIOC+'BlogPost' => :pencil,
      SIOC+'ChatLog' => :comments,
      SIOC+'Discussion' => :comments,
      SIOC+'Feed' => :feed,
      SIOC+'InstantMessage' => :comment,
      SIOC+'MailMessage' => :envelope,
      SIOC+'MicroblogPost' => :newspaper,
      SIOC+'Post' => :newspaper,
      SIOC+'SourceCode' => :code,
      SIOC+'Thread' => :openenvelope,
      SIOC+'Tweet' => :bird,
      SIOC+'Usergroup' => :group,
      SIOC+'WikiArticle' => :pencil,
      SIOC+'has_creator' => :user,
      SIOC+'has_discussion' => :comments,
      SIOC+'user_agent' => :mailer,
      Schema+'Person' => :user,
      Schema+'location' => :location,
      Size => :size,
      Sound => :speaker,
      Stat+'Archive' => :archive,
      Stat+'DataFile' => :tree,
      Stat+'File' => :file,
      Stat+'HTMLFile' => :html,
      Stat+'MarkdownFile' => :markup,
      Stat+'TextFile' => :textfile,
      Stat+'UriList' => :list,
      Stat+'WordDocument' => :word,
      Stat+'container' => :dir,
      Stat+'contains' => :dir,
      Stat+'height' => :height,
      Stat+'width' => :width,
      Title => :title,
      To => :userB,
      Type => :type,
      W3+'2000/01/rdf-schema#Resource' => :node,
    }
  end
end
2017-12-24T11:14:09+00:001564
Gemfile.lock
GEM
  remote: https://rubygems.org/
  specs:
    addressable (2.5.1)
      public_suffix (~> 2.0, >= 2.0.2)
    bcp47 (0.3.3)
      i18n
    builder (3.2.3)
    coderay (1.1.1)
    concurrent-ruby (1.0.5)
    daemons (1.2.4)
    dimensions (1.3.0)
    ebnf (1.1.1)
      rdf (~> 2.2)
      sxp (~> 1.0)
    equivalent-xml (0.6.0)
      nokogiri (>= 1.4.3)
    eventmachine (1.2.3)
    foreman (0.84.0)
      thor (~> 0.19.1)
    haml (5.0.1)
      temple (>= 0.8.0)
      tilt
    hamster (3.0.0)
      concurrent-ruby (~> 1.0)
    htmlentities (4.3.4)
    i18n (0.8.6)
    icalendar (2.4.1)
    json-ld (2.1.5)
      multi_json (~> 1.12)
      rdf (~> 2.2)
    kgio (2.11.0)
    ld-patch (0.3.1)
      ebnf (~> 1.0, >= 1.0.1)
      rdf (~> 2.0)
      rdf-xsd (~> 2.0)
      sparql (~> 2.0)
      sxp (~> 1.0)
    link_header (0.0.8)
    linkeddata (2.2.2)
      equivalent-xml (~> 0.6)
      json-ld (~> 2.1)
      ld-patch (~> 0.3)
      nokogiri (~> 1.7)
      rdf (~> 2.2)
      rdf-aggregate-repo (~> 2.1)
      rdf-isomorphic (~> 2.0)
      rdf-json (~> 2.0)
      rdf-microdata (~> 2.1)
      rdf-n3 (~> 2.1)
      rdf-normalize (~> 0.3)
      rdf-rdfa (~> 2.1)
      rdf-rdfxml (~> 2.0)
      rdf-reasoner (~> 0.4)
      rdf-tabular (~> 2.2)
      rdf-trig (~> 2.0)
      rdf-trix (~> 2.0)
      rdf-turtle (~> 2.2)
      rdf-vocab (~> 2.1)
      rdf-xsd (~> 2.1)
      sparql (~> 2.1)
      sparql-client (~> 2.1)
    mail (2.6.6)
      mime-types (>= 1.16, < 4)
    method_source (0.8.2)
    mime-types (3.1)
      mime-types-data (~> 3.2015)
    mime-types-data (3.2016.0521)
    mini_portile2 (2.2.0)
    multi_json (1.12.1)
    net-http-persistent (2.9.4)
    nokogiri (1.8.0)
      mini_portile2 (~> 2.2.0)
    nokogiri-diff (0.2.0)
      nokogiri (~> 1.5)
      tdiff (~> 0.3, >= 0.3.2)
    pry (0.10.4)
      coderay (~> 1.1.0)
      method_source (~> 0.8.1)
      slop (~> 3.4)
    pry-doc (0.10.0)
      pry (~> 0.9)
      yard (~> 0.9)
    public_suffix (2.0.5)
    rack (2.0.3)
    raindrops (0.18.0)
    rdf (2.2.6)
      hamster (~> 3.0)
      link_header (~> 0.0, >= 0.0.8)
    rdf-aggregate-repo (2.2.0)
      rdf (~> 2.0)
    rdf-isomorphic (2.0.0)
      rdf (~> 2.0)
    rdf-json (2.0.0)
      rdf (~> 2.0)
    rdf-microdata (2.2.1)
      htmlentities (~> 4.3)
      nokogiri (~> 1.7)
      rdf (~> 2.2)
      rdf-xsd (~> 2.1)
    rdf-n3 (2.1.0)
      rdf (~> 2.0)
    rdf-normalize (0.3.2)
      rdf (~> 2.0)
    rdf-rdfa (2.2.2)
      haml (~> 5.0)
      htmlentities (~> 4.3)
      rdf (~> 2.2)
      rdf-aggregate-repo (~> 2.2)
      rdf-xsd (~> 2.1)
    rdf-rdfxml (2.0.0)
      htmlentities (~> 4.3)
      rdf (~> 2.0)
      rdf-rdfa (~> 2.0)
      rdf-xsd (~> 2.0)
    rdf-reasoner (0.4.2)
      rdf (~> 2.2)
      rdf-vocab (~> 2.2)
      rdf-xsd (~> 2.1)
    rdf-tabular (2.2.0)
      addressable (~> 2.3)
      bcp47 (~> 0.3, >= 0.3.3)
      json-ld (~> 2.0)
      rdf (~> 2.1)
      rdf-vocab (~> 2.0)
      rdf-xsd (~> 2.0)
    rdf-trig (2.0.0)
      ebnf (~> 1.0, >= 1.0.1)
      rdf (~> 2.0)
      rdf-turtle (~> 2.0)
    rdf-trix (2.0.0)
      rdf (~> 2.0)
    rdf-turtle (2.2.0)
      ebnf (~> 1.1)
      rdf (~> 2.2)
    rdf-vocab (2.2.3)
      rdf (~> 2.2)
    rdf-xsd (2.2.0)
      rdf (~> 2.1)
    redcarpet (3.4.0)
    slop (3.6.0)
    sparql (2.2.1)
      builder (~> 3.2)
      ebnf (~> 1.1)
      rdf (~> 2.2)
      rdf-aggregate-repo (~> 2.2)
      rdf-xsd (~> 2.1)
      sparql-client (~> 2.1)
      sxp (~> 1.0)
    sparql-client (2.1.0)
      net-http-persistent (~> 2.9)
      rdf (~> 2.0)
    sxp (1.0.0)
      rdf (~> 2.0)
    tdiff (0.3.3)
    temple (0.8.0)
    thin (1.7.2)
      daemons (~> 1.0, >= 1.0.9)
      eventmachine (~> 1.0, >= 1.0.4)
      rack (>= 1, < 3)
    thor (0.19.4)
    tilt (2.0.7)
    unicorn (5.3.0)
      kgio (~> 2.6)
      raindrops (~> 0.7)
    yard (0.9.9)

PLATFORMS
  ruby

DEPENDENCIES
  dimensions
  foreman
  icalendar
  linkeddata
  mail
  nokogiri
  nokogiri-diff
  pry
  pry-doc
  rack
  redcarpet
  thin
  unicorn

BUNDLED WITH
   1.15.1
2017-11-26T20:54:34+00:004035
Gemfile
source "https://rubygems.org/"
gem 'dimensions'
gem 'foreman'
gem 'icalendar'
gem 'linkeddata'
gem 'mail'
gem 'nokogiri'
gem 'nokogiri-diff'
gem 'pry'
gem 'pry-doc'
gem 'rack'
gem 'redcarpet'
gem 'thin'
gem 'unicorn'
2017-11-23T10:57:16+00:00217
install
#!/usr/bin/env ruby
require 'fileutils'
require 'pathname'
loc = RbConfig::CONFIG["sitelibdir"] + '/'
FileUtils.mkdir_p loc unless Pathname(loc).exist?
FileUtils.ln_s (File.expand_path File.dirname __FILE__)+'/ww.rb', loc
2017-08-22T22:21:39+00:00222