commit 66a449fef4b3e85b71a096ad769de74d32cdc30a
Author: Karsten Loesing <[email protected]>
Date:   Mon Mar 13 20:19:41 2017 +0100

    Add metrics-lib page with tutorials.
    
    This commits contains lots of ideas from iwakeh and RaBe.
    
    Implements #21379.
---
 website/etc/web.xml                                |  11 +
 .../torproject/metrics/web/MetricsLibServlet.java  |  31 ++
 website/web/WEB-INF/development.jsp                |   2 +-
 website/web/WEB-INF/metrics-lib.jsp                | 375 +++++++++++++++++++++
 website/web/WEB-INF/top.jsp                        |   4 +
 website/web/css/prism.css                          | 179 ++++++++++
 website/web/css/style.css                          |  40 ++-
 website/web/js/prism.js                            |   6 +
 8 files changed, 646 insertions(+), 2 deletions(-)

diff --git a/website/etc/web.xml b/website/etc/web.xml
index bd7f4f1..63dd333 100644
--- a/website/etc/web.xml
+++ b/website/etc/web.xml
@@ -273,6 +273,17 @@
   </servlet-mapping>
 
   <servlet>
+    <servlet-name>MetricsLibServlet</servlet-name>
+    <servlet-class>
+      org.torproject.metrics.web.MetricsLibServlet
+    </servlet-class>
+  </servlet>
+  <servlet-mapping>
+    <servlet-name>MetricsLibServlet</servlet-name>
+    <url-pattern>/metrics-lib.html</url-pattern>
+  </servlet-mapping>
+
+  <servlet>
     <servlet-name>ResearchServlet</servlet-name>
     <servlet-class>
       org.torproject.metrics.web.ResearchServlet
diff --git a/website/src/org/torproject/metrics/web/MetricsLibServlet.java 
b/website/src/org/torproject/metrics/web/MetricsLibServlet.java
new file mode 100644
index 0000000..d71ea7a
--- /dev/null
+++ b/website/src/org/torproject/metrics/web/MetricsLibServlet.java
@@ -0,0 +1,31 @@
+/* Copyright 2017 The Tor Project
+ * See LICENSE for licensing information */
+
+package org.torproject.metrics.web;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class MetricsLibServlet extends AnyServlet {
+
+  private static final long serialVersionUID = -6009422570527820853L;
+
+  @Override
+  public void init() throws ServletException {
+    super.init();
+  }
+
+  @Override
+  public void doGet(HttpServletRequest request,
+      HttpServletResponse response) throws IOException, ServletException {
+
+    /* Forward the request to the JSP that does all the hard work. */
+    request.setAttribute("categories", this.categories);
+    request.getRequestDispatcher("WEB-INF/metrics-lib.jsp").forward(request,
+        response);
+  }
+}
+
diff --git a/website/web/WEB-INF/development.jsp 
b/website/web/WEB-INF/development.jsp
index 4bd3ea3..445f77b 100644
--- a/website/web/WEB-INF/development.jsp
+++ b/website/web/WEB-INF/development.jsp
@@ -21,7 +21,7 @@
       <h2>Parsing libraries <a href="#libraries" name="libraries" 
class="anchor">#</a></h2>
       <p>The following libraries help you with parsing Tor network data from 
the <a href="https://collector.torproject.org/"; target="_blank">CollecTor</a> 
service.</p>
       <ul>
-        <li><a href="https://dist.torproject.org/descriptor/"; 
target="_blank">metrics-lib</a> is a Java library to fetch and parse Tor 
descriptors.</li>
+        <li><a href="metrics-lib.html">metrics-lib</a> is a Java library to 
fetch and parse Tor descriptors.</li>
         <li><a href="https://stem.torproject.org/"; target="_blank">Stem</a> is 
a Python library that parses Tor descriptors.</li>
         <li><a href="https://github.com/NullHypothesis/zoossh"; 
target="_blank">Zoossh</a> is a parser written in Go for Tor-specific data 
formats.</li>
       </ul>
