Merge branch 'pull/2269'

This commit is contained in:
nightwing 2015-01-04 23:50:57 +04:00
commit b66fb6def8
6 changed files with 274 additions and 71 deletions

View file

@ -169,13 +169,44 @@ function loadDoc(name, callback) {
}); });
} }
function saveDoc(name, callback) {
var doc = fileCache[name] || name;
if (!doc || !doc.session)
return callback("Unknown document: " + name);
var path = doc.path;
var parts = path.split("/");
if (parts[0] == "docs")
path = "demo/kitchen-sink/" + path;
else if (parts[0] == "ace")
path = "lib/" + path;
upload(path, doc.session.getValue(), callback);
}
function upload(url, data, callback) {
url = net.qualifyURL(url);
if (!/https?:/.test(url))
return callback(new Error("Unsupported url scheme"));
var xhr = new XMLHttpRequest();
xhr.open("PUT", url, true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
callback(!/^2../.test(xhr.status));
}
};
xhr.send(data);
};
module.exports = { module.exports = {
fileCache: fileCache, fileCache: fileCache,
docs: sort(prepareDocList(docs)), docs: sort(prepareDocList(docs)),
ownSource: prepareDocList(ownSource), ownSource: prepareDocList(ownSource),
hugeDocs: prepareDocList(hugeDocs), hugeDocs: prepareDocList(hugeDocs),
initDoc: initDoc, initDoc: initDoc,
loadDoc: loadDoc loadDoc: loadDoc,
saveDoc: saveDoc,
}; };
module.exports.all = { module.exports.all = {
"Mode Examples": module.exports.docs, "Mode Examples": module.exports.docs,

View file

@ -126,7 +126,7 @@ var AbapHighlightRules = function() {
{token : "string", regex : "`", next : "start"}, {token : "string", regex : "`", next : "start"},
{defaultToken : "string"} {defaultToken : "string"}
] ]
} };
}; };
oop.inherits(AbapHighlightRules, TextHighlightRules); oop.inherits(AbapHighlightRules, TextHighlightRules);

111
static.js
View file

@ -10,37 +10,100 @@ var http = require("http")
// compatibility with node 0.6 // compatibility with node 0.6
if (!fs.exists) if (!fs.exists)
fs.exists = path.exists; fs.exists = path.exists;
http.createServer(function(request, response) { var allowSave = process.argv.indexOf("--allow-save") != -1;
var uri = url.parse(request.url).pathname http.createServer(function(req, res) {
, filename = path.join(process.cwd(), uri); var uri = url.parse(req.url).pathname
, filename = path.join(process.cwd(), uri);
fs.exists(filename, function(exists) { if (req.method == "PUT") {
if(!exists) { if (!allowSave)
response.writeHead(404, {"Content-Type": "text/plain"}); return error(res, 404, "Saving not allowed pass --allow-save to enable");
response.write("404 Not Found\n"); save(req, res, filename);
response.end();
return;
} }
if (fs.statSync(filename).isDirectory()) filename += '/index.html'; fs.exists(filename, function(exists) {
if (!exists)
return error(res, 404, "404 Not Found\n");
fs.readFile(filename, "binary", function(err, file) { if (fs.statSync(filename).isDirectory()) {
if(err) { var files = fs.readdirSync(filename);
response.writeHead(500, {"Content-Type": "text/plain"}); res.writeHead(200, {"Content-Type": "text/html"});
response.write(err + "\n");
response.end();
return;
}
var contentType = mime.lookup(filename) || "text/plain"; files.push(".", "..");
response.writeHead(200, {"Content-Type": contentType}); var html = files.map(function(name) {
response.write(file, "binary"); var href = uri + "/" + name;
response.end(); href = href.replace(/[\/\\]+/g, "/").replace(/\/$/g, "");
if (fs.statSync(filename + "/" + name + "/").isDirectory())
href += "/";
return "<a href='" + href + "'>" + name + "</a><br>";
});
res._hasBody && res.write(html.join(""));
res.end();
return;
}
fs.readFile(filename, "binary", function(err, file) {
if (err) {
res.writeHead(500, { "Content-Type": "text/plain" });
res.write(err + "\n");
res.end();
return;
}
var contentType = mime.lookup(filename) || "text/plain";
res.writeHead(200, { "Content-Type": contentType });
res.write(file, "binary");
res.end();
});
}); });
});
}).listen(port, ip); }).listen(port, ip);
console.log("http://localhost:" + port); function error(res, status, message, error) {
console.error(error || message);
res.writeHead(status, { "Content-Type": "text/plain" });
res.write(message);
res.end();
}
function save(req, res, filePath) {
var data = "";
req.on("data", function(chunk) {
data += chunk;
});
req.on("error", function() {
error(res, 404, "Could't save file");
});
req.on("end", function() {
try {
fs.writeFileSync(filePath, data);
}
catch (e) {
return error(res, 404, "Could't save file", e);
}
res.statusCode = 200;
res.end("OK");
});
}
function getLocalIps() {
var os = require("os");
var interfaces = os.networkInterfaces ? os.networkInterfaces() : {};
var addresses = [];
for (var k in interfaces) {
for (var k2 in interfaces[k]) {
var address = interfaces[k][k2];
if (address.family === "IPv4" && !address.internal) {
addresses.push(address.address);
}
}
}
return addresses;
}
console.log("http://" + (ip == "0.0.0.0" ? getLocalIps()[0] : ip) + ":" + port);

