<file_sep>define('main', [] , function(){



    return{
        common: function(){},
        index: function(){
            setTimeout(function(){alert("hello @world@")},3000);
        }
    };
});

<file_sep>#!/usr/bin/env python
#-*- coding:utf-8 -*-

'''
 render.py

 changelog
 2013-12-01[00:32:46]:created
 2013-12-14[23:52:33]:define TokenRender
 2013-12-17[12:13:55]:move removeCssDepsDeclaration out of class

 @info yinyong,osx-x64,UTF-8,192.168.1.101,py,/Users/yinyong/work/ursa2/src
 @author <EMAIL>
 @version 0.0.1
 @since 0.0.1
'''
from conf import C,log,BASE_DIR
import utils
import os
import re
import json
from timestamp import html_link,html_script,html_img,all_url,all as allt
from deps import DepsFinder
from replace import replace
from jinja2 import Template,Environment,FileSystemLoader,TemplateNotFound,TemplateSyntaxError

_template_dir = C('template_dir')

jinjaenv = Environment(loader = FileSystemLoader(utils.abspath(_template_dir),  C('encoding') ), extensions = ["jinja2.ext.do"] , autoescape = True )
build_jinjaenv = Environment( loader = FileSystemLoader( os.path.join( os.getcwd() , C('build_dir'), _template_dir) ,  C('encoding') ))
mgr_jinjaenv = Environment( loader = FileSystemLoader( os.path.join(BASE_DIR,'tpl') ,  C('encoding') ))

def render_file(filename,data = None,noEnvironment = False,build = False):
    '''
    渲染文件
    '''
    if noEnvironment:
        body = mgr_jinjaenv.get_template(filename)#Template(utils.readfile(filename))#这里应为绝对路径
    else:
        if build:
            body = build_jinjaenv.get_template(filename)
        else:
            body = jinjaenv.get_template(filename)
    return body.render(data or {})

def removeCssDepsDeclaration(html):
    '''
    移除HTML中对CSS的依赖声明
    '''
    return re.sub(r'<!\-\-[\s\S]*?@require[\s\S]*?\-\->','',html)

class TokenRender(object):
    '''
    '''
    @classmethod
    def __init__(self,token):
        self.__token = utils.filterPath(token)

        df = DepsFinder(token)
        self.__deps = df.find()
        self.__include_deps = df.findIncludes()
        self.__html = None

    @classmethod
    def getData(self,including_deps = True):
        data = {}
        if C('disable_deps_search') or not including_deps:
            deps = [self.__token+'.'+C('template_ext')]
        else:
            #复制
            deps = self.__deps[0:]
            deps.reverse()
        deps.insert(len(deps),self.__token+".json")
        deps.insert(0,"_ursa.json")
        for dep in deps:
            try:
                json_filepath = utils.abspath(os.path.join(C('data_dir'),re.sub(r'\.%s$'%C('template_ext'),'.json',dep)))
                content = utils.readfile(json_filepath)
                content = re.sub('\/\*[\s\S]*?\*\/','',content)
                json_data = json.loads(content)
                data.update(json_data)
            except Exception, e:
                e#log.warn('[getdata]%s:%s'%(json_filepath,e))
        return data

    @classmethod
    def render(self,build = False):
        '''
        查找数据文件依赖并渲染模板
        '''
        #remove '/'s at start
        if self.__html is None:
            data = self.getData()
            multoken = self.__token.split('/')
            data.update({'_token': self.__token.replace('/','_')})
            data.update({'_folder':multoken[0]})
            data.update({'_subtoken':multoken[1] if len(multoken)>1 else ""})
            tpl_path = self.__token + "." + C('template_ext')
            html = render_file( tpl_path,data,False,build)
            if C('server_add_timestamp'):
                #html = html_script(html)
                #html = html_link(html)
                #html = html_img(html)
                #html = all_url(html)
                html = allt(html)
            html = replace(html)
            if not build and not re.match(r'[\s\S]*?<html[\s\S]+?<body',html,re.I):
                #sub template
                css_deps = self.__getDepsCss(html)

                for tpl in self.__include_deps:
                    css = os.path.join('.',C('static_dir'),C('css_dir'),re.sub(r"\.%s"%C('template_ext'),".css",tpl))
                    css_deps.append(css)
                subparent = 'subparent.tpl'# os.path.join(BASE_DIR,"tpl",'subparent.tpl')
                html = render_file(subparent,{'name': self.__token,'content': html,'required_css': css_deps},noEnvironment = True)

            html = removeCssDepsDeclaration(html)
            self.__html = html
        return self.__html

    @classmethod
    def __getDepsCss(self,html):
        '''
        分析@require xxx.css,获取依赖
        '''
        ret = []
        iters = re.finditer(r'@require\s+?([/\w\-]+?\.css)',html,re.I)
        for it in reversed(list(iters)):
            css = it.group(1)
            css = utils.filterRelPath(css)
            ret.append( os.path.join('.',C('static_dir'),C('css_dir'),css) )
        return {}.fromkeys(ret).keys() 

if __name__ == '__main__':
    tr = TokenRender('index')
    print tr.render()
<file_sep>#!/usr/bin/env python
#-*- coding:utf-8 -*-

'''
 handler.py

 changelog
 2013-11-30[22:32:36]:created
 2013-12-11[10:51:16]:better error report
 2013-12-16[17:16:32]:category of tpls
 2013-12-25[15:17:46]:s function supported,showing static resource

 @info yinyong,osx-x64,UTF-8,192.168.1.101,py,/Users/yinyong/work/ursa2/src
 @author <EMAIL>
 @version 0.0.2
 @since 0.0.1
'''
import utils
import mimetypes
from urlparse import urlparse
import os
import sys
import subprocess
import re
import json
from conf import C,log,BASE_DIR
from render import TokenRender,render_file
from replace import replace
from timestamp import html_link,html_script,html_img,all_url
from jinja2 import TemplateNotFound,TemplateSyntaxError
import requests as R

def _token(path):
    '''
    去除后缀，取得相对于模板目录的相对无扩展名路径
    '''
    tpl_token = re.sub(r'\.(%s|%s|%s|%s)$'%(C('preview_ext'),'m','data',C('template_ext')),'',path)
    tpl_token = utils.filterRelPath(tpl_token)
    return tpl_token


def index(req,res):
    '''
    模板列表
    '''
    tpl_dir = C('template_dir')
    tpl_ext = C('template_ext')
    tpls = utils.FileSearcher(r'\.%s$'%tpl_ext,tpl_dir).search()
    _tpls = []
    _module_tpls = []
    _common_tpls = []

    for e in tpls:
        e = re.sub(r'\.%s'%tpl_ext,'',e)
        if C('ignore_parents') and e.endswith('parent'):
            continue
        #分类输出
        if e.startswith(C('module_dir') + '/'):
            _module_tpls.append(e)
        elif e.startswith(C('common_dir') + '/'):
            _common_tpls.append(e)
        else:
            _tpls.append(e);

    index_path ='index.html'
    html = render_file(index_path,{"tpls":_tpls,"module_tpls":_module_tpls,"common_tpls":_common_tpls},noEnvironment = True)
    res.send(html)

def help(req,res):
    '''
    加载帮助文件
    '''
    inner_path = 'help.html'
    r = R.get('https://raw.github.com/yanni4night/ursa2/master/README.md')
    html = render_file(inner_path,{'content':r.content},noEnvironment = True)
    res.send(html)

def rs(req,res):
    '''
    静态资源列表
    '''
    static_dir = C('static_dir')
    img_dir = os.path.join(static_dir,C('img_dir'))
    js_dir = os.path.join(static_dir,C('js_dir'))
    css_dir = os.path.join(static_dir,C('css_dir'))

    imgs = utils.FileSearcher(r'\.(png|bmp|gif|jpe?g|ico)$',img_dir).search()
    csses =  utils.FileSearcher(r'\.(css|less)$',css_dir).search()
    jses =  utils.FileSearcher(r'\.js$',js_dir).search()

    static_path = 'static.html'#os.path.join(BASE_DIR,'tpl','static.html')
    html = render_file(static_path,{"img":imgs,"css":csses,"js":jses,"img_dir":img_dir,"css_dir":css_dir,"js_dir":js_dir},noEnvironment = True)
    res.send(html)


def tpl(req,res):
    '''
    模板
    '''
    tpl_token = _token(req.path)
    try:
        tr =TokenRender(tpl_token)
        return res.send(tr.render())
    except TemplateNotFound as e:
        return res.send(code = 500,content = 'Template %s not found' % (str(e) ,))
    except TemplateSyntaxError as e:
        return res.send(code = 500,content = 'Template %s:%d Syntax Error:%s' % (e.filename,e.lineno,e.message))
    except Exception, e:
        return res.send(code = 500,content = '%s'%e)