diff --git a/website/web/WEB-INF/metrics-lib.jsp 
b/website/web/WEB-INF/metrics-lib.jsp
new file mode 100644
index 0000000..0dac339
--- /dev/null
+++ b/website/web/WEB-INF/metrics-lib.jsp
@@ -0,0 +1,375 @@
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"; %>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"; %>
+<jsp:include page="top.jsp">
+  <jsp:param name="pageTitle" value="Development &ndash; Tor Metrics"/>
+  <jsp:param name="navActive" value="Development"/>
+</jsp:include>
+
+    <div class="container">
+      <ul class="breadcrumb">
+        <li><a href="/">Home</a></li>
+        <li><a href="development.html">Development</a></li>
+        <li class="active">metrics-lib</li>
+      </ul>
+    </div>
+
+<div class="container">
+
+<div class="jumbotron">
+<div class="text-center">
+<h2>metrics-lib</h2>
+<p>metrics-lib is a Java API that facilitates processing Tor network data from 
the <a href="https://collector.torproject.org/";>CollecTor</a> service for 
statistical analysis and for building services and applications.</p>
+<a class="btn btn-primary btn-lg" style="margin: 10px" 
href="https://dist.torproject.org/descriptor/?C=M;O=D";><i class="fa 
fa-chevron-right" aria-hidden="true"></i> Download Release</a>
+<a class="btn btn-primary btn-lg" style="margin: 10px" 
href="https://gitweb.torproject.org/metrics-lib.git/plain/CHANGELOG.md";><i 
class="fa fa-chevron-right" aria-hidden="true"></i> View Change Log</a>
+<!--<a class="btn btn-primary btn-lg" style="margin: 10px" 
href="javadoc/index.html"><i class="fa fa-chevron-right" 
aria-hidden="true"></i> Browse JavaDocs</a>-->
+</div><!-- text-center -->
+</div><!-- jumbotron -->
+
+</div><!-- container -->
+<br>
+
+<br>
+
+<div class="container language-java">
+      <div class="row">
+        <div class="col-xs-12">
+
+<h1>metrics-lib</h1>
+
+<p>Welcome to metrics-lib, a Java API that facilitates processing Tor network 
data from the <a href="https://collector.torproject.org/";>CollecTor</a> service 
for statistical analysis and for building services and applications.</p>
+
+<p>In the tutorials below we're explaining the basic steps to get you started 
with metrics-lib.</p>
+
+<h2 id="prerequisites">Prerequisites and preparation <a href="#prerequisites" 
class="anchor">#</a></h2>
+
+<p>The following tutorials are written with an audience in mind that knows 
Java and to a lesser extent how Tor works.  We explain all data used in the 
tutorials.  More and most up-to-date information about descriptors can be found 
in the <a 
href="https://gitweb.torproject.org/torspec.git/tree/dir-spec.txt";>Tor 
directory protocol specification</a> and on the <a 
href="https://collector.torproject.org/";>CollecTor website</a>.</p>
+
+<p>All tutorials require you to <a 
href="https://dist.torproject.org/descriptor/?C=M;O=D";>download</a> the latest 
release of metrics-lib, follow the instructions to <a 
href="https://gitweb.torproject.org/metrics-lib.git/tree/README.md";>verify</a> 
its signature, extract the tarball locally, and copy the <code>lib/</code> and 
the <code>generated/</code> directories to your working directory for the 
tutorials.</p>
+
+<h2 id="tutorial1">Tutorial 1: Download descriptors from CollecTor <a 
href="#tutorial1" class="anchor">#</a></h2>
+
+<p>Let's start this tutorial series by doing something really simple.  We'll 
use metrics-lib to download <a 
href="https://collector.torproject.org/recent/relay-descriptors/consensuses/";>recent
 consensuses from CollecTor</a> and write them to a local directory.  We're not 