136
static.py
View file

@ -32,7 +32,9 @@ import time
import string import string
import sys import sys
from os import path, stat, getcwd from os import path, stat, getcwd
from fnmatch import fnmatch
from wsgiref import util from wsgiref import util
from wsgiref.validate import validator
from wsgiref.headers import Headers from wsgiref.headers import Headers
from wsgiref.simple_server import make_server from wsgiref.simple_server import make_server
from optparse import OptionParser from optparse import OptionParser
@ -84,6 +86,8 @@ class Cling(object):
not_modified = StatusApp('304 Not Modified', "") not_modified = StatusApp('304 Not Modified', "")
moved_permanently = StatusApp('301 Moved Permanently') moved_permanently = StatusApp('301 Moved Permanently')
method_not_allowed = StatusApp('405 Method Not Allowed') method_not_allowed = StatusApp('405 Method Not Allowed')
success_no_content = StatusApp('204 No Content', "")
server_error = StatusApp('500 Internal Server Error')
def __init__(self, root, **kw): def __init__(self, root, **kw):
"""Just set the root and any other attribs passes via **kw.""" """Just set the root and any other attribs passes via **kw."""
@ -93,9 +97,6 @@ class Cling(object):
def __call__(self, environ, start_response): def __call__(self, environ, start_response):
"""Respond to a request when called in the usual WSGI way.""" """Respond to a request when called in the usual WSGI way."""
if environ['REQUEST_METHOD'] not in ('GET', 'HEAD'):
headers = [('Allow', 'GET, HEAD')]
return self.method_not_allowed(environ, start_response, headers)
path_info = environ.get('PATH_INFO', '') path_info = environ.get('PATH_INFO', '')
full_path = self._full_path(path_info) full_path = self._full_path(path_info)
if not self._is_under_root(full_path): if not self._is_under_root(full_path):
@ -109,6 +110,23 @@ class Cling(object):
return self.moved_permanently(environ, start_response, headers) return self.moved_permanently(environ, start_response, headers)
else: else:
full_path = self._full_path(path_info + self.index_file) full_path = self._full_path(path_info + self.index_file)
try:
sz = int(environ['CONTENT_LENGTH'])
except:
sz = 0
if environ['REQUEST_METHOD'] == 'PUT' and sz > 0:
for putglob in self.puttable:
if fnmatch(path_info, putglob):
data = environ['wsgi.input'].read(sz)
try:
with open(full_path, "wb") as f: f.write(data)
return self.success_no_content(environ, start_response)
except:
print sys.exc_info()[1]
return self.server_error(environ, start_response)
if environ['REQUEST_METHOD'] not in ('GET', 'HEAD'):
headers = [('Allow', 'GET, HEAD')]
return self.method_not_allowed(environ, start_response, headers)
content_type = self._guess_type(full_path) content_type = self._guess_type(full_path)
try: try:
etag, last_modified = self._conditions(full_path, environ) etag, last_modified = self._conditions(full_path, environ)
@ -191,52 +209,90 @@ def cling_wrap(package_name, dir_name, **kw):
def command(): def command():
parser = OptionParser(usage="%prog DIR [HOST][:][PORT]", usage = "%prog [--help] [-d DIR] [-l [HOST][:PORT]] [-p GLOB[,GLOB...]]"
version="static 0.3.6") parser = OptionParser(usage=usage, version="static 0.3.6")
parser.add_option("-d", "--dir", dest="rootdir", default=".",
help="Root directory to serve. Defaults to '.' .", metavar="DIR")
parser.add_option("-l", "--listen", dest="listen", default="127.0.0.1:8888",
help="Listen on this interface (given by its hostname or IP) and port."+
" HOST defaults to 127.0.0.1. PORT defaults to 8888. "+
"Leave HOST empty to listen on all interfaces (INSECURE!).",
metavar="[HOST][:PORT]")
parser.add_option("-p", "--puttable", dest="puttable", default="",
help="Comma or space-separated list of request paths for which to"+
" permit PUT requests. Each path is a glob pattern that may "+
"contain wildcard characters '*' and/or '?'. "+
"'*' matches any sequence of characters, including the empty"+
" string. '?' matches exactly 1 arbitrary character. "+
"NOTE: Both '*' and '?' match slashes and dots. "+
"I.e. --puttable=* makes every file under DIR writable!",
metavar="GLOB[,GLOB...]")
parser.add_option("--validate", dest="validate", action="store_true",
default=False,
help="Enable HTTP validation. You don't need this unless "+
"you're developing static.py itself.")
options, args = parser.parse_args() options, args = parser.parse_args()
if len(args) in (1, 2): if len(args) > 0:
if len(args) == 2:
parts = args[1].split(":")
if len(parts) == 1:
host = parts[0]
port = None
elif len(parts) == 2:
host, port = parts
else:
sys.exit("Invalid host:port specification.")
elif len(args) == 1:
host, port = None, None
if not host:
host = '0.0.0.0'
if not port:
port = 8888
try:
port = int(port)
except:
sys.exit("Invalid host:port specification.")
app = Cling(args[0])
try:
make_server(host, port, app).serve_forever()
except KeyboardInterrupt, ki:
print "Cio, baby!"
except:
sys.exit("Problem initializing server.")
else:
parser.print_help(sys.stderr) parser.print_help(sys.stderr)
sys.exit(1) sys.exit(1)
parts = options.listen.split(":")
if len(parts) == 1:
try: # if the the listen argument consists only of a port number
port = int(parts[0])
host = None
except: # could not parse as port number => must be a host IP or name
host = parts[0]
port = None
elif len(parts) == 2:
host, port = parts
else:
sys.exit("Invalid host:port specification.")
def test(): if not host:
from wsgiref.validate import validator host = '0.0.0.0'
app = Cling(getcwd()) if not port:
port = 8888
try: try:
print "Serving " + getcwd() + " to http://localhost:8888" port = int(port)
make_server('0.0.0.0', 8888, validator(app)).serve_forever() if port <= 0 or port > 65535: raise ValueError
except:
sys.exit("Invalid host:port specification.")
puttable = set(path.abspath(p) for p in
options.puttable.replace(","," ").split())
if puttable and host not in ('127.0.0.1', 'localhost'):
print("Permitting PUT access for non-localhost connections may be unwise.")
options.rootdir = path.abspath(options.rootdir)
for p in puttable:
if not p.startswith(options.rootdir):
sys.exit("puttable path '%s' not under root '%s'" % (p, options.rootdir))
# cut off root prefix from puttable paths
puttable = set(p[len(options.rootdir):] for p in puttable)
app = Cling(options.rootdir, puttable=puttable)
if options.validate:
app = validator(app)
try:
print "Serving %s to http://%s:%d" % (options.rootdir, host, port)
if puttable:
print("The following paths (relative to server root) may be "+
"OVERWRITTEN via HTTP PUT.")
for p in puttable:
print p
make_server(host, port, app).serve_forever()
except KeyboardInterrupt, ki: except KeyboardInterrupt, ki:
print ""
print "Ciao, baby!" print "Ciao, baby!"
except:
sys.exit("Problem initializing server: %s" % sys.exc_info()[1])
if __name__ == '__main__': if __name__ == '__main__':
test() command()

