Index: applications/mobileblur/controllers/default.py ================================================================== --- applications/mobileblur/controllers/default.py +++ applications/mobileblur/controllers/default.py @@ -21,11 +21,18 @@ if login_form.accepts(request): try: results = newsblur.login(login_form.vars["username"], login_form.vars["password"]) response.cookies["nb_cookie"] = newsblur.cookies["newsblur_sessionid"] response.cookies["nb_cookie"]["path"] = "/" - print "cookie =", newsblur.cookies redirect(URL("index")) except Exception as ex: login_form.insert(-1, ex.message) + login_form._class = "alert-message block-message error" return dict(login_form=login_form) + + +def logout(): + response.cookies["nb_cookie"] = "" + response.cookies["nb_cookie"]["expires"] = -10 + response.cookies["nb_cookie"]["path"] = "/" + redirect(URL("index")) Index: applications/mobileblur/controllers/feeds.py ================================================================== --- applications/mobileblur/controllers/feeds.py +++ applications/mobileblur/controllers/feeds.py @@ -1,13 +1,32 @@ # -*- coding: utf-8 -*- from pprint import pprint +import time def view(): - stories = newsblur.feed(request.args[0])["stories"] - feeds = newsblur.feeds(flat=True)["feeds"] - feed = [feed for feed in feeds.itervalues() if feed["id"]==int(request.args[0])][0] + print "" + s = time.time() + feed = newsblur.feed(request.args[0]) + stories = feed["stories"] + print time.time() - s + + print feed.keys() + + if not feed.has_key("feed_title"): + s = time.time() + feeds = newsblur.feeds(flat=True)["feeds"] + print time.time() - s + + s = time.time() + feed = [feed for feed in feeds.itervalues() if feed["id"]==int(request.args[0])][0] + print time.time() - s + + response.title = feed["feed_title"] + return dict(stories=stories, feed=feed) + def mark_read(): - newsblur.mark_feed_as_read(request.vars["feed"]) + if len(request.args) > 0: + newsblur.mark_feed_as_read(request.args[0]) redirect(URL("default", "index")) Index: applications/mobileblur/controllers/stories.py ================================================================== --- applications/mobileblur/controllers/stories.py +++ applications/mobileblur/controllers/stories.py @@ -3,6 +3,10 @@ from pprint import pprint def view(): stories = newsblur.feed(request.vars["feed_id"])["stories"] story = [story for story in stories if story["id"]==request.vars["story"]][0] - return dict(story=story) + return dict(story=story, feed_id=request.vars["feed_id"]) + +def mark_read(): + results = newsblur.mark_story_as_read(request.vars["story_id"], request.vars["feed_id"]) + redirect(URL("default", "index")) Index: applications/mobileblur/models/0_helpers.py ================================================================== --- applications/mobileblur/models/0_helpers.py +++ applications/mobileblur/models/0_helpers.py @@ -2,11 +2,10 @@ newsblur = newsblur.NewsBlur() threshold = 0 thresholds = ["nt", "ps", "ng"] # indices -1, 0, 1 for negative, neutral, and positive intelligence filters -print request.cookies if [request.application, request.controller, request.function] != [request.application, "default", "login"]: if "nb_cookie" not in request.cookies.keys(): redirect(URL("default", "login")) else: newsblur.cookies["newsblur_sessionid"] = request.cookies["nb_cookie"].value Index: applications/mobileblur/modules/newsblur.py ================================================================== --- applications/mobileblur/modules/newsblur.py +++ applications/mobileblur/modules/newsblur.py @@ -21,12 +21,10 @@ Required parameters, username and password, must be of string type. ''' url = nb_url + 'api/login' results = requests.post(url, data={"username": username, "password": password}) - print "results.cookies =", results.cookies - print type(results.cookies) self.cookies = results.cookies results = simplejson.loads(results.content) if results["authenticated"] is False: raise Exception("The newsblur credentials you provided are invalid") return results Index: applications/mobileblur/static/css/base.css ================================================================== --- applications/mobileblur/static/css/base.css +++ applications/mobileblur/static/css/base.css @@ -25,17 +25,17 @@ - form and table padding - code blocks - left and right padding to quoted text - page layout alignment, width and padding (change this for spaces) - column widths (change this to use left_sidebar and right_sidebar) -- backrgound images and colors (change this for colors) +- background images and colors (change this for colors) - web2py specific (.flash, .error) Notice: - even if you use a different layout/css you may need classes .flash and .error - this is all color neutral except for #349C01 (header, links, lines) -- there are two backrgound images: images/background.png and images/header.png +- there are two background images: images/background.png and images/header.png License: This file is released under BSD and MIT */ @@ -143,13 +143,14 @@ /* always force a scrollbar in non-IE */ html { overflow-y: scroll; } /* Accessible focus treatment: people.opera.com/patrickl/experiments/keyboard/test */ a:hover, a:active { outline: none; } - +/* a, a:active, a:visited { color:#607890; } a:hover { color:#036; } +*/ ul, ol { margin-left: 1.8em; } ol { list-style-type: decimal; } /* Remove margins for navigation lists */ @@ -327,63 +328,18 @@ fieldset { border: 1px solid #dedede; padding: 6px; } legend { font-weight: bold; } input:focus, textarea:focus { background: #fafafa; } -p {text-indent:30px;} - -p, blockquote { - margin-bottom: 10px; -} - h1,h2,h3,h4,h5,h6 { line-height: 170%; } h1 {font-size: 2.0em;} h2 {font-size: 1.8em;} h3 {font-size: 1.4em;} h4 {font-size: 1.2em;} h5 {font-size: 1.0em;} h6 {font-size: 0.8em;} -/*********** page layout alignment, width and padding ***********/ -/*body {background-color: #000;}*/ -#container, #header, #page, #content, #statusbar, -#footer, #wrapper { display:block; line-height: 170%; } -#wrapper {width: 900px;} -#container { - margin: 0 auto; - padding: 0; -} -#wrapper {margin: 0 auto;} -#wrapper {background-color: #fff; padding: 5px;} -#statusbar { margin: 5px 0px 20px 0px;} -#footer { - margin-top: 30px; - padding: 5px; -} -#statusbar, #footer { - background: #eaeaea; - border-top: 1px #aaa solid; -} -#logo { - width: 68px; - height: 62px; - background: url(../images/logo.png); -} -#appname { - color: #cccccc; -} - -#right_sidebar { width: 160px; float:right; display: none; } -#left_sidebar { width: 160px; float:left; display: none; } -#content { float: left; /*width: 740px;*//*width: 63%;*/ /*width: 640px; float:left;*/ } /* uncomment this if you are going to use sidebars */ - -.auth_navbar { - top: 0px; - float: right; - padding: 3px 10px 3px 10px; -} - /*********** web2py specific ***********/ div.flash { font-weight: bold; display: none; position: fixed; @@ -533,10 +489,15 @@ /* Uncomment if you don't want iOS and WinMobile to mobile-optimize the text for you j.mp/textsizeadjust html { -webkit-text-size-adjust:none; -ms-text-size-adjust:none; } */ } +@media handheld { + body { + } +} + /* * print styles * inlined to avoid required HTTP connection www.phpied.com/delay-loading-your-print-css/ */ @@ -552,5 +513,82 @@ @page { margin: 0.5cm; } p, h2, h3 { orphans: 3; widows: 3; } h2, h3{ page-break-after: avoid; } } +body { +/* background-color: #f2c84b;*/ + color: #493F3E +} + +header { + background-color: #95392E; + padding: 1%; + border-bottom: 1px solid #95392E; + color: white; +} +header > h1 > a { + text-decoration: none; + color: white; + font-weight: bold; +} + +#story > header > a { + color: white; +} + +header > a> h2 { +/* border-top: 1px solid white;*/ + color: white; +} + +#story-list { +/* background-color: #f2c84b;*/ +} + +#story-list > .story > a, .feed > a { + text-decoration: none; + font-weight: bold; + margin-top: 0; +} +#story-list > .story > p { + font-size: .75em; +} + +#story-list > .story > .read { + color: #D99B48; +} +#story-list > .story > .unread, .feed > a { + color: #C57E3C; +} + +#story-list > .story, .feed { + padding: .5%; + border-top: 1px solid #95392E; + margin-top: .33%; +} + +span.ps, span.nt, span.ng { + padding: .05em; + border-radius: 5px; +} +span.ps { + background-color: #ABAA7A; +} +span.nt { + background-color: #D99B48; +} +span.ng { + background-color: red; +} + +footer { + background-color: #95392E; + padding: 1%; + border-top: 1px solid #95392E; + color: white; +} +footer > a { + text-decoration: none; + color: white; + font-weight: bold; +} Index: applications/mobileblur/views/default/index.html ================================================================== --- applications/mobileblur/views/default/index.html +++ applications/mobileblur/views/default/index.html @@ -1,12 +1,10 @@ -{{left_sidebar_enabled=right_sidebar_enabled=False}} {{extend 'layout.html'}} {{ for feed in feeds.itervalues(): }} - {{ if threshold == -1 and feed["ng"] > 0: }} <span class='ng'>[ {{= feed["ng"] }} ]</span> {{ pass }} - {{ if threshold <= 0 and feed["nt"] > 0: }} <span class='nt'>[ {{= feed["nt"] }} ]</span> {{ pass }} - {{if feed["ps"] > 0: }}<span class='ps'>[ {{= feed["ps"] }} ]</span> {{ pass }} - <a href="{{= URL(c="feeds", f="view", args=[feed["id"]]) }}">{{= feed["feed_title"] }}</a><br /> + <div class="feed"> + {{ if threshold == -1 and feed["ng"] > 0: }} <span class='ng'>{{= feed["ng"] }}</span> {{ pass }} + {{ if threshold <= 0 and feed["nt"] > 0: }} <span class='nt'>{{= feed["nt"] }}</span> {{ pass }} + {{if feed["ps"] > 0: }}<span class='ps'>{{= feed["ps"] }}</span> {{ pass }} + <a href="{{= URL(c="feeds", f="view", args=[feed["id"]]) }}">{{= feed["feed_title"] }}</a><br /> + </div> {{ pass }} - -{{block left_sidebar}}New Left Sidebar Content{{end}} -{{block right_sidebar}}New Right Sidebar Content{{end}} Index: applications/mobileblur/views/feeds/view.html ================================================================== --- applications/mobileblur/views/feeds/view.html +++ applications/mobileblur/views/feeds/view.html @@ -1,12 +1,28 @@ -{{left_sidebar_enabled=right_sidebar_enabled=False}} {{extend 'layout.html'}} -<h1>{{= feed["feed_title"] }}</h1> -<a href="{{= URL("mark_read", vars=dict(feed=feed["id"])) }}"> Mark feed as read</a> - -{{ for story in stories: }} - <a href="{{= URL(c="stories", f="view", vars=dict(story=story["id"], feed_id=feed["id"])) }}"><h2>{{= story["story_title"] }}</h2></a> -{{ pass }} - -{{block left_sidebar}}New Left Sidebar Content{{end}} -{{block right_sidebar}}New Right Sidebar Content{{end}} +{{ block header_bonus }} + <h2>{{= feed["feed_title"] }}</h2> + <a href="{{= URL("mark_read", args=[feed["id"]]) }}" class="button">Mark feed as read</a> +{{ end }} + +<section id="story-list"> +{{ +import time +s = time.time() +}} + {{ + for story in stories: + if sum([v for k,v in story["intelligence"].iteritems()]) < threshold: + continue + }} + + {{ print story["read_status"] }} + <div class="story"> + <a href="{{= URL(c="stories", f="view", vars=dict(story=story["id"], feed_id=feed["id"])) }}" class="{{= "unread" if story["read_status"] == 0 else "read" }}"> + {{= story["story_title"] }} + </a> + <p>{{= story["story_date"] }}</p> + </div> + {{ pass }} +{{ print time.time() - s }} +</section> Index: applications/mobileblur/views/layout.html ================================================================== --- applications/mobileblur/views/layout.html +++ applications/mobileblur/views/layout.html @@ -8,11 +8,11 @@ <!-- Always force latest IE rendering engine (even in intranet) & Chrome Frame Remove this if you use the .htaccess --> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> - <title>{{=response.title or request.application}}</title> + <title>{{ block title }}{{= " - ".join([response.title, request.application]) }}{{ end }}</title> <!-- http://dev.w3.org/html5/markup/meta.name.html --> <meta name="application-name" content="{{=request.application}}" /> <!-- Speaking of Google, don't forget to set your site up: @@ -23,11 +23,11 @@ j.mp/mobileviewport & davidbcalhoun.com/2010/viewport-metatag device-width: Occupy full width of the screen in its current orientation initial-scale = 1.0 retains dimensions instead of zooming out if page height > device height maximum-scale = 1.0 retains dimensions instead of zooming in if page width < device width --> - <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0"> + <meta name="viewport" content="width=320" /> <!-- Place favicon.ico and apple-touch-icon.png in the root of your domain and delete these references --> <link rel="shortcut icon" href="{{=URL('static','favicon.ico')}}" type="image/x-icon"> <link rel="apple-touch-icon" href="{{=URL('static','favicon.png')}}"> @@ -57,10 +57,12 @@ else: left_sidebar_style = 'style="display: none;"' if right_sidebar_enabled: right_sidebar_style = 'style="display: block;"' else: right_sidebar_style = 'style="display: none;"' style_content = 'style="width: %s"' % width_content }} +<!-- <link rel="stylesheet" href="http://twitter.github.com/bootstrap/1.4.0/bootstrap.min.css">--> + <link rel="stylesheet" href="{{= URL("static", "css/1140.css") }}"> </head> <!-- paulirish.com/2008/conditional-stylesheets-vs-css-hacks-answer-neither/ --> <!--[if lt IE 7 ]> <body class="ie6"> <![endif]--> <!--[if IE 7 ]> <body class="ie7"> <![endif]--> @@ -68,92 +70,39 @@ <!--[if IE 9 ]> <body class="ie9"> <![endif]--> <!--[if (gt IE 9)|!(IE)]><!--> <body> <!--<![endif]--> <div class="flash">{{=response.flash or ''}}</div> <!-- notification div --> - <div id="container"> - - <div id="wrapper"> - - <div id="header"> <!-- header and login nav --> - {{block header}} <!-- this is default header --> - {{try:}}{{=auth.navbar(action=URL('default','user'))}}{{except:pass}} - <h1><span id="appname">{{=request.application.capitalize()}}</span>App</h1> - <div style="clear: both;"></div><!-- Clear the divs --> - {{end}} - </div><!-- header --> - - <div id="statusbar"><!-- statusbar is menu zone --> - {{block statusbar}} <!-- this is default statusbar --> - {{#------ superfish menu ------}} - {{=MENU(response.menu,_class='sf-menu')}} - <script type="text/javascript"> - jQuery(document).ready(function(){ - jQuery('ul.sf-menu').superfish();}); - </script> - <div style="clear: both;"></div><!-- Clear the divs --> - {{end}} - </div><!-- statusbar --> - - <div id="page"> <!-- Here my central body --> - - {{if left_sidebar_enabled:}} - <div id="left_sidebar" {{=XML(left_sidebar_style)}} > - <div style="padding: 4px;"> - {{block left_sidebar}}Content Left Sidebar{{end}} - </div> - </div><!-- left_sidebar --> - {{pass}} - - <!-- content --> - <div id="content" {{=XML(style_content)}} > - {{include}} - </div> - <!-- content --> - - {{if right_sidebar_enabled:}} - <div id="right_sidebar" {{=XML(right_sidebar_style)}} > - <div style="padding: 4px;"> - {{block right_sidebar}}Content Right Sidebar{{end}} - </div> - </div><!-- right_sidebar --> - {{pass}} - - - <div style="clear: both;"></div><!-- Clear the divs --> - - </div><!-- page --> - - <div id="footer"> - {{block footer}} <!-- this is default footer --> - <a href="http://www.web2py.com/" style="float: left; padding-right: 6px;"> - <img src="{{=URL('static','images/poweredby.png')}}"/> - </a> - {{=T('Copyright')}} © 2010 - <div style="clear: both;"></div><!-- Clear the divs --> - {{end}} - - </div><!-- footer --> - </div><!-- wrapper --> - </div><!-- container --> + <header> + {{block header}} <!-- this is default header --> + <h1><a href="{{= URL("mobileblur", "default", "index") }}">MobileBlur</a></h1> + {{end}} + {{ block header_bonus }}{{end}} + </header> + + <section id="content" class-"container" {{=XML(style_content)}} > + {{include}} + </section> + + <footer> + <a href="{{= URL("default", "logout") }}">Log out</a> + </footer> <!--[if lt IE 7 ]> <script src="{{=URL('static','js/dd_belatedpng.js')}}"></script> <script> DD_belatedPNG.fix('img, .png_bg'); //fix any <img> or .png_bg background-images </script> <![endif]--> <!-- asynchronous google analytics: mathiasbynens.be/notes/async-analytics-snippet change the UA-XXXXX-X to be your site's ID --> - <!-- <script> - var _gaq = [['_setAccount', 'UA-XXXXX-X'], ['_trackPageview']]; + var _gaq = [['_setAccount', 'UA-27222314-1'], ['_trackPageview']]; (function(d, t) { var g = d.createElement(t), s = d.getElementsByTagName(t)[0]; g.async = true; g.src = '//www.google-analytics.com/ga.js'; s.parentNode.insertBefore(g, s); })(document, 'script'); </script> - --> </body> </html> Index: applications/mobileblur/views/stories/view.html ================================================================== --- applications/mobileblur/views/stories/view.html +++ applications/mobileblur/views/stories/view.html @@ -1,9 +1,13 @@ -{{left_sidebar_enabled=right_sidebar_enabled=False}} {{extend 'layout.html'}} -<a href="{{= story["story_permalink"] }}"><h1>{{= story["story_title"] }}</h1></a> - -{{= XML(story["story_content"]) }} - -{{block left_sidebar}}New Left Sidebar Content{{end}} -{{block right_sidebar}}New Right Sidebar Content{{end}} +{{ block header_bonus }} + <a href="{{= story["story_permalink"] }}"> + <h2>{{= story["story_title"] }}</h2> + </a> + <a href="{{= URL("mark_read", vars=dict(story_id=story["id"], feed_id=feed_id)) }}" class="button"> + Mark story as read + </a> +{{ end }} +<section id="story"> + {{= XML(story["story_content"]) }} +</section> ADDED routes.py Index: routes.py ================================================================== --- /dev/null +++ routes.py @@ -0,0 +1,166 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# default_application, default_controller, default_function +# are used when the respective element is missing from the +# (possibly rewritten) incoming URL +# +default_application = 'mobileblur' # ordinarily set in base routes.py +default_controller = 'default' # ordinarily set in app-specific routes.py +default_function = 'index' # ordinarily set in app-specific routes.py + +# routes_app is a tuple of tuples. The first item in each is a regexp that will +# be used to match the incoming request URL. The second item in the tuple is +# an applicationname. This mechanism allows you to specify the use of an +# app-specific routes.py. This entry is meaningful only in the base routes.py. +# +# Example: support welcome, admin, app and myapp, with myapp the default: + + +routes_app = ((r'/(?P<app>welcome|admin|app)\b.*', r'\g<app>'), + (r'(.*)', r'myapp'), + (r'/?(.*)', r'myapp')) + +# routes_in is a tuple of tuples. The first item in each is a regexp that will +# be used to match the incoming request URL. The second item in the tuple is +# what it will be replaced with. This mechanism allows you to redirect incoming +# routes to different web2py locations +# +# Example: If you wish for your entire website to use init's static directory: +# +# routes_in=( (r'/static/(?P<file>[\w./-]+)', r'/init/static/\g<file>') ) +# + +routes_in = ((r'.*:/favicon.ico', r'/examples/static/favicon.ico'), + (r'.*:/robots.txt', r'/examples/static/robots.txt'), + ((r'.*http://otherdomain.com.* (?P<any>.*)', r'/app/ctr\g<any>'))) + +# routes_out, like routes_in translates URL paths created with the web2py URL() +# function in the same manner that route_in translates inbound URL paths. +# + +routes_out = ((r'.*http://otherdomain.com.* /app/ctr(?P<any>.*)', r'\g<any>'), + (r'/app(?P<any>.*)', r'\g<any>')) + +# Error-handling redirects all HTTP errors (status codes >= 400) to a specified +# path. If you wish to use error-handling redirects, uncomment the tuple +# below. You can customize responses by adding a tuple entry with the first +# value in 'appName/HTTPstatusCode' format. ( Only HTTP codes >= 400 are +# routed. ) and the value as a path to redirect the user to. You may also use +# '*' as a wildcard. +# +# The error handling page is also passed the error code and ticket as +# variables. Traceback information will be stored in the ticket. +# +# routes_onerror = [ +# (r'init/400', r'/init/default/login') +# ,(r'init/*', r'/init/static/fail.html') +# ,(r'*/404', r'/init/static/cantfind.html') +# ,(r'*/*', r'/init/error/index') +# ] + +# specify action in charge of error handling +# +# error_handler = dict(application='error', +# controller='default', +# function='index') + +# In the event that the error-handling page itself returns an error, web2py will +# fall back to its old static responses. You can customize them here. +# ErrorMessageTicket takes a string format dictionary containing (only) the +# "ticket" key. + +# error_message = '<html><body><h1>%s</h1></body></html>' +# error_message_ticket = '<html><body><h1>Internal error</h1>Ticket issued: <a href="/admin/default/ticket/%(ticket)s" target="_blank">%(ticket)s</a></body></html>' + +# specify a list of apps that bypass args-checking and use request.raw_args +# +#routes_apps_raw=['myapp'] +#routes_apps_raw=['myapp', 'myotherapp'] + +def __routes_doctest(): + ''' + Dummy function for doctesting routes.py. + + Use filter_url() to test incoming or outgoing routes; + filter_err() for error redirection. + + filter_url() accepts overrides for method and remote host: + filter_url(url, method='get', remote='0.0.0.0', out=False) + + filter_err() accepts overrides for application and ticket: + filter_err(status, application='app', ticket='tkt') + + >>> import os + >>> import gluon.main + >>> from gluon.rewrite import regex_select, load, filter_url, regex_filter_out, filter_err, compile_regex + >>> regex_select() + >>> load(routes=os.path.basename(__file__)) + + >>> os.path.relpath(filter_url('http://domain.com/favicon.ico')) + 'applications/examples/static/favicon.ico' + >>> os.path.relpath(filter_url('http://domain.com/robots.txt')) + 'applications/examples/static/robots.txt' + >>> filter_url('http://domain.com') + '/init/default/index' + >>> filter_url('http://domain.com/') + '/init/default/index' + >>> filter_url('http://domain.com/init/default/fcn') + '/init/default/fcn' + >>> filter_url('http://domain.com/init/default/fcn/') + '/init/default/fcn' + >>> filter_url('http://domain.com/app/ctr/fcn') + '/app/ctr/fcn' + >>> filter_url('http://domain.com/app/ctr/fcn/arg1') + "/app/ctr/fcn ['arg1']" + >>> filter_url('http://domain.com/app/ctr/fcn/arg1/') + "/app/ctr/fcn ['arg1']" + >>> filter_url('http://domain.com/app/ctr/fcn/arg1//') + "/app/ctr/fcn ['arg1', '']" + >>> filter_url('http://domain.com/app/ctr/fcn//arg1') + "/app/ctr/fcn ['', 'arg1']" + >>> filter_url('HTTP://DOMAIN.COM/app/ctr/fcn') + '/app/ctr/fcn' + >>> filter_url('http://domain.com/app/ctr/fcn?query') + '/app/ctr/fcn ?query' + >>> filter_url('http://otherdomain.com/fcn') + '/app/ctr/fcn' + >>> regex_filter_out('/app/ctr/fcn') + '/ctr/fcn' + >>> filter_url('https://otherdomain.com/app/ctr/fcn', out=True) + '/ctr/fcn' + >>> filter_url('https://otherdomain.com/app/ctr/fcn/arg1//', out=True) + '/ctr/fcn/arg1//' + >>> filter_url('http://otherdomain.com/app/ctr/fcn', out=True) + '/fcn' + >>> filter_url('http://otherdomain.com/app/ctr/fcn?query', out=True) + '/fcn?query' + >>> filter_url('http://otherdomain.com/app/ctr/fcn#anchor', out=True) + '/fcn#anchor' + >>> filter_err(200) + 200 + >>> filter_err(399) + 399 + >>> filter_err(400) + 400 + >>> filter_url('http://domain.com/welcome', app=True) + 'welcome' + >>> filter_url('http://domain.com/', app=True) + 'myapp' + >>> filter_url('http://domain.com', app=True) + 'myapp' + >>> compile_regex('.*http://otherdomain.com.* (?P<any>.*)', '/app/ctr\g<any>')[0].pattern + '^.*http://otherdomain.com.* (?P<any>.*)$' + >>> compile_regex('.*http://otherdomain.com.* (?P<any>.*)', '/app/ctr\g<any>')[1] + '/app/ctr\\\\g<any>' + >>> compile_regex('/$c/$f', '/init/$c/$f')[0].pattern + '^.*?:https?://[^:/]+:[a-z]+ /(?P<c>\\\\w+)/(?P<f>\\\\w+)$' + >>> compile_regex('/$c/$f', '/init/$c/$f')[1] + '/init/\\\\g<c>/\\\\g<f>' + ''' + pass + +if __name__ == '__main__': + import doctest + doctest.testmod() +