Quellcode durchsuchen

versions 0.1

version-14
Rushabh Mehta vor 14 Jahren
Ursprung
Commit
30fca54f31
12 geänderte Dateien mit 234 neuen und 71 gelöschten Zeilen
  1. +1
    -0
      js/build.json
  2. +2
    -0
      js/core.js
  3. +9
    -4
      js/core.min.js
  4. +3
    -6
      js/wn/assets.js
  5. +42
    -0
      js/wn/versions.js
  6. +3
    -2
      js/wn/xmlhttp.js
  7. +2
    -5
      py/build/__init__.py
  8. +18
    -15
      py/build/bundle.py
  9. +18
    -11
      py/build/project.py
  10. +73
    -27
      py/build/version.py
  11. +9
    -0
      py/webnotes/handler.py
  12. +54
    -1
      wnf.py

+ 1
- 0
js/build.json Datei anzeigen

@@ -2,6 +2,7 @@
"core.min.js": [
"wn/provide.js",
"wn/xmlhttp.js",
"wn/versions.js",
"wn/assets.js",
"wn/require.js",
"wn/dom.js",


+ 2
- 0
js/core.js Datei anzeigen

@@ -1,3 +1,5 @@
// find files changed since last version
wn.versions.check();

// load all critical libraries
wn.require("lib/js/lib/jquery.min.js");


+ 9
- 4
js/core.min.js Datei anzeigen

@@ -5,9 +5,14 @@ parent=parent[n];}}
wn.provide('wn.settings');wn.provide('wn.ui');wn.xmlhttp={request:function(){if(window.XMLHttpRequest)
return new XMLHttpRequest();else if(window.ActiveXObject)
return new ActiveXObject("MsXml2.XmlHttp");},complete:function(req,callback,url){if(req.status==200||req.status==304){callback(req.responseText);}else{alert(url+' request error: '+req.statusText+' ('+req.status+')');}},get:function(url,callback,args,async){if(async===null)async=true;var req=wn.xmlhttp.request();req.onreadystatechange=function(){if(req.readyState==4){wn.xmlhttp.complete(req,callback,url)}}
req.open('GET',url,async);req.send(args?url+'?'+args:null);if(!async){wn.xmlhttp.complete(req,callback,url)}}}
wn.assets={executed_:{},exists:function(src){if('localStorage'in window&&localStorage.getItem(src)&&localStorage.getItem('[ts] '+src)==asset_timestamps_[src])
return true},add:function(src,txt){if('localStorage'in window){localStorage.setItem(src,txt);localStorage.setItem('[ts] '+src,asset_timestamps_[src]);}},get:function(src){return localStorage.getItem(src);},extn:function(src){return src.split('.').slice(-1)[0];},html_src:function(src){if(src.indexOf('/')!=-1){var t=src.split('/').slice(0,-1);t.push('src');t=t.join('/')+'/'+a.split('/').slice(-1)[0];}else{var t='src/'+src;}
var u=args?(url+'?'+args):url;req.open('GET',u,async);req.send(null);if(!async){wn.xmlhttp.complete(req,callback,url)}}}
wn.versions={is_latest:function(){if(window._version_number==(localStorage?localStorage['_version_number']:null)){return true;}
return false;},get_diff:function(){if(!localStorage)return;wn.xmlhttp.get('index.cgi',function(txt){r=JSON.parse(txt);if(r.exc){alert(r.exc);}
wn.versions.set(r.message);},'cmd=get_diff&version_number='+localStorage['_version_number'],false);},set:function(diff){for(var i=0;i<diff.length;i++){localStorage.removeItem(diff[i]);}
localStorage['_version_number']=_version_number;},check:function(){if(localStorage&&!localStorage['_version_number']){localStorage['_version_number']=_version_number;return;}
if(!wn.versions.is_latest())wn.versions.get_diff();}}
wn.assets={executed_:{},exists:function(src){if('localStorage'in window&&localStorage.getItem(src))
return true},add:function(src,txt){if('localStorage'in window){localStorage.setItem(src,txt);}},get:function(src){return localStorage.getItem(src);},extn:function(src){return src.split('.').slice(-1)[0];},html_src:function(src){if(src.indexOf('/')!=-1){var t=src.split('/').slice(0,-1);t.push('src');t=t.join('/')+'/'+a.split('/').slice(-1)[0];}else{var t='src/'+src;}
return t;},load:function(src){var t=wn.assets.extn(src)=='html'?wn.assets.html_src(src):src;wn.xmlhttp.get(t,function(txt){wn.assets.add(src,txt);},'q='&Math.floor(Math.random()*1000),false)},execute:function(src){if(!wn.assets.exists(src)){wn.assets.load(src);}
var type=wn.assets.extn(src);if(wn.assets.handler[type]){wn.assets.handler[type](wn.assets.get(src),src);wn.assets.executed_[src]=1;}},handler:{js:function(txt,src){wn.dom.eval(txt);},css:function(txt,src){var se=document.createElement('style');se.type="text/css";if(se.styleSheet){se.styleSheet.cssText=txt;}else{se.appendChild(document.createTextNode(txt));}
document.getElementsByTagName('head')[0].appendChild(se);},html:function(txt,src){var page=wn.dom.add($('.outer .inner').get(0),'div','content',null,txt);page.setAttribute("_src",src);}}}
@@ -52,4 +57,4 @@ return reviver.call(holder,key,value);}
text=String(text);cx.lastIndex=0;if(cx.test(text)){text=text.replace(cx,function(a){return'\\u'+
('0000'+a.charCodeAt(0).toString(16)).slice(-4);});}
if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,'@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']').replace(/(?:^|:|,)(?:\s*\[)+/g,''))){j=eval('('+text+')');return typeof reviver==='function'?walk({'':j},''):j;}
throw new SyntaxError('JSON.parse');};}}());wn.require("lib/js/lib/jquery.min.js");wn.require("lib/js/lib/history/history.min.js");$(document).bind('ready',function(){var base=window.location.href.split('#')[0];$.each($('a[softlink!="false"]'),function(i,v){if(v.href.substr(0,base.length)==base){var path=(v.href.substr(base.length));if(path.substr(0,1)!='#'){v.href=base+'#'+path;}}});if(!wn.settings.no_history&&window.location.hash){wn.page.set(window.location.hash.substr(1));}});
throw new SyntaxError('JSON.parse');};}}());wn.versions.check();wn.require("lib/js/lib/jquery.min.js");wn.require("lib/js/lib/history/history.min.js");$(document).bind('ready',function(){var base=window.location.href.split('#')[0];$.each($('a[softlink!="false"]'),function(i,v){if(v.href.substr(0,base.length)==base){var path=(v.href.substr(base.length));if(path.substr(0,1)!='#'){v.href=base+'#'+path;}}});if(!wn.settings.no_history&&window.location.hash){wn.page.set(window.location.hash.substr(1));}});