def data(req,res):
    '''
    获取对应模板的依赖数据组合
    '''
    tpl_token = _token(req.path)
    tr = TokenRender(tpl_token)
    data = tr.getData()
    return res.send(json.dumps(data),headers = {'Content-Type':'application/json'})

def m(req,res):
    '''
    输出对应json数据
    '''
    tpl_token = _token(req.path)
    data = ''
    json_path = os.path.join(C('data_dir'),tpl_token+".json")
    try:
        data = utils.readfile(json_path)
    except Exception, e:
        log.error('[m]%s:%s'%(json_path,e))
    mgr_path = 'mgr.html'# os.path.join(BASE_DIR,'tpl','mgr.html')
    html = render_file(mgr_path,{"name":tpl_token,"data":data},noEnvironment = True)
    res.send(html)

def so(req,res):
    '''
    保存json数据
    todo
    '''
    tpl_token = _token(req.param('tpl'))
    json_str = req.param('data')
    try:
        json_obj = json.loads(json_str)
        json_path = os.path.join(C('data_dir'),tpl_token+".json")
        utils.writeJSON(json_path,json_obj)
        res.redirect('/%s.%s'%(tpl_token,C('preview_ext')))
    except ValueError, e:
        res.send(render_file('json.html',{'json' : json_str,'tpl_token' : tpl_token} , noEnvironment = True))
    except Exception,e:
        log.error('[so]%s'%e)
        res.redirect('/%s.%s'%(tpl_token,C('preview_ext')))
    

def static(req,res):
    '''
    static resource
    '''
    req.path = utils.filterRelPath(req.path)
    o = urlparse(req.path)
    #取得绝对路径
    fd = utils.abspath(o.path)
    if fd.endswith('.css'):
        less = re.sub(r"\.css",".less",fd)
        if os.path.exists(less) and os.path.isfile(less):
            try:
                subprocess.call('lessc %s %s'%(less,fd),shell=True)
            except Exception, e:
                log.error('[less]%s'%e)
            
    if os.path.isfile(fd):
        mime = mimetypes.guess_type(fd,False)
        content_type = mime[0] or 'text/plain'#default text
        try:
            headers = {}
            #todo  custom defined
            if not utils.isBinary(fd):
                content = utils.readfile(fd)
                content = replace(content)
               
                if C('server_add_timestamp') :
                    if content_type.find('css') >= 0:
                        base_dir = os.path.dirname(fd)
                        content = all_url(content,base_dir)
                    elif content_type.find('html') >= 0:
                        content = html_link(content)
                        content = html_script(content)
                        content = html_img(content)
                        content = all_url(content)
                #http encoding header
                headers['Content-Encoding'] = C('encoding')
                headers['Cache-Control'] = 'nocache'
            else:
                #binary files
                content = utils.readfile(fd,'rb')

            headers['Content-Type'] = content_type
            res.send(content = content,headers = headers)
        except Exception, e:
            res.send(code = 500,content = '%s'%e)
            log.error("[static]%s"%e)
    else:
        if os.path.exists(fd):
            res.send(code = 403,content = 'Access %s is forbidden'%req.path)
        else:
            res.send(code = 404,content = "%s not found"%req.path)<file_sep>#!/usr/bin/env python
#-*- coding:utf-8 -*-
'''
 exception.py

 changelog
 2013-12-07[12:46:58]:created

 custom defined exceptions and errors

 @info yinyong,osx-x64,UTF-8,192.168.1.104,py,/Users/yinyong/work/ursa2/src
 @author <EMAIL>
 @version 0.0.1
 @since 0.0.1
'''
class ConfigurationError(Exception):
    '''
    ConfigurationError
    '''
    @classmethod
    def __init__(self, msg):
        self.msg=msg
    @classmethod
    def __str__(self):
        return self.msg

class DirectoryError(Exception):
    '''
    DirectoryError
    '''
    @classmethod
    def __init__(self,msg):
        self.msg=msg
    @classmethod
    def __str__(self):
        return self.msg

class FileSizeOverflowError(Exception):
    '''
    File size overflow Error
    '''
    @classmethod
    def __init__(self,msg):
        self.msg=msg
    @classmethod
    def __str__(self):
        return self.msg
        <file_sep>#!/usr/bin/env python
#-*- coding:utf-8 -*-
'''
 main.py

 changelog
 2013-11-30[00:21:29]:created

 @info yinyong,osx-x64,UTF-8,192.168.1.101,py,/Users/yinyong/work/ursa2/bin
 @author yinyong<EMAIL>
 @version 0.0.1
 @since 0.0.1
'''

from docopt import docopt
from __init__ import __version__ as version
import sys
import os

BASE_DIR = os.path.abspath(os.path.dirname(__file__)) 
sys.path.append(BASE_DIR)

def copyfiles(sourceDir, targetDir):
    '''
    拷贝文件和目录
    '''
    for file in os.listdir(sourceDir): 
        sourceFile = os.path.join(sourceDir,  file) 
        targetFile = os.path.join(targetDir,  file) 
        if os.path.isfile(sourceFile): 
            if not os.path.exists(targetDir):  
                os.makedirs(targetDir)  
            if not os.path.exists(targetFile) or(os.path.exists(targetFile) and (os.path.getsize(targetFile) != os.path.getsize(sourceFile))):  
                open(targetFile, "wb").write(open(sourceFile, "rb").read()) 
        if os.path.isdir(sourceFile): 
            copyfiles(sourceFile, targetFile)  


def run():
    '''
    ursa2

    Usage:
        ursa2 init
        ursa2 start [<port>]
        ursa2 build [<target>] [-chf]
        ursa2 help
        ursa2 (-v | --version)
    
    Options:
        -c --compress        compress js and css when building.
        -h --html            creating HTML when building.
        -v --version         show version.
        -f --force           force compile
    '''
    argv=docopt(run.__doc__,version=version)
    if argv.get('init'):
        if os.listdir( '.' ):
            print "It's not an empty folder.\nContinue may override your existing files.\nStill continue initializing?"
            iscontinue = raw_input('(y/n):').lower() in ['y' , 'yes','ok' ]
            if not iscontinue:
                print ('Initializing canceled.')
                sys.exit(1)
        print ('Initializing project...')
        copyfiles(os.path.join(BASE_DIR,'assets','clo'),'.')
        print ('Initializing successfully.Usage:')
        print ('       start serer:ursa2 start')
        print ('       build project:ursa2 build')
    elif argv.get('start'):
        server=__import__('server')
        server.run(argv.get('<port>'))
    elif argv.get('build'):
        build=__import__('build')
        builder=build.UrsaBuilder(argv.get('--compress'),argv.get('--html'),argv.get('<target>'),argv.get('--force'))
        builder.build();
    elif argv.get('help'):
        print 'https://github.com/yanni4night/ursa2/wiki'


if __name__=='__main__':
    run();<file_sep>#!/usr/bin/env python
#-*- coding:utf-8 -*-
'''
 timestamp.py

 changelog
 2013-12-01[15:59:50]:created
 2013-12-10[11:17:11]:clean

 @info yinyong,osx-x64,UTF-8,192.168.1.101,py,/Users/yinyong/work/ursa2/src
 @author <EMAIL>
 @version 0.0.1
 @since 0.0.1
'''
from conf import C,log
import re
import utils
import os
from replace import replace
from urlparse import urlparse,parse_qs

