# HG changeset patch # User "Hisn Yi, Chen " # Date 1228062442 -28800 # Node ID 7747bbe5b68e0e4f48df9cff470b3a4ee5a6aba1 # Parent 60c4b4b78a01e34f9d288c0da44894f26c361386 start to develope Information Exchange Center of Ikariam Game. (prototpye) diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/README --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/README Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,57 @@ +Definition + + * GAE: Google App Engine + * GAEO: Google App Engine Oil ( Web Framwork ) + +Requirment Of Develope Env + + * download eagle-eye and setup it. see more in eagel-eye README + * download GAE SDK and setup it. + * download GAEO Framwork and setup it. see more in GAEO document. + + * add following in ~/.bashrc + + export GAEO_HOME='~/codes/gaeo' # put your installation directory here. + export GAE_HOME='~/codes/google_appengine' + PATH=$PATH:$GAEO_HOME/bin:$GAE_HOME:~/bin/ + +Launch Web site + + * using GAE dev_server.py to launch. + * browse http://localhost:8080 to see the index page. + +Develope new version of models if chichchun has changed database schema. + + if db schema has changed, the GAEO models in web site need to rebuild, + a model generator will get colum informations in all tables in ikariam.sqlite, + and makes new GAEO models, which has a required properties, you may want to + modify it later if some properties is wrong. + + the model relation will not generate in this action, you have added it by hand. + + [ WARRING ] + it will re-generate all models, the web site can not work until + you have done the work that re-design models. + + $ ./rebuild_world + +How many model are implemented already? + + player, island, city, ally + +How to kwnow What input fileds of the model in POST ? + + use action "attrs", example: http://localhost:8080/player/attrs + +How to display model information ? + + use action "show", example: http://localhost:8080/player/show/1 + + p.s the last number 1 is the Ikariam Game ID of the model. + +Hot to get the datas of the model in JSON formate? + + use action "json", example: http//"localhost:8080/player/json/1" + + p.s the last number 1 is the Ikariam Game ID of the model. + \ No newline at end of file diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/app.yaml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/ikweb/app.yaml Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,17 @@ +application: ikweb +version: 1 +api_version: 1 +runtime: python + +handlers: +- url: /css + static_dir: assets/css +- url: /js + static_dir: assets/js +- url: /img + static_dir: assets/img +- url: /favicon.ico + static_files: favicon.ico + upload: favicon.ico +- url: .* + script: main.py diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/application/__init__.py diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/application/controller/__init__.py diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/application/controller/__init__.pyc Binary file ikweb/ikweb/application/controller/__init__.pyc has changed diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/application/controller/ally.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/ikweb/application/controller/ally.py Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,9 @@ +from google.appengine.ext import db + +from gaeo.controller import BaseController + +from model.ally import Ally + +class AllyController(BaseController): + + pass \ No newline at end of file diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/application/controller/ally.pyc Binary file ikweb/ikweb/application/controller/ally.pyc has changed diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/application/controller/city.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/ikweb/application/controller/city.py Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,9 @@ +from google.appengine.ext import db + +from gaeo.controller import BaseController + +from model.city import City + +class CityController(BaseController): + + pass \ No newline at end of file diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/application/controller/city.pyc Binary file ikweb/ikweb/application/controller/city.pyc has changed diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/application/controller/island.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/ikweb/application/controller/island.py Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,9 @@ +from google.appengine.ext import db + +from gaeo.controller import BaseController + +from model.island import Island + +class IslandController(BaseController): + + pass \ No newline at end of file diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/application/controller/player.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/ikweb/application/controller/player.py Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,10 @@ +from google.appengine.ext import db + +from gaeo.controller import BaseController + +from model.player import Player +from model.ally import Ally + +class PlayerController(BaseController): + + pass \ No newline at end of file diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/application/controller/player.pyc Binary file ikweb/ikweb/application/controller/player.pyc has changed diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/application/controller/welcome.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/ikweb/application/controller/welcome.py Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,5 @@ +from gaeo.controller import BaseController + +class WelcomeController(BaseController): + def index(self): + pass diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/application/controller/welcome.pyc Binary file ikweb/ikweb/application/controller/welcome.pyc has changed diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/application/model/__init__.py diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/application/model/__init__.pyc Binary file ikweb/ikweb/application/model/__init__.pyc has changed diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/application/model/ally.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/ikweb/application/model/ally.py Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,12 @@ +from google.appengine.ext import db +from gaeo.model import BaseModel + +class Ally(BaseModel): + game_id = db.IntegerProperty() + members_total = db.IntegerProperty(verbose_name='members_total',name='members_total') + name = db.TextProperty(verbose_name='name',name='name') + score = db.IntegerProperty(verbose_name='score',name='score') + lastupdate_at = db.DateTimeProperty(auto_now=True) + url = db.TextProperty(verbose_name='url',name='url') + + int_attrs = ['score','members_total','game_id'] \ No newline at end of file diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/application/model/ally.pyc Binary file ikweb/ikweb/application/model/ally.pyc has changed diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/application/model/city.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/ikweb/application/model/city.py Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,22 @@ +from google.appengine.ext import db +from gaeo.model import BaseModel + +from model.island import Island +from model.player import Player + +# city belongs to one player and one island. + +class City(BaseModel): + game_id = db.IntegerProperty() + name = db.TextProperty(verbose_name='name',name='name') + level = db.IntegerProperty(verbose_name='level',name='level') + island = db.ReferenceProperty(Island) + island_id = db.IntegerProperty(verbose_name='island_id',name='island_id') + player = db.ReferenceProperty(Player) + player_id = db.IntegerProperty(verbose_name='player',name='player') + status = db.StringProperty(verbose_name='status',name='status') + lastupdate_at = db.DateTimeProperty() + + unshow_attrs = ['owner_id','island_id'] + ref_attrs = ['island_id','player_id'] + int_attrs = ['level','game_id'] diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/application/model/city.pyc Binary file ikweb/ikweb/application/model/city.pyc has changed diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/application/model/island.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/ikweb/application/model/island.py Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,10 @@ +from google.appengine.ext import db +from gaeo.model import BaseModel + +class Island(BaseModel): + game_id = db.IntegerProperty() + name = db.TextProperty(verbose_name='name',name='name') + x = db.IntegerProperty(verbose_name='x',name='x') + y = db.IntegerProperty(verbose_name='y',name='y') + + int_attrs = ['x','y'] \ No newline at end of file diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/application/model/island.pyc Binary file ikweb/ikweb/application/model/island.pyc has changed diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/application/model/player.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/ikweb/application/model/player.py Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,26 @@ +from google.appengine.ext import db +from gaeo.model import BaseModel + +from model.ally import Ally + +class Player(BaseModel): + game_id = db.IntegerProperty() + ally = db.ReferenceProperty(Ally) + ally_id = db.IntegerProperty(verbose_name='ally_id',name='ally_id') + army_score_main = db.IntegerProperty(verbose_name='army_score_main',name='army_score_main') + building_score_main = db.IntegerProperty(verbose_name='building_score_main',name='building_score_main') + building_score_secondary = db.IntegerProperty(verbose_name='building_score_secondary',name='building_score_secondary') + name = db.StringProperty(verbose_name='name',name='name') + research_score_main = db.IntegerProperty(verbose_name='research_score_main',name='research_score_main') + research_score_secondary = db.IntegerProperty(verbose_name='research_score_secondary',name='research_score_secondary') + score = db.IntegerProperty(verbose_name='score',name='score') + trader_score_secondary = db.IntegerProperty(verbose_name='trader_score_secondary',name='trader_score_secondary') + lastupdate_at = db.DateTimeProperty(auto_now=True) + + unshow_attrs = ['ally'] + + int_attrs = ['army_score_main', 'building_score_main', + 'building_score_secondary', 'research_score_main', + 'research_score_secondary', 'score', + 'trader_score_secondary', + 'game_id', 'ally_id'] \ No newline at end of file diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/application/model/player.pyc Binary file ikweb/ikweb/application/model/player.pyc has changed diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/application/model/report.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/ikweb/application/model/report.py Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,20 @@ +from google.appengine.ext import db +from gaeo.model import BaseModel + +class Report(BaseModel): + attacker = db.TextProperty(verbose_name='attacker',name='attacker') + city = db.IntegerProperty(verbose_name='city',name='city') + cityLevel = db.IntegerProperty(verbose_name='cityLevel',name='cityLevel') + crystal = db.IntegerProperty(default=None,verbose_name='crystal',name='crystal') + date = db.DatetimeProperty(verbose_name='date',name='date') + defender = db.TextProperty(verbose_name='defender',name='defender') + gold = db.IntegerProperty(default=None,verbose_name='gold',name='gold') + id = db.IntegerProperty(verbose_name='id',name='id') + island = db.IntegerProperty(verbose_name='island',name='island') + sulfur = db.IntegerProperty(default=None,verbose_name='sulfur',name='sulfur') + targetCity = db.TextProperty(verbose_name='targetCity',name='targetCity') + time = db.DatetimeProperty(verbose_name='time',name='time') + wallLevel = db.IntegerProperty(verbose_name='wallLevel',name='wallLevel') + wine = db.IntegerProperty(default=None,verbose_name='wine',name='wine') + winner = db.TextProperty(verbose_name='winner',name='winner') + wood = db.IntegerProperty(default=None,verbose_name='wood',name='wood') diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/favicon.ico diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/gaeo/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/ikweb/gaeo/__init__.py Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2008 Lin-Chieh Shangkuan & Liang-Heng Chen +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +""" The gaeo library package. """ + +import re + +from google.appengine.ext import webapp + +from gaeo.dispatch import dispatcher + + +class Config: + """ The singleton of GAEO's configuration """ + + class __impl: + def __init__(self): + self.template_dir = '' + self.session_store = 'memcache' + self.app_name = '' + + __instance = None + + def __init__(self): + if Config.__instance is None: + Config.__instance = Config.__impl() + + self.__dict__['_Config__instance'] = Config.__instance + + def __getattr__(self, attr): + return getattr(self.__instance, attr) + + def __setattr__(self, attr, value): + return setattr(self.__instance, attr, value) + + +class MainHandler(webapp.RequestHandler): + """Handles all requests + """ + def get(self, *args): + self.__process_request() + + def post(self, *args): + self.__process_request() + + def head(self, *args): + self.__process_request() + + def options(self, *args): + self.__process_request() + + def put(self, *args): + self.__process_request() + + def delete(self, *args): + self.__process_request() + + def trace(self, *args): + self.__process_request() + + def __process_request(self): + """dispatch the request""" + dispatcher.dispatch(self) + diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/gaeo/__init__.pyc Binary file ikweb/ikweb/gaeo/__init__.pyc has changed diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/gaeo/controller/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/ikweb/gaeo/controller/__init__.py Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,237 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2008 GAEO Team. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""GAEO controller package +""" + +import new +import os +import re +import logging + +from google.appengine.ext.webapp import template + +import gaeo +import errors +import helper + +class BaseController(object): + """The BaseController is the base class of action controllers. + Action controller handles the requests from clients. + """ + def __init__(self, hnd, params = {}): + self.hnd = hnd + self.resp = self.response = hnd.response + self.req = self.request = hnd.request + self._env = self.request.environ # shortcut + self.params = params + + rp = hnd.request.params.mixed() + for k in rp: + self.params[k] = rp[k] + + self._controller = params['controller'] + self._action = params['action'] + self.has_rendered = False + self.__config = gaeo.Config() + + self.__tpldir = os.path.join( + self.__config.template_dir, + self._controller + ) + self._template_values = {} + + # implement parameter nesting as in rails + self.params=self.__nested_params(self.params) + + # detect the mobile platform + self._is_mobile = self.__detect_mobile() + self._is_iphone = self.__detect_iphone() + + # alias the cookies + self.cookies = self.request.cookies + + # create the session + try: + store = self.__config.session_store + exec('from gaeo.session.%s import %sSession' % + (store, store.capitalize())) + + self.session = eval('%sSession' % store.capitalize())( + hnd, '%s_session' % self.__config.app_name) + except: + raise errors.ControllerInitError('Initialize Session Error!') + + # add request method (get, post, head, put, ....) + self._request_method = self.request.environ['REQUEST_METHOD'].lower() + + # tell if an ajax call (X-Request) + self._is_xhr = self._env.has_key('HTTP_X_REQUESTED_WITH') and \ + self._env['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest' + + + # add helpers + helpers = dir(helper) + for h in helpers: + if not re.match('^__', h): + self.__dict__[h] = new.instancemethod(eval('helper.%s' % h), self, BaseController) + + #this can be used in application controller, it is meant to be called before every controller + def implicit_action(self): + pass + + def before_action(self): + pass + + def after_action(self): + pass + + def invalid_action(self): + """ If the router went to an invalid action """ + self.hnd.error(404) + self.render(text="Invalid action") + + def to_json(self, obj, **kwds): + """ Convert a dict to JSON. Inspired from SimpleJSON """ + from gaeo.controller.jsonencoder import JSONEncoder + + if not kwds: + return JSONEncoder(skipkeys=False, ensure_ascii=True, check_circular=True, + allow_nan=True, indent=None, separators=None, encoding='utf-8', + default=None).encode(obj) + else: + return JSONEncoder( + skipkeys=False, ensure_ascii=True, check_circular=True, + allow_nan=True, indent=None, separators=None, + encoding='utf-8', default=None, **kwds).encode(obj) + + def render(self, *html, **opt): + o = self.resp.out + h = self.resp.headers + + # check the default Content-Type is 'text/html; charset=utf-8' + h['Content-Type'] = 'text/html; charset=utf-8' + if html: + for h in html: + o.write(h.decode('utf-8')) + elif opt: + if opt.get('html'): + o.write(opt.get('html').decode('utf-8')) + elif opt.get('text'): + h['Content-Type'] = 'text/plain; charset=utf-8' + o.write(str(opt.get('text')).decode('utf-8')) + elif opt.get('json'): + h['Content-Type'] = 'application/json; charset=utf-8' + o.write(opt.get('json').decode('utf-8')) + elif opt.get('xml'): + h['Content-Type'] = 'text/xml; charset=utf-8' + o.write(opt.get('xml').decode('utf-8')) + elif opt.get('script'): + h['Content-Type'] = 'text/javascript; charset=utf-8' + o.write(opt.get('script').decode('utf-8')) + elif opt.get('template'): + context = self.__dict__ + if isinstance(opt.get('values'), dict): + context.update(opt.get('values')) + o.write(template.render( + os.path.join(self.__tpldir, + opt.get('template') + '.html'), + context + )) + elif opt.get('template_string'): + context = self.__dict__ + if isinstance(opt.get('values'), dict): + context.update(opt.get('values')) + from django.template import Context, Template + t = Template(opt.get('template_string').encode('utf-8')) + c = Context(context) + o.write(t.render(c)) + elif opt.get('image'): # for sending an image content + import imghdr + img_type = imghdr.what('ignored_filename', opt.get('image')) + h['Content-Type'] = 'image/' + img_type + o.write(opt.get('image')) + else: + raise errors.ControllerRenderTypeError('Render type error') + self.has_rendered = True + + def redirect(self, url, perm = False): + self.has_rendered = True # dirty hack, make gaeo don't find the template + self.hnd.redirect(url, perm) + + def respond_to(self, **blk): + """ according to self.params['format'] to respond appropriate stuff + """ + if self.params.has_key('format') and blk.has_key(self.params['format']): + logging.error(self.params['format']) + blk[self.params['format']]() + + def __detect_mobile(self): + h = self.request.headers + + # wap.wml + ha = h.get('Accept') + if ha and (ha.find('text/vnd.wap.wml') > -1 or ha.find('application/vnd.wap.xhtml+xml') > -1): + return True + + wap_profile = h.get('X-Wap-Profile') + profile = h.get("Profile") + opera_mini = h.get('X-OperaMini-Features') + ua_pixels = h.get('UA-pixels') + + if wap_profile or profile or opera_mini or ua_pixels: + return True + + # FIXME: add common user agents + common_uas = ['sony', 'noki', 'java', 'midp', 'benq', 'wap-', 'wapi'] + + ua = h.get('User-Agent') + if ua and ua[0:4].lower() in common_uas: + return True + + return False + + def __detect_iphone(self): + """ for detecting iPhone/iPod """ + ua = self.request.headers.get('User-Agent') + if ua: + ua = ua.lower(); + return ua.find('iphone') > -1 or ua.find('ipod') > -1 + else: + return False + + + # Helper methods for parameter nesting as in rails + def __appender(self,dict,arr,val): + if len(arr) > 1: + try: + dict[arr[0]] + except KeyError: + dict[arr[0]]={} + return {arr[0]: self.__appender(dict[arr[0]],arr[1:],val)} + else: + dict[arr[0]]=val + return + + def __nested_params(self,prm): + prm2={} + for param in prm: + parray = param.replace(']',"").split('[') + if len(parray) == 1: + parray = parray[0].split('-') + self.__appender(prm2,parray,prm[param]) + return prm2 + diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/gaeo/controller/__init__.pyc Binary file ikweb/ikweb/gaeo/controller/__init__.pyc has changed diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/gaeo/controller/errors.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/ikweb/gaeo/controller/errors.py Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2008 Lin-Chieh Shangkuan & Liang-Heng Chen +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +""" The gaeo controller errors """ + +class ControllerError(Exception): + """ Base error class of controllers' errors """ + +class ControllerInitError(ControllerError): + pass + +class ControllerRenderError(ControllerError): + """ error occured while render """ + +class ControllerRenderTypeError(ControllerRenderError): + """ Render an invalid type """ diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/gaeo/controller/errors.pyc Binary file ikweb/ikweb/gaeo/controller/errors.pyc has changed diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/gaeo/controller/helper.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/ikweb/gaeo/controller/helper.py Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2008 Lin-Chieh Shangkuan & Liang-Heng Chen +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +""" helper module """ + +def clear_session(self): + if self.session: + import logging + logging.info('clear session') + self.session.invalidate() \ No newline at end of file diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/gaeo/controller/helper.pyc Binary file ikweb/ikweb/gaeo/controller/helper.pyc has changed diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/gaeo/controller/jsonencoder.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/ikweb/gaeo/controller/jsonencoder.py Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,406 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +#Copyright (c) 2006 Bob Ippolito +# +# 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. +""" +Implementation of JSONEncoder +""" +import re + +try: + from simplejson._speedups import encode_basestring_ascii as c_encode_basestring_ascii +except ImportError: + pass + +ESCAPE = re.compile(r'[\x00-\x1f\\"\b\f\n\r\t]') +ESCAPE_ASCII = re.compile(r'([\\"]|[^\ -~])') +HAS_UTF8 = re.compile(r'[\x80-\xff]') +ESCAPE_DCT = { + '\\': '\\\\', + '"': '\\"', + '\b': '\\b', + '\f': '\\f', + '\n': '\\n', + '\r': '\\r', + '\t': '\\t', +} +for i in range(0x20): + ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % (i,)) + +# Assume this produces an infinity on all machines (probably not guaranteed) +INFINITY = float('1e66666') +FLOAT_REPR = repr + +def floatstr(o, allow_nan=True): + # Check for specials. Note that this type of test is processor- and/or + # platform-specific, so do tests which don't depend on the internals. + + if o != o: + text = 'NaN' + elif o == INFINITY: + text = 'Infinity' + elif o == -INFINITY: + text = '-Infinity' + else: + return FLOAT_REPR(o) + + if not allow_nan: + raise ValueError("Out of range float values are not JSON compliant: %r" + % (o,)) + + return text + + +def encode_basestring(s): + """ + Return a JSON representation of a Python string + """ + def replace(match): + return ESCAPE_DCT[match.group(0)] + return '"' + ESCAPE.sub(replace, s) + '"' + + +def py_encode_basestring_ascii(s): + if isinstance(s, str) and HAS_UTF8.search(s) is not None: + s = s.decode('utf-8') + def replace(match): + s = match.group(0) + try: + return ESCAPE_DCT[s] + except KeyError: + n = ord(s) + if n < 0x10000: + return '\\u%04x' % (n,) + else: + # surrogate pair + n -= 0x10000 + s1 = 0xd800 | ((n >> 10) & 0x3ff) + s2 = 0xdc00 | (n & 0x3ff) + return '\\u%04x\\u%04x' % (s1, s2) + return '"' + str(ESCAPE_ASCII.sub(replace, s)) + '"' + + +try: + encode_basestring_ascii = c_encode_basestring_ascii +except NameError: + encode_basestring_ascii = py_encode_basestring_ascii + + +class JSONEncoder(object): + """ + Extensible JSON encoder for Python data structures. + + Supports the following objects and types by default: + + +-------------------+---------------+ + | Python | JSON | + +===================+===============+ + | dict | object | + +-------------------+---------------+ + | list, tuple | array | + +-------------------+---------------+ + | str, unicode | string | + +-------------------+---------------+ + | int, long, float | number | + +-------------------+---------------+ + | True | true | + +-------------------+---------------+ + | False | false | + +-------------------+---------------+ + | None | null | + +-------------------+---------------+ + + To extend this to recognize other objects, subclass and implement a + ``.default()`` method with another method that returns a serializable + object for ``o`` if possible, otherwise it should call the superclass + implementation (to raise ``TypeError``). + """ + __all__ = ['__init__', 'default', 'encode', 'iterencode'] + item_separator = ', ' + key_separator = ': ' + def __init__(self, skipkeys=False, ensure_ascii=True, + check_circular=True, allow_nan=True, sort_keys=False, + indent=None, separators=None, encoding='utf-8', default=None): + """ + Constructor for JSONEncoder, with sensible defaults. + + If skipkeys is False, then it is a TypeError to attempt + encoding of keys that are not str, int, long, float or None. If + skipkeys is True, such items are simply skipped. + + If ensure_ascii is True, the output is guaranteed to be str + objects with all incoming unicode characters escaped. If + ensure_ascii is false, the output will be unicode object. + + If check_circular is True, then lists, dicts, and custom encoded + objects will be checked for circular references during encoding to + prevent an infinite recursion (which would cause an OverflowError). + Otherwise, no such check takes place. + + If allow_nan is True, then NaN, Infinity, and -Infinity will be + encoded as such. This behavior is not JSON specification compliant, + but is consistent with most JavaScript based encoders and decoders. + Otherwise, it will be a ValueError to encode such floats. + + If sort_keys is True, then the output of dictionaries will be + sorted by key; this is useful for regression tests to ensure + that JSON serializations can be compared on a day-to-day basis. + + If indent is a non-negative integer, then JSON array + elements and object members will be pretty-printed with that + indent level. An indent level of 0 will only insert newlines. + None is the most compact representation. + + If specified, separators should be a (item_separator, key_separator) + tuple. The default is (', ', ': '). To get the most compact JSON + representation you should specify (',', ':') to eliminate whitespace. + + If specified, default is a function that gets called for objects + that can't otherwise be serialized. It should return a JSON encodable + version of the object or raise a ``TypeError``. + + If encoding is not None, then all input strings will be + transformed into unicode using that encoding prior to JSON-encoding. + The default is UTF-8. + """ + + self.skipkeys = skipkeys + self.ensure_ascii = ensure_ascii + self.check_circular = check_circular + self.allow_nan = allow_nan + self.sort_keys = sort_keys + self.indent = indent + self.current_indent_level = 0 + if separators is not None: + self.item_separator, self.key_separator = separators + if default is not None: + self.default = default + self.encoding = encoding + + def _newline_indent(self): + return '\n' + (' ' * (self.indent * self.current_indent_level)) + + def _iterencode_list(self, lst, markers=None): + if not lst: + yield '[]' + return + if markers is not None: + markerid = id(lst) + if markerid in markers: + raise ValueError("Circular reference detected") + markers[markerid] = lst + yield '[' + if self.indent is not None: + self.current_indent_level += 1 + newline_indent = self._newline_indent() + separator = self.item_separator + newline_indent + yield newline_indent + else: + newline_indent = None + separator = self.item_separator + first = True + for value in lst: + if first: + first = False + else: + yield separator + for chunk in self._iterencode(value, markers): + yield chunk + if newline_indent is not None: + self.current_indent_level -= 1 + yield self._newline_indent() + yield ']' + if markers is not None: + del markers[markerid] + + def _iterencode_dict(self, dct, markers=None): + if not dct: + yield '{}' + return + if markers is not None: + markerid = id(dct) + if markerid in markers: + raise ValueError("Circular reference detected") + markers[markerid] = dct + yield '{' + key_separator = self.key_separator + if self.indent is not None: + self.current_indent_level += 1 + newline_indent = self._newline_indent() + item_separator = self.item_separator + newline_indent + yield newline_indent + else: + newline_indent = None + item_separator = self.item_separator + first = True + if self.ensure_ascii: + encoder = encode_basestring_ascii + else: + encoder = encode_basestring + allow_nan = self.allow_nan + if self.sort_keys: + keys = dct.keys() + keys.sort() + items = [(k, dct[k]) for k in keys] + else: + items = dct.iteritems() + _encoding = self.encoding + _do_decode = (_encoding is not None + and not (_encoding == 'utf-8')) + for key, value in items: + if isinstance(key, str): + if _do_decode: + key = key.decode(_encoding) + elif isinstance(key, basestring): + pass + # JavaScript is weakly typed for these, so it makes sense to + # also allow them. Many encoders seem to do something like this. + elif isinstance(key, float): + key = floatstr(key, allow_nan) + elif isinstance(key, (int, long)): + key = str(key) + elif key is True: + key = 'true' + elif key is False: + key = 'false' + elif key is None: + key = 'null' + elif self.skipkeys: + continue + else: + raise TypeError("key %r is not a string" % (key,)) + if first: + first = False + else: + yield item_separator + yield encoder(key) + yield key_separator + for chunk in self._iterencode(value, markers): + yield chunk + if newline_indent is not None: + self.current_indent_level -= 1 + yield self._newline_indent() + yield '}' + if markers is not None: + del markers[markerid] + + def _iterencode(self, o, markers=None): + if isinstance(o, basestring): + if self.ensure_ascii: + encoder = encode_basestring_ascii + else: + encoder = encode_basestring + _encoding = self.encoding + if (_encoding is not None and isinstance(o, str) + and not (_encoding == 'utf-8')): + o = o.decode(_encoding) + yield encoder(o) + elif o is None: + yield 'null' + elif o is True: + yield 'true' + elif o is False: + yield 'false' + elif isinstance(o, (int, long)): + yield str(o) + elif isinstance(o, float): + yield floatstr(o, self.allow_nan) + elif isinstance(o, (list, tuple)): + for chunk in self._iterencode_list(o, markers): + yield chunk + elif isinstance(o, dict): + for chunk in self._iterencode_dict(o, markers): + yield chunk + else: + if markers is not None: + markerid = id(o) + if markerid in markers: + raise ValueError("Circular reference detected") + markers[markerid] = o + for chunk in self._iterencode_default(o, markers): + yield chunk + if markers is not None: + del markers[markerid] + + def _iterencode_default(self, o, markers=None): + newobj = self.default(o) + return self._iterencode(newobj, markers) + + def default(self, o): + """ + Implement this method in a subclass such that it returns + a serializable object for ``o``, or calls the base implementation + (to raise a ``TypeError``). + + For example, to support arbitrary iterators, you could + implement default like this:: + + def default(self, o): + try: + iterable = iter(o) + except TypeError: + pass + else: + return list(iterable) + return JSONEncoder.default(self, o) + """ + raise TypeError("%r is not JSON serializable" % (o,)) + + def encode(self, o): + """ + Return a JSON string representation of a Python data structure. + + >>> JSONEncoder().encode({"foo": ["bar", "baz"]}) + '{"foo": ["bar", "baz"]}' + """ + # This is for extremely simple cases and benchmarks. + if isinstance(o, basestring): + if isinstance(o, str): + _encoding = self.encoding + if (_encoding is not None + and not (_encoding == 'utf-8')): + o = o.decode(_encoding) + if self.ensure_ascii: + return encode_basestring_ascii(o) + else: + return encode_basestring(o) + # This doesn't pass the iterator directly to ''.join() because the + # exceptions aren't as detailed. The list call should be roughly + # equivalent to the PySequence_Fast that ''.join() would do. + chunks = list(self.iterencode(o)) + return ''.join(chunks) + + def iterencode(self, o): + """ + Encode the given object and yield each string + representation as available. + + For example:: + + for chunk in JSONEncoder().iterencode(bigobject): + mysocket.write(chunk) + """ + if self.check_circular: + markers = {} + else: + markers = None + return self._iterencode(o, markers) + +__all__ = ['JSONEncoder'] diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/gaeo/controller/jsonencoder.pyc Binary file ikweb/ikweb/gaeo/controller/jsonencoder.pyc has changed diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/gaeo/dispatch/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/ikweb/gaeo/dispatch/__init__.py Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2008 Lin-Chieh Shangkuan & Liang-Heng Chen +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""Dispatcher Package +""" \ No newline at end of file diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/gaeo/dispatch/__init__.pyc Binary file ikweb/ikweb/gaeo/dispatch/__init__.pyc has changed diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/gaeo/dispatch/dispatcher.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/ikweb/gaeo/dispatch/dispatcher.py Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,132 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2008 Lin-Chieh Shangkuan & Liang-Heng Chen +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import re +import logging + +import router +import sys +import os +from traceback import * + +HTTP_ERRORS = { + '400': 'Bad Request', + '402': 'Payment Required', + '403': 'Forbidden', + '404': 'Not Found', + '500': 'Internal Server Error' +} + +TXMT_LINKS = False # set true to show textmate links on tracebacks +DEBUG = True # set true to show traceback on error pages + +def dispatch(hnd): + + # generate nice traceback with optional textmate links + def nice_traceback(traceback): + tb="" + for line in traceback.splitlines(1): + filename = re.findall('File "(.+)",', line) + linenumber = re.findall(', line\s(\d+),', line) + modulename = re.findall(', in ([A-Za-z]+)', line) + if filename and linenumber and not re.match("<(.+)>",filename[0]): + fn=filename[0] + mn="in %s" % modulename[0] if modulename else "" + fnshort=os.path.basename(fn) + ln=linenumber[0] + if TXMT_LINKS: + html="%s:%s %s %s" % (fn,ln,fnshort,ln,mn,line) + else: + html="%s:%s %s %s" % (fnshort,ln,mn,line) + tb+=html + else: + tb+=line + return tb + + # show error and write to log + def show_error(code, log_msg = ''): + hnd.error(code) + if sys.exc_info()[0]: + exception_name = sys.exc_info()[0].__name__ + exception_details = str(sys.exc_info()[1]) + exception_traceback = ''.join(format_exception(*sys.exc_info())) + special_info = str(exception_details) != str(log_msg) + logging.error(exception_name) + logging.error(exception_details) + logging.error(log_msg) + logging.error(exception_traceback) + hnd.response.out.write('

