PK ! IgI I vbuild/__init__.py#!/usr/bin/python2
# -*- coding: utf-8 -*-
# #############################################################################
# Copyright (C) 2018 manatlan manatlan[at]gmail(dot)com
#
# MIT licence
#
# https://github.com/manatlan/vbuild
# #############################################################################
__version__ = "0.8.1" # py2.7 & py3.5 !!!!
import re, os, json, glob, itertools, traceback, subprocess, pkgutil
try:
from HTMLParser import HTMLParser
import urllib2 as urlrequest
import urllib as urlparse
except ImportError:
from html.parser import HTMLParser
import urllib.request as urlrequest
import urllib.parse as urlparse
transHtml = lambda x: x # override them to use your own transformer/minifier
transStyle = lambda x: x
transScript = lambda x: x
partial = ""
fullPyComp = True # 3 states ;-)
# None : minimal py comp, it's up to u to include "pscript.get_full_std_lib()"
# False : minimal py comp, vbuild will include the std lib
# True : each component generate its needs (default)
hasLess = bool(pkgutil.find_loader("lesscpy"))
hasSass = bool(pkgutil.find_loader("scss"))
hasClosure = bool(pkgutil.find_loader("closure"))
class VBuildException(Exception):
pass
def minimize(code):
if hasClosure:
return jsmin(code)
else:
return jsminOnline(code)
def jsminOnline(code):
""" JS-minimize (transpile to ES5 JS compliant) thru a online service
(https://closure-compiler.appspot.com/compile)
"""
data = [
("js_code", code),
("compilation_level", "SIMPLE_OPTIMIZATIONS"),
("output_format", "json"),
("output_info", "compiled_code"),
("output_info", "errors"),
]
try:
req = urlrequest.Request(
"https://closure-compiler.appspot.com/compile",
urlparse.urlencode(data).encode("utf8"),
{"Content-type": "application/x-www-form-urlencoded; charset=UTF-8"},
)
response = urlrequest.urlopen(req)
r = json.loads(response.read())
response.close()
code = r.get("compiledCode", None)
except Exception as e:
raise VBuildException("minimize error: %s" % e)
if code:
return code
else:
raise VBuildException("minimize error: %s" % r.get("errors", None))
def jsmin(code): # need java & pip/closure
""" JS-minimize (transpile to ES5 JS compliant) with closure-compiler
(pip package 'closure', need java !)
"""
if hasClosure:
import closure # py2 or py3
else:
raise VBuildException(
"jsmin error: closure is not installed (sudo pip closure)"
)
cmd = ["java", "-jar", closure.get_jar_filename()]
try:
p = subprocess.Popen(
cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE
)
except Exception as e:
raise VBuildException("jsmin error: %s" % e)
out, err = p.communicate(code.encode("utf8"))
if p.returncode == 0:
return out.decode("utf8")
else:
raise VBuildException("jsmin error:" + err.decode("utf8"))
def preProcessCSS(cnt, partial=""):
""" Apply css-preprocessing on css rules (according css.type) using a partial or not
return the css pre-processed
"""
if cnt.type in ["scss", "sass"]:
if hasSass:
from scss.compiler import compile_string # lang="scss"
return compile_string(partial + "\n" + cnt.value)
else:
print("***WARNING*** : miss 'sass' preprocessor : sudo pip install pyscss")
return cnt.value
elif cnt.type in ["less"]:
if hasLess:
import lesscpy, six
return lesscpy.compile(
six.StringIO(partial + "\n" + cnt.value), minify=True
)
else:
print("***WARNING*** : miss 'less' preprocessor : sudo pip install lesscpy")
return cnt.value
else:
return cnt.value
class Content:
def __init__(self, v, typ=None):
self.type = typ
self.value = v.strip("\n\r\t ")
def __repr__(self):
return self.value
class VueParser(HTMLParser):
""" Just a class to extract from a buffer.
self.html/script/styles/scopedStyles are Content's object, or list of.
"""
voidElements = "area base br col command embed hr img input keygen link menuitem meta param source track wbr".split(
" "
)
def __init__(self, buf, name=""):
""" Extract stuff from the vue/buffer 'buf'
(name is just useful for naming the component in exceptions)
"""
HTMLParser.__init__(self)
self.name = name
self._p1 = None
self._level = 0
self._scriptLang = None
self._styleLang = None
self.rootTag = None
self.html, self.script, self.styles, self.scopedStyles = None, None, [], []
self.feed(buf.strip("\n\r\t "))
def handle_starttag(self, tag, attrs):
self._tag = tag
# don't manage if it's a void element
if tag not in self.voidElements:
self._level += 1
attributes = dict([(k.lower(), v and v.lower()) for k, v in attrs])
if tag == "style" and attributes.get("lang", None):
self._styleLang = attributes["lang"]
if tag == "script" and attributes.get("lang", None):
self._scriptLang = attributes["lang"]
if self._level == 1 and tag == "template":
if self._p1 is not None:
raise VBuildException(
"Component %s contains more than one template" % self.name
)
self._p1 = self.getOffset() + len(self.get_starttag_text())
if self._level == 2 and self._p1: # test p1, to be sure to be in a template
if self.rootTag is not None:
raise VBuildException(
"Component %s can have only one top level tag !" % self.name
)
self.rootTag = tag
def handle_endtag(self, tag):
if tag not in self.voidElements:
if (
tag == "template" and self._p1
): # don't watch the level (so it can accept mal formed html
self.html = Content(self.rawdata[self._p1 : self.getOffset()])
self._level -= 1
def handle_data(self, data):
if self._level == 1:
if self._tag == "script":
self.script = Content(data, self._scriptLang)
if self._tag == "style":
if "scoped" in self.get_starttag_text().lower():
self.scopedStyles.append(Content(data, self._styleLang))
else:
self.styles.append(Content(data, self._styleLang))
def getOffset(self):
lineno, off = self.getpos()
rtn = 0
for _ in range(lineno - 1):
rtn = self.rawdata.find("\n", rtn) + 1
return rtn + off
def mkPrefixCss(css, prefix=""):
"""Add the prexix (css selector) to all rules in the 'css'
(used to scope style in context)
"""
medias = []
while "@media" in css:
p1 = css.find("@media", 0)
p2 = css.find("{", p1) + 1
lv = 1
while lv > 0:
lv += 1 if css[p2] == "{" else -1 if css[p2] == "}" else 0
p2 += 1
block = css[p1:p2]
mediadef = block[: block.find("{")].strip()
mediacss = block[block.find("{") + 1 : block.rfind("}")].strip()
css = css.replace(block, "")
medias.append((mediadef, mkPrefixCss(mediacss, prefix)))
lines = []
css = re.sub(re.compile("/\*.*?\*/", re.DOTALL), "", css)
css = re.sub(re.compile("[ \t\n]+", re.DOTALL), " ", css)
for rule in re.findall(r"[^}]+{[^}]+}", css):
sels, decs = rule.split("{", 1)
if prefix:
l = [
(prefix + " " + i.replace(":scope", "").strip()).strip()
for i in sels.split(",")
]
else:
l = [(i.strip()) for i in sels.split(",")]
lines.append(", ".join(l) + " {" + decs.strip())
lines.extend(["%s {%s}" % (d, c) for d, c in medias])
return "\n".join(lines).strip("\n ")
class VBuild:
""" the main class, provide an instance :
.style : contains all the styles (scoped or not)
.script: contains a (js) Vue.component() statement to initialize the component
.html : contains the
.tags : list of component's name whose are in the vbuild instance
"""
def __init__(self, filename, content):
""" Create a VBuild class, by providing a :
filename: which will be used to name the component, and create the namespace for the template
content: the string buffer which contains the sfc/vue component
"""
if not filename:
raise VBuildException("Component %s should be named" % filename)
if type(content) != type(filename): # only py2, transform
if type(content) == unicode: # filename to the same type
filename = filename.decode("utf8") # of content to avoid
else: # troubles with implicit
filename = filename.encode("utf8") # ascii conversions (regex)
name = os.path.splitext(os.path.basename(filename))[0]
unique = filename[:-4].replace("/", "-").replace("\\", "-").replace(":", "-").replace(".", "-")
# unique = name+"-"+''.join(random.choice(string.letters + string.digits) for _ in range(8))
tplId = "tpl-" + unique
dataId = "data-" + unique
vp = VueParser(content, filename)
if vp.html is None:
raise VBuildException("Component %s doesn't have a template" % filename)
else:
html = re.sub(r"^<([\w-]+)", r"<\1 %s" % dataId, vp.html.value)
self.tags = [name]
# self.html="""""" % (tplId, transHtml(html) )
self._html = [(tplId, html)]
self._styles = []
for style in vp.styles:
self._styles.append(("", style, filename))
for style in vp.scopedStyles:
self._styles.append(("*[%s]" % dataId, style, filename))
# and set self._script !
if vp.script and ("class Component:" in vp.script.value):
######################################################### python
try:
self._script = [
mkPythonVueComponent(
name, "#" + tplId, vp.script.value, fullPyComp
)
]
except Exception as e:
raise VBuildException(
"Python Component '%s' is broken : %s"
% (filename, traceback.format_exc())
)
else:
######################################################### js
try:
self._script = [
mkClassicVueComponent(
name, "#" + tplId, vp.script and vp.script.value
)
]
except Exception as e:
raise VBuildException(
"JS Component %s contains a bad script" % filename
)
@property
def html(self):
""" Return HTML (script tags of embbeded components), after transHtml"""
l = []
for tplId, html in self._html:
l.append(
""""""
% (tplId, transHtml(html))
)
return "\n".join(l)
@property
def script(self):
""" Return JS (js of embbeded components), after transScript"""
js = "\n".join(self._script)
isPyComp = "_pyfunc_op_instantiate(" in js # in fact : contains
isLibInside = "var _pyfunc_op_instantiate" in js
if (fullPyComp is False) and isPyComp and not isLibInside:
import pscript
return transScript(pscript.get_full_std_lib() + "\n" + js)
else:
return transScript(js)
@property
def style(self):
""" Return CSS (styles of embbeded components), after preprocess css & transStyle"""
style = ""
try:
for prefix, s, filename in self._styles:
style += mkPrefixCss(preProcessCSS(s, partial), prefix) + "\n"
except Exception as e:
raise VBuildException(
"Component '%s' got a CSS-PreProcessor trouble : %s" % (filename, e)
)
return transStyle(style).strip()
def __add__(self, o):
same = set(self.tags).intersection(set(o.tags))
if same:
raise VBuildException("You can't have multiple '%s'" % list(same)[0])
self._html.extend(o._html)
self._script.extend(o._script)
self._styles.extend(o._styles)
self.tags.extend(o.tags)
return self
def __radd__(self, o):
return self if o == 0 else self.__add__(o)
def __getstate__(self):
return self.__dict__
def __setstate__(self, d):
self.__dict__ = d
def __repr__(self):
""" return an html ready represenation of the component(s) """
hh = self.html
jj = self.script
ss = self.style
s = ""
if ss:
s += "\n" % ss
if hh:
s += "%s\n" % hh
if jj:
s += "\n" % jj
return s
def mkClassicVueComponent(name, template, code):
if code is None:
js = "{}"
else:
p1 = code.find("{")
p2 = code.rfind("}")
if 0 <= p1 <= p2:
js = code[p1 : p2 + 1]
else:
raise Exception("Can't find valid content inside '{' and '}'")
return """var %s = Vue.component('%s', %s);""" % (
name,
name,
js.replace("{", "{template:'%s'," % template, 1),
)
def mkPythonVueComponent(name, template, code, genStdLibMethods=True):
""" Transpile the component 'name', which have the template 'template',
and the code 'code' (which should contains a valid Component class)
to a valid Vue.component js statement.
genStdLibMethods : generate own std lib method inline (with the js)
(if False: use pscript.get_full_std_lib() to get them)
"""
import pscript
code = code.replace(
"class Component:", "class C:"
) # minimize class name (todo: use py2js option for that)
exec(code, globals(), locals())
klass = locals()["C"]
computeds = []
watchs = []
methods = []
lifecycles = []
classname = klass.__name__
props = []
for oname, obj in vars(klass).items():
if callable(obj):
if not oname.startswith("_"):
if oname.startswith("COMPUTED_"):
computeds.append(
"%s: %s.prototype.%s," % (oname[9:], classname, oname)
)
elif oname.startswith("WATCH_"):
if obj.__defaults__:
varwatch = obj.__defaults__[
0
] # not neat (take the first default as whatch var)
watchs.append(
'"%s": %s.prototype.%s,' % (varwatch, classname, oname)
)
else:
raise VBuildException(
"name='var_to_watch' is not specified in %s" % oname
)
elif oname in [
"MOUNTED",
"CREATED",
"UPDATED",
"BEFOREUPDATE",
"BEFOREDESTROY",
"DESTROYED",
]:
lifecycles.append(
"%s: %s.prototype.%s," % (oname.lower(), classname, oname)
)
else:
methods.append("%s: %s.prototype.%s," % (oname, classname, oname))
elif oname == "__init__":
props = list(obj.__code__.co_varnames)[1:]
methods = "\n".join(methods)
computeds = "\n".join(computeds)
watchs = "\n".join(watchs)
lifecycles = "\n".join(lifecycles)
pyjs = pscript.py2js(
code, inline_stdlib=genStdLibMethods
) # https://pscript.readthedocs.io/en/latest/api.html
return (
"""
var %(name)s=(function() {
%(pyjs)s
function construct(constructor, args) {
function F() {return constructor.apply(this, args);}
F.prototype = constructor.prototype;
return new F();
}
return Vue.component('%(name)s',{
name: "%(name)s",
props: %(props)s,
template: '%(template)s',
data: function() {
var props=[]
var ll=%(props)s;
for(var i in ll) props.push( this.$props[ ll[i] ] )
var i=construct(%(classname)s,props) // new %(classname)s(...props)
return JSON.parse(JSON.stringify( i ));
},
computed: {
%(computeds)s
},
methods: {
%(methods)s
},
watch: {
%(watchs)s
},
%(lifecycles)s
})
})();
"""
% locals()
)
def render(*filenames):
""" Helpers to render VBuild's instances by providing filenames or pattern (glob's style)"""
isPattern = lambda f: ("*" in f) or ("?" in f)
files = []
for i in filenames:
if isinstance(i, list):
files.extend(i)
else:
files.append(i)
files = [glob.glob(i) if isPattern(i) else [i] for i in files]
files = list(itertools.chain(*files))
ll = []
for f in files:
try:
with open(f, "r+") as fid:
content = fid.read()
except IOError as e:
raise VBuildException(str(e))
ll.append(VBuild(f, content))
return sum(ll)
if __name__ == "__main__":
print("Less installed (lesscpy) :", hasLess)
print("Sass installed (pyScss) :", hasSass)
print("Closure installed (closure) :", hasClosure)
if os.path.isfile("tests.py"):
exec(open("tests.py").read())
# ~ if(os.path.isfile("test_py_comp.py")): exec(open("test_py_comp.py").read())
PK ! <) ) vbuild-0.8.1.dist-info/LICENSEMIT License
Copyright (c) 2018 manatlan
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.
PK !H|n-W Y vbuild-0.8.1.dist-info/WHEEL
A
н#Z;/"
bFF]xzwK;<*mTֻ0*Ri.4Vm0[H,JPK !HL#v vbuild-0.8.1.dist-info/METADATAXmSH_8u%K@v/W>I*[e,̀Ό~==#a&K.W)Ѽt?/O|!ȅX!&;яb.ԪȣǕoX0!U4y$?9#2Gwn^63ru+,NE--N|MSn&JRd*iVtto{$ Q%mxӏTꜥmE\*1;*;Lәrw$t.J
Q}U&K7ѿjMnZcFߋ7
c0FfHfb BDZiW+Aw1mI~촃v|OQ!US%
t9:ê2z!s~'G1ǥft.Y
G/ݟ}7/ݟ'bѕxan)cr`.FI]XY7*uvljp{!f!?O_J[Y2.nV!P¸Fjg1V60crPn7d)p=w7]'A4:>F7?D;$p6GSݳMgqQ!|ڈ kЩ._;2 2IK9DװY`(Ƕ),;ItF i{N,| >.ljJ^&$_h&$'kLD+e-)kÁXL
(4yT!6݁^;0kO>)\
6"#:6@!CBdbTaSu[8
3R~Ǐ5<.g9g#}d7C3cloů ƥ((
JQbd t;R:m2ucG6bkTU.`1w 3ܱ>I=zE(BqMUi.Ex p%).ԱYi(lzUBTרr4=}q}@+ϢȌ8RbMƾm(7UcjT
Ņm#*d吘}&@"W"8`]m
P)X\:4e$*#CIhi
CC/8;(ɢhlW3RT>Q*^g#1bW֝w;"Ƶ1!s~7pn"2ߓg3Z
p
C|.oI%A+1d埱gH&95=vO͙;-0#I ,Źqauo#0Ӧa_\y:}ڞ71_sHd[5Q)ZM:TqoI6Vm|dYD&цȍ앶?+`w:
o#AΖ
^ۍ$7s
3@V*E/<1JWW 滛?oecXz<69 IEPK !H n vbuild-0.8.1.dist-info/RECORDur0 л0C)lLM;xc:<2V4EϘ;.A;E0hPMqh|TxU+O8K
$!ٌ E"JbVtP4ҩכ+mFtlSȂn>:>ei]T ʻ6