View file

@ -19,7 +19,7 @@
border-bottom: solid 1px; border-bottom: solid 1px;
} }
.separator-h { .separator-h {
padding: 0 20px; padding: 0 10px;
} }
#closeBtn { #closeBtn {
background: rgba(245, 146, 146, 0.5); background: rgba(245, 146, 146, 0.5);
@ -59,6 +59,7 @@
<select id="modeEl" size="1"></select> <select id="modeEl" size="1"></select>
<input type="button" value="&#10227;" title="sync" id="syncToMode"></select> <input type="button" value="&#10227;" title="sync" id="syncToMode"></select>
<span id="tablist"></span> <span id="tablist"></span>
<input type="button" value="Save" id="saveButton1"></select>
<span class="separator-h"></span> <span class="separator-h"></span>
<label for="autorunEl">live preview</label> <label for="autorunEl">live preview</label>
<input type="checkbox" label="autorun" id="autorunEl" checked> <input type="checkbox" label="autorun" id="autorunEl" checked>
@ -70,6 +71,7 @@
<label for="themeEl">Theme</label> <label for="themeEl">Theme</label>
<select id="themeEl" size="1" value="textmate"></select> <select id="themeEl" size="1" value="textmate"></select>
<span class="separator-h"></span> <span class="separator-h"></span>
<input type="button" value="Save" id="saveButton2"></select>
<label for="doc">Document</label> <label for="doc">Document</label>
<select id="doc" size="1"></select> <select id="doc" size="1"></select>
</div> </div>

