Last active
December 27, 2015 17:19
-
-
Save jdorrance/7361535 to your computer and use it in GitHub Desktop.
sublimetext cq5 integration --> Will 'PUT' to the corresponding path in CQ5. Uses coffeescript if the file is *.coffee, and compiles to JS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sublime_plugin | |
class cq5Save(sublime_plugin.WindowCommand): | |
def run(self, **args): | |
view = self.window.active_view() | |
file_name = view.file_name() | |
if CQ5Poster.JCRROOT in file_name: | |
CQ5Poster(args).send_file(file_name) | |
else: print("file doesn't live beneath a jcr_root") | |
import base64, codecs, os, io, mimetypes, shlex, subprocess, sys, urllib.request, uuid | |
from xml.dom.minidom import parse, parseString | |
class CQ5Poster(): | |
# Replace these variables in the arg | |
host = "http://localhost:4502/" | |
username = 'admin' | |
password = 'admin' | |
COFFEE_SUFFIX = ".coffee" | |
JS_SUFFIX = ".js" | |
XML_SUFFIX = ".xml" | |
COFFEXEC = 'coffee.cmd' | |
JCRROOT = "jcr_root\\" | |
ENC = 'utf-8' | |
DIALOG = "dialog.xml" | |
DESIGN_DIALOG = "design_dialog.xml" | |
CLIENTLIB = "clientlibs" | |
TIMEOUT = 2 | |
def __init__(self, args=[]): | |
""" | |
args --> i.e. ({"host": "http://yourservername:4502/", "username" : "your_user_name", "password" : "your_password"}) | |
""" | |
for i in args: self.__dict__[i] = args[i] | |
def __getpw__(self): | |
return 'Basic %s' %(str(base64.b64encode(bytes(self.username + ":" + self.password,self.ENC)),self.ENC)) | |
def __run_command__(self,command): | |
p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) | |
return iter(p.stdout.readline, b'') | |
def __get_content__(self, file_name): | |
bits = b'' | |
if self.COFFEE_SUFFIX in file_name[-self.COFFEE_SUFFIX.__len__():]: | |
command = '%s -b -p -c "%s"' %(self.COFFEXEC,file_name) | |
for line in self.__run_command__(command): | |
bits += line | |
else: | |
with open(file_name, "rb") as f: | |
bits = f.read(10000000) | |
if self.DESIGN_DIALOG in file_name[-self.DESIGN_DIALOG.__len__():]: bits = bytes(str(bits,self.ENC).replace("<jcr:root","<design_dialog").replace("</jcr:root","</design_dialog"),self.ENC) | |
elif self.DIALOG in file_name[-self.DIALOG.__len__():]: bits = bytes(str(bits,self.ENC).replace("<jcr:root","<dialog").replace("</jcr:root","</dialog"),self.ENC) | |
return bits | |
def send_file(self, file_name): | |
""" | |
file_name --> the fully qualified path to the file that you wish to PUT/POST. Must have jcr_root in the path | |
Will log the status code of the send operation the std out | |
""" | |
basepath = file_name[file_name.rfind(self.JCRROOT) + self.JCRROOT.__len__():].replace("\\","/") | |
url = (self.host + basepath).replace("\\","/") | |
content = self.__get_content__(file_name) | |
opener = urllib.request.build_opener() | |
opener.addheaders = [('Content-Length', os.path.getsize(file_name)),('Authorization', self.__getpw__())] | |
content_type = "application/x-html" | |
get_method = lambda : 'PUT' | |
if self.COFFEE_SUFFIX in url[-self.COFFEE_SUFFIX.__len__():]: url = url[:-self.COFFEE_SUFFIX.__len__()] + self.JS_SUFFIX | |
if self.CLIENTLIB in url: | |
cliburl = url[:url.rfind(self.CLIENTLIB) + self.CLIENTLIB.__len__()] +".js" | |
cliburl2 = self.host + "var/clientlibs/" + cliburl[cliburl.rfind(self.host) + self.host.__len__():] | |
r0 = urllib.request.Request(cliburl2) | |
r0.get_method = lambda :'DELETE' | |
try: | |
print("Deleting the clientlib first @ %s, SC: %s" %(cliburl2, opener.open(r0,timeout = self.TIMEOUT).status)) | |
except Exception: | |
print ("most likely doesnt exist" ) | |
if self.XML_SUFFIX in file_name[-self.XML_SUFFIX.__len__():]: content_type = "application/xml" | |
if self.DIALOG in file_name[-self.DIALOG.__len__():]: | |
# First - we delete the existing tab, because CQ doesnt properly overwrite for some reason | |
r1 = urllib.request.Request(url.replace(url[-self.XML_SUFFIX.__len__():],"")) | |
r1.get_method = lambda :'DELETE' | |
try: | |
print("Deleting the dialog first @ %s, SC: %s" %(url, opener.open(r1,timeout = self.TIMEOUT).status)) | |
except Exception: | |
print ("most likely doesnt exist") | |
# Set up the POST operation for either the design dialog or the (regular) dialog.xml | |
get_method = lambda : 'POST' | |
# See http://sling.apache.org/documentation/bundles/manipulating-content-the-slingpostservlet-servlets-post.html#importing-content-structures | |
fields = [(':operation', 'import'), (':contentType', 'jcr.xml'), (':replaceProperties', 'true'),('replace','true')] | |
files = [] | |
_fn = self.DESIGN_DIALOG if self.DESIGN_DIALOG in file_name[-self.DESIGN_DIALOG.__len__():] else self.DIALOG | |
fields.extend([(':nameHint',_fn)]) | |
files.extend([(':content', _fn, content)]) | |
url = url.replace(url[-_fn.__len__():],"") | |
#Now we encode all this information | |
content_type, content = MultipartFormdataEncoder().encode(fields, files) | |
#Time to finish up building the request | |
r = urllib.request.Request(url, data=content) | |
r.add_header('Content-Type', content_type) | |
r.get_method = get_method | |
try: | |
print("sending file to %s content type is %s, SC:%s" %(url, content_type,opener.open(r,timeout = self.TIMEOUT).status)) | |
except Exception: | |
print("problem sending to %s" %url) | |
class MultipartFormdataEncoder(object): | |
def __init__(self): | |
self.boundary = uuid.uuid4().hex | |
self.content_type = 'multipart/form-data; boundary={}'.format(self.boundary) | |
@classmethod | |
def u(cls, s): | |
if sys.hexversion < 0x03000000 and isinstance(s, str): s = s.decode('utf-8') | |
if sys.hexversion >= 0x03000000 and isinstance(s, bytes): s = s.decode('utf-8') | |
return s | |
def iter(self, fields, files): | |
""" | |
fields is a sequence of (name, value) elements for regular form fields. | |
files is a sequence of (name, filename, file-type) elements for data to be uploaded as files | |
Yield body's chunk as bytes | |
""" | |
encoder = codecs.getencoder('utf-8') | |
for (key, value) in fields: | |
key = self.u(key) | |
yield encoder('--{}\r\n'.format(self.boundary)) | |
yield encoder(self.u('Content-Disposition: form-data; name="{}"\r\n').format(key)) | |
yield encoder('\r\n') | |
if isinstance(value, int) or isinstance(value, float): | |
value = str(value) | |
yield encoder(self.u(value)) | |
yield encoder('\r\n') | |
for (key, filename, fd) in files: | |
key = self.u(key) | |
filename = self.u(filename) | |
yield encoder('--{}\r\n'.format(self.boundary)) | |
yield encoder(self.u('Content-Disposition: form-data; name="{}"; filename="{}"\r\n').format(key, filename)) | |
yield encoder('Content-Type: {}\r\n'.format(mimetypes.guess_type(filename)[0] or 'application/octet-stream')) | |
yield encoder('\r\n') | |
yield (fd, len(fd)) | |
yield encoder('\r\n') | |
yield encoder('--{}--\r\b'.format(self.boundary)) | |
def encode(self, fields, files): | |
body = io.BytesIO() | |
for chunk, chunk_len in self.iter(fields, files): body.write(chunk) | |
return self.content_type, body.getvalue() | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[ | |
{ | |
"caption": "Save to CQ5", | |
"command": "cq5_save" | |
} | |
] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[ | |
{ "keys": ["ctrl+shift+s"], "command": "cq5_save", | |
"args": {"host": "http://localhost:4502/", "username" : "admin", "password" : "admin"} | |
} | |
] | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
can you provide some documentation of what exactly this plugin does? Does it save the scripts directly into cq5?