mirror of https://github.com/pallets/flask.git
				
				
				
			
		
			
				
	
	
		
			595 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			595 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Python
		
	
	
	
| # -*- coding: utf-8 -*-
 | |
| """
 | |
|     flask
 | |
|     ~~~~~
 | |
| 
 | |
|     A microframework based on Werkzeug.  It's extensively documented
 | |
|     and follows best practice patterns.
 | |
| 
 | |
|     :copyright: (c) 2010 by Armin Ronacher.
 | |
|     :license: BSD, see LICENSE for more details.
 | |
| """
 | |
| import os
 | |
| import sys
 | |
| import pkg_resources
 | |
| from threading import local
 | |
| from contextlib import contextmanager
 | |
| from jinja2 import Environment, PackageLoader
 | |
| from werkzeug import Request, Response, LocalStack, LocalProxy, \
 | |
|      create_environ, cached_property
 | |
| from werkzeug.routing import Map, Rule
 | |
| from werkzeug.exceptions import HTTPException, InternalServerError
 | |
| from werkzeug.contrib.securecookie import SecureCookie
 | |
| 
 | |
| # utilities we import from Werkzeug and Jinja2 that are unused
 | |
| # in the module but are exported as public interface.
 | |
| from werkzeug import abort, redirect
 | |
| from jinja2 import Markup, escape
 | |
| 
 | |
| 
 | |
| class FlaskRequest(Request):
 | |
|     """The request object used by default in flask.  Remembers the
 | |
|     matched endpoint and view arguments.
 | |
|     """
 | |
| 
 | |
|     def __init__(self, environ):
 | |
|         Request.__init__(self, environ)
 | |
|         self.endpoint = None
 | |
|         self.view_args = None
 | |
| 
 | |
| 
 | |
| class FlaskResponse(Response):
 | |
|     """The response object that is used by default in flask.  Works like the
 | |
|     response object from Werkzeug but is set to have a HTML mimetype by
 | |
|     default.
 | |
|     """
 | |
|     default_mimetype = 'text/html'
 | |
| 
 | |
| 
 | |
| class _RequestGlobals(object):
 | |
|     pass
 | |
| 
 | |
| 
 | |
| class _RequestContext(object):
 | |
|     """The request context contains all request relevant information.  It is
 | |
|     created at the beginning of the request and pushed to the
 | |
|     `_request_ctx_stack` and removed at the end of it.  It will create the
 | |
|     URL adapter and request object for the WSGI environment provided.
 | |
|     """
 | |
| 
 | |
|     def __init__(self, app, environ):
 | |
|         self.app = app
 | |
|         self.url_adapter = app.url_map.bind_to_environ(environ)
 | |
|         self.request = app.request_class(environ)
 | |
|         self.session = app.open_session(self.request)
 | |
|         self.g = _RequestGlobals()
 | |
|         self.flashes = None
 | |
| 
 | |
| 
 | |
| def url_for(endpoint, **values):
 | |
|     """Generates a URL to the given endpoint with the method provided.
 | |
| 
 | |
|     :param endpoint: the endpoint of the URL (name of the function)
 | |
|     :param values: the variable arguments of the URL rule
 | |
|     """
 | |
|     return _request_ctx_stack.top.url_adapter.build(endpoint, values)
 | |
| 
 | |
| 
 | |
| def flash(message):
 | |
|     """Flashes a message to the next request.  In order to remove the
 | |
|     flashed message from the session and to display it to the user,
 | |
|     the template has to call :func:`get_flashed_messages`.
 | |
| 
 | |
|     :param message: the message to be flashed.
 | |
|     """
 | |
|     session['_flashes'] = (session.get('_flashes', [])) + [message]
 | |
| 
 | |
| 
 | |
| def get_flashed_messages():
 | |
|     """Pulls all flashed messages from the session and returns them.
 | |
|     Further calls in the same request to the function will return
 | |
|     the same messages.
 | |
|     """
 | |
|     flashes = _request_ctx_stack.top.flashes
 | |
|     if flashes is None:
 | |
|         _request_ctx_stack.top.flashes = flashes = \
 | |
|             session.pop('_flashes', [])
 | |
|     return flashes
 | |
| 
 | |
| 
 | |
| def render_template(template_name, **context):
 | |
