diff --git a/VERSION b/VERSION index 4b269dd8..b8cb256d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-07-15 10:02:59) dev +Version 2.00.0 (2012-07-15 17:28:41) dev diff --git a/applications/admin/controllers/default.py b/applications/admin/controllers/default.py index 53a25b8a..3f040c44 100644 --- a/applications/admin/controllers/default.py +++ b/applications/admin/controllers/default.py @@ -8,11 +8,15 @@ if EXPERIMENTAL_STUFF: response.view = response.view.replace('default/','default.mobile/') response.menu = [] +import re from gluon.admin import * from gluon.fileutils import abspath, read_file, write_file from glob import glob import shutil import platform +from gluon.languages import (regex_language, read_possible_languages, + lang_sampling, read_dict, write_dict) + if DEMO_MODE and request.function in ['change_password','pack','pack_plugin','upgrade_web2py','uninstall','cleanup','compile_app','remove_compiled_app','delete','delete_plugin','create_file','upload_file','update_languages','reload_routes']: session.flash = T('disabled in demo mode') @@ -186,14 +190,14 @@ def site(): elif file_or_appurl and request.vars.filename: # fetch an application via URL or file upload f = None - if request.vars.appurl is not '': + if request.vars.appurl: try: f = urllib.urlopen(request.vars.appurl) except Exception, e: session.flash = DIV(T('Unable to download app because:'),PRE(str(e))) redirect(URL(r=request)) fname = request.vars.appurl - elif request.vars.file is not '': + elif request.vars.file: f = request.vars.file.file fname = request.vars.file.filename @@ -247,7 +251,7 @@ def report_progress(app): counter += int(m[1]) events.append([days,counter]) return events - + def pack(): app = get_app() @@ -357,7 +361,7 @@ def delete(): sender = sender[0] if 'nodelete' in request.vars: - redirect(URL(sender)) + redirect(URL(sender, anchor=request.vars.id)) elif 'delete' in request.vars: try: full_path = apath(filename, r=request) @@ -369,7 +373,7 @@ def delete(): except Exception: session.flash = T('unable to delete file "%(filename)s"', dict(filename=filename)) - redirect(URL(sender)) + redirect(URL(sender, anchor=request.vars.id2)) return dict(filename=filename, sender=sender) def enable(): @@ -384,22 +388,25 @@ def enable(): def peek(): """ Visualize object code """ - app = get_app() + app = get_app(request.vars.app) filename = '/'.join(request.args) + if request.vars.app: + path = abspath(filename, gluon=False) + else: + path = apath(filename, r=request) try: - data = safe_read(apath(filename, r=request)).replace('\r','') + data = safe_read(path).replace('\r','') except IOError: session.flash = T('file does not exist') redirect(URL('site')) extension = filename[filename.rfind('.') + 1:].lower() - return dict(app=request.args[0], + return dict(app=app, filename=filename, data=data, extension=extension) - def test(): """ Execute controller tests """ app = get_app() @@ -428,14 +435,18 @@ def search(): files2 = glob(os.path.join(path,'*/*.html')) files3 = glob(os.path.join(path,'*/*/*.html')) files=[x[len(path)+1:].replace('\\','/') for x in files1+files2+files3 if match(x,keywords)] - return response.json({'files':files}) + return response.json(dict(files=files, message=T('@Searching: **%s** %%{file}', len(files)))) def edit(): """ File edit handler """ # Load json only if it is ajax edited... - app = get_app() + app = get_app(request.vars.app) filename = '/'.join(request.args) - # Try to discover the file type + if request.vars.app: + path = abspath(filename) + else: + path = apath(filename, r=request) + # Try to discover the file type if filename[-3:] == '.py': filetype = 'python' elif filename[-5:] == '.html': @@ -450,9 +461,6 @@ def edit(): filetype = 'html' # ## check if file is not there - - path = apath(filename, r=request) - if ('revert' in request.vars) and os.path.exists(path + '.bak'): try: data = safe_read(path + '.bak') @@ -672,9 +680,13 @@ def edit_language(): """ Edit language file """ app = get_app() filename = '/'.join(request.args) - from gluon.languages import read_dict, write_dict strings = read_dict(apath(filename, r=request)) - keys = sorted(strings.keys(),lambda x,y: cmp(x.lower(), y.lower())) + + if '__corrupted__' in strings: + form = SPAN(strings['__corrupted__'],_class='error') + return dict(filename=filename, form=form) + + keys = sorted(strings.keys(),lambda x,y: cmp(unicode(x,'utf-8').lower(), unicode(y,'utf-8').lower())) rows = [] rows.append(H2(T('Original/Translation'))) @@ -711,6 +723,54 @@ def edit_language(): return dict(app=request.args[0], filename=filename, form=form) +def edit_plurals(): + """ Edit plurals file """ + #import ipdb; ipdb.set_trace() + app = get_app() + filename = '/'.join(request.args) + plurals = read_plural_dict(apath(filename, r=request)) # plural forms dictionary + nplurals = int(request.vars.nplurals)-1 # plural forms quantity + xnplurals = xrange(nplurals) + + if '__corrupted__' in plurals: + # show error message and exit + form = SPAN(plurals['__corrupted__'],_class='error') + return dict(filename=filename, form=form) + + keys = sorted(plurals.keys(),lambda x,y: cmp(unicode(x,'utf-8').lower(), unicode(y,'utf-8').lower())) + rows = [] + + row=[T("Singular Form")] + row.extend([T("Plural Form #%s", n+1) for n in xnplurals]) + table=TABLE(THEAD(TR(row))) + + for key in keys: + name = md5_hash(key) + forms = plurals[key] + + if len(forms) < nplurals: + forms.extend(None for i in xrange(nplurals-len(forms))) + + row = [B(key)] + row.extend([INPUT(_type='text', _name=name+'_'+str(n), value=forms[n], _size=20) for n in xnplurals]) + row.append(TD(TAG.BUTTON(T('delete'), _onclick='return delkey("%s")' % name))) + rows.append(TR(row, _id=name)) + if rows: + table.append(TBODY(rows)) + rows=[table, INPUT(_type='submit', _value=T('update'))] + form = FORM(*rows) + if form.accepts(request.vars, keepvalues=True): + new_plurals = dict() + for key in keys: + name = md5_hash(key) + if form.vars[name+'_0']==chr(127): continue + new_plurals[key] = [form.vars[name+'_'+str(n)] for n in xnplurals] + write_plural_dict(apath(filename, r=request), new_plurals) + session.flash = T('file saved on %(time)s', dict(time=time.ctime())) + redirect(URL(r=request, args=request.args, vars=dict(nplurals=request.vars.nplurals))) + return dict(app=request.args[0], filename=filename, form=form) + + def about(): """ Read about info """ app = get_app() @@ -794,8 +854,34 @@ def design(): statics.sort() # Get all languages - languages = listdir(apath('%s/languages/' % app, r=request), '[\w-]*\.py') - + all_languages = dict([(lang+'.py',info[0]) for lang,info + in read_possible_languages(apath(app, r=request)).iteritems() + if info[2]!=0]) # info[2] is langfile_mtime: + # get only existing files + languages = sorted(all_languages) + + plural_rules = {} + try: + all_plurals = read_possible_plurals() + except: + all_plurals = {} # not implemented yet + for langfile,lang in all_languages.iteritems(): + lang=lang.strip() + match_language = regex_language.match(lang) + if match_language: + match_language = tuple(part + for part in match_language.groups() + if part) + plang = lang_sampling(match_language, all_plurals.keys()) + if plang: + plural=all_plurals[plang] + plural_rules[langfile]=(plural[0],plang,plural[1],plural[3]) + else: + plural_rules[langfile]=(0,lang,'plural_rules-%s.py'%lang,'') + + plurals = listdir(apath('%s/languages/' % app, r=request), + '^plural-[\w-]+\.py$') + #Get crontab cronfolder = apath('%s/cron' % app, r=request) if not os.path.exists(cronfolder): os.mkdir(cronfolder) @@ -821,6 +907,8 @@ def design(): include=include, statics=filter_plugins(statics,plugins), languages=languages, + plurals=plurals, + plural_rules=plural_rules, crontab=crontab, plugins=plugins) @@ -830,7 +918,7 @@ def delete_plugin(): plugin = request.args(1) plugin_name='plugin_'+plugin if 'nodelete' in request.vars: - redirect(URL('design',args=app)) + redirect(URL('design', args=app, anchor=request.vars.id)) elif 'delete' in request.vars: try: for folder in ['models','views','controllers','static','modules']: @@ -847,7 +935,7 @@ def delete_plugin(): except Exception: session.flash = T('unable to delete file plugin "%(plugin)s"', dict(plugin=plugin)) - redirect(URL('design',args=request.args(0))) + redirect(URL('design', args=request.args(0), anchor=request.vars.id2)) return dict(plugin=plugin) def plugin(): @@ -940,24 +1028,56 @@ def plugin(): def create_file(): """ Create files handler """ try: - app = get_app(name=request.vars.location.split('/')[0]) - path = apath(request.vars.location, r=request) + anchor='#'+request.vars.id if request.vars.id else '' + if request.vars.app: + app = get_app(request.vars.app) + path = abspath(request.vars.location, gluon=False) + else: + app = get_app(name=request.vars.location.split('/')[0]) + path = apath(request.vars.location, r=request) filename = re.sub('[^\w./-]+', '_', request.vars.filename) + if path[-7:] == '/rules/': + # Handle plural rules files + if len(filename) == 0: + raise SyntaxError + if not filename[-3:] == '.py': + filename += '.py' + lang = re.match('^plural_rules-(.*)\.py$',filename).group(1) + langinfo = read_possible_languages(apath(app, r=request))[lang] + text = dedent(""" + #!/usr/bin/env python + # -*- coding: utf8 -*- + # Plural-Forms for %(lang)s (%(langname)s) - if path[-11:] == '/languages/': + nplurals=2 # for example, English language has 2 forms: + # 1 singular and 1 plural + + # Determine plural_id for number *n* as sequence of positive + # integers: 0,1,... + # NOTE! For singular form ALWAYS return plural_id = 0 + get_plural_id = lambda n: int(n != 1) + + # Construct and return plural form of *word* using + # *plural_id* (which ALWAYS>0). This function will be executed + # for words (or phrases) not found in plural_dict dictionary. + # By default this function simply returns word in singular: + construct_plural_form = lambda word, plural_id: word + """)[1:] % dict(lang=langinfo[0], langname=langinfo[1]) + + elif path[-11:] == '/languages/': # Handle language files if len(filename) == 0: raise SyntaxError if not filename[-3:] == '.py': filename += '.py' - app = path.split('/')[-3] path=os.path.join(apath(app, r=request),'languages',filename) if not os.path.exists(path): safe_write(path, '') + # create language xx[-yy].py file: findT(apath(app, r=request), filename[:-3]) session.flash = T('language file "%(filename)s" created/updated', - dict(filename=filename)) - redirect(request.vars.sender) + dict(filename=filename)) + redirect(request.vars.sender+anchor) elif path[-8:] == '/models/': # Handle python models @@ -1024,7 +1144,7 @@ def create_file(): filename = 'plugin_%s/%s' % (request.vars.plugin, filename) text = '' else: - redirect(request.vars.sender) + redirect(request.vars.sender+anchor) full_filename = os.path.join(path, filename) dirpath = os.path.dirname(full_filename) @@ -1039,18 +1159,20 @@ def create_file(): log_progress(app,'CREATE',filename) session.flash = T('file "%(filename)s" created', dict(filename=full_filename[len(path):])) + vars={} + if request.vars.id: vars['id']=request.vars.id + if request.vars.app: vars['app']=request.vars.app redirect(URL('edit', - args=[os.path.join(request.vars.location, filename)])) + args=[os.path.join(request.vars.location, filename)], vars=vars)) except Exception, e: if not isinstance(e,HTTP): session.flash = T('cannot create file') - redirect(request.vars.sender) + redirect(request.vars.sender+anchor) def upload_file(): """ File uploading handler """ - try: filename = None app = get_app(name=request.vars.location.split('/')[0]) @@ -1123,7 +1245,7 @@ def errors(): hash2error = dict() - for fn in listdir(errors_path, '^\w.*'): + for fn in listdir(errors_path, '^[a-fA-F0-9.\-]+$'): fullpath = os.path.join(errors_path, fn) if not os.path.isfile(fullpath): continue try: diff --git a/applications/admin/models/menu.py b/applications/admin/models/menu.py index dc28e7f8..0ec6a810 100644 --- a/applications/admin/models/menu.py +++ b/applications/admin/models/menu.py @@ -9,12 +9,12 @@ response.title = '%s %s' % (_f, '/'.join(request.args)) response.subtitle = 'admin' response.menu = [(T('Site'), _f == 'site', URL(_a,'default','site'))] -if request.args: - _t = request.args[0] +if request.vars.app or request.args: + _t = request.vars.app or request.args[0] response.menu.append((T('Edit'), _c == 'default' and _f == 'design', URL(_a,'default','design',args=_t))) response.menu.append((T('About'), _c == 'default' and _f == 'about', - URL(_a,'default','about',args=_t))) + URL(_a,'default','about',args=_t,))) response.menu.append((T('Errors'), _c == 'default' and _f == 'errors', URL(_a,'default','errors',args=_t))) response.menu.append((T('Versioning'), diff --git a/applications/admin/static/css/styles.css b/applications/admin/static/css/styles.css index 08a82d72..f75bdfe1 100644 --- a/applications/admin/static/css/styles.css +++ b/applications/admin/static/css/styles.css @@ -1245,3 +1245,6 @@ color: #222; } .ie9 #query_panel {padding-bottom:2px;} +.error, .error a {color:red} +.pluralsform thead td {font-weight:bold; font-size:1.2em; padding-bottom:5px} +.pluralsform td {padding-left:5px} diff --git a/applications/admin/views/default/design.html b/applications/admin/views/default/design.html index c24bc5af..74b3b0b7 100644 --- a/applications/admin/views/default/design.html +++ b/applications/admin/views/default/design.html @@ -2,36 +2,43 @@ {{ def all(items): return reduce(lambda a,b:a and b,items,True) -def peekfile(path,file): - return A(file.replace('\\\\','/'),_href=URL('peek', args=(app, path, file))) -def editfile(path,file): - return A(SPAN(T('Edit')),_class='button editbutton',_href=URL('edit', args=(app, path, file))) +def peekfile(path,file,vars={},title=None): + args=(path,file) if 'app' in vars else (app,path,file) + return A(file.replace('\\\\','/'),_title=title,_href=URL('peek', args=args, vars=vars)) +def editfile(path,file,vars={}): + args=(path,file) if 'app' in vars else (app,path,file) + return A(SPAN(T('Edit')),_class='button editbutton',_href=URL('edit', args=args, vars=vars)) def testfile(path,file): return A(TAG[''](IMG(_src=URL('static', 'images/test_icon.png'), _alt=T('test')), SPAN(T("Run tests in this file (to run all files, you may also use the button labelled 'test')"))), _class='icon test tooltip',_href=URL('test', args=(app, file))) -def editlanguagefile(path,file): - return A(SPAN(T('Edit')),_class='button editbutton',_href=URL('edit_language', args=(app, path, file))) -def file_upload_form(location): +def editlanguagefile(path,file,vars={}): + return A(SPAN(T('Edit')),_class='button editbutton',_href=URL('edit_language', args=(app, path, file), vars=vars)) +def editpluralsfile(path,file,vars={}): + return A(SPAN(T('Edit')),_class='button editbutton',_href=URL('edit_plurals', args=(app, path, file), vars=vars)) +def file_upload_form(location, anchor=None): form=FORM(T("upload file:")," ", INPUT(_type="file",_name="file")," ",T("and rename it:")," ", INPUT(_type="text",_name="filename",requires=IS_NOT_EMPTY), INPUT(_type="hidden",_name="location",_value=location), - INPUT(_type="hidden",_name="sender",_value=URL('design',args=app)), + INPUT(_type="hidden",_name="sender",_value=URL('design',args=app, anchor=anchor)), INPUT(_type="submit",_value=T("upload")),_action=URL('upload_file')) return form -def file_create_form(location): +def file_create_form(location, anchor=None): form=FORM(T("create file with filename:")," ", INPUT(_type="text",_name="filename",requires=IS_NOT_EMPTY), INPUT(_type="hidden",_name="location",_value=location), INPUT(_type="hidden",_name="sender",_value=URL('design',args=app)), + INPUT(_type="hidden",_name="id",_value=anchor), INPUT(_type="submit",_value=T("Create")),_action=URL('create_file')) return form -def upload_plugin_form(app): +def upload_plugin_form(app, anchor=None): form=FORM(T("upload plugin file:")," ", INPUT(_type="file",_name="pluginfile"), + INPUT(_type="hidden",_name="id",_value=anchor), INPUT(_type="submit",_value=T("upload"))) return form -def deletefile(arglist): - return A(TAG[''](IMG(_src=URL('static', 'images/delete_icon.png')), SPAN(T('Delete this file (you will be asked to confirm deletion)'))), _class='icon delete tooltip', _href=URL('delete',args=arglist,vars=dict(sender=request.function+'/'+app))) +def deletefile(arglist, vars={}): + vars.update({'sender':request.function+'/'+app}) + return A(TAG[''](IMG(_src=URL('static', 'images/delete_icon.png')), SPAN(T('Delete this file (you will be asked to confirm deletion)'))), _class='icon delete tooltip', _href=URL('delete',args=arglist,vars=vars)) }} {{block sectionclass}}design{{end}} @@ -73,13 +80,14 @@ def deletefile(arglist): -
{{=file_create_form('%s/models/' % app)}}
+
{{=file_create_form('%s/models/' % app, 'models')}}
@@ -112,14 +120,15 @@ for c in controllers: controller_functions+=[c[:-3]+'/%s.html'%x for x in functi {{pass}} -
{{=file_create_form('%s/controllers/' % app)}}
+
{{=file_create_form('%s/controllers/' % app, 'controllers')}}
@@ -143,13 +152,14 @@ for c in controllers: controller_functions+=[c[:-3]+'/%s.html'%x for x in functi {{if not views:}}

{{=T("There are no views")}}

{{pass}} -
{{=file_create_form('%s/views/' % app)}}
+
{{=file_create_form('%s/views/' % app, 'views')}}
@@ -172,20 +182,60 @@ for c in controllers: controller_functions+=[c[:-3]+'/%s.html'%x for x in functi {{=button(URL('update_languages/'+app), T('update all languages'))}} {{if not languages:}}

{{=T("There are no translators, only default language is supported")}}

{{pass}} - -
{{=file_create_form('%s/static/' % app)}} - {{=file_upload_form('%s/static/' % app)}}
+
{{=file_create_form('%s/static/' % app, 'static')}} + {{=file_upload_form('%s/static/' % app, 'static')}}
@@ -250,19 +300,22 @@ for c in controllers: controller_functions+=[c[:-3]+'/%s.html'%x for x in functi {{if not modules:}}

{{=T("There are no modules")}}

{{pass}} -
{{=file_create_form('%s/modules/' % app)}} - {{=file_upload_form('%s/modules/' % app)}}
+
{{=file_create_form('%s/modules/' % app, 'modules')}} + {{=file_upload_form('%s/modules/' % app, 'modules')}}
@@ -280,15 +333,16 @@ for c in controllers: controller_functions+=[c[:-3]+'/%s.html'%x for x in functi {{if plugins:}} {{else:}}

{{=T('There are no plugins')}}

{{pass}} -
{{=upload_plugin_form(app)}}
+
{{=upload_plugin_form(app, 'plugins')}}
@@ -299,11 +353,12 @@ jQuery(document).ready(function(){ if(code==13 && jQuery('#search').val()){ jQuery.getJSON('{{=URL('search',args=request.args)}}?keywords='+escape(jQuery('#search').val()),null,function(data, textStatus, xhr){ jQuery('.component_contents li, .formfield, .comptools').hide(); - files=data['files']; + files=data['files']; + message=data['message']; for(var i=0; i{{=T("Peeking at file")}} "{{=filename}}"

-{{=button(URL('design',args=request.args[0]), T('back'))}} -{{=button(URL('edit',args=request.args), T('Edit'))}} +{{=button(URL('design',args=request.vars.app if request.vars.app else request.args[0], anchor=request.vars.id), T('back'))}} +{{=button(URL('edit',args=request.args, vars=request.vars), T('Edit'))}}

{{ diff --git a/applications/admin/views/default/site.html b/applications/admin/views/default/site.html index a31cc758..6a54cb5a 100644 --- a/applications/admin/views/default/site.html +++ b/applications/admin/views/default/site.html @@ -58,7 +58,7 @@ {{if is_manager():}}
-

{{="Version %s.%s.%s (%s) %s" % myversion}}

+

{{=T("Version %s.%s.%s (%s) %s", myversion)}}

{{if session.check_version:}}

{{=T('Checking for upgrades...')}} diff --git a/applications/welcome/views/default/index.html b/applications/welcome/views/default/index.html index a947c48e..33d02b26 100644 --- a/applications/welcome/views/default/index.html +++ b/applications/welcome/views/default/index.html @@ -7,11 +7,14 @@

{{=T('How did you get here?')}}

  1. {{=T('You are successfully running web2py')}}
  2. -
  3. {{=T('You visited the url')}} {{=A(request.env.path_info,_href=request.env.path_info)}}
  4. -
  5. {{=T('Which called the function')}} {{=A(request.function+'()',_href='#')}} {{=T('located in the file')}} - {{=A('web2py/applications/%(application)s/controllers/%(controller)s.py'%request,_href=URL('admin','default','peek',args=(request.application,'controllers',request.controller+'.py')))}}
  6. -
  7. {{=T('The output of the file is a dictionary that was rendered by the view')}} - {{=A('web2py/applications/%(application)s/views/%(controller)s/index.html'%request,_href=URL('admin','default','peek',args=(request.application,'views',request.controller,'index.html')))}}
  8. +
  9. {{=T('You visited the url %s', A(request.env.path_info,_href=request.env.path_info))}}
  10. +
  11. {{=T('Which called the function %s located in the file %s', + (A(request.function+'()',_href='#'), + A('web2py/applications/%(application)s/controllers/%(controller)s.py'%request, + _href=URL('admin','default','peek', args=(request.application,'controllers',request.controller+'.py')))))}}
  12. +
  13. {{=T('The output of the file is a dictionary that was rendered by the view %s', + A('web2py/applications/%(application)s/views/%(controller)s/index.html'%request, + _href=URL('admin','default','peek',args=(request.application,'views',request.controller,'index.html'))))}}
  14. {{=T('You can modify this application and adapt it to your needs')}}
diff --git a/gluon/languages.py b/gluon/languages.py index 09670e4d..9b1ee1c0 100644 --- a/gluon/languages.py +++ b/gluon/languages.py @@ -20,6 +20,11 @@ from cfs import getcfs from thread import allocate_lock from html import XML, xmlescape +try: + from gluon.html import MARKMIN +except ImportError: + MARKMIN = None + __all__ = ['translator', 'findT', 'update_all_languages'] is_gae = settings.global_settings.web2py_runtime_gae