def _addtimestamp(content,reg,base_dir,force_abspath=False):
    '''
    以base_dir为基础目录，在content中搜寻匹配reg的URL并尝试追加时间戳
    
    reg:匹配URL的正则，其中\3为URL
    force_abspath:强制路径为绝对路径，即以WD为base_dir
    '''
    iters=re.finditer(reg,content,re.I|re.M)
    t=C('timestamp_name')
    for it in reversed(list(iters)):
        start = content[0:it.start(1)]
        url = it.group(3)
        end = content[it.end(1):]
        local_url = replace(url)
        parsed_url = urlparse(local_url)
        parsed_query = parse_qs(parsed_url.query)

        #已经有时间戳的不再添加
        #带协议的不再添加
        if not local_url or not parsed_url.path:
            continue
        #具有模板语法的不予添加
        elif parsed_url.path.find('{{') >=0 or parsed_url.path.find('{%') >= 0:
            log.warn('twig syntax found in %s'%parsed_url.path)
            continue
        elif re.match(r'^\s*(about:|data:|#)',local_url):
            log.warn('%s is an invalid url'%local_url)
            continue
        elif parsed_query.get(t) is not None:
            log.warn("%s has a timestamp"%local_url)
            continue
        elif parsed_url.scheme  or local_url.startswith('//'):
            log.warn("%s has a scheme"%local_url)
            continue
        
        if os.path.isabs(parsed_url.path) or force_abspath:
            #绝对路径，则以当前工程根目录为root
            timestamp = utils.get_file_timestamp(utils.abspath(base_dir + parsed_url.path))
        else:
            #相对目录，则此当前文件目录为root
            #应该仅在CSS内使用相对路径
            timestamp = utils.get_file_timestamp(os.path.join(base_dir,parsed_url.path))

        #计算不到时间戳则忽略
        if not timestamp:
            continue

        parsed_url = urlparse(url)
        new_query = parsed_url.query
        if '' == new_query:
            new_query = t+"="+timestamp
        else:
            new_query+='&%s=%s'%(t,timestamp)
        if '' == parsed_url.fragment:
            new_fragment = ''
        else:
            new_fragment = '#'+parsed_url.fragment

        if not parsed_url.scheme:
            new_scheme = ''
        else:
            new_scheme = parsed_url.scheme+"://"

        new_url = new_scheme + parsed_url.netloc + parsed_url.path + '?' + new_query + new_fragment
        content = start + (it.group(2) or '') + new_url + (it.group(2) or '') + end

    return content

def html_link(content,base_dir=".",force_abspath = True):
    '''
    向<html>中<link>元素添加时间戳
    '''
    return _addtimestamp(content,r'<link.* href=(([\'"])(.*?\.css.*?)\2)',base_dir,force_abspath)

def html_script(content,base_dir=".",force_abspath = True):
    '''
    向<html>中<script>元素添加时间戳
    '''
    return _addtimestamp(content,r'<script.* src=(([\'"])(.*?\.js.*?)\2)',base_dir,force_abspath)

def html_img(content,base_dir='.',force_abspath = True):
    '''
    向<html>中<img>元素添加时间戳
    '''
    return _addtimestamp(content,r'<img.* src=(([\'"])(.*?\.(png|gif|jpe?g|bmp|ico).*?)\2)',base_dir,force_abspath)

def all_url(content,base_dir="."):
    '''
    向HTML、CSS中的url()添加时间戳
    '''
    return _addtimestamp(content,r'url\((([\'"])?([\S]+?)\2?)\)',base_dir)

def all(content,base_dir='.',force_abspath=True):
    '''
    添加所有类型的时间戳
    '''
    content=html_link(content,base_dir,force_abspath)
    content=html_script(content,base_dir,force_abspath)
    content=html_img(content,base_dir,force_abspath)
    content=all_url(content,base_dir)

    return content

if __name__ == '__main__':
    sample="url(/static/css/font.css?x={{key}}&ty=09&bn==56)"
    print sample
    print all_url(sample)<file_sep>#!/usr/bin/env python
#-*- coding:utf-8 -*-
'''
 proxy.py

 changelog
 2013-12-11[17:23:52]:created

 @info yinyong,osx-x64,UTF-8,10.129.164.77,py,/Volumes/yinyong/ursa2/src
 @author <EMAIL>
 @version 0.0.1
 @since 0.0.1
'''

import requests as R
from urlparse import urlparse
import utils
import re
from conf import C,log


def get_proxy_url(req_path,reg,target):
    '''
    '''
    if not utils.isStr(reg) or not utils.isStr(target):
        log.warn('%s:%s is an illegal proxy pair'%(reg,target))
        return None
    if reg.startswith('regex:'):
        #正则匹配
        reg = reg[6:]
        if re.match(r'%s'%reg,req_path):
            target_path = re.sub(r'%s'%reg,r'%s'%target,req_path)
            return target_path
    elif reg.startswith('exact:'):
        #精确匹配
        reg = reg[6:]
        if reg == req_path:
            target_path = target
            return target_path
    else :
        #变量输出
        s = re.search('\$\{(.+)\}' , target)
        target_path= target
        if s:
            name = s.group(1)
            pattern = re.sub( '\{.+\}' , '(.+)' , reg )
            m = re.match(pattern, req_path )
            if m:
                path = m.group(1)
                target_path = target_path.replace( '${'+name+'}' , path )
                return target_path
    return None

def proxy(target_url,req,res):
    '''
    '''
    if not target_url:
        return res.send(code = 500,content = 'Empty url not supported')

    #二进制资源直接重定向
    parsed_url = urlparse(target_url)
    
    if utils.isBinary(parsed_url.path,strict = True):
        return res.redirect(target_url)

    if 'GET' == req.method:
        request = R.get
    elif 'POST' == req.method:
        request = R.post

    try:
        #通知远端服务器不要压缩
        if req.headers.get('accept-encoding'):
            del req.headers['accept-encoding']
        if req.headers.get('host'):
            del req.headers['host']
        log.info('[proxy]requesting %s'%target_url)

        r = request(target_url,headers = req.headers)
        #本地服务器覆写Date和Server
        if r.headers.get('date'):
            del r.headers['date']
        if r.headers.get('server'):
            del r.headers['server']
        if r.headers.get('transfer-encoding'):
            del r.headers['transfer-encoding']

        log.info('[proxy] status=%d'%r.status_code)

        return res.send(code = r.status_code,content = r.content or '',headers =  r.headers)
    except Exception, e:
        log.error('[proxy]%s'%e)
        return res.send(code = 500,content = '%s'%e)

def __test_content_type(url = False):
    r = R.get(url or 'http://www.sogou.com/web?query=k',headers={})
    print r.content#.get('Content-Type')

def __test_get_proxy_url():
    #print get_proxy_url('/search/xxx/1','regex:/search/(.+?)/(\d)','if you see this (\\1)(\\2)')
    #print get_proxy_url('/search/xxx/2','exact:/search/xxx/2','if you see this')
    print get_proxy_url('/search/xxx/3/2','/search/xxx/{m/d}','if you see this (${m/d})')

if __name__ == '__main__':
    __test_content_type()
    __test_content_type('http://www.sogou.com')<file_sep>#!/usr/bin/env python
#-*- coding:utf-8 -*-
'''
 replace.py

 changelog
 2013-12-01[11:42:20]:created

 @info yinyong,osx-x64,UTF-8,192.168.1.101,py,/Users/yinyong/work/ursa2/src
 @author <EMAIL>
 @version 0.0.1
 @since 0.0.1
'''

from conf import C,log
import re
import utils

range_item=0

def replace(content,target=None):
    '''
    替换@@变量
    '''
    TM_TOKEN = '@tm:(.+?)@'
    DATE_TOKEN = '@date@';
    COMMON_TOKEN = r'@([\w\-\/\\]+?)@'
    

    iters = re.finditer( TM_TOKEN , content )
    for i in reversed(list(iters)):
        file_path = i.group(1)
        if file_path.startswith('/'):
            file_path=file_path[1:]
        file_path=utils.abspath(file_path)
        content = content[0:i.start(0)] + utils.get_file_timestamp(file_path) + content[i.end(0):]

    iters = re.finditer( DATE_TOKEN , content )
    for i in reversed(list(iters)):
        content = content[0:i.start(0)] + utils.getDate()  + content[i.end(0):]

    iters = re.finditer( COMMON_TOKEN , content )

    for i in reversed(list(iters)):
        name = i.group(1)
        value = C(name,target)
        if value is not None:
            if utils.isStr(value) and value.find('{num}') != -1:
                num = int(C('num',target))
                num = range(num+1)
                substr100 = content[i.end(0):i.end(0)+100]#what
                istimestamp = substr100.find('%s='%C('timestamp_name'))
                if istimestamp != -1:#has timestamp
                    try:
                        tm = int(substr100[istimestamp+2:istimestamp+3])
                    except ValueError:
                        continue
                    if tm >= len(num):
                        tm = tm - len(num)
                    value = value.replace( '{num}' , str(tm) )
                else:
                    global range_item
                    value = value.replace( '{num}' , str(num[range_item]) )
                    range_item = range_item + 1
                    if range_item >= len(num):
                        range_item = 0
            content = content[0:i.start(0)] + str(value) + content[i.end(0):]
    return content<file_sep>#!/usr/bin/env python