|     """Renders a template from the template folder with the given
 | |
|     context.
 | |
| 
 | |
|     :param template_name: the name of the template to be rendered
 | |
|     :param context: the variables that should be available in the
 | |
|                     context of the template.
 | |
|     """
 | |
|     current_app.update_template_context(context)
 | |
|     return current_app.jinja_env.get_template(template_name).render(context)
 | |
| 
 | |
| 
 | |
| def render_template_string(source, **context):
 | |
|     """Renders a template from the given template source string
 | |
|     with the given context.
 | |
| 
 | |
|     :param template_name: the sourcecode of the template to be
 | |
|                           rendered
 | |
|     :param context: the variables that should be available in the
 | |
|                     context of the template.
 | |
|     """
 | |
|     current_app.update_template_context(context)
 | |
|     return current_app.jinja_env.from_string(source).render(context)
 | |
| 
 | |
| 
 | |
| class Flask(object):
 | |
|     """The flask object implements a WSGI application and acts as the central
 | |
|     object.  It is passed the name of the module or package of the
 | |
|     application.  Once it is created it will act as a central registry for
 | |
|     the view functions, the URL rules, template configuration and much more.
 | |
| 
 | |
|     The name of the package is used to resolve resources from inside the
 | |
|     package or the folder the module is contained in depending on if the
 | |
|     package parameter resolves to an actual python package (a folder with
 | |
|     an `__init__.py` file inside) or a standard module (just a `.py` file).
 | |
| 
 | |
|     For more information about resource loading, see :func:`open_resource`.
 | |
| 
 | |
|     Usually you create a :class:`Flask` instance in your main module or
 | |
|     in the `__init__.py` file of your package like this::
 | |
| 
 | |
|         from flask import Flask
 | |
|         app = Flask(__name__)
 | |
|     """
 | |
| 
 | |
|     #: the class that is used for request objects
 | |
|     request_class = FlaskRequest
 | |
| 
 | |
|     #: the class that is used for response objects
 | |
|     response_class = FlaskResponse
 | |
| 
 | |
|     #: path for the static files.  If you don't want to use static files
 | |
|     #: you can set this value to `None` in which case no URL rule is added
 | |
|     #: and the development server will no longer serve any static files.
 | |
|     static_path = '/static'
 | |
| 
 | |
|     #: if a secret key is set, cryptographic components can use this to
 | |
|     #: sign cookies and other things.  Set this to a complex random value
 | |
|     #: when you want to use the secure cookie for instance.
 | |
|     secret_key = None
 | |
| 
 | |
|     #: The secure cookie uses this for the name of the session cookie
 | |
|     session_cookie_name = 'session'
 | |
| 
 | |
|     #: options that are passed directly to the Jinja2 environment
 | |
|     jinja_options = dict(
 | |
|         autoescape=True,
 | |
|         extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_']
 | |
|     )
 | |
| 
 | |
|     def __init__(self, package_name):
 | |
|         #: the debug flag.  Set this to `True` to enable debugging of
 | |
|         #: the application.  In debug mode the debugger will kick in
 | |
|         #: when an unhandled exception ocurrs and the integrated server
 | |
|         #: will automatically reload the application if changes in the
 | |
|         #: code are detected.
 | |
|         self.debug = False
 | |
| 
 | |
|         #: the name of the package or module.  Do not change this once
 | |
|         #: it was set by the constructor.
 | |
|         self.package_name = package_name
 | |
| 
 | |
|         #: a dictionary of all view functions registered.  The keys will
 | |
|         #: be function names which are also used to generate URLs and
 | |
|         #: the values are the function objects themselves.
 | |
|         #: to register a view function, use the :meth:`route` decorator.
 | |
|         self.view_functions = {}
 | |
| 
 | |
|         #: a dictionary of all registered error handlers.  The key is
 | |
|         #: be the error code as integer, the value the function that
 | |
|         #: should handle that error.
 | |
|         #: To register a error handler, use the :meth:`errorhandler`
 | |
|         #: decorator.
 | |
|         self.error_handlers = {}
 | |
| 
 | |
|         #: a list of functions that should be called at the beginning
 | |
|         #: of the request before request dispatching kicks in.  This
 | |
|         #: can for example be used to open database connections or
 | |