doing anything with those consensuses yet, though we'll get back to that in a 
bit.</p>
+
+<p>We'll need to tell metrics-lib five pieces of information for this:</p>
+
+<ol>
+<li>the CollecTor base URL without trailing slash 
(<code>"https://collector.torproject.org";</code>),</li>
+<li>which remote directories to collect descriptors from (<code>new String[] { 
"/recent/relay-descriptors/consensuses/" }</code>),</li>
+<li>the minimum last-modified time of files to be collected 
(<code>0L</code>),</li>
+<li>the local directory to write files to (<code>new 
File("descriptors")</code>), and</li>
+<li>whether to delete all local files that do not exist remotely anymore 
(<code>false</code>).</li>
+</ol>
+
+<p>Create a new file <code>DownloadConsensuses.java</code> with the following 
content:</p>
+
+<pre><code>import org.torproject.descriptor.*;
+
+import java.io.File;
+
+public class DownloadConsensuses {
+  public static void main(String[] args) {
+
+    // Download consensuses published in the last 72 hours, which will take up 
to five minutes and require several hundred MB on the local disk.
+    DescriptorCollector descriptorCollector = 
DescriptorSourceFactory.createDescriptorCollector();
+    descriptorCollector.collectDescriptors(
+        // Download from Tor's main CollecTor instance,
+        "https://collector.torproject.org";,
+        // include only network status consensuses
+        new String[] { "/recent/relay-descriptors/consensuses/" },
+        // regardless of last-modified time,
+        0L,
+        // write to the local directory called descriptors/,
+        new File("descriptors"),
+        // and don't delete extraneous files that do not exist remotely 
anymore.
+        false);
+  }
+}
+</code></pre>
+
+<p>If you haven't already done so, prepare the working directory for this 
tutorial as described <a href="#prerequisites">above</a>.</p>
+
+<p>Compile and run the Java file:</p>
+
+<pre>
+javac -cp lib/\*:generated/dist/signed/\* DownloadConsensuses.java
+</pre>
+<pre>
+java -cp .:lib/\*:generated/dist/signed/\* DownloadConsensuses
+</pre>
+
+<p>This will take up to five minutes and require several hundred MB on the 
local disk.</p>
+
+<p>If you want to play a bit with this code, you could extend it to also 
download recent bridge extra-info descriptors from CollecTor, which are stored 
in <code>/recent/bridge-descriptors/extra-infos/</code> and which we'll need 
for tutorial 3 below.  (If you're too <strike>impatient</strike> curious, 
scroll down to the bottom of this page for the diff.)</p>
+
+<h2 id="tutorial2">Tutorial 2: Relay capacity by Tor version <a 
href="#tutorial2" class="anchor">#</a></h2>
+
+<p>If you just followed tutorial 1 above, you now have a bunch of consensuses 
on your disk.  Let's do something with those and look at relay capacity by Tor 
version.  A possible use case could be that the Tor developers debate which of 
the older versions to turn into long-term supported versions, and you want to 
contribute more facts to that discussion by telling them how much relay 
capacity each version provides.</p>
+
+<p>Consider the following snippet from a consensus document showing a single 
relay to get an idea of the underlying data:</p>
+
+<pre>
+[...]
+r PrivacyRepublic0001 XOzFwwrMSz3kYnkjI5Zwh8xT2Uc WLlCQj3gVELkwIBh3EWxG74LZ2E 
2017-03-04 08:16:22 178.32.181.96 443 80
+s Exit Fast Guard HSDir Running Stable V2Dir Valid
+v Tor 0.2.8.9
+pr Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 Link=1-4 LinkAuth=1 
Microdesc=1-2 Relay=1-2
+w Bandwidth=136000
+p reject 22,25,109-110,119,143,465,563,587,6881-6889
+[...]
+</pre>
+
+<p>We're interested in the Tor version number without patch level 
(<code>0.2.8</code>) and the consensus weight (<code>136000</code>).</p>
+
+<p>Create a new file <code>ConsensusWeightByVersion.java</code> with the 
following content:</p>
+
+<pre><code class="language-java">import org.torproject.descriptor.*;
+
+import java.io.File;
+import java.util.*;
+
+public class ConsensusWeightByVersion {
+  public static void main(String[] args) {
+
+    // Download consensuses.
+    DescriptorCollector descriptorCollector = 
DescriptorSourceFactory.createDescriptorCollector();
+    descriptorCollector.collectDescriptors("https://collector.torproject.org";, 
new String[] { "/recent/relay-descriptors/consensuses/" }, 0L, new 
File("descriptors"), false);
+
+    // Keep local counters for extracted descriptor data.
+    long totalBandwidth = 0L;
+    SortedMap&lt;String, Long&gt; bandwidthByVersion = new TreeMap&lt;&gt;();
+
+    // Read descriptors from disk.
+    DescriptorReader descriptorReader = 
DescriptorSourceFactory.createDescriptorReader();
+
+    // Add the directory with descriptors to the descriptor reader.
+    descriptorReader.addDirectory(new 
File("descriptors/recent/relay-descriptors/consensuses"));
+    Iterator&lt;DescriptorFile&gt; descriptorFiles = 
descriptorReader.readDescriptors();
+    while (descriptorFiles.hasNext()) { // Iterate over all descriptor files 
found.
+      DescriptorFile descriptorFile = descriptorFiles.next();
+
+      // Now, iterate over the descriptors contained in the file.
+      for (Descriptor descriptor : descriptorFile.getDescriptors()) {
+        if (!(descriptor instanceof RelayNetworkStatusConsensus)) {
+          // We're only interested in consensuses.
+          continue;
+        }
+        RelayNetworkStatusConsensus consensus = (RelayNetworkStatusConsensus) 
descriptor;
+        for (NetworkStatusEntry entry : consensus.getStatusEntries().values()) 
{
+          String version = entry.getVersion();
+          if (!version.startsWith("Tor ") || version.length() &lt; 9) {
+            // We're only interested in a.b.c type versions for this example.
+            continue;
+          }
+          // Remove the 'Tor ' prefix and anything starting at the patch level.
+          version = version.substring(4, 9);
+          long bandwidth = entry.getBandwidth();
+          totalBandwidth += bandwidth;
+          if (bandwidthByVersion.containsKey(version)) {
+            bandwidthByVersion.put(version, bandwidth + 
bandwidthByVersion.get(version));
+          } else {
+            bandwidthByVersion.put(version, bandwidth);
+          }
+        }
+      }
+    }
+
+    // Print out fractions of consensus weight by Tor version.
+    if (totalBandwidth &gt; 0L) {
+      for (Map.Entry&lt;String, Long&gt; e : bandwidthByVersion.entrySet()) {
+        System.out.printf("%s -&gt; %4.1f%%%n", e.getKey(), (100.0 * (double) 
e.getValue() / (double) totalBandwidth));
+      }
+    }
+  }
+}
+</code></pre>
+
+<p>If you haven't already done so, prepare the working directory for this 
tutorial as described <a href="#prerequisites">above</a>.</p>
+
+<p>Compile and run the Java file:</p>
+
+<pre>
+javac -cp lib/\*:generated/dist/signed/\* ConsensusWeightByVersion.java
+</pre>
+<pre>
+java -cp .:lib/\*:generated/dist/signed/\* ConsensusWeightByVersion
+</pre>
+
+<p>There will be some log statements, and the final output should now contain 
lines like the following:</p>
+
+<pre>
+0.2.4 -&gt;  4.2%
+0.2.5 -&gt;  9.4%
+0.2.6 -&gt;  6.2%
+0.2.7 -&gt;  8.3%
+0.2.8 -&gt; 15.0%
+0.2.9 -&gt; 46.7%
+0.3.0 -&gt;  9.4%
+0.3.1 -&gt;  0.8%
+</pre>
+
+<p>These are the numbers we were looking for.  Now you should know what to do 
to extract interesting data from consensuses.  Want to give that another try 
and filter relays with the <code>Exit</code> flag to learn about exit capacity 
by Tor version?  Hint: You'll want to check for 
<code>entry.getFlags().contains("Exit")</code>.  Of course, you could as well 
continue with the next tutorial below.  (Or you could scroll down to the bottom 
of this page to see the diff.)</p>
+
+<h2 id="tutorial3">Tutorial 3: Frequency of transports <a href="#tutorial3" 
class="anchor">#</a></h2>
+
+<p>In the previous tutorial we looked at relay descriptors, so let's now look 
a bit at bridge descriptors.</p>
+
+<p>Every bridge publishes its transports in its extra-info descriptors that it 
periodically sends to the bridge authority.  Let's count the frequency of 
transports.  A possible use case could be that the Pluggable Transports 
developers debate which of the transport name is the least pronouncable, and 
you want to give them numbers to talk about something much more useful 
instead.</p>
+
+<p>Consider this snippet from a bridge extra-info descriptor:</p>
+
+<pre>
+extra-info LeifEricson 3E0908F131AC417C48DDD835D78FB6887F4CD126
+[...]
+transport obfs2
+transport scramblesuit
+transport obfs3
+transport obfs4
+transport fte
+</pre>
+
+<p>What we need to do is extract the list of transport names 
(<code>obfs2</code>, <code>scramblesuit</code>, etc.) together with the bridge 
fingerprint (<code>3E0908F131AC417C48DDD835D78FB6887F4CD126</code>).  
Considering the fingerprint is important, so that we avoid double-counting 
transports provided by the same bridge.</p>
+
+<p>Create a new file <code>PluggableTransports.java</code> with the following 
content:</p>
+
+<pre><code class="language-java">import org.torproject.descriptor.*;
+
+import java.io.File;
+import java.util.*;
+
+public class PluggableTransports {
+  public static void main(String[] args) {
+
+    DescriptorCollector descriptorCollector = 
DescriptorSourceFactory.createDescriptorCollector();
+    descriptorCollector.collectDescriptors("https://collector.torproject.org";, 
new String[] { "/recent/bridge-descriptors/extra-infos/" }, 0L, new 
File("descriptors"), false);
+
+    Set&lt;String&gt; observedFingerprints = new HashSet&lt;&gt;();
+    SortedMap&lt;String, Integer&gt; countedTransports = new TreeMap&lt;&gt;();
+
+    DescriptorReader descriptorReader = 
DescriptorSourceFactory.createDescriptorReader();
+    descriptorReader.addDirectory(new 
File("descriptors/recent/bridge-descriptors/extra-infos"));
+    Iterator&lt;DescriptorFile&gt; descriptorFiles = 
descriptorReader.readDescriptors();
+    while (descriptorFiles.hasNext()) {
+      DescriptorFile descriptorFile = descriptorFiles.next();
+      for (Descriptor descriptor : descriptorFile.getDescriptors()) {
+        if (!(descriptor instanceof BridgeExtraInfoDescriptor)) {
+          continue;
+        }
+        BridgeExtraInfoDescriptor extraInfo = (BridgeExtraInfoDescriptor) 
descriptor;
+        String fingerprint = extraInfo.getFingerprint();
+        if (observedFingerprints.add(fingerprint)) {
+          for (String transport : extraInfo.getTransports()) {
+            if (countedTransports.containsKey(transport)) {
+              countedTransports.put(transport, 1 + 
countedTransports.get(transport));
+            } else {
+              countedTransports.put(transport, 1);
+            }
+          }
+        }
+      }
+    }
+
+    if (!observedFingerprints.isEmpty()) {
+      double totalObservedFingerprints = observedFingerprints.size();
+      for (Map.Entry&lt;String, Integer&gt; e : countedTransports.entrySet()) {
+        System.out.printf("%20s -&gt; %4.1f%%%n", e.getKey(), (100.0 * 
(double) e.getValue() / totalObservedFingerprints));
+      }
+    }
+  }
+}
+</code></pre>
+
+<p>If you haven't already done so, prepare the working directory for this 
tutorial as described <a href="#prerequisites">above</a>.</p>
+
+<p>Compile and run the Java file:</p>
+
+<pre>
+javac -cp lib/\*:generated/dist/signed/\* PluggableTransports.java
+</pre>
+<pre>
+java -cp .:lib/\*:generated/dist/signed/\* PluggableTransports
+</pre>
+
+<p>The output should contain lines like the following:</p>
+
+<pre>
+                 fte -> 11.7%
+                meek ->  0.2%
+               obfs2 ->  0.8%
+               obfs3 -> 35.7%
+     obfs3_websocket ->  0.0%
+               obfs4 -> 72.8%
+        scramblesuit -> 29.6%
+           snowflake ->  0.0%
+           websocket ->  0.8%
+</pre>
+
+<p>As above, we'll leave it up to you to further expand this code.  For 
example, how does the result change if you count transport <i>combinations</i> 
rather than transports?  Hint: you won't need anything else from metrics-lib, 
but you'll need to add some code to order transport names and write them to a 
string.  (And if you'd rather look up the solution, scroll down a bit to see 
the diff.)</p>
+
+<h2 id="nextsteps">Next steps <a href="#nextsteps" class="anchor">#</a></h2>
+
+<p>Want to write more code that uses metrics-lib?  Be sure to read the 
JavaDocs while developing new services or applications using Tor network 
data.</p>
+
+<p>Ran into a problem, found a bug, or came up with a cool new feature?  Feel 
free to <a href="https://metrics.torproject.org/about.html#contact";>contact 
us</a>.  Alternatively, take a look at the <a 
href="https://trac.torproject.org/projects/tor";>bug tracker</a> and open a 
ticket if there's none for your issue yet.</p>
+
+<p>Interested in writing <a 
href="https://gitweb.torproject.org/metrics-lib.git/";>code</a> for metrics-lib? 
 Please take a look at the Tor Metrics team <a 