#-*- coding:utf-8 -*-
'''
 server.py

 changelog
 2013-11-30[17:37:42]:created
 2013-12-11[10:22:12]:https supported
 2013-12-25[15:16:44]:static /s supported

 @info yinyong,osx-x64,UTF-8,192.168.1.101,py,/Users/yinyong/work/ursa2/src
 @author <EMAIL>
 @version 0.0.2
 @since 0.0.1
 @todo https support,proxy support
'''

from  BaseHTTPServer import HTTPServer,BaseHTTPRequestHandler
from http import Request,Response
from route import Route
from conf import C,log,BASE_DIR
import utils
import ssl
import os
import socket
from OpenSSL import SSL
from SocketServer import BaseServer
from handler import static,index,tpl,so,m,data,rs,help

ursa_router = Route()
ursa_router.get(r'^/$' , index)
ursa_router.get(r'^/rs$' , rs)
ursa_router.get(r'^/help$' , help)
ursa_router.get(r'\.%s$'%C('preview_ext') , tpl)
ursa_router.post(r'\.so$' , so)
ursa_router.get(r'\.m$' , m)
ursa_router.get(r'\.data$' , data)
ursa_router.get(r'^/.+' , static)

class UrsaHTTPRequestHandler(BaseHTTPRequestHandler):
    '''
    HTTP请求处理器
    '''

    def do_GET(self):
        '''
        处理GET请求
        '''
        ursa_router.distribute_request(self)

    def do_POST(self):
        '''
        todo:处理POST请求
        '''
        ursa_router.distribute_request(self)

    @classmethod
    def version_string(self):
        '''
        服务器名字
        '''
        return 'Ursa2'

    @classmethod
    def log_date_time_string(self):
        '''
        服务器日志日期格式
        '''
        return utils.getDate(fmt="%Y/%m/%d %H:%M:%S")
    
    def log_request(self , code , message=''):
        '''
        服务器日志输出
        '''
        if C('log_http_request'):
            return BaseHTTPRequestHandler.log_request(self,code,message)
        else:
            return ''
    def setup(self):
        '''
        for https
        '''
        self.connection = self.request
        self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
        self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)


class SecureHTTPServer(HTTPServer):
    '''
    HTTPS服务器，来自互联网
    '''
    def __init__(self, server_address, HandlerClass):
        BaseServer.__init__(self, server_address, HandlerClass)
        ctx = SSL.Context(SSL.SSLv23_METHOD)

        ctx.use_privatekey_file( os.path.join(BASE_DIR,"assets","privatekey.pem"))
        ctx.use_certificate_file( os.path.join(BASE_DIR,"assets","certificate.pem"))

        self.socket = SSL.Connection(ctx, socket.socket(self.address_family, self.socket_type))

        self.server_bind()
        self.server_activate()

def get_local_ip():
    '''
    获取本机IP
    '''
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        s.connect(('w3.org',80))
        return s.getsockname()[0]
    except Exception, e:
        return '127.0.0.1'
    finally:
       try:
            s.close()
       except Exception, e:
           e

def run(port=8000):
    '''
    服务器启动入口
    '''
    try:
        port=int(port)
    except Exception, e:
        try:
            port=int(C('server_port'))
        except Exception, e:
            port=8000
 
    server_addr=('',port);
    try:
        if 'https' == C('protocol'):
            httpd=SecureHTTPServer(server_addr,UrsaHTTPRequestHandler)
            protocol = 'https'
        else:
            httpd=HTTPServer(server_addr,UrsaHTTPRequestHandler)
            protocol = 'http'
            
        print "Server listen on %s://%s:%d/" % (protocol,get_local_ip(), port)
        httpd.serve_forever()
    except (KeyboardInterrupt , SystemExit):
        print "Shutting down.Goodbye!"
        httpd.socket.close()
    except socket.error,e:
        log.error(e)
        sys.exit(1)

if __name__ == '__main__':
    run()<file_sep>#!/usr/bin/env python
#-*- coding:utf-8 -*-
'''
 http.py

 changelog
 2013-11-30[18:59:59]:created

 @info yinyong,osx-x64,UTF-8,192.168.1.101,py,/Users/yinyong/work/ursa2/src
 @author <EMAIL>
 @version 0.0.1
 @since 0.0.1
'''
from urlparse import urlparse,parse_qs
from conf import log,C
import utils
import re
import sys

reload(sys) 
sys.setdefaultencoding('utf-8')

class Request(object):
    '''
    HTTP请求
    '''

    @classmethod
    def __init__(self,http_req_handler):
        '''
        构造Request对象，
        拷贝HTTPRequestHandler的相关值
        '''
        self.path=urlparse(http_req_handler.path).path
        self.method=http_req_handler.command
        self.client_port=http_req_handler.client_address[1]
        self.client_host=http_req_handler.client_address[0]
        self.headers=http_req_handler.headers

        if 'GET' == self.method:
            #get请求解析url
            o=urlparse(http_req_handler.path)
            q=parse_qs(o.query)
            self.body=q
        elif 'POST' == self.method:
            #todo,post复杂解析
            content_len=int(http_req_handler.headers.get('Content-Length'))
            content=http_req_handler.rfile.read(content_len)
            q=parse_qs(content)
            self.body=q

    @classmethod
    def param(self,key):
        '''
        获取参数
        '''
        o=self.body.get(key)
        if o is None:
            return None

        if len(o) == 1:
            return o[0]
        else:
            return o

class Response(object):
    '''
    HTTP响应
    '''

    @classmethod
    def __init__(self,http_req_handler):
        '''
        '''
        self.http_req_handler=http_req_handler

    @classmethod
    def redirect(self,location,code=302):
        '''
        302重定向
        '''
        self.http_req_handler.send_response(302)
        self.http_req_handler.send_header('Location',location)
        self.http_req_handler.end_headers()
        self.http_req_handler.wfile.close()

    @classmethod
    def send(self,content=None,content_len=None,code=200,headers={}):
        '''
        '''
        self.http_req_handler.send_response(code)

        headers['connection'] = 'close'
        for k in headers.keys():
            self.http_req_handler.send_header(k,headers.get(k))
        if not headers.get('Content-Type'):
            headers['Content-Type']='text/html;charset='+C('encoding')

        if content is not None and not re.match(utils.BINARY_CONTENT_TYPE_KEYWORDS,headers['Content-Type'],re.IGNORECASE):
            try:
                content=unicode(content).encode(C('encoding'),'ignore')
            except Exception, e:
                log.error('[send]%s'%(e))

        #填充Content-Length头
        if headers.get('Content-Length') is None and content_len is None and content is not None:
            content_len=len(content)
            if content_len > 0:
                self.http_req_handler.send_header('Content-Length',content_len)

        self.http_req_handler.end_headers()

        if content is not None:
                self.http_req_handler.wfile.write(content)

        self.http_req_handler.wfile.close()<file_sep>#!/usr/bin/env python
#-*- coding:utf-8 -*-
'''
 build.py

 changelog
 2013-12-07[12:01:09]:created
 2013-12-10[22:40:30]:add build time show
 2013-12-15[01:04:04]:less supported
 2013-12-24[21:04:45]:'ignore_parents' setting

 @info yinyong,osx-x64,UTF-8,192.168.1.104,py,/Users/yinyong/work/ursa2/src
 @author <EMAIL>
 @version 0.0.2
 @since 0.0.1
'''

from conf import C,log,BASE_DIR
import logging
import shutil
import os
import re
import sys
import subprocess
import utils
import time
from render import TokenRender,removeCssDepsDeclaration
from exception import ConfigurationError,DirectoryError
from replace import replace
from timestamp import html_link,html_script,html_img,all_url,all as allt

RJS_PATH = os.path.join(BASE_DIR,'assets','r.js')
RPL_PATH = os.path.join(BASE_DIR,'assets','rpl.js')

#custom defined yuicompressor
__ycpath = C('yuicompressor')
YC_PATH = None
if __ycpath and utils.isStr( __ycpath ):
    if not os.path.exists( __ycpath ):
        log.warn('%s does not exist'%__ycpath )
    elif not os.path.isfile( __ycpath ) or not __ycpath.endswith('.jar'):
        log.warn('%s is not valid yuicompressor file')
    else:
        YC_PATH = __ycpath

if not YC_PATH:
    YC_PATH = os.path.join(BASE_DIR,'assets','yuicompressor-2.4.8.jar')