|         #: getting hold of the currently logged in user.
 | |
|         #: To register a function here, use the :meth:`request_init`
 | |
|         #: decorator.
 | |
|         self.request_init_funcs = []
 | |
| 
 | |
|         #: a list of functions that are called at the end of the
 | |
|         #: request.  Tha function is passed the current response
 | |
|         #: object and modify it in place or replace it.
 | |
|         #: To register a function here use the :meth:`request_shtdown`
 | |
|         #: decorator.
 | |
|         self.request_shutdown_funcs = []
 | |
|         self.url_map = Map()
 | |
| 
 | |
|         if self.static_path is not None:
 | |
|             self.url_map.add(Rule(self.static_path + '/<filename>',
 | |
|                                   build_only=True, endpoint='static'))
 | |
| 
 | |
|         #: the Jinja2 environment.  It is created from the
 | |
|         #: :attr:`jinja_options` and the loader that is returned
 | |
|         #: by the :meth:`create_jinja_loader` function.
 | |
|         self.jinja_env = Environment(loader=self.create_jinja_loader(),
 | |
|                                      **self.jinja_options)
 | |
|         self.jinja_env.globals.update(
 | |
|             url_for=url_for,
 | |
|             get_flashed_messages=get_flashed_messages
 | |
|         )
 | |
| 
 | |
|     def create_jinja_loader(self):
 | |
|         """Creates the Jinja loader.  By default just a package loader for
 | |
|         the configured package is returned that looks up templates in the
 | |
|         `templates` folder.  To add other loaders it's possible to
 | |
|         override this method.
 | |
|         """
 | |
|         return PackageLoader(self.package_name)
 | |
| 
 | |
|     def update_template_context(self, context):
 | |
|         """Update the template context with some commonly used variables.
 | |
|         This injects request, session and g into the template context.
 | |
| 
 | |
|         :param context: the context as a dictionary that is updated in place
 | |
|                         to add extra variables.
 | |
|         """
 | |
|         reqctx = _request_ctx_stack.top
 | |
|         context['request'] = reqctx.request
 | |
|         context['session'] = reqctx.session
 | |
|         context['g'] = reqctx.g
 | |
| 
 | |
|     def run(self, host='localhost', port=5000, **options):
 | |
|         """Runs the application on a local development server.  If the
 | |
|         :attr:`debug` flag is set the server will automatically reload
 | |
|         for code changes and show a debugger in case an exception happened.
 | |
| 
 | |
|         :param host: the hostname to listen on.  set this to ``'0.0.0.0'``
 | |
|                      to have the server available externally as well.
 | |
|         :param port: the port of the webserver
 | |
|         :param options: the options to be forwarded to the underlying
 | |
|                         Werkzeug server.  See :func:`werkzeug.run_simple`
 | |
|                         for more information.
 | |
|         """
 | |
|         from werkzeug import run_simple
 | |
|         if 'debug' in options:
 | |
|             self.debug = options.pop('debug')
 | |
|         if self.static_path is not None:
 | |
|             options['static_files'] = {
 | |
|                 self.static_path:   (self.package_name, 'static')
 | |
|             }
 | |
|         options.setdefault('use_reloader', self.debug)
 | |
|         options.setdefault('use_debugger', self.debug)
 | |
|         return run_simple(host, port, self, **options)
 | |
| 
 | |
|     @cached_property
 | |
|     def test(self):
 | |
|         """A test client for this application"""
 | |
|         from werkzeug import Client
 | |
|         return Client(self, self.response_class, use_cookies=True)
 | |
| 
 | |
|     def open_resource(self, resource):
 | |
|         """Opens a resource from the application's resource folder.  To see
 | |
|         how this works, consider the following folder structure::
 | |
| 
 | |
|             /myapplication.py
 | |
|             /schemal.sql
 | |
|             /static
 | |
|                 /style.css
 | |
|             /template
 | |
|                 /layout.html
 | |
|                 /index.html
 | |
| 
 | |
|         If you want to open the `schema.sql` file you would do the
 | |
|         following::
 | |
| 
 | |
|             with app.open_resource('schema.sql') as f:
 | |
|                 contents = f.read()
 | |
|                 do_something_with(contents)
 | |
| 
 | |
|         :param resource: the name of the resource.  To access resources within
 | |
|                          subfolders use forward slashes as separator.
 | |
|         """
 | |