%s

' % HTTP_ERRORS[str(code)]) + if DEBUG: + tb=nice_traceback(exception_traceback) + if special_info: logging.error(log_msg) + hnd.response.out.write('

%s: %s

' % (exception_name, exception_details)) + if special_info: hnd.response.out.write('
 %s 
' % log_msg) + hnd.response.out.write('

Traceback

') + hnd.response.out.write('
 %s 
' % tb) + else: + hnd.response.out.write('

%s

' % log_msg) + + # resolve the URL + url = hnd.request.path + r = router.Router() + route = r.resolve(url) + + if route is None: + try: + raise Exception('invalid URL') + except Exception, e: + show_error(500, e) + else: + # create the appropriate controller + try: + exec('from controller import %s' % route['controller']) in globals() + ctrl = eval('%s.%sController' % ( + route['controller'], + route['controller'].capitalize() + ))(hnd, route) + + # dispatch + logging.info('URL "%s" is dispatched to: %sController#%s', + url, + route['controller'].capitalize(), + route['action']) + except ImportError, e: + show_error(404, "Controller doesn't exist") + except AttributeError, e: # the controller has not been defined. + show_error(404, "Controller doesn't exist") + except Exception, e: + show_error(500, e) + else: + try: + ctrl.attrs + action = getattr(ctrl, route['action'], None) + if action is not None: + ctrl.implicit_action() + ctrl.before_action() + action() + ctrl.after_action() + + if not ctrl.has_rendered: + ctrl.render(template=route['action'], values=ctrl.__dict__) + else: # invalid action + logging.error('Invalid action `%s` in `%s`' % (route['action'], route['controller'])) + try: + raise Exception('invalid action') + except Exception, e: + show_error(500, e) + except Exception, e: + show_error(500, e) diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/gaeo/dispatch/dispatcher.pyc Binary file ikweb/ikweb/gaeo/dispatch/dispatcher.pyc has changed diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/gaeo/dispatch/router.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/ikweb/gaeo/dispatch/router.py Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,174 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2008 Lin-Chieh Shangkuan & Liang-Heng Chen +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import re +from copy import copy +import logging + +class RuleError(Exception): + """Base Error""" + +class RuleNoControllerError(RuleError): + """No controller""" + +class Rule(object): + """ Handles each routing rule. """ + def __init__(self, pattern, **param): + super(Rule, self).__init__() + + self.pattern = pattern[:-1] if pattern.endswith('/') else pattern + self.regex = self.pattern + self.param = param + self.matches = re.findall(':([^/\.]+)', self.pattern) + + for i in range(len(self.matches)): + self.regex = self.regex.replace(':' + self.matches[i], '([^/\.]+)') + self.param[self.matches[i]] = i + self.validate() + + def __eq__(self, other): + return self.regex == other.regex + + def __getattr__(self, attr): + try: + return getattr(self, 'param')[attr] + except KeyError: + raise AttributeError, attr + + def __str__(self): + from operator import itemgetter + return ', '.join(['%s: %s' % (k, v) for k, v in \ + sorted(self.param.items(), key = itemgetter(1))]) + + def match_url(self, url): + if url.endswith('/'): + url = url[:-1] + try: + mat = re.findall(self.regex, url)[0] + except IndexError: + return None + + param = copy(self.param) + if isinstance(mat, basestring): + if self.matches: + param[self.matches[0]] = mat + elif isinstance(mat, tuple): + for i in range(len(mat)): + param[self.matches[i]] = mat[i] + return param + + def url_for(self, controller, **param): + param['controller'] = controller + url = self.pattern + for match in self.matches: + if match not in param: + return None + url = url.replace(':' + match, str(param[match])) + del param[match] + + # extra parameters + ep = '&'.join(['%s=%s' % (k, v) for k, v in param.items() if k not in self.param]) + + return url + '?' + ep if ep else url + + def validate(self): + if 'controller' not in self.param: + raise RuleNoControllerError + + if 'action' not in self.param: + self.param['action'] = 'index' + + if not self.regex.startswith('^'): + self.regex = '^' + self.regex + if not self.regex.endswith('$'): + self.regex = self.regex + '$' + + +class Router: + """ Handles the url routing... """ + + class __impl: + def __init__(self): + self.__routing_root = { + 'controller': 'welcome', + 'action': 'index', + } + self.__routing_table = [] + # used to store default pattern (but match last) + self.__routing_table_fallback = [ + Rule('/:controller/:action'), + Rule('/:controller') + ] + + def connect(self, pattern, **tbl): + """ Add routing pattern """ + + rule = Rule(pattern, **tbl) + if rule not in self.__routing_table: + self.__routing_table.append(rule) + + def disconnect(self, pattern): + rule = Rule(pattern) + if rule in self.__routing_table: + self.__routing_table.remove(rule) + + def root(self, **map): + """ Set the root (/) routing... """ + self.__routing_root['controller'] = \ + map.get('controller', self.__routing_root['controller']) + self.__routing_root['action'] = \ + map.get('action', self.__routing_root['action']) + + def resolve(self, url): + """ Resolve the url to the correct mapping """ + + if url == '/': + return self.__routing_root + + ret = self.__resolve_by_table(url, self.__routing_table) + if ret is None: # fallback + ret = self.__resolve_by_table(url, self.__routing_table_fallback) + return ret + + def __resolve_by_table(self, url, rules): + """ Resolve url by the given table """ + for r in rules: + ret = r.match_url(url) + if ret: + return ret + return None + + def url_for(self, controller, **param): + for r in self.__routing_table: + ret = r.url_for(controller, **param) + if ret: + return ret + return None + + __instance = None + + def __init__(self): + if Router.__instance is None: + Router.__instance = Router.__impl() + self.__dict__['_Router__instance'] = Router.__instance + + def __getattr__(self, attr): + return getattr(self.__instance, attr) + + def __setattr__(self, attr, value): + return setattr(self.__instance, attr, value) + diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/gaeo/dispatch/router.pyc Binary file ikweb/ikweb/gaeo/dispatch/router.pyc has changed diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/gaeo/model/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/ikweb/gaeo/model/__init__.py Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2008 GAEO Team. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""GAEO model package +""" +import re + +from google.appengine.ext import db + +def pluralize(noun): + if re.search('[sxz]$', noun): + return re.sub('$', 'es', noun) + elif re.search('[^aeioudgkprt]h$', noun): + return re.sub('$', 'es', noun) + elif re.search('[^aeiou]y$', noun): + return re.sub('y$', 'ies', noun) + else: + return noun + 's' + +class BaseModel(db.Model): + """BaseModel is the base class of data model.""" + + @classmethod + def belongs_to(cls, ref_cls): + """ Declare a many-to-one relationship """ + if ref_cls is None: + raise Exception('No referenced class') + + ref_name = ref_cls.__name__.lower() + if ref_name not in cls._properties: + attr = db.ReferenceProperty(ref_cls, collection_name=pluralize(cls.__name__.lower())) + cls._properties[ref_name] = attr + attr.__property_config__(cls, ref_name) + + @classmethod + def has_and_belongs_to_many(cls, ref_cls): + if ref_cls is None: + raise Exception('No referenced class') + + f_name = pluralize(cls.__name__.lower()) + t_name = pluralize(ref_cls.__name__.lower()) + + if t_name not in cls._properties: + attr = db.ListProperty(db.Key) + cls._properties[t_name] = attr + attr.__property_config__(cls, t_name) + if f_name not in ref_cls._properties: + attr = property(lambda self: cls.gql('WHERE %s = :1' % t_name, self.key())) + ref_cls._properties[f_name] = attr + attr.__property_config__(ref_cls, f_name) + + @classmethod + def named_scope(cls, name, order_by=None, **conds): + if name not in cls._properties: + cond_str = "WHERE " + for cond in conds.iterkeys(): + if len(cond_str) > 6: + cond_str += ' AND ' + cond_str += '%s %s' % (cond, conds[cond]) + + if order_by: + cond_str += ' ORDER BY %s' % order_by + + attr = property(lambda self: cls.gql(cond_str)) + cls._properties[name] = attr + attr.__property_config__(cls, name) + + def update_attributes(self, kwd_dict = {}, **kwds): + """Update the specified properties""" + need_change = False + + # if user passed a dict, merge to kwds (Issue #3) + if kwd_dict: + kwd_dict.update(kwds) + kwds = kwd_dict + + props = self.properties() + for prop in props.values(): + if prop.name in kwds: + if not need_change: + need_change = True + prop.__set__(self, kwds[prop.name]) + + if need_change: + self.update() + + def set_attributes(self, kwd_dict = {}, **kwds): + """set the specified properties, but not update""" + + # Issue #3 + if kwd_dict: + kwd_dict.update(kwds) + kwds = kwd_dict + + props = self.properties() + for prop in props.values(): + if prop.name in kwds: + prop.__set__(self, kwds[prop.name]) + + def save(self): + self.put() + + def update(self): + self.put() + diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/gaeo/model/__init__.pyc Binary file ikweb/ikweb/gaeo/model/__init__.pyc has changed diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/gaeo/session/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/ikweb/gaeo/session/__init__.py Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2008 Lin-Chieh Shangkuan & Liang-Heng Chen +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +""" GAEO Session package """ +import logging +from random import choice +from string import digits, letters + +SESSIONID_LEN = 64 +POOL = digits + letters + +class Session(dict): + """ Session is an abstract class that declares sessions basic + operations. """ + + def __init__(self, hnd, name, timeout): + """The Session's constructor. + + @param hnd The webapp.ReqeustHanlder object. + @param name The session name. + @param timeout The time interval (in sec) that the session expired. + """ + + dict.__init__(self) + self._name = name + self._hnd = hnd + self._timeout = timeout + self._id = ''.join([ choice(POOL) for i in range(SESSIONID_LEN) ]) + + self._invalidated = False + + def save(self): + pass + + def invalidate(self): + pass diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/gaeo/session/__init__.pyc Binary file ikweb/ikweb/gaeo/session/__init__.pyc has changed diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/gaeo/session/memcache.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/ikweb/gaeo/session/memcache.py Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2008 Lin-Chieh Shangkuan & Liang-Heng Chen +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +""" GAEO Session - memcache store """ +import random +import pickle +import logging + +from google.appengine.api import memcache + +from gaeo import session + +class MemcacheSession(session.Session): + """ session that uses memcache """ + + def __init__(self, hnd, name = 'gaeo_session', timeout = 60 * 60): + super(MemcacheSession, self).__init__(hnd, name, timeout) + + # check from cookie + if name in hnd.request.cookies: + self._id = hnd.request.cookies[name] + session_data = memcache.get(self._id) + if session_data: + self.update(pickle.loads(session_data)) + memcache.set(self._id, session_data, timeout) + else: # not in the cookie, set it + cookie = '%s=%s' % (name, self._id) + hnd.response.headers.add_header('Set-Cookie', cookie) + + def put(self): + if not self._invalidated: + memcache.set(self._id, pickle.dumps(self.copy()), self._timeout) + + def invalidate(self): + """Invalidates the session data""" + self._hnd.response.headers.add_header( + 'Set-Cookie', + '%s=; expires=Thu, 1-Jan-1970 00:00:00 GMT;' % self._name + ) + memcache.delete(self._id) + self.clear() + self._invalidated = True diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/gaeo/session/memcache.pyc Binary file ikweb/ikweb/gaeo/session/memcache.pyc has changed diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/gaeo/view/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/ikweb/gaeo/view/__init__.py Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2008 GAEO Team. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +""" GAEO View package """ diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/gaeo/view/__init__.pyc Binary file ikweb/ikweb/gaeo/view/__init__.pyc has changed diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/gaeo/view/helper/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/ikweb/gaeo/view/helper/__init__.py Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2008 GAEO Team +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""" GAEO view helper module """ \ No newline at end of file diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/gaeo/view/helper/__init__.pyc Binary file ikweb/ikweb/gaeo/view/helper/__init__.pyc has changed diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/gaeo/view/helper/ajax.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/ikweb/gaeo/view/helper/ajax.py Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2008 GAEO Team +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""" GAEO view ajax helpers """ + +def link_to_function(title, script, html = {}): + """ Return a link that contains onclick scripts """ + html_part = '' + if html: + for key, val in html.items(): + space = ' ' if html_part else '' + html_part = '%s%s="%s"' % (space, key, val) + + return '%s' % (html_part, script, title) + + +def javascript_tag(scripts): + """ Wraps scripts with ' % scripts + + +def remote_script(url, **opts): + """ Create a ajax request script """ + method = opts.get('method', 'get'); + # request parameters + data = opts.get('data', '') + # callback function + callback = opts.get('callback', '') + dataType = opts.get('dataType', '') + + params = "'%s'" % data if data else '' + if callback: + cbf = 'function(data, textStatus){%s}' % callback + params = params + ', %s' % cbf if params else cbf + params = params + ", '%s'" % dataType if dataType else params + if params: + params = ', ' + params + + return """$.%s('%s'%s)""" % (method, url, params) + +def link_to_remote(title, url, **opts): + """ Create a link that request a remote action (jQuery-based) """ + # check if the title is specified + if not title: + raise Exception('Missing title of the link.') + + # remote action + if not url: + raise Exception('Missing remote action.') + + script = remote_script(url, **opts) + return link_to_function(title, script, opts.get('html', {})) + + +def load_from_remote(title, url, target, **opts): + """ Create a link that load data from a remote action (jQuery-based) """ + # check if the title is specified + if not title: + raise Exception('Missing title of the link.') + + # remote action url + if not url: + raise Exception('Missing remote action.') + + # load target #id + if not target: + raise Exception('Missing the id of loaded data target.') + + data = opts.get('data', '') + callback = opts.get('callback', '') + params = data + if callback: + cbf = 'function(){%s}' % callback + params = '%s,%s' % (params, cbf) if params else cbf + + params = ', ' + params if params else '' + script = """$('#%s').load('%s'%s);""" % (target, url, params) + + return link_to_function(title, script, opts.get('html', {})) + + +def periodically_call_remote(url, **opts): + """ Periodically call a remote action. (jQuery-based) """ + if not url: + raise Exception('Missing remote action url.') + + # frequency, default 1000 ms + freq = opts.get('frequency', 1000) + script = "setInterval(function(){%s}, %s)" % (remote_script(url, **opts), freq) + return javascript_tag(script) + diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/gaeo/view/helper/ajax.pyc Binary file ikweb/ikweb/gaeo/view/helper/ajax.pyc has changed diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/index.yaml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/ikweb/index.yaml Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,23 @@ +indexes: + +# AUTOGENERATED + +# This index.yaml is automatically updated whenever the dev_appserver +# detects that a new type of query is run. If you want to manage the +# index.yaml file manually, remove the above marker line (the line +# saying "# AUTOGENERATED"). If you want to manage some indexes +# manually, move them above the marker line. The index.yaml file is +# automatically uploaded to the admin console when you next deploy +# your application using appcfg.py. + +# Used once in query history. +- kind: Player + properties: + - name: game_id + - name: lastupdate_at + +# Used 4 times in query history. +- kind: Player + properties: + - name: lastupdate_at + direction: desc diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/main.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/ikweb/main.py Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,56 @@ +import logging +import os +import re +import sys +import wsgiref.handlers + +from google.appengine.ext import webapp + +import gaeo +from gaeo.dispatch import router + +def initRoutes(): + r = router.Router() + + #TODO: add routes here + + r.connect('/:controller/:action/:id') + +def initPlugins(): + """ + Initialize the installed plugins + """ + plugins_root = os.path.join(os.path.dirname(__file__), 'plugins') + if os.path.exists(plugins_root): + plugins = os.listdir(plugins_root) + for plugin in plugins: + if not re.match('^__', plugin): + try: + exec('from plugins import %s' % plugin) + except ImportError: + logging.error('Unable to import %s' % (plugin)) + except SyntaxError: + logging.error('Unable to import name %s' % (plugin)) + +def main(): + # add the project's directory to the import path list. + sys.path.append(os.path.dirname(__file__)) + sys.path.append(os.path.join(os.path.dirname(__file__), 'application')) + + # get the gaeo's config (singleton) + config = gaeo.Config() + # setup the templates' location + config.template_dir = os.path.join( + os.path.dirname(__file__), 'application', 'templates') + + initRoutes() + # initialize the installed plugins + initPlugins() + + app = webapp.WSGIApplication([ + (r'.*', gaeo.MainHandler), + ], debug=True) + wsgiref.handlers.CGIHandler().run(app) + +if __name__ == '__main__': + main() diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/plugins/__init__.py diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/plugins/__init__.pyc Binary file ikweb/ikweb/plugins/__init__.pyc has changed diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/plugins/ikariam/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/ikweb/plugins/ikariam/__init__.py Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,105 @@ +def load_model(modelname): + try: + return eval(modelname) + except NameError: + module = __import__("model.%s" % modelname.lower(), {}, {}, [modelname]) + return getattr(module,modelname) + +class IkariamModelPlugin: + + server = "s4" + base_url = "http://%s.ikariam.tw" % server + + @classmethod + def get_by_gameid(cls, id): + obj = cls.gql("WHERE game_id ="+str(id)+" ORDER BY lastupdate_at").get() + return obj + + def _mk_attrs(self): + from copy import copy + attrs = copy(self.params) + + for ref_name in ref_attrs: + try: + modelname = self.params[ref_name].replace('_id','') + model = load_model(modelname) + obj = model.get_by_game_id(self.params[ref_name]) + attrs[modelname] = obj + except KeyError: + pass + + return self._tr_attrtype(attrs, 'int', int_attrs) + +class IkaraimControllerPlugin: + + def _get_modelname(self): + return self.__class__.__name__.replace('Controller','') + + def attrs(self): + modelname = self._get_modelname() + model = load_model(modelname) + + def fn(e): + if e not in getattr(model, 'unshow_attrs', []) and \ + e not in ['__module__','__doc__','_properties', + 'unshow_attrs', 'int_attrs', 'lastupdate_at']: + return e + + self.ui().render_result(200, '
'.join(filter(fn, model.__dict__.keys()))) + + def show(self): + datas = self._get_modeldatas() + + content = [] + for k,v in datas.items(): + content.append("%s:%s
"%(k,v)) + + self.ui().render_result(200, ''.join(content)) + + def json(self): + datas = self._get_modeldatas() + self.ui().render_json_of(datas) + + def _get_modeldatas(self): + modelname = self._get_modelname() + model = load_model(modelname) + obj = model.get_by_gameid(self.params['id']) + return self._tr_attrtype(obj._entity,'str') + + def create(self): + self._update('create') + + def edit(self): + self._update('edit') + + def _update(self, mode='create'): + modelname = self._get_modelname() + model = load_model(modelname) + + if 'edit' == mode: + obj = model.get_by_gameid(self.params['game_id']) + else: + obj = model + attrs = self._mk_attrs() + obj.update_attributes(attrs) + obj.save() + + def _tr_attrtype(self, attrs, type, include_attrs=None): + + if not include_attrs: + attrnames = attrs.keys() + else: + attrnames = include_attrs + + for attrname in attrnames: + try: + attrs[attrname] = eval(type)(attrs[attrname]) + except KeyError: + pass + return attrs + +from gaeo.model import BaseModel +BaseModel.__bases__ += (IkariamModelPlugin,) + +from gaeo.controller import BaseController +BaseController.__bases__ += (IkaraimControllerPlugin,) \ No newline at end of file diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/plugins/ikariam/__init__.pyc Binary file ikweb/ikweb/plugins/ikariam/__init__.pyc has changed diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/plugins/lazyui/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/ikweb/plugins/lazyui/__init__.py Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,132 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2008 Hsin Yi, Chen +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License +from gaeo.view.helper import ajax + +class LazyUiPlugin: + + def ui(self, frontend = 'html'): + """ + get web page ui helper. + # close dialog window. + self.ui().close_dialog(); + """ + cls = 'Lazy'+frontend.capitalize()+'Ui' + self.__dict__.setdefault('_uihelper', eval(cls)(self) ) + return self.__dict__.get('_uihelper') + +class LazyHtmlUi: + __instance = None + + def __init__(self, ctrl): + """ + Controller Decoration. + """ + if LazyHtmlUi.__instance is None: + LazyHtmlUi.__instance = ctrl + + def __getattr__(self, attr): + return getattr(self.__instance, attr) + + def __setattr__(self, attr, value): + return setattr(self.__instance, attr, value) + + def close_dialog(self, **after_act): + """ + close web borwser diaolog window. + + # close the window and reload parent page. + close_dialog(after_act = {'action':'update_parent'} + + # using custom javascript code to controll parent page, for excample + # remove a block. + callback_js = self.create_controlls_parent_js('Editor.inserHTML', 'hello') + close_dialog(after_act = {'msg':'sucess.','action':'update_parent', 'callback':callback_js}) + + # close the window and redirect. + close_dialog(after_act = {'action':'redirect','to_url':'http://www.example.com'}) + """ + scripts = after_act.get('msg') and "alert('%s');" % after_act.get('msg') + + if 'redirect' == after_act.get('action'): + self.redirect(after_act.get('to_url', '/') ) + elif 'update_parent' == after_act.get('action'): + if after_act.get('callback'): + scripts += after_act.get('callback') + else: + scripts += "window.parent.location.reload();" + + scripts += "window.close();" + self.render_js(scripts) + + def controlls_parent(self, ctrl, *argvs ): + """ + calls parent window function, or object method. + + # insert a JPG image with 500x500 size to parent window. + controlls_parent_window('ImgEditor.insert', [ '/images/no.jpg', 500, 500] ) + """ + scripts = self.create_controlls_parent_window_js(ctrl, argvs) + self.render_js(scripts) + + def create_controlls_parent_js(self, ctrl, *argvs): + return "window.parent.%s(%s);" % ( ctrl, str(argvs) and ','.join( [ '"'+str(x)+'"' for x in argvs ] ) ) + + def alert_and_redirect(self, msg = '', dest_url = None): + """ + alert message and redirect to next url if destention url is speficied, + otherwise, the web siet viewer will be redirect back to his last browsed page. + + alert_and_redirect('action success!', 'http://example.com') + """ + scripts = "alert('%s');" % msg + if dest_url: + scripts += "document.location.href='%s'" % ( msg, dest_url ) + else: + scripts += "window.back();" + self.render_js(scripts) + + def render_result(self, http_code=200, content=''): + """ + display result info for frontend controller. + for example, swfupload file uploader requires a page that + displays http state code to know the result after uploading + files is finished. + """ + self.hnd.error(http_code) + self.render(content) + + def render_js(self, scripts): + """ + render javasciprt code to output. + + render_js("alert('hellow');document.location.href='/'") + """ + self.render(ajax.javascript_tag(scripts)) + + def render_json_of(self, obj): + """ + render JSON of an object + """ + self.render(self.to_json(obj)) + + def render_xml_of(self, obj): + """ + render XML of an object + """ + self.render(self.to_xml(obj)) + +from gaeo.controller import BaseController +BaseController.__bases__ += (LazyUiPlugin,) diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/ikweb/plugins/lazyui/__init__.pyc Binary file ikweb/ikweb/plugins/lazyui/__init__.pyc has changed diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/rebuild_world --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/rebuild_world Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,8 @@ +#/bin/bash +EAGLE_EYE=$HOME/lazyproject/eagle-eye +echo "setup ikariam web site." + +db_schema=$EAGLE_EYE/ikariam.sqlite +echo "load sqlite file:"$db_schema +cd ikweb +python ../tools/lazygen_model.py sqlite:///$db_schema diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/tools/dummy.sqlite Binary file ikweb/tools/dummy.sqlite has changed diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/tools/functional_test.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/tools/functional_test.py Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,29 @@ +from lazy.www import c + +def create_player(): + datas = {'game_id':1, + 'army_score_main':10000, + 'building_score_main':1000, + 'building_score_secondary':100, + 'name':'tplayer', + 'research_score_main':122, + 'research_score_secondary':122, + 'score':10000, + 'trader_score_secondary':1000000 + } + c("http://localhost:8080/player/create").post(datas) + +def edit_player(): + datas = {'game_id':1, + 'name':'tfplayer' + } + c("http://localhost:8080/player/edit").post(datas) + +import sys + +act = sys.argv[1] +modelname = sys.argv[2] + +funcname = "%s_%s" % (act,modelname) +print 'test',funcname +eval(funcname)() diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/tools/lazy/__init__.py diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/tools/lazy/__init__.pyc Binary file ikweb/tools/lazy/__init__.pyc has changed diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/tools/lazy/www/README.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/tools/lazy/www/README.py Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,16 @@ +""" +Requirements: + + lxml - python libxml binding + + it needs to installing the following packages before install lxml. + + * libxml 2.6.21 or later. It can be found here: http://xmlsoft.org/downloads.html + * libxslt 1.1.15 or later. It can be found here: http://xmlsoft.org/XSLT/downloads.html + + If you use Ubuntu, here is what you need to do. + + $ apt-get install libxml2-dev libxslt1-dev + $ eazy_install lxml + +""" \ No newline at end of file diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/tools/lazy/www/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/tools/lazy/www/__init__.py Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2008 Hsin Yi, Chen +""" + [Note] the project is not available yet. + + A web page fetcing tool chain that has a JQuery-like selector and supports chain working. + + Here is an exmaple can show the the main idea, To restrive a content you want + in a div box in a web page, and then post and restrive next content in the other + web page with the param you just maked from the content in first restriving. + finally, storage the production. + + def func(s): + return {'msg':s} + + try: + c("http://example.tw/").get().find("////ul/text()") \ + .build_param( func ).post_to("http://example2.com") \ + .save_as('hellow.html') + except: + pass + + more complex example + + try: + c("http://example.tw/").retry(4, '5m').get() \ + .find("#id > div"). \ + .build_param( func ).post_to("http://example2.com") \ + .save_as('hellow.html') \ + .end().find("#id2 > img").download('pretty-%s.jpg'). \ + tar_and_zip("pretty_girl.tar.gz") + except NotFound: + print "the web page is not found." + except NoPermissionTosave: + print "the files can not be save with incorrect permission." + else: + print "unknow error." +""" +from lazy.www.work import WorkFlow +from lazy.www.work.fetch import Fetcher +from lazy.www.work.storage import FileStorager +from lazy.www.core import SemiProduct +import os +import sys +import re + +def parse_scheme(scheme): + try: + return re.findall("(\w+):\/\/(.*\/?)",scheme)[0] + except: + sys.stdout.write("the scheme is not supported.") + sys.exit() + +def c(scheme, worker=None): + """ + connect to a web apge + + >>> c('http://localhost:8080').get().worker.working_product.content + 'It works!!\\n' + + >>> c('http://localhost:8080').get().find('//text()') + 'It works!!\\n' + """ + target_type, path = parse_scheme(scheme) + + #@todo: SemiProduct Factory. + if worker is None: + if 'file' == target_type: + s= SemiProduct(source=path) + worker = FileStorager(s) + else: + s= SemiProduct(source=scheme) + worker = Fetcher(s) + + return WorkFlow(worker) + +if __name__ == '__main__': + import doctest + doctest.testmod() diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/tools/lazy/www/__init__.pyc Binary file ikweb/tools/lazy/www/__init__.pyc has changed diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/tools/lazy/www/core/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/tools/lazy/www/core/__init__.py Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,12 @@ + +class SemiProduct: + + last_work = None + source = None + content = None + + def __init__(self, **kwds): + self.source = kwds.get('source','') + + def __str__(self): + return self.content \ No newline at end of file diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/tools/lazy/www/core/__init__.pyc Binary file ikweb/tools/lazy/www/core/__init__.pyc has changed diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/tools/lazy/www/core/utils.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/tools/lazy/www/core/utils.py Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,4 @@ + +def mix_in(py_class, mixin_class): + if mixin_class not in py_class.__bases__: + py_class.__bases__ += (mixin_class,) \ No newline at end of file diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/tools/lazy/www/work/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/tools/lazy/www/work/__init__.py Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,11 @@ +from lazy.www.work.fetch import Fetcher +from lazy.www.work.find import Finder +from lazy.www.core import SemiProduct + +class WorkFlow: + + def __init__(self, worker): + self.worker = worker + + def __getattr__(self, name): + return getattr(self.worker, name) diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/tools/lazy/www/work/__init__.pyc Binary file ikweb/tools/lazy/www/work/__init__.pyc has changed diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/tools/lazy/www/work/fetch.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/tools/lazy/www/work/fetch.py Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,72 @@ +import urllib2 +import urllib +import cookielib +import os + +class Fetcher: + + opener = None + + working_product = None + + """ + A Semi Production Decoration for content fetching. + + handles content restriving. + + >>> o = Fetcher( SemiProduct(source="http://localhost:8080") ) + >>> o.get().working_product.content + 'It works!!\\n' + """ + def __init__(self, working_product): + self.working_product = working_product + + def get(self, data = {}): + """ + send datas via http get method. + """ + res = urllib2.urlopen(self.working_product.source, urllib.urlencode(data)) + return res.read() + + def post(self, data = {} ): + """ + send datas via http post method. + + >>> o = Fetcher( SemiProduct(source="http://localhost:8080") ) + >>> o.post({'a':'b'}).working_product.content + 'It works!!\\n' + """ + res = urllib2.urlopen(self.working_product.source, urllib.urlencode(data)) + return res.read() + + def refer(self, refer_url): + """ + refer getter/setter. + + >>> o = Fetcher( SemiProduct(source="http://localhost:8080") ) + >>> o.refer('http://www.example.com') + """ + raise NotImplementedError + + def retry(self, count = 0, intval = 0, timeout = 0): + """ + retry to fetch the content. + + >>> o = Fetcher( SemiProduct(source="http://localhost:8080") ) + >>> o.retry(4) + """ + raise NotImplementedError + +class Retry: + + """ + A Fetcher Decoration for retry goal. + + + """ + def __init__(self, fetcher): + raise NotImplementedError + +if __name__ == '__main__': + import doctest + doctest.testmod() \ No newline at end of file diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/tools/lazy/www/work/fetch.pyc Binary file ikweb/tools/lazy/www/work/fetch.pyc has changed diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/tools/lazy/www/work/find.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/tools/lazy/www/work/find.py Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,27 @@ +from lxml import etree +from cStringIO import StringIO + +class Finder: + + dom_tree = None + xpath = None + + def __init__(self, working_product): + self.working_prodcut = working_product + + self.encoding = 'utf8' + + def find(self, express , callback = None): + + if self.dom_tree is None: self.set_dom_tree(self.working_prodcut.content) + + xpath = self.dom_tree.xpath(express) + + if callback: return self.callback(xpath) + return xpath + + def set_dom_tree(self, content): + stream = StringIO(content) + + parser = etree.XMLParser(encoding=self.encoding) + self.dom_tree = etree.parse(stream, parser) \ No newline at end of file diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/tools/lazy/www/work/find.pyc Binary file ikweb/tools/lazy/www/work/find.pyc has changed diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/tools/lazy/www/work/storage.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/tools/lazy/www/work/storage.py Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,34 @@ +class FileStorager: + + opener = None + working_product = None + + """ + A Semi Production Decoration for content storaging. + + handles content storaging. + + >>> o = Fetcher( SemiProduct(source="file:///tmp/a.txt") ) + >>> o.get().working_product.content + 'It works!!\\n' + """ + def __init__(self, working_product): + self.working_product = working_product + + def get(self, data = {}): + """ + send datas via http get method. + """ + res = open(self.working_product.source) + return res.read() + + def post(self, data = {} ): + """ + send datas via http post method. + + >>> o = Fetcher( SemiProduct(source="file:///tmp/a.txt") ) + >>> o.post({'a':'b'}).working_product.content + 'It works!!\\n' + """ + res = open(self.working_product.source) + return res.read() \ No newline at end of file diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/tools/lazy/www/work/storage.pyc Binary file ikweb/tools/lazy/www/work/storage.pyc has changed diff -r 60c4b4b78a01 -r 7747bbe5b68e ikweb/tools/lazygen_model.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ikweb/tools/lazygen_model.py Mon Dec 01 00:27:22 2008 +0800 @@ -0,0 +1,228 @@ +#!/usr/bin/env python +import re +import os +import sys + +from sqlalchemy import * +# dirty import XD +gaeo_home = os.getenv('GAEO_HOME').replace('~',os.getenv('HOME'))+'/bin' +try: + sys.path.append(gaeo_home) + from gaeogen import * +except ImportError, e: + print "can not import gaeogen, the gaeogen path maybe wrong, got path:%s" % gaeo_home + sys.exit() + + +def sqlite_column_mapper(model_class, table_obj): + """ + map sqlite columns of the table to GAEO Model + + >>> metadata = MetaData() + >>> users_table = Table('users', metadata, + ... Column('id', Integer, primary_key=True), + ... Column('name', String), + ... Column('fullname', String), + ... Column('password', String) + ... ) + >>> model = sqlite_column_mapper(GenModel, users_table) + >>> model.generate_content() + """ + def convertor(col_type): + lowertype = col_type.lower() + + if re.match('char', lowertype): col_type = "String" + if re.match('(int|integer)', lowertype): col_type = 'Integer' + return col_type + + model = model_class(table_obj.name) + + for column in table_obj.columns: + model.add_property( "%s" % GenProperty(column.name, + column, + convertor)) + return model + +class GenProperty(object): + """ + GAE Property Generator. + + class Property(verbose_name=None, name=None, default=None, required=False, validator=None, choices=None) + """ + db_prefix = "^(SL|PG|Max|MS|FB|Ac|Info|Oracle)" + + def __init__(self, name, column=False, convertor=False): + """ + >>> col = Column('summary', String(2000)) + >>> p = GenProperty('summary', col) + >>> "%s" % p + "summary:StringProperty(verbose_name='summary',name='summary')" + >>> meta = MetaData() + >>> meta.bind = create_engine("sqlite:///dummy.sqlite") + >>> t = Table('ally', meta, autoload=True) + >>> p = GenProperty('name', t.columns.get('name')) + >>> "%s" % p + "name:TextProperty(verbose_name='name',name='name')" + >>> p = GenProperty('name', t.columns.get('time')) + >>> "%s" % p + "name:DatetimeProperty(verbose_name='name',name='name')" + """ + self.name = name + self.column = column + + # very dirty ... + if column: + self.type_name = re.sub( self.db_prefix,'', + column.type.__class__.__name__) + if convertor: + self.type_name = convertor(self.type_name) + + def __str__(self): + return self.gen() + + def gen(self): + """ + generate gaeo property code. + """ + clause= self.create_clause(self.type_name, self.column) + return gae_property_code(self.name, self.type_name, clause) + + def create_clause(self, type, column): + """ + create property cluase. + + >>> s = Column('summary', String(2000)) + >>> code = GenProperty(s.name).create_clause('string', s) + >>> code + {'verbose_name': 'summary', 'name': 'summary'} + """ + clause= self.create_basic_clause(column) + + try: + other_caluse = getattr(self, 'create_%s_clause' % type.lower())(column) + clause.update(other_caluse) + except: + pass + + return clause + + def create_basic_clause(self, column): + """ + create basic property cluase. + """ + clause = {'verbose_name':self.name, 'name':self.name} + + if column.default: + clause.setdefault('default', column.default) + elif column.server_default: + clause.setdefault('default', column.server_default) + + return clause + +def gae_property_code(name, type, clause): + """ + generate google app engine property code. + + >>> gae_property_code('name', 'string', {'default':'hychen','name':'hi'}) + "name:StringProperty(default='hychen',name='hi')" + """ + argv = [] + + def _quote(v): + try: + return "'"+v+"'" + except TypeError: + pass + + for k,v in clause.items(): + argv.append("%s=%s" % (k, _quote(v) ) ) + + return "%s:%sProperty(%s)" % (name, + type.capitalize(), + ','.join(argv)) + +class ModelMaker: + + is_build = False + + def __init__(self, **kwds): + """ + to create GAEO Model + + >>> maker = ModelMaker(schema="sqlite:///dummy.sqlite", autoload=True) + >>> maker.build() + """ + self.models = [] + self.set_engine(kwds['schema']) + + try: + if True == kwds['autoload']: + self.table_names = self.db_engine.table_names() + else: + self.table_names = kwds['include_tables'] + except KeyError: + print "input wrong argv." + + def set_engine(self, schema): + """ + set db engine + """ + self.db_engine = create_engine(schema) + self.meta = MetaData() + self.meta.bind = self.db_engine + + def build(self): + """ + build models by talbse in database. + """ + mapper_func = '%s_column_mapper' % self.db_engine.name.lower() + + for table_name in self.table_names: + table = Table(table_name, self.meta, autoload=True) + self.models.append( eval(mapper_func)( GenModel, table) ) + + self.is_build = True + + def save(self): + """ + save model + """ + if self.is_build: + application_dir = os.path.join(os.getcwd(), 'application') + model_dir = os.path.join(application_dir, 'model') + + # check if the model directory had been created + if not os.path.exists(os.path.join(model_dir, '__init__.py')): + create_file(os.path.join(model_dir, '__init__.py'), []) + + for model in self.models: + print 'Creating Model %s ...' % model.name + model.save(os.path.join(model_dir, '%s.py' % model.name)) + else: + print "not build yet." + +def gen_models_from_db(db_schema, include_tables=False): + """ + generate models form database. + + gen_models_from_db("sqlite://dumy.sqlite") + + # spefiy tables + gen_models_from_db("sqlite://dumy.sqlite", include_tables=['tb1','tb2']) + """ + if not include_tables: + maker = ModelMaker(schema=db_schema, autoload=True) + else: + maker = ModelMaker(schema=db_schema, include_tables=include_tables) + + maker.build() + maker.save() + +if '__main__' == __name__: + import sys + + if 'test' == sys.argv[1]: + import doctest + doctest.testmod() + else: + gen_models_from_db(sys.argv[1])