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 – 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<String, Long> bandwidthByVersion = new TreeMap<>();
+
+ // 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<DescriptorFile> 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() < 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 > 0L) {
+ for (Map.Entry<String, Long> e : bandwidthByVersion.entrySet()) {
+ System.out.printf("%s -> %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 -> 4.2%
+0.2.5 -> 9.4%
+0.2.6 -> 6.2%
+0.2.7 -> 8.3%
+0.2.8 -> 15.0%
+0.2.9 -> 46.7%
+0.3.0 -> 9.4%
+0.3.1 -> 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<String> observedFingerprints = new HashSet<>();
+ SortedMap<String, Integer> countedTransports = new TreeMap<>();
+
+ DescriptorReader descriptorReader =
DescriptorSourceFactory.createDescriptorReader();
+ descriptorReader.addDirectory(new
File("descriptors/recent/bridge-descriptors/extra-infos"));
+ Iterator<DescriptorFile> 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<String, Integer> e : countedTransports.entrySet()) {
+ System.out.printf("%20s -> %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<>(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,"&").replace(/</g,"<").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,""")+'"'}).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