|         return pkg_resources.resource_stream(self.package_name, resource)
 | |
| 
 | |
|     def open_session(self, request):
 | |
|         """Creates or opens a new session.  Default implementation stores all
 | |
|         session data in a signed cookie.  This requires that the
 | |
|         :attr:`secret_key` is set.
 | |
| 
 | |
|         :param request: an instance of :attr:`request_class`.
 | |
|         """
 | |
|         key = self.secret_key
 | |
|         if key is not None:
 | |
|             return SecureCookie.load_cookie(request, self.session_cookie_name,
 | |
|                                             secret_key=key)
 | |
| 
 | |
|     def save_session(self, session, response):
 | |
|         """Saves the session if it needs updates.  For the default
 | |
|         implementation, check :meth:`open_session`.
 | |
| 
 | |
|         :param session: the session to be saved (a
 | |
|                         :class:`~werkzeug.contrib.securecookie.SecureCookie`
 | |
|                         object)
 | |
|         :param request: an instance of :attr:`response_class`
 | |
|         """
 | |
|         if session is not None:
 | |
|             session.save_cookie(response, self.session_cookie_name)
 | |
| 
 | |
|     def add_url_rule(self, rule, endpoint, **options):
 | |
|         """Connects a URL rule.  Works exactly like the :meth:`route`
 | |
|         decorator but does not register the view function for the endpoint.
 | |
| 
 | |
|         Basically this example::
 | |
| 
 | |
|             @app.route('/')
 | |
|             def index():
 | |
|                 pass
 | |
| 
 | |
|         Is equivalent to the following::
 | |
| 
 | |
|             def index():
 | |
|                 pass
 | |
|             app.add_url_rule('index', '/')
 | |
|             app.view_functions['index'] = index
 | |
| 
 | |
|         :param rule: the URL rule as string
 | |
|         :param endpoint: the endpoint for the registered URL rule.  Flask
 | |
|                          itself assumes the name of the view function as
 | |
|                          endpoint
 | |
|         :param options: the options to be forwarded to the underlying
 | |
|                         :class:`~werkzeug.routing.Rule` object
 | |
|         """
 | |
|         options['endpoint'] = endpoint
 | |
|         options.setdefault('methods', ('GET',))
 | |
|         self.url_map.add(Rule(rule, **options))
 | |
| 
 | |
|     def route(self, rule, **options):
 | |
|         """A decorator that is used to register a view function for a
 | |
|         given URL rule.  Example::
 | |
| 
 | |
|             @app.route('/')
 | |
|             def index():
 | |
|                 return 'Hello World'
 | |
| 
 | |
|         Variables parts in the route can be specified with angular
 | |
|         brackets (``/user/<username>``).  By default a variable part
 | |
|         in the URL accepts any string without a slash however a differnt
 | |
|         converter can be specified as well by using ``<converter:name>``.
 | |
| 
 | |
|         Variable parts are passed to the view function as keyword
 | |
|         arguments.
 | |
| 
 | |
|         The following converters are possible:
 | |
| 
 | |
|         =========== ===========================================
 | |
|         `int`       accepts integers
 | |
|         `float`     like `int` but for floating point values
 | |
|         `path`      like the default but also accepts slashes
 | |
|         =========== ===========================================
 | |
| 
 | |
|         Here some examples::
 | |
| 
 | |
|             @app.route('/')
 | |
|             def index():
 | |
|                 pass
 | |
| 
 | |
|             @app.route('/<username>')
 | |
|             def show_user(username):
 | |
|                 pass
 | |
| 
 | |
|             @app.route('/post/<int:post_id>')
 | |
|             def show_post(post_id):
 | |
|                 pass
 | |
| 
 | |
|         An important detail to keep in mind is how Flask deals with trailing
 | |
|         slashes.  The idea is to keep each URL unique so the following rules
 | |
|         apply:
 | |
| 
 | |
|         1. If a rule ends with a slash and is requested without a slash
 | |
|            by the user, the user is automatically redirected to the same
 | |
|            page with a trailing slash attached.
 | |
|         2. If a rule does not end with a trailing slash and the user request
 | |
|            the page with a trailing slash, a 404 not found is raised.
 | |
| 
 | |
|         This is consistent with how web servers deal with static files.  This
 | |
|         also makes it possible to use relative link targets safely.
 | |
| 
 | |
|         The :meth:`route` decorator accepts a couple of other arguments
 | |
|         as well:
 | |
| 
 | |
|         :param rule: the URL rule as string
 | |
|         :param methods: a list of methods this rule should be limited
 | |
|                         to (``GET``, ``POST`` etc.).  By default a rule
 | |
|                         just listens for ``GET`` (and implicitly ``HEAD``).
 | |
|         :param subdomain: specifies the rule for the subdoain in case
 | |
|                           subdomain matching is in use.
 | |
|         :param strict_slashes: can be used to disable the strict slashes
 | |
|                                setting for this rule.  See above.
 | |
|         :param options: other options to be forwarded to the underlying
 | |
|                         :class:`~werkzeug.routing.Rule` object.
 | |
|         """
 | |