+ 3
- 6
js/wn/assets.js Datei anzeigen

@@ -1,19 +1,17 @@
// library to mange assets (js, css, models, html) etc in the app.
// will try and get from localStorge if latest are available
// or will load them via xmlhttp
// depends on asset_timestamps_ loaded via boot
// depends on wn.versions to manage versioning

wn.assets = {
// keep track of executed assets
executed_ : {},
// check if the asset exists in
// localstorage and if the timestamp
// matches with the loaded timestamp
// localstorage
exists: function(src) {
if('localStorage' in window
&& localStorage.getItem(src)
&& localStorage.getItem('[ts] '+src) == asset_timestamps_[src])
&& localStorage.getItem(src))
return true
},
@@ -22,7 +20,6 @@ wn.assets = {
add: function(src, txt) {
if('localStorage' in window) {
localStorage.setItem(src, txt);
localStorage.setItem('[ts] ' + src, asset_timestamps_[src]);
}
},


+ 42
- 0
js/wn/versions.js Datei anzeigen

@@ -0,0 +1,42 @@
// manage app versioning
// get the last_version_number from the server (loaded)
// and update based on it

wn.versions = {
is_latest: function() {
if(window._version_number == (localStorage ? localStorage['_version_number'] : null)) {
return true;
}
return false;
},
// get the change list of all files
// from current version and local version
get_diff: function() {
if(!localStorage) return;
wn.xmlhttp.get('index.cgi', function(txt) {
// add it to localstorage
r = JSON.parse(txt);
if(r.exc) { alert(r.exc); }
wn.versions.set(r.message);
}, 'cmd=get_diff&version_number=' + localStorage['_version_number'], false);
},
// set will clear all changes since the last update
set: function(diff) {
for(var i=0; i<diff.length; i++) {
localStorage.removeItem(diff[i]);
}
localStorage['_version_number'] = _version_number;
},
check: function() {
if(localStorage && !localStorage['_version_number']) {
// first load
localStorage['_version_number'] = _version_number;
return;
}
if(!wn.versions.is_latest()) wn.versions.get_diff();
}
}

+ 3
- 2
js/wn/xmlhttp.js Datei anzeigen

@@ -25,8 +25,9 @@ wn.xmlhttp = {
wn.xmlhttp.complete(req, callback, url)
}
}
req.open('GET', url, async);
req.send(args ? url + '?' + args : null);
var u = args ? (url + '?' + args) : url;
req.open('GET', u, async);
req.send(null);
// for sync
if(!async) {


+ 2
- 5
py/build/__init__.py Datei anzeigen

@@ -2,20 +2,17 @@ verbose = True
force_rebuild = False
no_minify = False

def run():
def run(path):
"""
Run the builder
"""
global verbose
import sys, os

sys.path.append('py')
sys.path.append('lib/py')

from build.project import Project

verbose = True
Project().build()
Project().build(path)
if __name__=='__main__':
run()

+ 18
- 15
py/build/bundle.py Datei anzeigen

@@ -40,7 +40,9 @@ class Bundle:
f = open(outfile, 'w')
f.write(temp.getvalue())
f.close()
self.timestamps.update(outfile)

self.vc.repo.add(outfile)

if verbose: print 'Wrote %s' % outfile

return temp
@@ -50,13 +52,15 @@ class Bundle:
Returns true if the files are changed since last build
"""
import os
from build import force_rebuild
from build import force_rebuild, verbose

if force_rebuild:
return True
for f in files:
if f in self.timestamps.dirty:
if f in self.dirty:
if verbose:
print '*** %s changed' % f
return True
return False

@@ -82,7 +86,7 @@ class Bundle:
jsm.minify(temp, out)

out.close()
self.timestamps.update(outfile)
self.vc.repo.add(outfile)

new_size = os.path.getsize(outfile)

@@ -92,7 +96,7 @@ class Bundle:
print 'Compressed: %.2f kB' % (new_size / 1024.0)
print 'Reduction: %.1f%%' % (float(org_size - new_size) / org_size * 100)

def make(self, path):
def make(self, bpath):
"""
Build (stitch + compress) the file defined in build.json
"""
@@ -101,10 +105,12 @@ class Bundle:
# open the build.json file and read
# the dict
bfile = open(os.path.join(path, 'build.json'), 'r')
bfile = open(bpath, 'r')
bdata = json.loads(bfile.read())
bfile.close()
path = os.path.dirname(bpath)
for outfile in bdata:
prefix, fname = False, outfile

@@ -115,8 +121,6 @@ class Bundle:
# build the file list relative to the main folder
fl = [os.path.relpath(os.path.join(path, f), os.curdir) for f in bdata[outfile]]

self.timestamps.bundled += fl

if self.changed(fl):
# js files are minified by default unless explicitly
# mentioned in the prefix.
@@ -127,16 +131,15 @@ class Bundle:
else:
self.concat(fl, os.path.relpath(os.path.join(path, fname), os.curdir))
def bundle(self, timestamps):
def bundle(self, vc):
"""
Build js files from "build.json"
Build js files from "build.json" found in version control
"""
import os
self.timestamps = timestamps
self.dirty = vc.repo.uncommitted()
self.vc = vc

# walk the parent folder and build all files as defined in the build.json files
for wt in os.walk('.', followlinks=True):
if 'build.json' in wt[2]:
# found build file
self.make(os.path.abspath(wt[0]))
for b in vc.repo.sql("select fname from bundles"):
self.make(os.path.abspath(os.path.join(vc.root_path, b[0])))


+ 18
- 11
py/build/project.py Datei anzeigen

@@ -14,11 +14,9 @@ class Project:
"""
load libraries
"""
from build.timestamps import Timestamps
from build.bundle import Bundle
from nav import Nav
self.timestamps = Timestamps()
self.bundle = Bundle()
self.nav = Nav()

@@ -29,11 +27,13 @@ class Project:
import json

corejs = open('lib/js/core.min.js', 'r')
v = int(self.vc.repo.get_value('last_version_number') or 0) + 1
boot = 'var asset_timestamps_=' + self.timestamps.get('json', ('js', 'html', 'css')) \
+ '\n' + corejs.read()
boot = ('window._version_number="%s"' % str(v)) + \
'\n' + corejs.read()
corejs.close()
return boot

def render_templates(self):
@@ -69,15 +69,22 @@ class Project:
print "Rendered %s | %.2fkb" % (fpath, os.path.getsize(fpath) / 1024.0)

def build(self):
def build(self, root_path):
"""
Build all js files, timestamps.js, index.html and template.html
Build all js files, index.html and template.html
"""
from build.version import VersionControl
# make bundles
self.bundle.bundle(self.timestamps)
self.vc = VersionControl(root_path)
self.vc.add_all()
# index, template if framework is dirty
if self.timestamps.dirty:
if self.vc.repo.uncommitted():
self.bundle.bundle(self.vc)
self.render_templates()
self.timestamps.write()

# again add all bundles
self.vc.add_all()
self.vc.repo.commit()
self.vc.close()

+ 73
- 27
py/build/version.py Datei anzeigen

@@ -7,6 +7,7 @@
uncommitted (fname, ftype, content, timestamp)
files (fname, ftype, content, timestamp, version)
log (fname, ftype, version)
bundle_files (fname primary key)
Discussion:
@@ -27,23 +28,29 @@ TODO
import unittest
import os

root_path = os.path.abspath(os.path.join(os.path.abspath(os.path.dirname(__file__)), '..', '..'))
test_file = {'fname':'test.js', 'ftype':'js', 'content':'test_code', 'timestamp':'1100'}
root_path = os.path.abspath(os.curdir)

def edit_file():
# edit a file
p = os.path.join(root_path, 'js/core.min.js')
content = open(p, 'r').read()
p = os.path.join(root_path, 'lib/js/core.js')

# read
f1 = open(p, 'r')
content = f1.read()
f1.close()
# write
f = open(p, 'w')
f.write(content)
f.close()
return p
return os.path.relpath(p, root_path)
verbose = False

class TestVC(unittest.TestCase):
def setUp(self):
self.vc = VersionControl(root_path)
self.vc = VersionControl()
self.vc.repo.setup()
def test_add(self):
@@ -86,13 +93,12 @@ class TestVC(unittest.TestCase):
self.vc.repo.commit()

p = edit_file()

# add
self.vc.add_all()

# check if added
ret = self.vc.repo.sql("select * from uncommitted", as_dict=1)
self.assertTrue(ret[0]['fname']==p)
self.assertTrue(p in [r['fname'] for r in ret])

def test_merge(self):
self.vc.add_all()
@@ -114,7 +120,7 @@ class TestVC(unittest.TestCase):
self.vc.merge(self.vc.repo, self.vc.master)
log = self.vc.master.diff(int(self.vc.master.get_value('last_version_number'))-1)
self.assertTrue(log, [p])
self.assertTrue(p in log)
def tearDown(self):
self.vc.close()
@@ -124,25 +130,26 @@ class TestVC(unittest.TestCase):


class VersionControl:
def __init__(self, root):
def __init__(self, root=None):
#self.master = Repository(self, 'versions-master.db')
self.root(root)
self.set_root(root)

self.repo = Repository(self, 'versions-local.db')
self.ignore_folders = ['.git', '.', '..']
self.ignore_files = ['pyc', 'DS_Store', 'txt', 'db-journal', 'db']
self.ignore_files = ['py', 'pyc', 'DS_Store', 'txt', 'db-journal', 'db']
def setup_master(self):
self.master = Repository(self, 'versions-master.db')
def root(self, path=None):
def set_root(self, path=None):
"""
set / reset root and connect
(the root path is the path of the folder)
"""
if path:
self.root_path = path
else:
return self.root_path
if not path:
raise Exception, 'path must be given'
self.root_path = path
def timestamp(self, path):
"""
@@ -164,12 +171,16 @@ class VersionControl:
wt[1].remove(folder)
for fname in wt[2]:
fpath = os.path.join(wt[0], fname)

if fname.endswith('build.json'):
self.repo.add_bundle(fpath)
continue
if fname.split('.')[-1] in self.ignore_files:
# nothing to do
continue
fpath = os.path.join(wt[0], fname)
# file does not exist
if not self.repo.exists(fpath):
if verbose:
@@ -208,6 +219,7 @@ class VersionControl:
target.commit(d[0])

def close(self):
self.repo.conn.commit()
self.repo.conn.close()
class Repository:
@@ -223,12 +235,14 @@ class Repository:
"""
setup the schema
"""
print "setting up %s..." % self.db_path
self.cur.executescript("""
create table properties(pkey primary key, value);
create table uncommitted(fname primary key, ftype, content, timestamp);
create table files (fname primary key, ftype, content, timestamp, version);
create table log (fname, ftype, version);
create table versions (number integer primary key, version);
create table bundles(fname primary key);
""")
def sql(self, query, values=(), as_dict=None):
@@ -267,13 +281,17 @@ class Repository:
"""
add to uncommitted
"""
import os
# commit relative path
fname = os.path.relpath(fname, self.vc.root_path)
if not ftype:
ftype = fname.split('.')[-1]
if not timestamp:
timestamp = self.vc.timestamp(fname)
self.sql("insert into uncommitted(fname, ftype, timestamp, content) values (?, ?, ?, ?)" \
self.sql("insert or replace into uncommitted(fname, ftype, timestamp, content) values (?, ?, ?, ?)" \
, (fname, ftype, timestamp, content))
def new_version(self):
@@ -296,15 +314,30 @@ class Repository:
def commit(self, version=None):
"""
rebuild bundles if necessary
copy uncommitted files to repository, update the log and add the change
"""
# make bundles
from bundle import Bundle
Bundle().bundle(self.vc)
# get a new version number
if not version:
version = self.new_version()
if not version: version = self.new_version()

self.update_number(version)

# find added files to commit
self.add_from_uncommitted(version)
# clear uncommitted
self.sql("delete from uncommitted")
def add_from_uncommitted(self, version):
"""
move files from uncommitted table to files table
"""
added = self.sql("select * from uncommitted", as_dict=1)
for f in added:
@@ -316,21 +349,20 @@ class Repository:
""", (f['fname'], f['ftype'], f['timestamp'], f['content'], version))
# update log
self.add_log(f['fname'], f['ftype'], version)
# clear uncommitted
self.sql("delete from uncommitted")
self.add_log(f['fname'], f['ftype'], version)
def exists(self, fname):
"""
true if exists
"""
fname = os.path.relpath(fname, self.vc.root_path)
return self.sql("select fname from files where fname=?", (fname,))

def timestamp(self, fname):
"""
get timestamp
"""
fname = os.path.relpath(fname, self.vc.root_path)
return int(self.sql("select timestamp from files where fname=?", (fname,))[0][0] or 0)

def diff(self, number):
@@ -345,6 +377,12 @@ class Repository:
return list(set([f[0] for f in ret]))
def uncommitted(self):
"""
return list of uncommitted files
"""
return [f[0] for f in self.sql("select fname from uncommitted")]
def get_file(self, fname):
"""
return file info as dict
@@ -356,7 +394,15 @@ class Repository:
add file to log
"""
self.sql("insert into log(fname, ftype, version) values (?,?,?)", (fname, ftype, version))
def add_bundle(self, fname):
"""
add to bundles
"""
self.sql("insert or replace into bundles(fname) values (?)", (fname,))
if __name__=='__main__':
import os, sys
sys.path.append('py')
sys.path.append('lib/py')
unittest.main()

+ 9
- 0
py/webnotes/handler.py Datei anzeigen

@@ -40,6 +40,15 @@ def runserverobj(arg=None):
def logout():
webnotes.login_manager.logout()

# versions
# --------

def get_diff():
import os
v = webnotes.form_dict.get('version_number')
from build.version import VersionControl
webnotes.response['message'] = VersionControl(os.path.abspath(os.path.curdir)).repo.diff(v)

# DocType Mapper
# ------------------------------------------------------------------------------------



+ 54
- 1
wnf.py Datei anzeigen

@@ -1,4 +1,57 @@
#!/usr/bin/env python

print 'hello'
import os, sys

from py.build import version
version.verbose = True


def run():
sys.path.append('lib')
sys.path.append('lib/py')
vc = version.VersionControl(os.path.abspath(os.curdir))

if len(sys.argv)<2:
print "wnframework version control utility"
print
print "Usage: wnf build|add|commit|diff|merge|setup"

cmd = sys.argv[1]

if cmd=='build':
from py import build
build.run(os.path.abspath(os.curdir))
if cmd=='add':
if not len(sys.argv)>1:
print 'usage: wnf add path/to/file'
return
vc.repo.add(sys.argv[2])
if cmd=='commit':
if len(sys.argv>2) and sys.argv[2]=='-a':
vc.add_all()
vc.repo.commit()
if cmd=='diff':
vc.repo.uncommitted()
if cmd=='merge':
vc.setup_master()
if sys.argv[2]=='local':
vc.merge(vc.repo, vc.master)
elif sys.argv[2]=='master':
vc.merge(vc.master, vc.repo)
else:
print "usage: wnf merge local|master"
print "help: parameter (local or master) is the source"

if cmd=='setup':
vc.repo.setup()

vc.close()

if __name__=='__main__':
run()

Laden…
Abbrechen
Speichern