Initial upload
This commit is contained in:
commit
82bea0f74a
24
LICENSE
Normal file
24
LICENSE
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
Copyright (C) 2017 Özgür Kesim <oec-github@kesim.org>
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
8
READMNE.md
Normal file
8
READMNE.md
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# playdot - A simple playground for dpic and dot
|
||||||
|
|
||||||
|
A simple webservice that displays an editor for dpic- and dot-input and shows
|
||||||
|
the SVG-output or error messages below it. Allows for sharing of links to the
|
||||||
|
input.
|
||||||
|
|
||||||
|
You must install dpic from https://ece.uwaterloo.ca/~aplevich/dpic and graphviz
|
||||||
|
first before you can use godpicdot.
|
222
index.html
Normal file
222
index.html
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<!-- Author: Özgür Kesim <oec-go@kesim.org> 2017 -->
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
{{ $name := .Cur.Name }}
|
||||||
|
<title>playdot - {{$name}} online</title>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/static/codemirror.css">
|
||||||
|
<script src="/static/codemirror.js"></script>
|
||||||
|
<script src="/static/{{$name}}.js"></script>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
height: 100vh;
|
||||||
|
width: 100%;
|
||||||
|
margin:0;
|
||||||
|
overflow:hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#top {
|
||||||
|
padding:10px;
|
||||||
|
margin:0px;
|
||||||
|
font-size:20px;
|
||||||
|
font-family:Sans-serif;
|
||||||
|
background: {{ .Cur.BgColor }};
|
||||||
|
}
|
||||||
|
|
||||||
|
#source {
|
||||||
|
height: 40vh;
|
||||||
|
width: 100%;
|
||||||
|
overflow:auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#run {
|
||||||
|
margin: 10pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
#doc {
|
||||||
|
margin: 20px;
|
||||||
|
font-size: 9pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
#output {
|
||||||
|
width: 100%;
|
||||||
|
height: 50vh;
|
||||||
|
overflow:auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.on {
|
||||||
|
background:white;
|
||||||
|
color:{{.Cur.BgColor}};
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
padding: 5px;
|
||||||
|
// border: 2px solid lime;
|
||||||
|
}
|
||||||
|
|
||||||
|
#link {
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
background-color: {{ .Cur.BgColor }};
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
padding: 7px 13px;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 16px;
|
||||||
|
margin: 4px 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
background-color: lightsalmon;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="top">Tools: {{ range .Tools }}
|
||||||
|
{{ if eq .Name $name }} <span class="on">{{$name}}</span> {{ else }} <a href="/{{.Name}}">{{.Name}}</a> {{ end }} |
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
<!--
|
||||||
|
TODO:
|
||||||
|
- add download-button for output in other formats
|
||||||
|
-->
|
||||||
|
<textarea name="source" id="source">
|
||||||
|
{{ .Cur.Example }}
|
||||||
|
</textarea>
|
||||||
|
<div id="mid">
|
||||||
|
<button id="run" class="button" onclick="run()" title="or ctrl-enter in editor">run</button>
|
||||||
|
<button id="save" class="button" onclick="share()">share</button><span id="link"></span>
|
||||||
|
<input id="file" type="file" name="file" class="button"/>
|
||||||
|
<input id="scale" type ="range" min ="0.5" max="3" step ="0.1" value ="1"/>
|
||||||
|
<span id="doc">
|
||||||
|
Documentation:
|
||||||
|
{{ range $link, $text := .Cur.Documentation }}
|
||||||
|
<a href="{{$link}}" target="_blank">{{$text}}</a> /
|
||||||
|
{{ end }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div id="output"></div>
|
||||||
|
<script>
|
||||||
|
var output = document.getElementById("output");
|
||||||
|
var source = document.getElementById("source");
|
||||||
|
var link = document.getElementById("link");
|
||||||
|
var filein = document.getElementById("file");
|
||||||
|
var scale = document.getElementById("scale");
|
||||||
|
|
||||||
|
|
||||||
|
var editor = CodeMirror.fromTextArea(source, {
|
||||||
|
lineNumbers: true,
|
||||||
|
smartIndent: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
window.onkeydown=function(e) {
|
||||||
|
if (e.ctrlKey && e.key == "Enter") {
|
||||||
|
e.preventDefault();
|
||||||
|
run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filein.onchange=function() {
|
||||||
|
var file = filein.files[0];
|
||||||
|
var reader = new FileReader();
|
||||||
|
reader.onload = function (e) {
|
||||||
|
editor.setValue(e.target.result);
|
||||||
|
};
|
||||||
|
reader.readAsText(file);
|
||||||
|
window.location.hash="";
|
||||||
|
}
|
||||||
|
|
||||||
|
scale.onchange=function() {
|
||||||
|
var svg=output.querySelector("svg");
|
||||||
|
var sc = scale.valueAsNumber;
|
||||||
|
if (svg) {
|
||||||
|
svg.height.baseVal.value = svg.viewBox.baseVal.height*sc;
|
||||||
|
svg.width.baseVal.value = svg.viewBox.baseVal.width*sc;
|
||||||
|
console.log("scale:", sc, "svg:", svg.height.baseVal.value, svg.width.baseVal.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onhashchange = hashloc;
|
||||||
|
|
||||||
|
|
||||||
|
function run() {
|
||||||
|
var data = editor.getValue().trim();
|
||||||
|
if (!data) return;
|
||||||
|
link.innerHtml = "";
|
||||||
|
var req = new XMLHttpRequest();
|
||||||
|
req.open("POST", "/{{$name}}/c", false);
|
||||||
|
req.onreadystatechange = function() {
|
||||||
|
switch (req.status) {
|
||||||
|
case 200:
|
||||||
|
output.innerHTML = req.responseText;
|
||||||
|
break;
|
||||||
|
case 400:
|
||||||
|
output.innerHTML = '<pre class="error">'+req.responseText+'</pre>';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
req.send(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function share() {
|
||||||
|
var data = editor.getValue();
|
||||||
|
var req = new XMLHttpRequest();
|
||||||
|
req.open("POST", "/{{$name}}/s", false);
|
||||||
|
req.onreadystatechange = function() {
|
||||||
|
switch (req.status) {
|
||||||
|
case 200:
|
||||||
|
link.innerHTML = "<i>saved as</i> <a href=\"/{{$name}}/#"+req.responseText+"!\">#"+req.responseText+"!</a>";
|
||||||
|
document.title= "{{$name}}#"+req.responseText;
|
||||||
|
document.location.hash=req.responseText+"!";
|
||||||
|
break;
|
||||||
|
case 400:
|
||||||
|
output.innerHTML = '<pre class="error">'+req.responseText+'</pre>';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
req.send(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function load(id) {
|
||||||
|
link.innerHtml = "";
|
||||||
|
var req = new XMLHttpRequest();
|
||||||
|
req.open("GET", "/{{$name}}/l/"+id, false);
|
||||||
|
req.onreadystatechange = function() {
|
||||||
|
switch (req.status) {
|
||||||
|
case 200:
|
||||||
|
editor.setValue(req.responseText);
|
||||||
|
break;
|
||||||
|
case 400:
|
||||||
|
editor.setValue("");
|
||||||
|
output.innerHTML = '<pre class="error">'+req.responseText+'</pre>';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
req.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
function hashloc() {
|
||||||
|
var hash = window.location.hash;
|
||||||
|
if (hash) {
|
||||||
|
var parts = (hash.split('#')[1]).split('!');
|
||||||
|
var id = parts[0];
|
||||||
|
if (id) {
|
||||||
|
load(id);
|
||||||
|
document.title="{{$name}}#"+id;
|
||||||
|
if (parts.length > 1) run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hashloc();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
167
main.go
Normal file
167
main.go
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"html/template"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
tls = flag.String("t", ":8443", "[ip]:port to tls-listen to")
|
||||||
|
nontls = flag.String("l", "", "optional, non-tls [ip]:port to listen to")
|
||||||
|
config = flag.String("cfg", "tools.json", "config file with tool-definitions")
|
||||||
|
cert = flag.String("cert", "cert.pem", "certitifate")
|
||||||
|
key = flag.String("key", "key.pem", "key")
|
||||||
|
savedir = flag.String("d", "saved", "direcotry to save the pics.")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Tool struct {
|
||||||
|
Name string
|
||||||
|
Cmd string
|
||||||
|
Args []string
|
||||||
|
Suffix string
|
||||||
|
Description string
|
||||||
|
Documentation map[string]string
|
||||||
|
Example string
|
||||||
|
BgColor string
|
||||||
|
}
|
||||||
|
|
||||||
|
var tools = []Tool{}
|
||||||
|
|
||||||
|
func (t Tool) execute(in io.Reader, w http.ResponseWriter) {
|
||||||
|
var cmd = exec.Command(t.Cmd, t.Args...) // Call to dot with SVG-output in safe-mode
|
||||||
|
cmd.Stdin = in
|
||||||
|
err, buf := &bytes.Buffer{}, &bytes.Buffer{}
|
||||||
|
cmd.Stderr = err
|
||||||
|
cmd.Stdout = buf
|
||||||
|
if e := cmd.Run(); e == nil {
|
||||||
|
io.Copy(w, buf)
|
||||||
|
} else {
|
||||||
|
log.Printf("%s returned error\n", t.Name)
|
||||||
|
http.Error(w, err.String(), http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Tool) compile() func(http.ResponseWriter, *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
t.execute(r.Body, w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Tool) svg() func(http.ResponseWriter, *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
name := filepath.Base(r.URL.Path)
|
||||||
|
if file, err := os.Open(filepath.Join(*savedir, name+t.Suffix)); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
http.Error(w, "couldn't open file", http.StatusBadRequest)
|
||||||
|
} else {
|
||||||
|
defer file.Close()
|
||||||
|
t.execute(file, w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxSnippetSize = 64 * 1024
|
||||||
|
|
||||||
|
func (t Tool) save() func(http.ResponseWriter, *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
body, err := ioutil.ReadAll(io.LimitReader(r.Body, maxSnippetSize))
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
http.Error(w, "body too large", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.Body.Close()
|
||||||
|
|
||||||
|
// Create a filename taking the first 10 characters of the sha1-sum of
|
||||||
|
// content. Based on code from the golang-playground.
|
||||||
|
h := sha1.New()
|
||||||
|
io.Copy(h, bytes.NewBuffer(body))
|
||||||
|
sum := h.Sum(nil)
|
||||||
|
b := make([]byte, base64.URLEncoding.EncodedLen(len(sum)))
|
||||||
|
base64.URLEncoding.Encode(b, sum)
|
||||||
|
name := string(b)[:10]
|
||||||
|
|
||||||
|
// Write snippet to file. TODO: Shall we return error if file exists?
|
||||||
|
if file, err := os.Create(filepath.Join(*savedir, name+t.Suffix)); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
http.Error(w, "couldn't create file", http.StatusInternalServerError)
|
||||||
|
} else if err = file.Chmod(0644); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
http.Error(w, "couldn't setup file", http.StatusInternalServerError)
|
||||||
|
} else if _, err = io.Copy(file, bytes.NewBuffer(body)); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
http.Error(w, "couldn't write file", http.StatusInternalServerError)
|
||||||
|
} else if err = file.Close(); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
http.Error(w, "couldn't close file", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
w.Write([]byte(name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Tool) load() func(http.ResponseWriter, *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
name := filepath.Base(r.URL.Path)
|
||||||
|
if file, err := os.Open(filepath.Join(*savedir, name+t.Suffix)); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
http.Error(w, "couldn't open file", http.StatusBadRequest)
|
||||||
|
} else {
|
||||||
|
defer file.Close()
|
||||||
|
io.Copy(w, file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Tool) index() func(http.ResponseWriter, *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if tmpl, err := template.ParseFiles("index.html"); err != nil {
|
||||||
|
log.Printf("error parsing index.html: %v", err)
|
||||||
|
http.Error(w, "internal error", http.StatusInternalServerError)
|
||||||
|
} else if err = tmpl.ExecuteTemplate(w, "index.html", map[string]interface{}{"Tools": tools, "Cur": t}); err != nil {
|
||||||
|
log.Printf("error executing template for %s: %v", t.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if cfg, err := os.Open(*config); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
} else if err = json.NewDecoder(cfg).Decode(&tools); err != nil {
|
||||||
|
log.Fatalf("error loading %s: %v\n", *config, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tool := range tools {
|
||||||
|
pre := "/" + tool.Name + "/"
|
||||||
|
http.HandleFunc(pre+"c", tool.compile())
|
||||||
|
http.HandleFunc(pre+"s", tool.save())
|
||||||
|
http.HandleFunc(pre+"l/", tool.load())
|
||||||
|
http.HandleFunc(pre+"svg/", tool.svg())
|
||||||
|
http.HandleFunc(pre, tool.index())
|
||||||
|
log.Println("handler for", pre, "registered")
|
||||||
|
}
|
||||||
|
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
|
||||||
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
http.Redirect(w, r, "/"+tools[0].Name, http.StatusFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(*nontls) > 0 {
|
||||||
|
log.Println("listening non-tls on", *nontls)
|
||||||
|
log.Fatal(http.ListenAndServe(*nontls, nil))
|
||||||
|
}
|
||||||
|
log.Println("listening tls on", *tls)
|
||||||
|
log.Fatal(http.ListenAndServeTLS(*tls, *cert, *key, nil))
|
||||||
|
}
|
14
saved/0rfrbnTjeZ.dot
Normal file
14
saved/0rfrbnTjeZ.dot
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
digraph test123 {
|
||||||
|
a -> b -> c;
|
||||||
|
a -> {x y};
|
||||||
|
b [shape=box];
|
||||||
|
c [label="hello\nworld",color=blue,fontsize=13,
|
||||||
|
fontname="Palatino-Italic",fontcolor=red,style=filled];
|
||||||
|
a -> z [label="hi", weight=100];
|
||||||
|
x -> z [label="multi-line\nlabel"];
|
||||||
|
edge [style=dashed,color=red];
|
||||||
|
b -> x;
|
||||||
|
{rank=same; b x}
|
||||||
|
}
|
||||||
|
|
7
saved/Qv0BzgZVII.pic
Normal file
7
saved/Qv0BzgZVII.pic
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
.PS
|
||||||
|
|
||||||
|
box "foo"; arrow ->; box "bar"
|
||||||
|
|
||||||
|
.PE
|
||||||
|
|
21
static/LICENSE-codemirror
Normal file
21
static/LICENSE-codemirror
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (C) 2017 by Marijn Haverbeke <marijnh@gmail.com> and others
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
341
static/codemirror.css
Normal file
341
static/codemirror.css
Normal file
@ -0,0 +1,341 @@
|
|||||||
|
/* BASICS */
|
||||||
|
|
||||||
|
.CodeMirror {
|
||||||
|
/* Set height, width, borders, and global font properties here */
|
||||||
|
font-family: monospace;
|
||||||
|
height: 40%;
|
||||||
|
color: black;
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* PADDING */
|
||||||
|
|
||||||
|
.CodeMirror-lines {
|
||||||
|
padding: 4px 0; /* Vertical padding around content */
|
||||||
|
}
|
||||||
|
.CodeMirror pre {
|
||||||
|
padding: 0 4px; /* Horizontal padding of content */
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||||
|
background-color: white; /* The little square between H and V scrollbars */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GUTTER */
|
||||||
|
|
||||||
|
.CodeMirror-gutters {
|
||||||
|
border-right: 1px solid #ddd;
|
||||||
|
background-color: #f7f7f7;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.CodeMirror-linenumbers {}
|
||||||
|
.CodeMirror-linenumber {
|
||||||
|
padding: 0 3px 0 5px;
|
||||||
|
min-width: 20px;
|
||||||
|
text-align: right;
|
||||||
|
color: #999;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-guttermarker { color: black; }
|
||||||
|
.CodeMirror-guttermarker-subtle { color: #999; }
|
||||||
|
|
||||||
|
/* CURSOR */
|
||||||
|
|
||||||
|
.CodeMirror-cursor {
|
||||||
|
border-left: 1px solid black;
|
||||||
|
border-right: none;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
/* Shown when moving in bi-directional text */
|
||||||
|
.CodeMirror div.CodeMirror-secondarycursor {
|
||||||
|
border-left: 1px solid silver;
|
||||||
|
}
|
||||||
|
.cm-fat-cursor .CodeMirror-cursor {
|
||||||
|
width: auto;
|
||||||
|
border: 0 !important;
|
||||||
|
background: #7e7;
|
||||||
|
}
|
||||||
|
.cm-fat-cursor div.CodeMirror-cursors {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cm-animate-fat-cursor {
|
||||||
|
width: auto;
|
||||||
|
border: 0;
|
||||||
|
-webkit-animation: blink 1.06s steps(1) infinite;
|
||||||
|
-moz-animation: blink 1.06s steps(1) infinite;
|
||||||
|
animation: blink 1.06s steps(1) infinite;
|
||||||
|
background-color: #7e7;
|
||||||
|
}
|
||||||
|
@-moz-keyframes blink {
|
||||||
|
0% {}
|
||||||
|
50% { background-color: transparent; }
|
||||||
|
100% {}
|
||||||
|
}
|
||||||
|
@-webkit-keyframes blink {
|
||||||
|
0% {}
|
||||||
|
50% { background-color: transparent; }
|
||||||
|
100% {}
|
||||||
|
}
|
||||||
|
@keyframes blink {
|
||||||
|
0% {}
|
||||||
|
50% { background-color: transparent; }
|
||||||
|
100% {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Can style cursor different in overwrite (non-insert) mode */
|
||||||
|
.CodeMirror-overwrite .CodeMirror-cursor {}
|
||||||
|
|
||||||
|
.cm-tab { display: inline-block; text-decoration: inherit; }
|
||||||
|
|
||||||
|
.CodeMirror-rulers {
|
||||||
|
position: absolute;
|
||||||
|
left: 0; right: 0; top: -50px; bottom: -20px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.CodeMirror-ruler {
|
||||||
|
border-left: 1px solid #ccc;
|
||||||
|
top: 0; bottom: 0;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* DEFAULT THEME */
|
||||||
|
|
||||||
|
.cm-s-default .cm-header {color: blue;}
|
||||||
|
.cm-s-default .cm-quote {color: #090;}
|
||||||
|
.cm-negative {color: #d44;}
|
||||||
|
.cm-positive {color: #292;}
|
||||||
|
.cm-header, .cm-strong {font-weight: bold;}
|
||||||
|
.cm-em {font-style: italic;}
|
||||||
|
.cm-link {text-decoration: underline;}
|
||||||
|
.cm-strikethrough {text-decoration: line-through;}
|
||||||
|
|
||||||
|
.cm-s-default .cm-keyword {color: #708;}
|
||||||
|
.cm-s-default .cm-atom {color: #219;}
|
||||||
|
.cm-s-default .cm-number {color: #164;}
|
||||||
|
.cm-s-default .cm-def {color: #00f;}
|
||||||
|
.cm-s-default .cm-variable,
|
||||||
|
.cm-s-default .cm-punctuation,
|
||||||
|
.cm-s-default .cm-property,
|
||||||
|
.cm-s-default .cm-operator {}
|
||||||
|
.cm-s-default .cm-variable-2 {color: #05a;}
|
||||||
|
.cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;}
|
||||||
|
.cm-s-default .cm-comment {color: #a50;}
|
||||||
|
.cm-s-default .cm-string {color: #a11;}
|
||||||
|
.cm-s-default .cm-string-2 {color: #f50;}
|
||||||
|
.cm-s-default .cm-meta {color: #555;}
|
||||||
|
.cm-s-default .cm-qualifier {color: #555;}
|
||||||
|
.cm-s-default .cm-builtin {color: #30a;}
|
||||||
|
.cm-s-default .cm-bracket {color: #997;}
|
||||||
|
.cm-s-default .cm-tag {color: #170;}
|
||||||
|
.cm-s-default .cm-attribute {color: #00c;}
|
||||||
|
.cm-s-default .cm-hr {color: #999;}
|
||||||
|
.cm-s-default .cm-link {color: #00c;}
|
||||||
|
|
||||||
|
.cm-s-default .cm-error {color: #f00;}
|
||||||
|
.cm-invalidchar {color: #f00;}
|
||||||
|
|
||||||
|
.CodeMirror-composing { border-bottom: 2px solid; }
|
||||||
|
|
||||||
|
/* Default styles for common addons */
|
||||||
|
|
||||||
|
div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
|
||||||
|
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
|
||||||
|
.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
|
||||||
|
.CodeMirror-activeline-background {background: #e8f2ff;}
|
||||||
|
|
||||||
|
/* STOP */
|
||||||
|
|
||||||
|
/* The rest of this file contains styles related to the mechanics of
|
||||||
|
the editor. You probably shouldn't touch them. */
|
||||||
|
|
||||||
|
.CodeMirror {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-scroll {
|
||||||
|
overflow: scroll !important; /* Things will break if this is overridden */
|
||||||
|
/* 30px is the magic margin used to hide the element's real scrollbars */
|
||||||
|
/* See overflow: hidden in .CodeMirror */
|
||||||
|
margin-bottom: -30px; margin-right: -30px;
|
||||||
|
padding-bottom: 30px;
|
||||||
|
height: 100%;
|
||||||
|
outline: none; /* Prevent dragging from highlighting the element */
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.CodeMirror-sizer {
|
||||||
|
position: relative;
|
||||||
|
border-right: 30px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The fake, visible scrollbars. Used to force redraw during scrolling
|
||||||
|
before actual scrolling happens, thus preventing shaking and
|
||||||
|
flickering artifacts. */
|
||||||
|
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 6;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.CodeMirror-vscrollbar {
|
||||||
|
right: 0; top: 0;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
.CodeMirror-hscrollbar {
|
||||||
|
bottom: 0; left: 0;
|
||||||
|
overflow-y: hidden;
|
||||||
|
overflow-x: scroll;
|
||||||
|
}
|
||||||
|
.CodeMirror-scrollbar-filler {
|
||||||
|
right: 0; bottom: 0;
|
||||||
|
}
|
||||||
|
.CodeMirror-gutter-filler {
|
||||||
|
left: 0; bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-gutters {
|
||||||
|
position: absolute; left: 0; top: 0;
|
||||||
|
min-height: 100%;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
.CodeMirror-gutter {
|
||||||
|
white-space: normal;
|
||||||
|
height: 100%;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
margin-bottom: -30px;
|
||||||
|
}
|
||||||
|
.CodeMirror-gutter-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 4;
|
||||||
|
background: none !important;
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
.CodeMirror-gutter-background {
|
||||||
|
position: absolute;
|
||||||
|
top: 0; bottom: 0;
|
||||||
|
z-index: 4;
|
||||||
|
}
|
||||||
|
.CodeMirror-gutter-elt {
|
||||||
|
position: absolute;
|
||||||
|
cursor: default;
|
||||||
|
z-index: 4;
|
||||||
|
}
|
||||||
|
.CodeMirror-gutter-wrapper ::selection { background-color: transparent }
|
||||||
|
.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
|
||||||
|
|
||||||
|
.CodeMirror-lines {
|
||||||
|
cursor: text;
|
||||||
|
min-height: 1px; /* prevents collapsing before first draw */
|
||||||
|
}
|
||||||
|
.CodeMirror pre {
|
||||||
|
/* Reset some styles that the rest of the page might have set */
|
||||||
|
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
|
||||||
|
border-width: 0;
|
||||||
|
background: transparent;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
margin: 0;
|
||||||
|
white-space: pre;
|
||||||
|
word-wrap: normal;
|
||||||
|
line-height: inherit;
|
||||||
|
color: inherit;
|
||||||
|
z-index: 2;
|
||||||
|
position: relative;
|
||||||
|
overflow: visible;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
-webkit-font-variant-ligatures: contextual;
|
||||||
|
font-variant-ligatures: contextual;
|
||||||
|
}
|
||||||
|
.CodeMirror-wrap pre {
|
||||||
|
word-wrap: break-word;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-linebackground {
|
||||||
|
position: absolute;
|
||||||
|
left: 0; right: 0; top: 0; bottom: 0;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-linewidget {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-widget {}
|
||||||
|
|
||||||
|
.CodeMirror-rtl pre { direction: rtl; }
|
||||||
|
|
||||||
|
.CodeMirror-code {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Force content-box sizing for the elements where we expect it */
|
||||||
|
.CodeMirror-scroll,
|
||||||
|
.CodeMirror-sizer,
|
||||||
|
.CodeMirror-gutter,
|
||||||
|
.CodeMirror-gutters,
|
||||||
|
.CodeMirror-linenumber {
|
||||||
|
-moz-box-sizing: content-box;
|
||||||
|
box-sizing: content-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-measure {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-cursor {
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.CodeMirror-measure pre { position: static; }
|
||||||
|
|
||||||
|
div.CodeMirror-cursors {
|
||||||
|
visibility: hidden;
|
||||||
|
position: relative;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
div.CodeMirror-dragcursors {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-focused div.CodeMirror-cursors {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-selected { background: #d9d9d9; }
|
||||||
|
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
|
||||||
|
.CodeMirror-crosshair { cursor: crosshair; }
|
||||||
|
.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
|
||||||
|
.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
|
||||||
|
|
||||||
|
.cm-searching {
|
||||||
|
background-color: #ffa;
|
||||||
|
background-color: rgba(255, 255, 0, .4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Used to force a border model for a node */
|
||||||
|
.cm-force-border { padding-right: .1px; }
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
/* Hide the cursor when printing */
|
||||||
|
.CodeMirror div.CodeMirror-cursors {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* See issue #2901 */
|
||||||
|
.cm-tab-wrap-hack:after { content: ''; }
|
||||||
|
|
||||||
|
/* Help users use markselection to safely style text background */
|
||||||
|
span.CodeMirror-selectedtext { background: none; }
|
9622
static/codemirror.js
Normal file
9622
static/codemirror.js
Normal file
File diff suppressed because it is too large
Load Diff
84
static/dot.js
Normal file
84
static/dot.js
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||||
|
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports == "object" && typeof module == "object")
|
||||||
|
mod(require("../../lib/codemirror"));
|
||||||
|
else if (typeof define == "function" && define.amd)
|
||||||
|
define(["../../lib/codemirror"], mod);
|
||||||
|
else
|
||||||
|
mod(CodeMirror);
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
CodeMirror.defineMode('troff', function() {
|
||||||
|
|
||||||
|
var words = {};
|
||||||
|
|
||||||
|
function tokenBase(stream) {
|
||||||
|
if (stream.eatSpace()) return null;
|
||||||
|
|
||||||
|
var sol = stream.sol();
|
||||||
|
var ch = stream.next();
|
||||||
|
|
||||||
|
if (ch === '\\') {
|
||||||
|
if (stream.match('fB') || stream.match('fR') || stream.match('fI') ||
|
||||||
|
stream.match('u') || stream.match('d') ||
|
||||||
|
stream.match('%') || stream.match('&')) {
|
||||||
|
return 'string';
|
||||||
|
}
|
||||||
|
if (stream.match('m[')) {
|
||||||
|
stream.skipTo(']');
|
||||||
|
stream.next();
|
||||||
|
return 'string';
|
||||||
|
}
|
||||||
|
if (stream.match('s+') || stream.match('s-')) {
|
||||||
|
stream.eatWhile(/[\d-]/);
|
||||||
|
return 'string';
|
||||||
|
}
|
||||||
|
if (stream.match('\(') || stream.match('*\(')) {
|
||||||
|
stream.eatWhile(/[\w-]/);
|
||||||
|
return 'string';
|
||||||
|
}
|
||||||
|
return 'string';
|
||||||
|
}
|
||||||
|
if (sol && (ch === '.' || ch === '\'')) {
|
||||||
|
if (stream.eat('\\') && stream.eat('\"')) {
|
||||||
|
stream.skipToEnd();
|
||||||
|
return 'comment';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sol && ch === '.') {
|
||||||
|
if (stream.match('B ') || stream.match('I ') || stream.match('R ')) {
|
||||||
|
return 'attribute';
|
||||||
|
}
|
||||||
|
if (stream.match('TH ') || stream.match('SH ') || stream.match('SS ') || stream.match('HP ')) {
|
||||||
|
stream.skipToEnd();
|
||||||
|
return 'quote';
|
||||||
|
}
|
||||||
|
if ((stream.match(/[A-Z]/) && stream.match(/[A-Z]/)) || (stream.match(/[a-z]/) && stream.match(/[a-z]/))) {
|
||||||
|
return 'attribute';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stream.eatWhile(/[\w-]/);
|
||||||
|
var cur = stream.current();
|
||||||
|
return words.hasOwnProperty(cur) ? words[cur] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function tokenize(stream, state) {
|
||||||
|
return (state.tokens[0] || tokenBase) (stream, state);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
startState: function() {return {tokens:[]};},
|
||||||
|
token: function(stream, state) {
|
||||||
|
return tokenize(stream, state);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
CodeMirror.defineMIME('text/troff', 'troff');
|
||||||
|
CodeMirror.defineMIME('text/x-troff', 'troff');
|
||||||
|
CodeMirror.defineMIME('application/x-troff', 'troff');
|
||||||
|
|
||||||
|
});
|
84
static/dpic.js
Normal file
84
static/dpic.js
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||||
|
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports == "object" && typeof module == "object")
|
||||||
|
mod(require("../../lib/codemirror"));
|
||||||
|
else if (typeof define == "function" && define.amd)
|
||||||
|
define(["../../lib/codemirror"], mod);
|
||||||
|
else
|
||||||
|
mod(CodeMirror);
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
CodeMirror.defineMode('troff', function() {
|
||||||
|
|
||||||
|
var words = {};
|
||||||
|
|
||||||
|
function tokenBase(stream) {
|
||||||
|
if (stream.eatSpace()) return null;
|
||||||
|
|
||||||
|
var sol = stream.sol();
|
||||||
|
var ch = stream.next();
|
||||||
|
|
||||||
|
if (ch === '\\') {
|
||||||
|
if (stream.match('fB') || stream.match('fR') || stream.match('fI') ||
|
||||||
|
stream.match('u') || stream.match('d') ||
|
||||||
|
stream.match('%') || stream.match('&')) {
|
||||||
|
return 'string';
|
||||||
|
}
|
||||||
|
if (stream.match('m[')) {
|
||||||
|
stream.skipTo(']');
|
||||||
|
stream.next();
|
||||||
|
return 'string';
|
||||||
|
}
|
||||||
|
if (stream.match('s+') || stream.match('s-')) {
|
||||||
|
stream.eatWhile(/[\d-]/);
|
||||||
|
return 'string';
|
||||||
|
}
|
||||||
|
if (stream.match('\(') || stream.match('*\(')) {
|
||||||
|
stream.eatWhile(/[\w-]/);
|
||||||
|
return 'string';
|
||||||
|
}
|
||||||
|
return 'string';
|
||||||
|
}
|
||||||
|
if (sol && (ch === '.' || ch === '\'')) {
|
||||||
|
if (stream.eat('\\') && stream.eat('\"')) {
|
||||||
|
stream.skipToEnd();
|
||||||
|
return 'comment';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sol && ch === '.') {
|
||||||
|
if (stream.match('B ') || stream.match('I ') || stream.match('R ')) {
|
||||||
|
return 'attribute';
|
||||||
|
}
|
||||||
|
if (stream.match('TH ') || stream.match('SH ') || stream.match('SS ') || stream.match('HP ')) {
|
||||||
|
stream.skipToEnd();
|
||||||
|
return 'quote';
|
||||||
|
}
|
||||||
|
if ((stream.match(/[A-Z]/) && stream.match(/[A-Z]/)) || (stream.match(/[a-z]/) && stream.match(/[a-z]/))) {
|
||||||
|
return 'attribute';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stream.eatWhile(/[\w-]/);
|
||||||
|
var cur = stream.current();
|
||||||
|
return words.hasOwnProperty(cur) ? words[cur] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function tokenize(stream, state) {
|
||||||
|
return (state.tokens[0] || tokenBase) (stream, state);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
startState: function() {return {tokens:[]};},
|
||||||
|
token: function(stream, state) {
|
||||||
|
return tokenize(stream, state);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
CodeMirror.defineMIME('text/troff', 'troff');
|
||||||
|
CodeMirror.defineMIME('text/x-troff', 'troff');
|
||||||
|
CodeMirror.defineMIME('application/x-troff', 'troff');
|
||||||
|
|
||||||
|
});
|
29
tools.json
Normal file
29
tools.json
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"Name": "dot",
|
||||||
|
"Cmd": "dot",
|
||||||
|
"Args": ["-Tsvg", "-v"],
|
||||||
|
"Suffix": ".dot",
|
||||||
|
"Description": "Graphviz, dot",
|
||||||
|
"BgColor": "CadetBlue",
|
||||||
|
"Documentation": {
|
||||||
|
"http://graphviz.org/content/dot-language": "DOT Language",
|
||||||
|
"http://graphviz.org/": "Get graphviz from here"
|
||||||
|
},
|
||||||
|
"Example": "\ndigraph test123 {\n\ta -> b -> c;\n\ta -> {x y};\n\tb [shape=box];\n\tc [label=\"hello\\nworld\",color=blue,fontsize=13,\n\tfontname=\"Palatino-Italic\",fontcolor=red,style=filled];\n\ta -> z [label=\"hi\", weight=100];\n\tx -> z [label=\"multi-line\\nlabel\"];\n\tedge [style=dashed,color=red];\n\tb -> x;\n\t{rank=same; b x}\n} \n"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"Name": "dpic",
|
||||||
|
"Cmd": "dpic",
|
||||||
|
"Args": ["-z", "-v"],
|
||||||
|
"Suffix": ".pic",
|
||||||
|
"BgColor": "Green",
|
||||||
|
"Documentation": {
|
||||||
|
"/static/dpicdoc.pdf": "DPIC manual",
|
||||||
|
"/static/gpic.raymond.pdf": "GPIC (by E.Raymond)",
|
||||||
|
"https://ece.uwaterloo.ca/~aplevich/dpic": "Get DPIC from here"
|
||||||
|
},
|
||||||
|
"Example": "\n.PS\n\nbox \"foo\"; arrow ->; box \"bar\"\n\n.PE\n"
|
||||||
|
}
|
||||||
|
]
|
Loading…
Reference in New Issue
Block a user