Added:
branches/0.9.2-dev/twill/tests/test-multi-browser.py
- copied unchanged from r71,
/branches/twillbrowser_enhancement_issue1/tests/test-multi-browser.py
Modified:
branches/0.9.2-dev/twill/ (props changed)
branches/0.9.2-dev/twill/README.html (props changed)
branches/0.9.2-dev/twill/README.txt (props changed)
branches/0.9.2-dev/twill/doc/ (props changed)
branches/0.9.2-dev/twill/examples/ (props changed)
branches/0.9.2-dev/twill/setup.py (props changed)
branches/0.9.2-dev/twill/tests/ (props changed)
branches/0.9.2-dev/twill/tests/test-form.py
branches/0.9.2-dev/twill/twill-fork (props changed)
branches/0.9.2-dev/twill/twill-sh (props changed)
branches/0.9.2-dev/twill/twill/browser.py
branches/0.9.2-dev/twill/twill/commands.py
branches/0.9.2-dev/twill/twill/utils.py
Log:
merged in twillbrowser_enhancement branch
Modified: branches/0.9.2-dev/twill/tests/test-form.py
==============================================================================
--- branches/0.9.2-dev/twill/tests/test-form.py (original)
+++ branches/0.9.2-dev/twill/tests/test-form.py Thu Apr 2 15:06:34 2009
@@ -43,7 +43,7 @@
try:
commands.fv('1', 'selecttest', 'value')
assert 0
- except ClientForm.ItemNotFoundError:
+ except TwillAssertionError:
pass
# test ambiguous match to name
Modified: branches/0.9.2-dev/twill/twill/browser.py
==============================================================================
--- branches/0.9.2-dev/twill/twill/browser.py (original)
+++ branches/0.9.2-dev/twill/twill/browser.py Thu Apr 2 15:06:34 2009
@@ -12,13 +12,26 @@
# wwwsearch imports
import _mechanize_dist as mechanize
from _mechanize_dist import BrowserStateError, LinkNotFoundError,
ClientForm
+from _mechanize_dist._headersutil import is_html
# twill package imports
from _browser import PatchedMechanizeBrowser
+import utils
from utils import print_form, ConfigurableParsingFactory, \
- ResultWrapper, unique_match, HistoryStack
-from errors import TwillException
-
+ ResultWrapper, unique_match, HistoryStack, \
+ set_form_control_value, run_tidy
+from errors import TwillException, TwillAssertionError
+
+## agent map
+AGENTS = dict(ie5='Mozilla/4.0 (compatible; MSIE 5.0; Windows NT 5.1)',
+ ie55='Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.1)',
+ ie6='Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)',
+ moz17='Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US;
rv:1.7) Gecko/20040616',
+ opera7='Opera/7.0 (Windows NT 5.1; U) [en]',
+ konq32='Mozilla/5.0 (compatible; Konqueror/3.2.3; Linux
2.4.14; X11; i686)',
+ saf11='Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us)
AppleWebKit/100 (KHTML, like Gecko) Safari/100',
+ aol9='Mozilla/4.0 (compatible; MSIE 5.5; AOL 9.0; Windows NT
5.1)',
+ )
#
# TwillBrowser
@@ -31,7 +44,19 @@
Public variables:
* result -- mechanize-style 'result' object.
+
+ For individual method docsctrings, assume b is an instance of
TwillBrowser
"""
+ _orig_options = dict(readonly_controls_writeable=False,
+ use_tidy=True,
+ require_tidy=False,
+ use_BeautifulSoup=True,
+ require_BeautifulSoup=False,
+ allow_parse_errors=True,
+ with_default_realm=False,
+ acknowledge_equiv_refresh=True,
+ )
+
def __init__(self):
#
# create special link/forms parsing code to run tidy on HTML first.
@@ -74,6 +99,9 @@
# callables to be called after each page load.
self._post_load_hooks = []
+ # options
+ self._options = self._orig_options.copy()
+
### get/set HTTP authentication stuff.
def _set_creds(self, creds):
@@ -167,7 +195,7 @@
if self.result:
return self.result.get_url()
return None
-
+
def find_link(self, pattern):
"""
Find the first link with a URL, link text, or name matching the
@@ -211,6 +239,9 @@
"""
Set the agent string to the given value.
"""
+ ## reset agent if is a key in AGENTS:
+ agent = AGENTS.get(agent, agent)
+
for i in xrange(len(self._browser.addheaders)):
if self._browser.addheaders[i][0] == "User-agent":
del self._browser.addheaders[i]
@@ -547,3 +578,284 @@
for callable in self._post_load_hooks:
callable(func_name, *args, **kwargs)
+
+ #
+ # methods moved down from commands
+ #
+
+ def formvalue(self, formname, fieldname, value):
+ """
+ >> formvalue <formname> <field> <value>
+
+ Set value of a form field.
+
+ There are some ambiguities in the way formvalue deals with lists:
+ 'formvalue' will *add* the given value to a list of multiple
selection,
+ for lists that allow it.
+
+ Forms are matched against 'formname' as follows:
+ 1. regexp match to actual form name;
+ 2. if 'formname' is an integer, it's tried as an index.
+
+ Form controls are matched against 'fieldname' as follows:
+ 1. unique exact match to control name;
+ 2. unique regexp match to control name;
+ 3. if fieldname is an integer, it's tried as an index;
+ 4. unique & exact match to submit-button values.
+
+ Formvalue ignores read-only fields completely; if they're readonly,
+ nothing is done, unless the config options ('config' command) are
+ changed.
+
+ 'formvalue' is available as 'fv' as well.
+ """
+ form = self.get_form(formname)
+ if not form:
+ raise ValueError, "no matching forms!"
+
+ control = self.get_form_field(form, fieldname)
+
+ self.clicked(form, control)
+
+ if control.readonly and
self._options['readonly_controls_writeable']:
+ print>>OUT, 'forcing read-only form field to writeable'
+ control.readonly = False
+
+ if control.readonly or isinstance(control,
ClientForm.IgnoreControl):
+ print>>OUT, 'form field is read-only or ignorable; nothing
done.'
+ return
+
+ if isinstance(control, ClientForm.FileControl):
+ raise TwillException('form field is for file upload;
use "formfile" instead')
+
+ set_form_control_value(control, value)
+
+ fv = formvalue
+
+ def follow(self, what):
+ """
+ >> b.follow(regexp)
+
+ Find the first matching link on the page & visit it
+ """
+ regexp = re.compile(what)
+ link = self.find_link(regexp)
+ if link:
+ self.follow_link(link)
+ else:
+ raise ValueError, "no links match '%s'" % what
+
+ def save_html(self, filename=None):
+ """
+ >> save_html [<filename>]
+
+ Save the HTML for the current page into <filename>. If no filename
+ given, construct the filename from the URL.
+ """
+ html = self.get_html()
+ if html is None:
+ print>>OUT, "No page to save."
+ return
+
+ if filename is None:
+ url = self.get_url()
+ url = url.split('?')[0]
+ filename = url.split('/')[-1]
+ if filename is "":
+ filename = 'index.html'
+
+ print>>OUT, "(Using filename '%s')" % (filename,)
+
+ f = open(filename, 'w')
+ f.write(html)
+ f.close()
+
+ def formclear(self, formname):
+ """
+ >> b.formclear(formname)
+
+ Run 'clear' on all of the controls in this form.
+ """
+ form = self.get_form(formname)
+ for control in form.controls:
+ if control.readonly:
+ continue
+
+ control.clear()
+
+ def formaction(self, formname, action):
+ """
+ >> b.formaction(formname, action_url)
+
+ Sets action parameter on form to action_url
+ """
+ form = self.get_form(formname)
+ form.action = action
+
+ fa = formaction
+
+ def formfile(self, formname, fieldname, filename, content_type=None):
+ """
+ >> b.formfile(form, field, filename[, content_type])
+
+ Upload a file via an "upload file" form field.
+ """
+ import os.path
+ filename = filename.replace('/', os.path.sep)
+
+ form = self.get_form(formname)
+ control = self.get_form_field(form, fieldname)
+
+ if not control.is_of_kind('file'):
+ raise TwillException('ERROR: field is not a file upload
field!')
+
+ self.clicked(form, control)
+ fp = open(filename, 'rb')
+ control.add_file(fp, content_type, filename)
+
+ print>>OUT, '\nAdded file "%s" to file upload field "%s"\n' %
(filename,
+
control.name,)
+
+ def add_auth(self, realm, uri, user, passwd):
+ """
+ >> b.add_auth(realm, uri, user, passwd)
+
+ Add HTTP Basic Authentication information for the given realm/uri.
+ """
+ # swap around the type of HTTPPasswordMgr and
+ # HTTPPasswordMgrWithDefaultRealm depending on if
with_default_realm
+ # is on or not.
+ if self._options['with_default_realm']:
+ realm = None
+
+ if self.creds.__class__ == mechanize.HTTPPasswordMgr:
+ passwds = self.creds.passwd
+ self.creds = mechanize.HTTPPasswordMgrWithDefaultRealm()
+ self.creds.passwd = passwds
+ print>>OUT, 'Changed to using
HTTPPasswordMgrWithDefaultRealm'
+ else:
+ if self.creds.__class__ ==
mechanize.HTTPPasswordMgrWithDefaultRealm:
+ passwds = self.creds.passwd
+ self.creds = mechanize.HTTPPasswordMgr()
+ self.creds.passwd = passwds
+ print>>OUT, 'Changed to using HTTPPasswordMgr'
+
+ self.creds.add_password(realm, uri, user, passwd)
+
+ print>>OUT, "Added auth info: realm '%s' / URI '%s' / user '%s'" %
(realm,
+ uri,
+
user,)
+
+ def add_extra_header(self, header_key, header_value):
+ """
+ >> b.add_header(name, value)
+
+ Add an HTTP header to each HTTP request. See 'show_extra_headers'
and
+ 'clear_extra_headers'.
+ """
+ self._browser.addheaders += [(header_key, header_value)]
+
+ def show_extra_headers(self):
+ """
+ >> b.show_extra_headers()
+
+ Show any extra headers being added to each HTTP request.
+ """
+ l = self._browser.addheaders
+
+ if l:
+ print 'The following HTTP headers are added to each request:'
+
+ for k, v in l:
+ print ' "%s" = "%s"' % (k, v,)
+
+ print ''
+ else:
+ print '** no extra HTTP headers **'
+
+ def clear_extra_headers(self):
+ """
+ >> b.clear_extra_headers()
+
+ Remove all user-defined HTTP headers. See 'add_extra_header' and
+ 'show_extra_headers'.
+ """
+ self._browser.addheaders = []
+
+ def config(self, key=None, value=None):
+ """
+ >> b.config([key[, int value]])
+
+ Configure/report various options. If no <value> is given, report
+ the current key value; if no <key> given, report current settings.
+
+ So far:
+
+ * 'acknowledge_equiv_refresh', default 1 -- follow
HTTP-EQUIV=REFRESH
+ * 'readonly_controls_writeable', default 0 -- make ro controls
writeable
+ * 'require_tidy', default 0 -- *require* that tidy be installed
+ * 'use_BeautifulSoup', default 1 -- use the BeautifulSoup parser
+ * 'use_tidy', default 1 -- use tidy, if it's installed
+ * 'with_default_realm', default 0 -- use a default realm for HTTP
AUTH
+
+ Deprecated:
+ * 'allow_parse_errors' has been removed.
+ """
+
+ if key is None:
+ keys = self._options.keys()
+ keys.sort()
+
+ print>>OUT, 'current configuration:'
+ for k in keys:
+ print>>OUT, '\t%s : %s' % (k, self._options[k])
+ print>>OUT, ''
+ else:
+ v = self._options.get(key)
+ if v is None:
+ print>>OUT, '*** no such configuration key', key
+ print>>OUT, 'valid keys
are:', ";".join(self._options.keys())
+ raise TwillException('no such configuration key: %s' %
(key,))
+ elif value is None:
+ print>>OUT, ''
+ print>>OUT, 'key %s: value %s' % (key, v)
+ print>>OUT, ''
+ else:
+ value = utils.make_boolean(value)
+ self._options[key] = value
+
+ def info(self):
+ """
+ >> b.info()
+
+ Report information on current page.
+ """
+ current_url = self.get_url()
+ if current_url is None:
+ print "We're not on a page!"
+ return
+
+ content_type =
self._browser._response.info().getheaders("content-type")
+ check_html = is_html(content_type, current_url)
+
+ code = self.get_code()
+
+
+ print >>OUT, '\nPage information:'
+ print >>OUT, '\tURL:', current_url
+ print >>OUT, '\tHTTP code:', code
+ print >>OUT, '\tContent type:', content_type[0],
+ if check_html:
+ print >>OUT, '(HTML)'
+ else:
+ print ''
+ if check_html:
+ title = self.get_title()
+ print >>OUT, '\tPage title:', title
+
+ forms = self.get_all_forms()
+ if len(forms):
+ print >>OUT, '\tThis page contains %d form(s)' %
(len(forms),)
+
+ print >>OUT, ''
+
Modified: branches/0.9.2-dev/twill/twill/commands.py
==============================================================================
--- branches/0.9.2-dev/twill/twill/commands.py (original)
+++ branches/0.9.2-dev/twill/twill/commands.py Thu Apr 2 15:06:34 2009
@@ -76,6 +76,8 @@
browser = TwillBrowser()
+_options = browser._options
+
def get_browser():
return browser
@@ -90,8 +92,7 @@
browser = TwillBrowser()
global _options
- _options = {}
- _options.update(_orig_options)
+ _options = browser._options
###
@@ -192,28 +193,26 @@
Find the first matching link on the page & visit it.
"""
- regexp = re.compile(what)
- link = browser.find_link(regexp)
-
- if link:
- browser.follow_link(link)
+ try:
+ browser.follow(what)
+ except ValueError:
+ raise TwillAssertionError("no links match '%s'" % what)
+ else:
return browser.get_url()
- raise TwillAssertionError("no links match to '%s'" % (what,))
-
-def _parseFindFlags(flags):
- KNOWN_FLAGS = {
- 'i': re.IGNORECASE,
- 'm': re.MULTILINE,
- 's': re.DOTALL,
- }
- finalFlags = 0
- for char in flags:
- try:
- finalFlags |= KNOWN_FLAGS[char]
- except IndexError:
- raise TwillAssertionError("unknown 'find' flag %r" % char)
- return finalFlags
+def _parseFindFlags(flags):
+ KNOWN_FLAGS = {
+ 'i': re.IGNORECASE,
+ 'm': re.MULTILINE,
+ 's': re.DOTALL,
+ }
+ finalFlags = 0
+ for char in flags:
+ try:
+ finalFlags |= KNOWN_FLAGS[char]
+ except IndexError:
+ raise TwillAssertionError("unknown 'find' flag %r" % char)
+ return finalFlags
def find(what, flags=''):
"""
@@ -294,23 +293,7 @@
Save the HTML for the current page into <filename>. If no filename
given, construct the filename from the URL.
"""
- html = browser.get_html()
- if html is None:
- print>>OUT, "No page to save."
- return
-
- if filename is None:
- url = browser.get_url()
- url = url.split('?')[0]
- filename = url.split('/')[-1]
- if filename is "":
- filename = 'index.html'
-
- print>>OUT, "(Using filename '%s')" % (filename,)
-
- f = open(filename, 'w')
- f.write(html)
- f.close()
+ browser.save_html(filename)
def sleep(interval=1):
"""
@@ -321,15 +304,7 @@
"""
time.sleep(float(interval))
-_agent_map = dict(
- ie5='Mozilla/4.0 (compatible; MSIE 5.0; Windows NT 5.1)',
- ie55='Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.1)',
- ie6='Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)',
- moz17='Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7)
Gecko/20040616',
- opera7='Opera/7.0 (Windows NT 5.1; U) [en]',
- konq32='Mozilla/5.0 (compatible; Konqueror/3.2.3; Linux 2.4.14; X11;
i686)',
- saf11='Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/100
(KHTML, like Gecko) Safari/100',
- aol9='Mozilla/4.0 (compatible; MSIE 5.5; AOL 9.0; Windows NT 5.1)',)
+
def agent(what):
"""
@@ -340,9 +315,7 @@
Some convenient shortcuts:
ie5, ie55, ie6, moz17, opera7, konq32, saf11, aol9.
"""
- what = what.strip()
- agent = _agent_map.get(what, what)
- browser.set_agent_string(agent)
+ browser.set_agent_string(what)
def submit(submit_button=None):
"""
@@ -378,7 +351,7 @@
Show all of the links on the current page.
"""
browser.showlinks()
- return browser._browser.links()
+ return list(browser._browser.links())
def showhistory():
"""
@@ -395,12 +368,7 @@
Run 'clear' on all of the controls in this form.
"""
- form = browser.get_form(formname)
- for control in form.controls:
- if control.readonly:
- continue
-
- control.clear()
+ browser.formclear(formname)
def formvalue(formname, fieldname, value):
"""
@@ -428,27 +396,11 @@
'formvalue' is available as 'fv' as well.
"""
- form = browser.get_form(formname)
- if not form:
+ try:
+ browser.formvalue(formname, fieldname, value)
+ except ValueError:
raise TwillAssertionError("no matching forms!")
- control = browser.get_form_field(form, fieldname)
-
- browser.clicked(form, control)
-
- if control.readonly and _options['readonly_controls_writeable']:
- print>>OUT, 'forcing read-only form field to writeable'
- control.readonly = False
-
- if control.readonly or isinstance(control, ClientForm.IgnoreControl):
- print>>OUT, 'form field is read-only or ignorable; nothing done.'
- return
-
- if isinstance(control, ClientForm.FileControl):
- raise TwillException('form field is for file upload;
use "formfile" instead')
-
- set_form_control_value(control, value)
-
fv = formvalue
def formaction(formname, action):
@@ -457,32 +409,17 @@
Sets action parameter on form to action_url
"""
- form = browser.get_form(formname)
- form.action = action
+ browser.formaction(formname, action)
fa = formaction
def formfile(formname, fieldname, filename, content_type=None):
"""
>> formfile <form> <field> <filename> [ <content_type> ]
-
+
Upload a file via an "upload file" form field.
"""
- import os.path
- filename = filename.replace('/', os.path.sep)
-
- form = browser.get_form(formname)
- control = browser.get_form_field(form, fieldname)
-
- if not control.is_of_kind('file'):
- raise TwillException('ERROR: field is not a file upload field!')
-
- browser.clicked(form, control)
- fp = open(filename, 'rb')
- control.add_file(fp, content_type, filename)
-
- print>>OUT, '\nAdded file "%s" to file upload field "%s"\n' %
(filename,
- control.name,)
+ browser.formfile(formname, fieldname, filename, content_type)
def extend_with(module_name):
"""
@@ -581,7 +518,7 @@
def show_cookies():
"""
>> show_cookies
-
+
Show all of the cookies in the cookie jar.
"""
browser.show_cookies()
@@ -589,32 +526,10 @@
def add_auth(realm, uri, user, passwd):
"""
>> add_auth <realm> <uri> <user> <passwd>
-
+
Add HTTP Basic Authentication information for the given realm/uri.
"""
- # swap around the type of HTTPPasswordMgr and
- # HTTPPasswordMgrWithDefaultRealm depending on if with_default_realm
- # is on or not.
- if _options['with_default_realm']:
- realm = None
-
- if browser.creds.__class__ == mechanize.HTTPPasswordMgr:
- passwds = browser.creds.passwd
- browser.creds = mechanize.HTTPPasswordMgrWithDefaultRealm()
- browser.creds.passwd = passwds
- print>>OUT, 'Changed to using HTTPPasswordMgrWithDefaultRealm'
- else:
- if browser.creds.__class__ ==
mechanize.HTTPPasswordMgrWithDefaultRealm:
- passwds = browser.creds.passwd
- browser.creds = mechanize.HTTPPasswordMgr()
- browser.creds.passwd = passwds
- print>>OUT, 'Changed to using HTTPPasswordMgr'
-
- browser.creds.add_password(realm, uri, user, passwd)
-
- print>>OUT, "Added auth info: realm '%s' / URI '%s' / user '%s'" %
(realm,
- uri,
- user,)
+ browser.add_auth(realm, uri, user, passwd)
def debug(what, level):
"""
@@ -770,7 +685,7 @@
Add an HTTP header to each HTTP request. See 'show_extra_headers' and
'clear_extra_headers'.
"""
- browser._browser.addheaders += [(header_key, header_value)]
+ browser.add_extra_header(header_key, header_value)
def show_extra_headers():
"""
@@ -778,17 +693,7 @@
Show any extra headers being added to each HTTP request.
"""
- l = browser._browser.addheaders
-
- if l:
- print>>OUT, 'The following HTTP headers are added to each request:'
-
- for k, v in l:
- print>>OUT, ' "%s" = "%s"' % (k, v,)
-
- print>>OUT, ''
- else:
- print>>OUT, '** no extra HTTP headers **'
+ browser.show_extra_headers()
def clear_extra_headers():
"""
@@ -797,22 +702,11 @@
Remove all user-defined HTTP headers. See 'add_extra_header' and
'show_extra_headers'.
"""
- browser._browser.addheaders = []
+ browser.clear_extra_headers()
### options
-_orig_options = dict(readonly_controls_writeable=False,
- use_tidy=True,
- require_tidy=False,
- use_BeautifulSoup=True,
- require_BeautifulSoup=False,
- allow_parse_errors=True,
- with_default_realm=False,
- acknowledge_equiv_refresh=True
- )
-_options = {}
-_options.update(_orig_options) # make a copy
def config(key=None, value=None):
"""
@@ -833,29 +727,7 @@
Deprecated:
* 'allow_parse_errors' has been removed.
"""
- import utils
-
- if key is None:
- keys = _options.keys()
- keys.sort()
-
- print>>OUT, 'current configuration:'
- for k in keys:
- print>>OUT, '\t%s : %s' % (k, _options[k])
- print>>OUT, ''
- else:
- v = _options.get(key)
- if v is None:
- print>>OUT, '*** no such configuration key', key
- print>>OUT, 'valid keys are:', ";".join(_options.keys())
- raise TwillException('no such configuration key: %s' % (key,))
- elif value is None:
- print>>OUT, ''
- print>>OUT, 'key %s: value %s' % (key, v)
- print>>OUT, ''
- else:
- value = utils.make_boolean(value)
- _options[key] = value
+ browser.config(key, value)
def info():
"""
@@ -863,31 +735,5 @@
Report information on current page.
"""
- current_url = browser.get_url()
- if current_url is None:
- print>>OUT, "We're not on a page!"
- return
-
- content_type =
browser._browser._response.info().getheaders("content-type")
- check_html = is_html(content_type, current_url)
-
- code = browser.get_code()
-
-
- print >>OUT, '\nPage information:'
- print >>OUT, '\tURL:', current_url
- print >>OUT, '\tHTTP code:', code
- print >>OUT, '\tContent type:', content_type[0],
- if check_html:
- print >>OUT, '(HTML)'
- else:
- print >>OUT, ''
- if check_html:
- title = browser.get_title()
- print >>OUT, '\tPage title:', title
-
- forms = browser.get_all_forms()
- if len(forms):
- print >>OUT, '\tThis page contains %d form(s)' % (len(forms),)
-
- print >>OUT, ''
+ browser.info()
+
Modified: branches/0.9.2-dev/twill/twill/utils.py
==============================================================================
--- branches/0.9.2-dev/twill/twill/utils.py (original)
+++ branches/0.9.2-dev/twill/twill/utils.py Thu Apr 2 15:06:34 2009
@@ -462,6 +462,9 @@
def _is_valid_filename(f):
return not (f.endswith('~') or f.endswith('.bak') or
f.endswith('.old'))
+def _is_valid_directory(d):
+ return not ('/.svn/' in d or d.endswith('/.svn'))
+
def gather_filenames(arglist):
"""
Collect script files from within directories.
@@ -472,12 +475,11 @@
if os.path.isdir(filename):
thislist = []
for (dirpath, dirnames, filenames) in os.walk(filename):
- if '.svn' in dirpath: # ignore subversion files
- continue
- for f in filenames:
- if _is_valid_filename(f):
- f = os.path.join(dirpath, f)
- thislist.append(f)
+ if _is_valid_directory(dirpath):
+ for f in filenames:
+ if _is_valid_filename(f):
+ f = os.path.join(dirpath, f)
+ thislist.append(f)
thislist.sort()
l.extend(thislist)