View file

@ -53,6 +53,8 @@ util.bindDropdown("doc", function(value) {
doclist.loadDoc(value, function(session) { doclist.loadDoc(value, function(session) {
if (session) { if (session) {
editor2.setSession(session); editor2.setSession(session);
session.getUndoManager().markClean();
updateSaveButtonState(null, editor2);
} }
}); });
}); });
@ -60,6 +62,7 @@ util.bindDropdown("doc", function(value) {
var modeEl = document.getElementById("modeEl"); var modeEl = document.getElementById("modeEl");
util.fillDropdown(modeEl, modelist.modes); util.fillDropdown(modeEl, modelist.modes);
var modeSessions = {}; var modeSessions = {};
util.bindDropdown(modeEl, function(value) { util.bindDropdown(modeEl, function(value) {
if (modeSessions[value]) { if (modeSessions[value]) {
editor1.setSession(modeSessions[value]); editor1.setSession(modeSessions[value]);
@ -68,14 +71,18 @@ util.bindDropdown(modeEl, function(value) {
} }
var hp = "./lib/ace/mode/" + value + "_highlight_rules.js"; var hp = "./lib/ace/mode/" + value + "_highlight_rules.js";
net.get(hp, function(text) { net.get(hp, function(text) {
text = util.stripLeadingComments(text);
var session = new EditSession(text); var session = new EditSession(text);
session.setUndoManager(new UndoManager()); session.setUndoManager(new UndoManager());
modeSessions[value] = session; modeSessions[value] = session;
session.setMode("ace/mode/javascript"); session.setMode("ace/mode/javascript", function() {
if (session.getLine(0).match(/^\s*\//))
session.toggleFoldWidget(0); // fold licence comment
});
editor1.setSession(modeSessions[value]); editor1.setSession(modeSessions[value]);
session.getUndoManager().markClean();
updateSaveButtonState(null, editor1);
schedule(); schedule();
}); });
}); });
@ -85,6 +92,50 @@ document.getElementById("syncToMode").onclick = function() {
docEl.onchange(); docEl.onchange();
run(); run();
}; };
editor1.saveButton = document.getElementById("saveButton1");
editor2.saveButton = document.getElementById("saveButton2");
editor1.saveButton.editor = editor1;
editor2.saveButton.editor = editor2;
editor1.saveButton.onclick = function() {
doclist.saveDoc({
path: "./lib/ace/mode/" + modeEl.value + "_highlight_rules.js",
session: editor1.session
}, function(err) {
handleSaveResult(err, editor1);
});
};
editor1.commands.bindKey({
win: "Ctrl-S", mac: "Cmd-s"
}, editor1.saveButton.onclick);
editor2.saveButton.onclick = function() {
doclist.saveDoc(docEl.value, function(err) {
handleSaveResult(err, editor2);
});
};
editor2.commands.bindKey({
win: "Ctrl-S", mac: "Cmd-s"
}, editor2.saveButton.onclick);
function updateSaveButtonState(e, editor){
editor.saveButton.disabled = editor.session.getUndoManager().isClean();
}
editor1.on("input", updateSaveButtonState);
editor2.on("input", updateSaveButtonState);
function handleSaveResult(err, editor) {
if (err) {
return log(
"Write access to this file is disabled.\n"+
"To enable saving your changes to disk, clone the Ace repository\n"+
"and run the included web server with the --allow-write option\n"+
"`node static.js --allow-write` or `static.py --puttable=*`"
);
}
editor.session.getUndoManager().markClean();
updateSaveButtonState(null, editor);
}
document.getElementById("perfTest").onclick = function() { document.getElementById("perfTest").onclick = function() {
var lines = editor2.session.doc.getAllLines(); var lines = editor2.session.doc.getAllLines();
if (!lines.length) if (!lines.length)