class UrsaBuilder(object):
    '''
    build project:
    copy and handle static resources
    generate html with json data
    '''
    @classmethod
    def __init__(self,compress,html,target = None,force = False):
        '''
        预先计算一些基础路径
        '''
        log.setLevel(logging.DEBUG)

        self._force = force

        self._compress = compress
        self._generate_html = html
        self._target = target
        self._tpl_dir = C('template_dir')
        self._build_dir = C('build_dir')
        self._static_dir = C('static_dir')
        self._js_dir = C('js_dir')
        self._css_dir = C('css_dir')
        self._compile_dir = C('compile_folder') or ''#copy and repalce

        #check dunplicated
        if os.path.relpath(self._build_dir,self._tpl_dir) == '.':
            raise ConfigurationError('template_dir and build_dir are dumplicated')
        elif os.path.relpath(self._build_dir,self._static_dir) == '.':
            raise ConfigurationError('template_dir and static_dir are dumplicated')
        elif self._compile_dir and os.path.relpath(self._compile_dir,self._static_dir) == '.':
            raise ConfigurationError('compile_dir and static_dir are dunplicated')
        elif self._compile_dir and os.path.relpath(self._compile_dir,self._build_dir) == '.':
            raise ConfigurationError('compile_dir and build_dir are dunplicated')
        elif self._compile_dir and os.path.relpath(self._compile_dir,self._tpl_dir) == '.':
            raise ConfigurationError('compile_dir and tpl_dir are dunplicated')

        self._build_static_dir = os.path.join(self._build_dir,self._static_dir)
        self._build_css_dir = os.path.join(self._build_static_dir,self._css_dir,C('css_folder') or '')
        self._build_js_dir = os.path.join(self._build_static_dir,self._js_dir,C('js_folder') or '')
        self._build_tpl_dir = os.path.join(self._build_dir,self._tpl_dir)
        
        if self._compile_dir:
            self._build_compile_dir = os.path.join(self._build_dir,self._compile_dir)

        self._build_html_dir = os.path.join(self._build_dir,C('html_dir'))

    @classmethod
    def build(self):
        '''
        do build
        '''
        tmbegin = time.time()
        self._check();
        log.info ('copying directories')
        self._dir();
        log.info('handling less...');
        self._less();
        log.info ('handling css...')
        self._css();
        log.info ('handling  javascript...')
        self._js();
        log.info ('handling template...')
        self._tpl();
        if self._generate_html:
            log.info ('handling html...')
            self._html();
        log.info ('replacing all token')
        self._replace();
        log.info ('Time cost %s s.' % (time.time()-tmbegin) )

    @classmethod
    def _check(self):
        '''
        检查是否具备一个ursa2工程必备的文件和目录
        '''
        require_dirs = [self._tpl_dir,self._static_dir];
        for d in require_dirs:
            if not os.path.exists(d):
                raise DirectoryError('Ursas project requires %s directory'%d)

    @classmethod
    def _dir(self):
        '''
        handle build dir
        '''
        shutil.rmtree(self._build_dir,True)
        os.mkdir(self._build_dir);
        shutil.copytree(self._static_dir,self._build_static_dir)
        shutil.copytree(self._tpl_dir,self._build_tpl_dir)

        if self._compile_dir:
            shutil.copytree(self._compile_dir,self._build_compile_dir)

    @classmethod
    def _less(self):
        '''
        handle less files to css

        '''
        all_less_files = utils.FileSearcher(r'\.less$',self._build_css_dir,relative = False).search()
        for less in all_less_files:
            try:
                subprocess.call('lessc %s %s'%(less,re.sub(r"\.less",".css",less)),shell = True)
                os.remove(less)
            except Exception,e:
                if self._force:
                    log.error('[less]%s'%e)
                else:
                    raise e

    @classmethod
    def _css(self):
        '''
        handle css

        r.js会对不同目录下CSS合并时的URL进行修正，因而对于@something@开头的路径会被认为是相对路径，
        产生修正错误，解决方案是先对所有CSS文件进行变量替换，时间戳添加，再由r.js合并。这会降低处理速
        度但可以解决该问题。

        考虑到速度，此过程仅支持在build时进行，开发服务器访问时不能使用。

        所有静态资源路径都应该使用绝对路径，避免在CSS中引用相对路径的图像资源。
        '''

        #搜索所有CSS文件
        all_css_files = utils.FileSearcher(r'\.css$',self._build_css_dir,relative = False).search()

        #替换和加时间戳
        for dst in all_css_files:
            try:
                content = utils.readfile(dst)
                content = all_url(content,os.path.dirname(dst))
                content = replace(content,self._target)
                utils.writefile(dst,content)
            except Exception,e:
                if self._force:
                    log.error('[css]%s'%e)
                else:
                    raise e

        #仅对指定的CSS进行r.js合并
        css_modules = C('require_css_modules')
        if not utils.isList(css_modules):
            css_modules = ['main']

        for css in css_modules:
            try:
                if not utils.isStr(css):
                    continue;
                css = re.sub(r'\/*','',css)
                if not css.endswith('.css'):
                    css += '.css'
                css_realpath = os.path.join(self._build_css_dir,css)
                self.build_css(css_realpath,css_realpath)
                continue
            except Exception,e:
                if self._force:
                    log.error('[less]%s'%e)
                else:
                    raise e

    @classmethod
    def build_css(self,src,dst):
        '''
        handle one css src to dst

        合并和按需压缩
        '''
        subprocess.call('node %s -o cssIn=%s out=%s'%(RJS_PATH,src,dst),shell = True)
        if self._compress:
            subprocess.call( 'java -jar ' + YC_PATH + ' --type css --charset ' + C('encoding') + ' ' + dst + ' -o ' + dst , shell = True )

    @classmethod
    def _js(self):
        '''
        handle js

        JS文件不同于CSS，其本身不能引用其它相对路径的静态资源，因此可以实现
        先合并再替换、加时间戳，无需预先处理所有js文件。
        '''
        js_modules = C('require_js_modules')
        if not utils.isList(js_modules):
            js_modules = C('require_modules')

        if not utils.isList(js_modules):
            js_modules = ['main']

        for js in js_modules:
            try:
                if not utils.isStr(js):
                    continue;
                js = re.sub(r'^\/+','',js)
                if not js.endswith('.js'):
                    js += '.js'
                js_realpath = os.path.join(self._build_js_dir,js)
                self.build_js(js_realpath,js_realpath,self._build_js_dir)
            except Exception,e:
                if self._force:
                    log.error('[less]%s'%e)
                else:
                    raise e

    @classmethod
    def build_js(self,src,dst,base_dir):
        '''
        handle one js src to dst

        合并、替换、加时间戳并按需压缩。
        '''
        js = os.path.relpath(src,base_dir)
        subprocess.call( 'node ' + RJS_PATH +' -o name=' + js[0:-3] + ' out='+ dst + ' optimize=none baseUrl='\
            + base_dir , shell = True)
        #repalce
        content = utils.readfile(dst)
        content = replace(content,self._target)
        utils.writefile(dst,content)
        if C('js_ascii_only'):
            subprocess.call( 'node ' + RPL_PATH +' '+dst+' '+dst,shell = True)
        if self._compress:
            subprocess.call( 'java -jar ' + YC_PATH + ' --type js --charset ' + C('encoding') + ' ' + dst + ' -o ' + dst , shell = True )
                
    @classmethod
    def _tpl(self):
        '''
        handle tempaltes

        模板仅需加时间戳和变量替换。

        这里需要加入额外的{compile_dir}文件夹下的文本文件。
        '''
        fs = utils.FileSearcher(r'\.%s$'%C('template_ext'),self._build_tpl_dir,relative = False)
        tpls = fs.search()
        if self._compile_dir:
            nfs = utils.FileSearcher(r'.+',self._build_compile_dir,relative = False)
            compile_files = nfs.search()
            for f in compile_files:
                if not utils.isBinary(f):
                    tpls.insert(0,f)

        for tpl in tpls:
            try:
                content = utils.readfile(tpl)
                #模板的静态资源相对目录应该写死为cwd，即资源路径应该始终是绝对路径
                content = allt(content,self._build_dir,force_abspath = False)
                content = replace(content,self._target)
                content = removeCssDepsDeclaration(content)
                utils.writefile(tpl,content)
            except Exception,e:
                if self._force:
                    log.error('[tpl]%s'%e)
                else:
                    raise e

    @classmethod
    def _replace(self):
        '''
        替换所有文本的变量
        '''
        files = utils.FileSearcher(r'.+',self._build_dir).search()
        for f in files:
            f = os.path.join(self._build_dir,f)
            if not utils.isBinary(f):
                try:
                    content = utils.readfile(f)
                    content = replace(content,self._target)
                    utils.writefile(f,content)
                except Exception,e:
                    if self._force:
                        log.error('[replace][%s]%s'%(f,e))
                    else:
                        e

    @classmethod
    def _html(self):
        '''
        generate html

        HTML直接以输出好的模板文件做渲染。

        由于数据原因个别子模板单独渲染会失败，这里用{html_force_output}变量
        可以跳过这类模板。

        TODO：考虑支持require_html_modules
        '''
        fs = utils.FileSearcher(r'\.%s$'%C('template_ext'),self._build_tpl_dir)
        tpls = fs.search()
        for tpl in tpls:
            if C('ignore_parents') and tpl.endswith('parent.'+C('template_ext')):
                continue
            try:
                tr = TokenRender(re.sub(r'\.%s$'%C('template_ext'),'',tpl))
                html = tr.render( build = True)
                target_dir= os.path.join(self._build_html_dir,os.path.dirname(tpl))
                if not os.path.exists(target_dir):
                    os.makedirs(target_dir)
                dst_file = re.sub(r'\.%s$'%C('template_ext'),'.html',os.path.join(self._build_html_dir,tpl))
                utils.writefile(dst_file,html)
            except Exception,e:
                if not C('html_force_output') and not self._force:
                    raise e
                else:
                    log.error(e)