href="https://trac.torproject.org/projects/tor/wiki/org/teams/MetricsTeam/Volunteers";>wiki
 page</a> to find out how to contribute.</p>
+
+<p>Scrolled down just to see where we're hiding the solutions of the three 
little riddles above?  Here are the diffs:</p>
+
+<pre><code class="language-diff">diff -Nur src/DownloadConsensuses.java 
src/DownloadConsensuses.java
+--- src/DownloadConsensuses.java        2017-03-07 17:48:35.000000000 +0100
++++ src/DownloadConsensuses.java        2017-03-10 23:02:51.000000000 +0100
+@@ -11,7 +11,7 @@
+         // Download from Tor's main CollecTor instance,
+         "https://collector.torproject.org";,
+         // include only network status consensuses
+-        new String[] { "/recent/relay-descriptors/consensuses/" },
++        new String[] { "/recent/bridge-descriptors/extra-infos/" },
+         // regardless of last-modified time,
+         0L,
+         // write to the local directory called descriptors/,
+</code></pre>
+
+<pre><code class="language-diff">diff -Nur src/ConsensusWeightByVersion.java 
src/ConsensusWeightByVersion.java
+--- src/ConsensusWeightByVersion.java   2017-03-10 23:00:40.000000000 +0100
++++ src/ConsensusWeightByVersion.java   2017-03-10 23:03:18.000000000 +0100
+@@ -31,6 +31,9 @@
+         }
+         RelayNetworkStatusConsensus consensus = (RelayNetworkStatusConsensus) 
descriptor;
+         for (NetworkStatusEntry entry : 
consensus.getStatusEntries().values()) {
++          if (!entry.getFlags().contains("Exit")) {
++            continue;
++          }
+           String version = entry.getVersion();
+           if (!version.startsWith("Tor ") || version.length() < 9) {
+             // We're only interested in a.b.c type versions for this example.
+</code></pre>
+
+<pre><code class="language-diff">diff -Nur src/PluggableTransports.java 
src/PluggableTransports.java
+--- src/PluggableTransports.java        2017-03-10 23:01:43.000000000 +0100
++++ src/PluggableTransports.java        2017-03-10 23:03:43.000000000 +0100
+@@ -24,12 +24,11 @@
+         BridgeExtraInfoDescriptor extraInfo = (BridgeExtraInfoDescriptor) 
descriptor;
+         String fingerprint = extraInfo.getFingerprint();
+         if (observedFingerprints.add(fingerprint)) {
+-          for (String transport : extraInfo.getTransports()) {
+-            if (countedTransports.containsKey(transport)) {
+-              countedTransports.put(transport, 1 + 
countedTransports.get(transport));
+-            } else {
+-              countedTransports.put(transport, 1);
+-            }
++          String transports = new 
TreeSet&lt;&gt;(extraInfo.getTransports()).toString();
++          if (countedTransports.containsKey(transports)) {
++            countedTransports.put(transports, 1 + 
countedTransports.get(transports));
++          } else {
++            countedTransports.put(transports, 1);
+           }
+         }
+       }
+</code></pre>
+
+</div> <!-- col -->
+</div> <!-- row -->
+</div> <!-- container -->
+
+<jsp:include page="bottom.jsp"/>
+
diff --git a/website/web/WEB-INF/top.jsp b/website/web/WEB-INF/top.jsp
index f34031b..b5e6a76 100644
--- a/website/web/WEB-INF/top.jsp
+++ b/website/web/WEB-INF/top.jsp
@@ -33,6 +33,10 @@
   <link rel="stylesheet" href="css/font-awesome.min.css">
   <link rel="stylesheet" href="fonts/source-sans-pro.css">
 
+  <!-- Prism -->
+  <link rel="stylesheet" href="css/prism.css">
+  <script src="js/prism.js"></script>
+
   <!-- custom styles and javascript -->
   <link rel="stylesheet" href="css/style.css">
   <script src="js/script.js"></script>
diff --git a/website/web/css/prism.css b/website/web/css/prism.css
new file mode 100644
index 0000000..54ca58b
--- /dev/null
+++ b/website/web/css/prism.css
@@ -0,0 +1,179 @@
+/* 
http://prismjs.com/download.html?themes=prism&languages=clike+diff+java&plugins=line-numbers
 */
+/**
+ * prism.js default theme for JavaScript, CSS and HTML
+ * Based on dabblet (http://dabblet.com)
+ * @author Lea Verou
+ */
+
+code[class*="language-"],
+pre[class*="language-"] {
+       color: black;
+       background: none;
+       text-shadow: 0 1px white;
+       font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
+       text-align: left;
+       white-space: pre;
+       word-spacing: normal;
+       word-break: normal;
+       word-wrap: normal;
+       line-height: 1.5;
+
+       -moz-tab-size: 4;
+       -o-tab-size: 4;
+       tab-size: 4;
+
+       -webkit-hyphens: none;
+       -moz-hyphens: none;
+       -ms-hyphens: none;
+       hyphens: none;
+}
+
+pre[class*="language-"]::-moz-selection, pre[class*="language-"] 
::-moz-selection,
+code[class*="language-"]::-moz-selection, code[class*="language-"] 
::-moz-selection {
+       text-shadow: none;
+       background: #b3d4fc;
+}
+
+pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
+code[class*="language-"]::selection, code[class*="language-"] ::selection {
+       text-shadow: none;
+       background: #b3d4fc;
+}
+
+@media print {
+       code[class*="language-"],
+       pre[class*="language-"] {
+               text-shadow: none;
+       }
+}
+
+/* Code blocks */
+pre[class*="language-"] {
+       padding: 1em;
+       margin: .5em 0;
+       overflow: auto;
+}
+
+:not(pre) > code[class*="language-"],
+pre[class*="language-"] {
+       background: #f5f2f0;
+}
+
+/* Inline code */
+:not(pre) > code[class*="language-"] {
+       padding: .1em;
+       border-radius: .3em;
+       white-space: normal;
+}
+
+.token.comment,
+.token.prolog,
+.token.doctype,
+.token.cdata {
+       color: slategray;
+}
+
+.token.punctuation {
+       color: #999;
+}
+
+.namespace {
+       opacity: .7;
+}
+
+.token.property,
+.token.tag,
+.token.boolean,
+.token.number,
+.token.constant,
+.token.symbol,
+.token.deleted {
+       color: #905;
+}
+
+.token.selector,
+.token.attr-name,
+.token.string,
+.token.char,
+.token.builtin,
+.token.inserted {
+       color: #690;
+}
+
+.token.operator,
+.token.entity,
+.token.url,
+.language-css .token.string,
+.style .token.string {
+       color: #a67f59;
+       background: hsla(0, 0%, 100%, .5);
+}
+
+.token.atrule,
+.token.attr-value,
+.token.keyword {
+       color: #07a;
+}
+
+.token.function {
+       color: #DD4A68;
+}
+
+.token.regex,
+.token.important,
+.token.variable {
+       color: #e90;
+}
+
+.token.important,
+.token.bold {
+       font-weight: bold;
+}
+.token.italic {
+       font-style: italic;
+}
+
+.token.entity {
+       cursor: help;
+}
+
+pre.line-numbers {
+       position: relative;
+       padding-left: 3.8em;
+       counter-reset: linenumber;
+}
+
+pre.line-numbers > code {
+       position: relative;
+}
+
+.line-numbers .line-numbers-rows {
+       position: absolute;
+       pointer-events: none;
+       top: 0;
+       font-size: 100%;
+       left: -3.8em;
+       width: 3em; /* works for line-numbers below 1000 lines */
+       letter-spacing: -1px;
+       border-right: 1px solid #999;
+
+       -webkit-user-select: none;
+       -moz-user-select: none;
+       -ms-user-select: none;
+       user-select: none;
+
+}
+
+       .line-numbers-rows > span {
+               pointer-events: none;
+               display: block;
+               counter-increment: linenumber;
+       }
+
+               .line-numbers-rows > span:before {
+                       content: counter(linenumber);
+                       color: #999;
+                       display: block;
+                       padding-right: 0.8em;
+                       text-align: right;
+               }
diff --git a/website/web/css/style.css b/website/web/css/style.css
index 88cc2fa..70764ad 100644
--- a/website/web/css/style.css
+++ b/website/web/css/style.css
@@ -812,4 +812,42 @@ body.noscript #navbar-toggle-checkbox:checked ~ .collapse {
 
 
 
-
+/* code highlighting */
+
+.token.selector,
+.token.attr-name,
+.token.string,
+.token.char,
+.token.builtin,
+.token.property,
+.token.tag,
+.token.boolean,
+.token.number,
+.token.constant,
+.token.symbol,
+.token.deleted,
+.token.function {
+  color: #c7254e; /* red */
+}
+
+.token.atrule,
+.token.attr-value,
+.token.keyword,
+.token.inserted {
+  color: #7d4698; /* purple */
+}
+
+.token.comment,
+.token.prolog,
+.token.doctype,
+.token.cdata {
+  color: #999; /* light gray */
+}
+
+.token.operator,
+.token.entity,
+.token.url,
+.language-css .token.string,
+.style .token.string {
+  color: #000; /* black */
+}
diff --git a/website/web/js/prism.js b/website/web/js/prism.js
new file mode 100644
index 0000000..f2b3e1e
--- /dev/null
+++ b/website/web/js/prism.js
@@ -0,0 +1,6 @@
+/* 
http://prismjs.com/download.html?themes=prism&languages=clike+diff+java&plugins=line-numbers
 */
+var _self="undefined"!=typeof window?window:"undefined"!=typeof 
WorkerGlobalScope&&self instanceof 
WorkerGlobalScope?self:{},Prism=function(){var 
e=/\blang(?:uage)?-(\w+)\b/i,t=0,n=_self.Prism={manual:_self.Prism&&_self.Prism.manual,util:{encode:function(e){return
 e instanceof a?new 
a(e.type,n.util.encode(e.content),e.alias):"Array"===n.util.type(e)?e.map(n.util.encode):e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/\u00a0/g,"
 ")},type:function(e){return Object.prototype.toString.call(e).match(/\[object 
(\w+)\]/)[1]},objId:function(e){return 
e.__id||Object.defineProperty(e,"__id",{value:++t}),e.__id},clone:function(e){var
 t=n.util.type(e);switch(t){case"Object":var a={};for(var r in 
e)e.hasOwnProperty(r)&&(a[r]=n.util.clone(e[r]));return a;case"Array":return 
e.map&&e.map(function(e){return n.util.clone(e)})}return 
e}},languages:{extend:function(e,t){var a=n.util.clone(n.languages[e]);for(var 
r in t)a[r]=t[r];return a},insertBefore:function(e,t,a,r){r=r||n.languages;var 
l=r[e]
 ;if(2==arguments.length){a=arguments[1];for(var i in 
a)a.hasOwnProperty(i)&&(l[i]=a[i]);return l}var o={};for(var s in 
l)if(l.hasOwnProperty(s)){if(s==t)for(var i in 
a)a.hasOwnProperty(i)&&(o[i]=a[i]);o[s]=l[s]}return 
n.languages.DFS(n.languages,function(t,n){n===r[e]&&t!=e&&(this[t]=o)}),r[e]=o},DFS:function(e,t,a,r){r=r||{};for(var
 l in 
e)e.hasOwnProperty(l)&&(t.call(e,l,e[l],a||l),"Object"!==n.util.type(e[l])||r[n.util.objId(e[l])]?"Array"!==n.util.type(e[l])||r[n.util.objId(e[l])]||(r[n.util.objId(e[l])]=!0,n.languages.DFS(e[l],t,l,r)):(r[n.util.objId(e[l])]=!0,n.languages.DFS(e[l],t,null,r)))}},plugins:{},highlightAll:function(e,t){var
 a={callback:t,selector:'code[class*="language-"], [class*="language-"] code, 
code[class*="lang-"], [class*="lang-"] 
code'};n.hooks.run("before-highlightall",a);for(var 
r,l=a.elements||document.querySelectorAll(a.selector),i=0;r=l[i++];)n.highlightElement(r,e===!0,a.callback)},highlightElement:function(t,a,r){for(var
 l,i,o=t;o&&!e.test(o.className
 
);)o=o.parentNode;o&&(l=(o.className.match(e)||[,""])[1].toLowerCase(),i=n.languages[l]),t.className=t.className.replace(e,"").replace(/\s+/g,"
 ")+" 
language-"+l,o=t.parentNode,/pre/i.test(o.nodeName)&&(o.className=o.className.replace(e,"").replace(/\s+/g,"
 ")+" language-"+l);var 
s=t.textContent,u={element:t,language:l,grammar:i,code:s};if(n.hooks.run("before-sanity-check",u),!u.code||!u.grammar)return
 u.code&&(u.element.textContent=u.code),n.hooks.run("complete",u),void 
0;if(n.hooks.run("before-highlight",u),a&&_self.Worker){var g=new 
Worker(n.filename);g.onmessage=function(e){u.highlightedCode=e.data,n.hooks.run("before-insert",u),u.element.innerHTML=u.highlightedCode,r&&r.call(u.element),n.hooks.run("after-highlight",u),n.hooks.run("complete",u)},g.postMessage(JSON.stringify({language:u.language,code:u.code,immediateClose:!0}))}else
 
u.highlightedCode=n.highlight(u.code,u.grammar,u.language),n.hooks.run("before-insert",u),u.element.innerHTML=u.highlightedCode,r&&r.call(t),n.hooks.
 
run("after-highlight",u),n.hooks.run("complete",u)},highlight:function(e,t,r){var
 l=n.tokenize(e,t);return 
a.stringify(n.util.encode(l),r)},tokenize:function(e,t){var 
a=n.Token,r=[e],l=t.rest;if(l){for(var i in l)t[i]=l[i];delete t.rest}e:for(var 
i in t)if(t.hasOwnProperty(i)&&t[i]){var 
o=t[i];o="Array"===n.util.type(o)?o:[o];for(var s=0;s<o.length;++s){var 
u=o[s],g=u.inside,c=!!u.lookbehind,h=!!u.greedy,f=0,d=u.alias;if(h&&!u.pattern.global){var
 
p=u.pattern.toString().match(/[imuy]*$/)[0];u.pattern=RegExp(u.pattern.source,p+"g")}u=u.pattern||u;for(var
 m=0,y=0;m<r.length;y+=r[m].length,++m){var v=r[m];if(r.length>e.length)break 
e;if(!(v instanceof a)){u.lastIndex=0;var 
b=u.exec(v),k=1;if(!b&&h&&m!=r.length-1){if(u.lastIndex=y,b=u.exec(e),!b)break;for(var
 
w=b.index+(c?b[1].length:0),_=b.index+b[0].length,P=m,A=y,j=r.length;j>P&&_>A;++P)A+=r[P].length,w>=A&&(++m,y=A);if(r[m]instanceof
 
a||r[P-1].greedy)continue;k=P-m,v=e.slice(y,A),b.index-=y}if(b){c&&(f=b[1].length);var
 w=b.index+f,b=
 
b[0].slice(f),_=w+b.length,x=v.slice(0,w),O=v.slice(_),S=[m,k];x&&S.push(x);var 
N=new 
a(i,g?n.tokenize(b,g):b,d,b,h);S.push(N),O&&S.push(O),Array.prototype.splice.apply(r,S)}}}}}return
 r},hooks:{all:{},add:function(e,t){var 
a=n.hooks.all;a[e]=a[e]||[],a[e].push(t)},run:function(e,t){var 
a=n.hooks.all[e];if(a&&a.length)for(var 
r,l=0;r=a[l++];)r(t)}}},a=n.Token=function(e,t,n,a,r){this.type=e,this.content=t,this.alias=n,this.length=0|(a||"").length,this.greedy=!!r};if(a.stringify=function(e,t,r){if("string"==typeof
 e)return e;if("Array"===n.util.type(e))return e.map(function(n){return 
a.stringify(n,t,e)}).join("");var 
l={type:e.type,content:a.stringify(e.content,t,r),tag:"span",classes:["token",e.type],attributes:{},language:t,parent:r};if("comment"==l.type&&(l.attributes.spellcheck="true"),e.alias){var
 
i="Array"===n.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(l.classes,i)}n.hooks.run("wrap",l);var
 o=Object.keys(l.attributes).map(function(e){return e+'="'+(l.attrib
 utes[e]||"").replace(/"/g,"&quot;")+'"'}).join(" ");return"<"+l.tag+' 
class="'+l.classes.join(" ")+'"'+(o?" 
"+o:"")+">"+l.content+"</"+l.tag+">"},!_self.document)return 
_self.addEventListener?(_self.addEventListener("message",function(e){var 
t=JSON.parse(e.data),a=t.language,r=t.code,l=t.immediateClose;_self.postMessage(n.highlight(r,n.languages[a],a)),l&&_self.close()},!1),_self.Prism):_self.Prism;var
 
r=document.currentScript||[].slice.call(document.getElementsByTagName("script")).pop();return
 
r&&(n.filename=r.src,!document.addEventListener||n.manual||r.hasAttribute("data-manual")||("loading"!==document.readyState?window.requestAnimationFrame?window.requestAnimationFrame(n.highlightAll):window.setTimeout(n.highlightAll,16):document.addEventListener("DOMContentLoaded",n.highlightAll))),_self.Prism}();"undefined"!=typeof
 module&&module.exports&&(module.exports=Prism),"undefined"!=typeof 
global&&(global.Prism=Prism);
+Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\w\W]*?\*\//,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0}],string:{pattern:/(["'])(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/i,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,"boolean":/\b(true|false)\b/,"function":/[a-z0-9_]+(?=\()/i,number:/\b-?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)\b/i,operator:/--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/,punctuation:/[{}[\];(),.:]/};
+Prism.languages.diff={coord:[/^(?:\*{3}|-{3}|\+{3}).*$/m,/^@@.*@@$/m,/^\d+.*$/m],deleted:/^[-<].*$/m,inserted:/^[+>].*$/m,diff:{pattern:/^!(?!!).+$/m,alias:"important"}};
+Prism.languages.java=Prism.languages.extend("clike",{keyword:/\b(abstract|continue|for|new|switch|assert|default|goto|package|synchronized|boolean|do|if|private|this|break|double|implements|protected|throw|byte|else|import|public|throws|case|enum|instanceof|return|transient|catch|extends|int|short|try|char|final|interface|static|void|class|finally|long|strictfp|volatile|const|float|native|super|while)\b/,number:/\b0b[01]+\b|\b0x[\da-f]*\.?[\da-fp\-]+\b|\b\d*\.?\d+(?:e[+-]?\d+)?[df]?\b/i,operator:{pattern:/(^|[^.])(?:\+[+=]?|-[-=]?|!=?|<<?=?|>>?>?=?|==?|&[&=]?|\|[|=]?|\*=?|\/=?|%=?|\^=?|[?:~])/m,lookbehind:!0}}),Prism.languages.insertBefore("java","function",{annotation:{alias:"punctuation",pattern:/(^|[^.])@\w+/,lookbehind:!0}});
+!function(){"undefined"!=typeof 
self&&self.Prism&&self.document&&Prism.hooks.add("complete",function(e){if(e.code){var
 
t=e.element.parentNode,s=/\s*\bline-numbers\b\s*/;if(t&&/pre/i.test(t.nodeName)&&(s.test(t.className)||s.test(e.element.className))&&!e.element.querySelector(".line-numbers-rows")){s.test(e.element.className)&&(e.element.className=e.element.className.replace(s,"")),s.test(t.className)||(t.className+="
 line-numbers");var n,a=e.code.match(/\n(?!$)/g),l=a?a.length+1:1,r=new 
Array(l+1);r=r.join("<span></span>"),n=document.createElement("span"),n.setAttribute("aria-hidden","true"),n.className="line-numbers-rows",n.innerHTML=r,t.hasAttribute("data-start")&&(t.style.counterReset="linenumber
 
"+(parseInt(t.getAttribute("data-start"),10)-1)),e.element.appendChild(n)}}})}();

_______________________________________________
tor-commits mailing list
[email protected]
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits

Reply via email to