|         def decorator(f):
 | |
|             self.add_url_rule(rule, f.__name__, **options)
 | |
|             self.view_functions[f.__name__] = f
 | |
|             return f
 | |
|         return decorator
 | |
| 
 | |
|     def errorhandler(self, code):
 | |
|         """A decorator that is used to register a function give a given
 | |
|         error code.  Example::
 | |
| 
 | |
|             @app.errorhandler(404)
 | |
|             def page_not_found():
 | |
|                 return 'This page does not exist', 404
 | |
| 
 | |
|         You can also register a function as error handler without using
 | |
|         the :meth:`errorhandler` decorator.  The following example is
 | |
|         equivalent to the one above::
 | |
| 
 | |
|             def page_not_found():
 | |
|                 return 'This page does not exist', 404
 | |
|             app.error_handlers[404] = page_not_found
 | |
| 
 | |
|         :param code: the code as integer for the handler
 | |
|         """
 | |
|         def decorator(f):
 | |
|             self.error_handlers[code] = f
 | |
|             return f
 | |
|         return decorator
 | |
| 
 | |
|     def request_init(self, f):
 | |
|         """Registers a function to run before each request."""
 | |
|         self.request_init_funcs.append(f)
 | |
|         return f
 | |
| 
 | |
|     def request_shutdown(self, f):
 | |
|         """Register a function to be run after each request."""
 | |
|         self.request_shutdown_funcs.append(f)
 | |
|         return f
 | |
| 
 | |
|     def match_request(self):
 | |
|         """Matches the current request against the URL map and also
 | |
|         stores the endpoint and view arguments on the request object
 | |
|         is successful, otherwise the exception is stored.
 | |
|         """
 | |
|         rv = _request_ctx_stack.top.url_adapter.match()
 | |
|         request.endpoint, request.view_args = rv
 | |
|         return rv
 | |
| 
 | |
|     def dispatch_request(self):
 | |
|         """Does the request dispatching.  Matches the URL and returns the
 | |
|         return value of the view or error handler.  This does not have to
 | |
|         be a response object.  In order to convert the return value to a
 | |
|         proper response object, call :func:`make_response`.
 | |
|         """
 | |
|         try:
 | |
|             endpoint, values = self.match_request()
 | |
|             return self.view_functions[endpoint](**values)
 | |
|         except HTTPException, e:
 | |
|             handler = self.error_handlers.get(e.code)
 | |
|             if handler is None:
 | |
|                 return e
 | |
|             return handler(e)
 | |
|         except Exception, e:
 | |
|             handler = self.error_handlers.get(500)
 | |
|             if self.debug or handler is None:
 | |
|                 raise
 | |
|             return handler(e)
 | |
| 
 | |
|     def make_response(self, rv):
 | |
|         """Converts the return value from a view function to a real
 | |
|         response object that is an instance of :attr:`response_class`.
 | |
| 
 | |
|         The following types are allowd for `rv`:
 | |
| 
 | |
|         ======================= ===========================================
 | |
|         :attr:`response_class`  the object is returned unchanged
 | |
|         :class:`str`            a response object is created with the
 | |
|                                 string as body
 | |
|         :class:`unicode`        a response object is created with the
 | |
|                                 string encoded to utf-8 as body
 | |
|         :class:`tuple`          the response object is created with the
 | |
|                                 contents of the tuple as arguments
 | |
|         a WSGI function         the function is called as WSGI application
 | |
|                                 and buffered as response object
 | |
|         ======================= ===========================================
 | |
| 
 | |
|         :param rv: the return value from the view function
 | |
|         """
 | |