if __name__ == '__main__':
    builder = UrsaBuilder(True,True,'online')
    builder._replace()<file_sep>define('main', ["text!./tpl/list.tpl"], function(tpl) {
	!window.console && (
		window.console = {
			log: function() {},
			debug: function() {},
			error: function() {},
			clear: function() {}
		}
	);


	return {
		common: function() {}
	};
});<file_sep>'''
 log.py

 changelog
 2013-12-11[10:19:52]:copyright

 This is from the Internet.

 @info yinyong,osx-x64,Undefined,10.129.164.77,py,/Volumes/yinyong/ursa2/src
 @author <EMAIL>
 @version 0.0.1
 @since 0.0.1
'''
import logging
import time
import logging.handlers
try:
    import curses
    curses.setupterm()
except:
    curses = None

    #COLOR_BLACK    0
    #COLOR_RED      1
    #COLOR_GREEN    2
    #COLOR_YELLOW   3
    #COLOR_BLUE     4
    #COLOR_MAGENTA  5
    #COLOR_CYAN     6
    #COLOR_WHITE    7
    
class MessageFormatter(logging.Formatter):
    def __init__(self, color, *args, **kwargs):
        logging.Formatter.__init__(self, *args, **kwargs)
        self._color = color
        if color and curses:
            fg_color = unicode(curses.tigetstr("setaf") or curses.tigetstr("setf") or "", "ascii")
            self._colors = {
                # GREEN
                logging.DEBUG: unicode(curses.tparm(fg_color, 2), "ascii"),
                # COLOR_CYAN
                logging.INFO: unicode(curses.tparm(fg_color, 6), "ascii"), 
                # Yellow
                logging.WARNING: unicode(curses.tparm(fg_color, 3), "ascii"),
                # COLOR_MAGENTA
                logging.ERROR: unicode(curses.tparm(fg_color, 5), "ascii"),
                # Red
                logging.FATAL: unicode(curses.tparm(fg_color, 1), "ascii"),
                }
            self._normal = unicode(curses.tigetstr("sgr0"), "ascii")

    def format(self, record):
        try:
            record.message = record.getMessage()
        except Exception, e:
            record.message = "Bad message (%r): %r" % (e, record.__dict__)
        record.asctime = time.strftime("%Y/%m/%d %H:%M:%S", self.converter(record.created))
        
        prefix = '[%(levelname)-7s %(asctime)s] ' % record.__dict__
        #if self._color and curses:
        #    prefix = (self._colors.get(record.levelno, self._normal) + prefix + self._normal)
        formatted = prefix + record.message#unicode(record.message, 'utf-8', 'ignore')
        if record.exc_info:
            if not record.exc_text:
                record.exc_text = self.formatException(record.exc_info)
        if record.exc_text:
            formatted = formatted.rstrip() + "\n" + record.exc_text
        if self._color and curses:
            formatted = (self._colors.get(record.levelno, self._normal) + formatted + self._normal)
        return formatted.replace("\n", "\n    ")

class GMessageLog(object):
    def __init__(self):
        self._LEVE = {1:logging.INFO, 2:logging.WARNING, 3:logging.ERROR, 4:logging.DEBUG, 5:logging.FATAL}
        self.loger = logging.getLogger()
        self.init()
    
    def init(self):
        handler = logging.StreamHandler()
        handler.setFormatter(MessageFormatter(color=True))
        self.loger.addHandler(handler)
        
    def message(self, msg, leve=1):
        if leve == 1: self.loger.info(msg)
        elif leve == 2: self.loger.warning(msg)
        elif leve == 3: self.loger.error(msg)
        elif leve == 4: self.loger.debug(msg)
        elif leve == 5: self.loger.fatal(msg)

    def setLevel(self,level):
        self.loger.setLevel(level)

    def info(self,msg):
        self.message(msg,1)

    def warn(self,msg):
        self.message(msg,2)

    def debug(self,msg):
        self.message(msg,4)

    def error(self,msg):
        self.message(msg,3)

    def fatal(self,msg):
        self.message(msg,5)

if __name__ == '__main__':
    log=GMessageLog()
    log.setLevel(logging.FATAL)
    log.fatal("hello word")<file_sep>Ursa2
=====
Enhanced version of [ursa](https://github.com/sogou-ufo/ursa).

ursa2 supports [JSON Auto Combining](https://github.com/yanni4night/ursa2/wiki/JSON-Auto-Combining) , 
[Sub Tpl Preview](https://github.com/yanni4night/ursa2/wiki/Sub-Tpl-Preview) 
and more detail features,such as, supporting _url()_ in sub directory of css,
which would bring bugs in previous,required data output in _http://origin/tpl.data_ url path,etc.


Feature
=====
 - Local HTTP/HTTPS server
 - Custom variables replace
 - Jinja2/Twig like template engine
 - Web data simulation
 - JSON data auto combining
 - JS/CSS merge&optimize
 - LESS auto translation
 - GET/POST(quirk) proxy
 - Intelligent timestamp

Install
=====
Checkout from [GitHub](https://github.com/yanni4night/ursa2) and run :
    
    $ python setup.py install

or use Python Package Index:

    $ pip install ursa2

Super permission may be required.

Usage
=====

###initialize project

    $ ursa2 init

###start server
     
     $ ursa2 start [port]

###view template list
    
open browser to http://[hostname]:[port]/

###view static resource list

open browser to http://[hostname]:[port]/rs

###build project

    $ ursa2 build [target][--compress][--html]


Dependencies
=====
 - Unix/Linux/OS X
 - Java 1.5+ (for yuicompressor)
 - Python 2.6+ (3.x not supported)
 - [node](https://github.com/joyent/node) (for less)
 - [less](https://github.com/less/less.js)
 - [docopt](https://github.com/docopt/docopt) (for log)
 - [jinja2](https://github.com/mitsuhiko/jinja2)
 - [requests](https://github.com/kennethreitz/requests) (for proxy)


Documents
=====
 - [Manifest.json](https://github.com/yanni4night/ursa2/wiki/manifest.json)
 - [Ursa2 Specification](https://github.com/yanni4night/ursa2/wiki/Ursa2-Specification)
 - [JSON Auto Combining](https://github.com/yanni4night/ursa2/wiki/JSON-Auto-Combining)
 - [Sub Tpl Preview](https://github.com/yanni4night/ursa2/wiki/Sub-Tpl-Preview)
 - [Upgrade Notes](https://github.com/yanni4night/ursa2/wiki/Upgrade-notes)

Quirk
=====
 - Server plugin not supported.
 - Mobile project initializing not supported.
 - POST data in proxy not supported.
 - Relative path of static resource not supported(which you should never do).

LICENCE
=====
The MIT License (MIT)

Copyright (c) 2013 YinYong

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.
<file_sep>#!/usr/bin/env python
#-*- coding:utf-8 -*-
'''
 conf.py

 changelog
 2013-11-29[14 : 38 : 24] : created
 2013-11-30[15 : 39 : 31] : finished
 2013-12-01[19 : 23 : 17] : empty string supported

 @info yinyong,osx-x64,UTF-8,10.129.164.117,py,/Users/yinyong/work/ursa2
 @author <EMAIL>
 @version 0.0.1
 @since 0.0.1
'''
import os
import json
import re
import logging
from  log import GMessageLog
from datetime import datetime
import time

log = GMessageLog()

#配置缓存
_last_read_time = 0
_conf_cache = {}

#配置文件名称
_MANIFEST_FILE = 'manifest.json'
#配置文件的缓存间隔，读取文件后此事件内不再读取,单位s
_MIN_CACHE_INTERVAL = 1

#默认target，用于查找与环境相关的替换变量
_DEFAULT_TARGET = 'local'

#ursa2 Python源文件目录，用于查找assetss
BASE_DIR = os.path.abspath(os.path.dirname(__file__)) 

#默认配置选项
_DEFAULT_ITEMS = {
    'encoding' : ' utf-8',#项目编码，要求所有文件及输出均为此值
    'protocol' : ' http',#使用协议，http或https
    'server_add_timestamp' : False,#是否在本地调试服务器上追加时间戳
    'disable_deps_search' : False,#禁止模拟数据搜索
    'enable_proxy' : False,#代理开关
    'server_port' : 8000,#http 或https端口
    'log_level' : 'debug',#日志级别
    'timestamp_name' : 't',#时间戳参数名
    'log_http_request' : False,#是否打印HTTP请求日志
    'template_dir' : 'template',#模板目录
    'data_dir' : '_data',#JSON数据目录
    'module_dir' : '_module',#under template_dir
    'common_dir' : '_common',#under template_dir
    'static_dir' : "static",#静态资源目录
    'css_dir' : "css",#under static_dir
    'css_folder' : '',#{css_dir}下的子目录
    'js_dir' : "js",#under static_dir
    'js_folder' : '',#{js_dir}下的子目录
    'img_dir':'img',
    'compile_folder' : None,#额外的目录，文本文件仅做替换和时间戳处理
    'build_dir' : 'build',#生成目录
    'html_dir' : 'html',#under build_dir
    'template_ext' : 'tpl',#模板文件扩展名
    'preview_ext' : 'ut',#实时访问渲染后模板的URL后缀
    'num' : 10,#随机变量的最大值
    'max_readable_filesize' : 1024*1024,#可读取的最大文件，文件过大会严重迟缓并占用内存
    'yuicompressor' : None,#自定义yuicompressor的路径
    'js_ascii_only' : False,#转义多字节为ASCII
    'ignore_parents':True,#是否忽略对parent模板的处理
};


def _getConf():
    '''
    获取配置文件中整个json对象
    '''
    #缓存
    global _last_read_time,_MIN_CACHE_INTERVAL,_conf_cache
    now = time.time()
    if now-_last_read_time<_MIN_CACHE_INTERVAL:
        _last_read_time = now
        return _conf_cache

    ret = None
    try:
        conffile = os.path.join('.' , _MANIFEST_FILE)
        if os.path.exists(conffile):
            f = open(conffile)
            body = f.read()
            f.close()
            #去除/**/注释，支持多行
            #todo，字符串中应该予以保留
            ret = json.loads(re.sub('\/\*[\s\S]*?\*\/','',body))
        else:
            log.error("%s is required" % _MANIFEST_FILE)
    except Exception, e:
        log.error("%s" % e)
    _conf_cache = ret
    return ret

def C(key,target = None,default_val = None):
    '''
    获取指定key的配置值，顺序为{target : {}},{local : {}},{},_DEFAULT_ITEMS,_default_val
    '''
    global _DEFAULT_ITEMS,_DEFAULT_TARGET

    if type(key) not in (type(''),type(u'')):
        return None
    conf = _getConf()
    if conf is None:
        return _DEFAULT_ITEMS.get(key) or default_val

    k = target
    if target is None:
        k = _DEFAULT_TARGET

    dic = conf.get(k)
    #target 或local存在
    if type(dic) == type({}):
        if dic.get(key) is not None:
            return dic.get(key)
    if conf.get(key) is not None:
        return conf.get(key)
    else:
        return _DEFAULT_ITEMS.get(key) or default_val

#日志级别设置
__illegal_log_levels = {'info' : logging.INFO,'debug' : logging.DEBUG,'warn' : logging.WARNING,'error' : logging.ERROR,'critical' : logging.CRITICAL}
__log_level = C('log_level')
if __illegal_log_levels.get(__log_level) is not None:
    log.setLevel(__illegal_log_levels.get(__log_level))
else:
    log.setLevel(__illegal_log_levels.get('error'))

if __name__ == '__main__':
    print C('template_dir');
<file_sep>#!/usr/bin/env python
#-*- coding:utf-8 -*-
'''
 utils.py

 changelog
 2013-11-30[14:36:11]:created
 2013-12-22[21:14:45]:add isBinary function

 常用工具

 @info yinyong,osx-x64,UTF-8,192.168.1.101,py,/Users/yinyong/work/ursa2/src
 @author <EMAIL>
 @version 0.0.2
 @since 0.0.1
'''

import time
import os
import json
import re
import hashlib
import codecs
import mimetypes
from conf import C,log
from exception import FileSizeOverflowError

#根据MIME来判断是二进制文件的正则表达式
BINARY_CONTENT_TYPE_KEYWORDS=r'(image|video|flash|audio|powerpoint|msword|octet\-stream)'

mimetypes.init()

def isBinary(fname , strict=False):
    '''
    判断文件是否是二进制文件
    '''
    mime = mimetypes.guess_type(fname,False)
    content_type = mime[0]
    if not content_type and strict:
        return False
    else:
        content_type = content_type or 'application/octet-stream'
    return True if re.search(BINARY_CONTENT_TYPE_KEYWORDS,content_type,re.I) else False


def isInt(v):
    '''
    type int
    '''
    return type(v) == type(1)

def isStr(s):
    '''
    type string
    '''
    return type(s) in (type(u''),type(''))

def isDict(d):
    '''
    type dict
    '''
    return type(d) == type({})

def isList(d):
    '''
    type list
    '''
    return type(d) == type([])

def isTuple(t):
    '''
    type tuple
    '''
    return type(t) == type(())

def abspath(path):
    '''
    取得WD下文件或目录的绝对路径
    path：WD下文件或目录，会被强转成相对路径
    '''
    if not isStr(path):
        raise TypeError('path must be a string')
    path = filterRelPath(path)
    return os.path.abspath(os.path.join(os.getcwd(),path))

def readfile(filename  , mode='r'):
    '''
    默认以文本方式读取整个文件
    '''
    try:

        if os.path.getsize(filename) > C('max_readable_filesize'):
            raise FileSizeOverflowError('%s size overflow %d'%(filename,C('max_readable_filesize')))

        if 'b' in mode:#Binary file
            f = open( filename , mode )
        else:
            f = codecs.open(filename , mode ,C('encoding'))
    
        body = f.read()
        f.close()
        return body
    except Exception,e:
        raise

def writefile(filename , content):
    '''
    write conent to a file
    '''
    try:
        f = codecs.open(filename , 'w' , C('encoding'))
        f.write(content)
        f.close()
    except:
        log.error("write to %s failed" % filename)
        raise

def writeJSON(filename,data):
    '''
    write file in JSON
    '''
    writefile(filename, json.dumps(data , sort_keys = True , indent = 4, separators = ( ',' , ': ')) )

def dorepeat(data):
    '''
    to be checked
    '''
    if type(data)==type({}):
        for item in data.keys():
            dorepeat(data[item])
            if re.search( '@\d+$' , item ):
                name = item.split('@')[0]
                times = item.split('@')[1]
                
                if int(times):
                    for time in range(int(times)):
                        if not data.get(name):
                            data[name] = []
                        data[name].append(data[item])
def md5toInt(md5):
    """将md5得到的字符串变化为6位数字传回。
    基本算法是将得到的32位字符串切分成两部分，每部分按16进制得整数后除997，求得的余数相加
    最终得到6位
    
    Arguments:
    - `md5`:
    """
    md5 = [ md5[:16] , md5[16:] ]
    result = ''
    for item in md5:
        num = str( int( item , 16 ) % 997 ).zfill(3)
        result = result+num
        
    return result

def get_file_timestamp(fpath):
    '''
    取得文件时间戳
    fpath:绝对路径
    '''
    try:
        f = readfile(fpath , 'rb')
        m = hashlib.md5()
        m.update(f)
        md5 = md5toInt(m.hexdigest())
        return md5
    except Exception,e:
        log.error('[TimeStamp]%s'%e)
    return ''

def getDate(fmt = "%Y%m%d%H%M%S"):
    '''
    获取当前时间的格式化字符串
    '''
    return time.strftime(fmt or '', time.localtime())

def filterPath(path):
    '''
    路径中'//'变'/'
    '''
    return re.sub(r'/+','/',path)

def filterRelPath(path):
    '''
    同filterPath,但去除开头的'/'
    '''
    path = filterPath(path)
    return re.sub(r'^/+','',path)


class FileSearcher(object):
    '''
    搜索一个目录下所有符合规定文件名的文件,
    默认返回相对于初始目录的相对路径
    '''
    @classmethod
    def __init__(self, pattern = r'.+', start_dir = '.',relative = True,traverse = True):
        '''
        pattern:文件名的正则过滤表达式;
        start_dir:搜索目录;
        relative:是否输出相对目录;
        traverse:是否遍历目录搜索
        '''
        self.regexp = re.compile(pattern)
        self.start_dir = start_dir
        self.result = []
        self.relative = relative
        self.traverse = traverse
    @classmethod
    def search(self):
        '''
        执行搜索输出
        '''
        if os.path.isdir(self.start_dir):
            os.path.walk(self.start_dir,self._visit,None)
        else:
            log.warn('you are walking a non-dir %s'%self.start_dir)

        return self.result

    @classmethod
    def _visit(self,argv, directoryName,filesInDirectory):
        '''
        '''
        for fname in filesInDirectory:                   
            fpath = os.path.join(directoryName, fname)
            if os.path.isfile(fpath) and self.regexp.findall(fname):
                if self.relative:
                    fpath = os.path.relpath(fpath,self.start_dir)
                self.result.append(fpath)

if __name__ == '__main__':
    print isBinary('f.cur',True)<file_sep>#!/usr/bin/env python
#-*- coding:utf-8 -*-
'''
 __init__.py

 changelog
 2013-11-30[00:09:04]:created
 2013-12-18[10:19:03]:upgrade to 0.0.3
 2013-12-18[21:47:07]:upgrade to 0.0.4
 2013-12-19[00:06:07]:upgrade to 0.0.5 
 2013-12-19[00:08:26]:upgrade to 0.0.6
 2013-12-19[00:43:39]:upgrade to 0.0.10 release

 @info yinyong,osx-x64,UTF-8,192.168.1.101,py,/Users/yinyong/work/ursa2/src
 @author <EMAIL>
 @version 0.0.10
 @since 0.0.1
'''

__version__='0.0.12'
<file_sep>#!/usr/bin/env python
#-*- coding:utf-8 -*-
'''
 route.py

 changelog
 2013-11-30[18:52:47]:created

 @info yinyong,osx-x64,UTF-8,192.168.1.101,py,/Users/yinyong/work/ursa2/src
 @author <EMAIL>
 @version 0.0.1
 @since 0.0.1
'''
from http import Request,Response
import re
from conf import C,log
from urlparse import urlparse
import utils
from proxy import proxy , get_proxy_url

class Route(object):
    '''
    路由器
    '''
    handlers=[];

    @classmethod
    def distribute_request(self,http_req_handler):
        '''
        根据URL匹配规则路由请求到相应地处理器
        '''
        path = urlparse(http_req_handler.path).path
        handled = False

        #代理支持
        if C('enable_proxy') and utils.isDict(C('proxy')):
            for reg,target in C('proxy').items():
                target_path = get_proxy_url(http_req_handler.path,reg,target)
                if target_path:
                     log.info('[proxy](%s) to (%s)'%(http_req_handler.path,target_path))
                     return proxy(target_path,Request(http_req_handler),Response(http_req_handler))

        for h in self.handlers:
            if 'ALL' == h.get('method') or h.get('method') == http_req_handler.command and re.findall(h.get('pattern'),path):
                handled = True
                ret = (h.get('handler'))(Request(http_req_handler),Response(http_req_handler))
                if True == ret:
                    continue
                else:
                    break
        #if not handled by any handlers,405
        if not handled:
            log.error('%s is not handled'%path)
            http_req_handler.send_header(405,'%s not supported'%path)
            http_req_handler.end_headers()
            self.http_req_handler.wfile.close()

    @classmethod
    def get(self,pattern,handler):
        '''
        注册GET请求处理器
        '''
        self._register(pattern,handler,'GET')

    @classmethod
    def post(self,pattern,handler):
        '''
        注册POST请求处理器
        '''
        self._register(pattern,handler,'POST')

    @classmethod
    def all(self,pattern,handler):
        '''
        同时注册POST与GET请求处理器
        '''
        self._register(pattern,handler,'ALL')

    @classmethod
    def _register(self,pattern,handler,method):
        '''
        注册路由控制器
        '''
        self.handlers.append({'pattern':pattern,'handler':handler,'method':method})<file_sep>#!/usr/bin/env python
#-*- coding:utf-8 -*-
'''
 setup.py

 changelog
 2013-11-30[00:06:29]:created
 2013-12-16[20:56:26]:upgrade to 0.0.2

 @info yinyong,osx-x64,UTF-8,192.168.1.101,py,/Users/yinyong/work/ursa2
 @author <EMAIL>
 @version 0.0.2
 @since 0.0.1
'''

from setuptools import setup, find_packages
from ursa2.__init__ import __version__ as version

setup(
    name = "ursa2",
    version = version,
    packages = ['ursa2'],
    install_requires = ['docopt>=0.6.1','jinja2>2.6','requests>=2.0.0'],
    include_package_data = True,
    package_data = {},
    long_description = open('README.md').read(),
    author = "<NAME>",
    author_email = "<EMAIL>",
    description = "Ursa2 is an enhanced version of ursa which is a powerful front web developing environment.",
    license = "MIT",
    keywords = "ursa,fe,develop,web,jinja2,build",
    url = "https://github.com/yanni4night/ursa2",
    entry_points = {
        'console_scripts':[
            'ursa2=ursa2.main:run'
            ]
        }
)<file_sep>#!/usr/bin/env node

var fs = require('fs');
var arg = process.argv.slice(2);

if (arg[0]) {
    try {
        var content = fs.readFileSync(arg[0], 'utf-8');
        content = content.replace(/[^\x00-\xff]/g, function(chinese) {
            return escape(chinese).replace(/%u/g, '\\u');
        });
        fs.writeFileSync(arg[1] || arg[0], content, 'utf-8');
        console.log('transfer ' + arg[0] + ' ok');
    } catch (e) {
        console.log(e);
    }
} else {
    console.log('are you testing me?');
}<file_sep>#!/usr/bin/env python
#-*- coding:utf-8 -*-
'''
 deps.py

 changelog
 2013-12-02[15:48:27]:created
 2013-12-14[12:18:46]:include collection supported
 2013-12-24[21:00:03]:'ignore_parents' setting

 @info yinyong,osx-x64,UTF-8,10.129.173.95,py,/Users/yinyong/work/ursa2/src
 @author <EMAIL>
 @version 0.0.2
 @since 0.0.1
'''
import re
import utils
import os
from conf import C,log

class DepsFinder(object):
    '''
    递归搜索指定模板文件的依赖模板
    '''
    @classmethod
    def __init__(self,tpl):
        '''
        tpl:模板，相对于template_dir目录
        '''
        self._result = []
        self._include_result = []
        #x=>x.tpl
        if not tpl.endswith('.%s'%C('template_ext')):
            tpl += '.%s'%C('template_ext')
        self._tpl = tpl
        self._history = {}
        self._done = False
        self._pattern = re.compile(r'\{%\s*(include|extends)\s+([\'"])([\w\/\\\.-]+\.'+C('template_ext')+r')\2\s*%\}',re.I|re.M)

    @classmethod
    def _search(self,tpl):
        '''
        递归搜索
        '''
        try:
            abspath = utils.abspath(os.path.join(C('template_dir'),tpl))

            content = utils.readfile(abspath)
            iters = re.finditer(self._pattern,content)

            for i in reversed(list(iters)):
                tpl = utils.filterRelPath(i.group(3))
                if C('ignore_parents') and tpl.endswith('parent.'+C('template_ext')):
                    continue
                if self._history.get(tpl) is None:
                    self._result.append(tpl)
                    self._history[tpl] = 1
                    if 'include' == i.group(1):
                        self._include_result.append(tpl)
                    self._search(tpl)
        except Exception, e:
            log.error('[deps]%s'%e)

    @classmethod
    def find(self):
        '''
        获取依赖的接口
        '''
        if not self._done:
            self._search(self._tpl)
        self._done = True
        return self._result;

    @classmethod
    def findIncludes(self):
        '''
        获取依赖的接口
        '''
        if not self._done:
            self.find()
        return self._include_result;

    @classmethod
    def get_tpl():
        '''
        '''
        return self._tpl

    @classmethod
    def __del__(self):
        self._history = {}
        self._result = []

if __name__ == '__main__':
    df=DepsFinder(u'/template/index')
    print df.find()