|         if isinstance(rv, self.response_class):
 | |
|             return rv
 | |
|         if isinstance(rv, basestring):
 | |
|             return self.response_class(rv)
 | |
|         if isinstance(rv, tuple):
 | |
|             return self.response_class(*rv)
 | |
|         return self.response_class.force_type(rv, request.environ)
 | |
| 
 | |
|     def preprocess_request(self):
 | |
|         """Called before the actual request dispatching and will
 | |
|         call every as :func:`request_init` decorated function.
 | |
|         If any of these function returns a value it's handled as
 | |
|         if it was the return value from the view and further
 | |
|         request handling is stopped.
 | |
|         """
 | |
|         for func in self.request_init_funcs:
 | |
|             rv = func()
 | |
|             if rv is not None:
 | |
|                 return rv
 | |
| 
 | |
|     def process_response(self, response):
 | |
|         """Can be overridden in order to modify the response object
 | |
|         before it's sent to the WSGI server.
 | |
| 
 | |
|         :param response: a :attr:`response_class` object.
 | |
|         :return: a new response object or the same, has to be an
 | |
|                  instance of :attr:`response_class`.
 | |
|         """
 | |
|         session = _request_ctx_stack.top.session
 | |
|         if session is not None:
 | |
|             self.save_session(session, response)
 | |
|         for handler in self.request_shutdown_funcs:
 | |
|             response = handler(response)
 | |
|         return response
 | |
| 
 | |
|     def wsgi_app(self, environ, start_response):
 | |
|         """The actual WSGI application.  This is not implemented in
 | |
|         `__call__` so that middlewares can be applied:
 | |
| 
 | |
|             app.wsgi_app = MyMiddleware(app.wsgi_app)
 | |
| 
 | |
|         :param environ: a WSGI environment
 | |
|         :param start_response: a callable accepting a status code,
 | |
|                                a list of headers and an optional
 | |
|                                exception context to start the response
 | |
|         """
 | |
|         with self.request_context(environ):
 | |
|             rv = self.preprocess_request()
 | |
|             if rv is None:
 | |
|                 rv = self.dispatch_request()
 | |
|             response = self.make_response(rv)
 | |
|             response = self.process_response(response)
 | |
|             return response(environ, start_response)
 | |
| 
 | |
|     @contextmanager
 | |
|     def request_context(self, environ):
 | |
|         """Creates a request context from the given environment and binds
 | |
|         it to the current context.  This must be used in combination with
 | |
|         the `with` statement because the request is only bound to the
 | |
|         current context for the duration of the `with` block.
 | |
| 
 | |
|         Example usage::
 | |
| 
 | |
|             with app.request_context(environ):
 | |
|                 do_something_with(request)
 | |
| 
 | |
|         :params environ: a WSGI environment
 | |
|         """
 | |
|         _request_ctx_stack.push(_RequestContext(self, environ))
 | |
|         try:
 | |
|             yield
 | |
|         finally:
 | |
|             _request_ctx_stack.pop()
 | |
| 
 | |
|     def test_request_context(self, *args, **kwargs):
 | |
|         """Creates a WSGI environment from the given values (see
 | |
|         :func:`werkzeug.create_environ` for more information, this
 | |
|         function accepts the same arguments).
 | |
|         """
 | |
|         return self.request_context(create_environ(*args, **kwargs))
 | |
| 
 | |
|     def __call__(self, environ, start_response):
 | |
|         """Shortcut for :attr:`wsgi_app`"""
 | |
|         return self.wsgi_app(environ, start_response)
 | |
| 
 | |
| 
 | |
| # context locals
 | |
| _request_ctx_stack = LocalStack()
 | |
| current_app = LocalProxy(lambda: _request_ctx_stack.top.app)
 | |
| request = LocalProxy(lambda: _request_ctx_stack.top.request)
 | |
| session = LocalProxy(lambda: _request_ctx_stack.top.session)
 | |
| g = LocalProxy(lambda: _request_ctx_stack.top.g)
 |