[MERGE] merged lp:~openerp/openobject-server/trunk/

bzr revid: hmo@tinyerp.com-20100531155256-5zcd5g9p910ffujy
This commit is contained in:
Harry (Open ERP)
2010-05-31 21:22:56 +05:30
19 changed files with 8299 additions and 89 deletions
+2 -8
View File
@@ -3,6 +3,7 @@
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
# Copyright (C) 2010 OpenERP s.a. (<http://openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@@ -716,14 +717,7 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, **kwargs):
for kind in ('init', 'update'):
if package.state=='to upgrade':
# upgrading the module information
modobj.write(cr, 1, [mid], {
'description': package.data.get('description', ''),
'shortdesc': package.data.get('name', ''),
'author': package.data.get('author', 'Unknown'),
'website': package.data.get('website', ''),
'license': package.data.get('license', 'GPL-2'),
'certificate': package.data.get('certificate') or None,
})
modobj.write(cr, 1, [mid], modobj.get_values_from_terp(package.data))
load_init_update_xml(cr, m, idref, mode, kind)
load_data(cr, m, idref, mode)
if hasattr(package, 'demo') or (package.dbdemo and package.state != 'installed'):
+13 -2
View File
@@ -3,6 +3,7 @@
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
# Copyright (C) 2010 OpenERP s.a. (<http://openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@@ -25,7 +26,8 @@
'version': '1.2',
'category': 'Generic Modules/Base',
'description': """The kernel of OpenERP, needed for all installation.""",
'author': 'Tiny',
'author': 'OpenERP s.a.',
'maintainer': 'OpenERP s.a.',
'website': 'http://www.openerp.com',
'depends': [],
'init_xml': [
@@ -67,7 +69,16 @@
'security/ir.model.access.csv'
],
'demo_xml': ['base_demo.xml', 'res/partner/partner_demo.xml', 'res/partner/crm_demo.xml','test/base_test.xml'],
'demo_xml': [
'base_demo.xml',
'res/partner/partner_demo.xml',
'res/partner/crm_demo.xml',
],
'test': [
'test/base_test.xml',
'test/test_context.xml',
'bug_lp541545.xml',
],
'installable': True,
'active': True,
'certificate': '0076807797149',
+12 -1
View File
@@ -1293,7 +1293,18 @@
<field name="currency_id" ref="INR"/>
<field eval="time.strftime('%Y-01-01')" name="name"/>
</record>
<record id="rateINR2001" model="res.currency.rate">
<field name="rate">0.631</field>
<field name="currency_id" ref="INR"/>
<field name="name">2001-01-01</field>
</record>
<record id="rateINR2002" model="res.currency.rate">
<field name="rate">0.632</field>
<field name="currency_id" ref="INR"/>
<field name="name">2002-01-01</field>
</record>
<record id="AUD" model="res.currency">
<field name="name">AUD</field>
<field name="code">AUD</field>
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -7,13 +7,13 @@ msgstr ""
"Project-Id-Version: OpenERP Server 5.0.4\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2009-12-18 08:39+0000\n"
"PO-Revision-Date: 2010-05-29 04:20+0000\n"
"PO-Revision-Date: 2010-05-30 04:12+0000\n"
"Last-Translator: goranc <goranc@gmail.com>\n"
"Language-Team: openerp-translators\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2010-05-30 03:44+0000\n"
"X-Launchpad-Export-Date: 2010-05-31 03:46+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
"Language: hr\n"
+2 -2
View File
@@ -8,13 +8,13 @@ msgstr ""
"Project-Id-Version: openobject-server\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2009-12-18 08:39+0000\n"
"PO-Revision-Date: 2010-05-29 04:21+0000\n"
"PO-Revision-Date: 2010-05-30 04:11+0000\n"
"Last-Translator: Harry (Open ERP) <hmo@tinyerp.com>\n"
"Language-Team: Japanese <ja@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2010-05-30 03:43+0000\n"
"X-Launchpad-Export-Date: 2010-05-31 03:46+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
#. module: base
+2 -2
View File
@@ -7,13 +7,13 @@ msgstr ""
"Project-Id-Version: OpenERP Server 5.0.0\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2009-12-18 08:39+0000\n"
"PO-Revision-Date: 2010-05-29 04:18+0000\n"
"PO-Revision-Date: 2010-05-30 04:09+0000\n"
"Last-Translator: Anders Wallenquist <anders.wallenquist@vertel.se>\n"
"Language-Team: <>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2010-05-30 03:44+0000\n"
"X-Launchpad-Export-Date: 2010-05-31 03:46+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
#. module: base
+2 -2
View File
@@ -7,13 +7,13 @@ msgstr ""
"Project-Id-Version: OpenERP Server 5.0.0\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2009-12-18 08:39+0000\n"
"PO-Revision-Date: 2010-05-29 04:19+0000\n"
"PO-Revision-Date: 2010-05-30 04:13+0000\n"
"Last-Translator: Fabien (Open ERP) <fp@tinyerp.com>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2010-05-30 03:44+0000\n"
"X-Launchpad-Export-Date: 2010-05-31 03:46+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
#. module: base
+2 -2
View File
@@ -7,13 +7,13 @@ msgstr ""
"Project-Id-Version: OpenERP Server 5.0.4\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2009-12-18 08:39+0000\n"
"PO-Revision-Date: 2010-05-29 04:22+0000\n"
"PO-Revision-Date: 2010-05-30 04:11+0000\n"
"Last-Translator: OpenERP Administrators <Unknown>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2010-05-30 03:44+0000\n"
"X-Launchpad-Export-Date: 2010-05-31 03:47+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
#. module: base
+2 -2
View File
@@ -77,14 +77,14 @@ class ir_ui_menu(osv.osv):
ids = super(ir_ui_menu, self).search(cr, uid, args, super_offset,
super_limit, order,
context=context, count=super_count)
if uid == 1 or count:
if uid == 1:
return ids
if not ids:
if count:
return 0
return []
modelaccess = self.pool.get('ir.model.access')
user_groups = set(self.pool.get('res.users').read(cr, 1, uid, ['groups_id'])['groups_id'])
+56 -35
View File
@@ -3,6 +3,7 @@
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
# Copyright (C) 2010 OpenERP s.a. (<http://openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@@ -23,6 +24,7 @@ import tarfile
import re
import urllib
import os
import imp
import tools
from osv import fields, osv, orm
import zipfile
@@ -122,6 +124,8 @@ class module(osv.osv):
'shortdesc': fields.char('Short Description', size=256, readonly=True, translate=True),
'description': fields.text("Description", readonly=True, translate=True),
'author': fields.char("Author", size=128, readonly=True),
'maintainer': fields.char('Maintainer', size=128, readonly=True),
'contributors': fields.text('Contributors', readonly=True),
'website': fields.char("Website", size=256, readonly=True),
# attention: Incorrect field names !!
@@ -191,6 +195,27 @@ class module(osv.osv):
return super(module, self).unlink(cr, uid, ids, context=context)
@staticmethod
def _check_external_dependencies(terp):
depends = terp.get('external_dependencies')
if not depends:
return
for pydep in depends.get('python', []):
parts = pydep.split('.')
parts.reverse()
path = None
while parts:
part = parts.pop()
try:
f, path, descr = imp.find_module(part, path and [path] or None)
except ImportError:
raise ImportError('No module named %s' % (pydep,))
for binary in depends.get('bin', []):
if tools.find_in_path(binary) is None:
raise Exception('Unable to find %r in path' % (binary,))
def state_update(self, cr, uid, ids, newstate, states_to_update, context={}, level=100):
if level<1:
raise orm.except_orm(_('Error'), _('Recursion error in modules dependencies !'))
@@ -206,6 +231,11 @@ class module(osv.osv):
else:
od = self.browse(cr, uid, ids2)[0]
mdemo = od.demo or mdemo
terp = self.get_module_info(module.name)
try:
self._check_external_dependencies(terp)
except Exception, e:
raise orm.except_orm(_('Error'), _('Unable %s the module "%s" because an external dependencie is not met: %s' % (newstate, module.name, e.args[0])))
if not module.dependencies_id:
mdemo = module.demo
if module.state in states_to_update:
@@ -279,6 +309,19 @@ class module(osv.osv):
self.update_translations(cr, uid, ids)
return True
@staticmethod
def get_values_from_terp(terp):
return {
'description': terp.get('description', ''),
'shortdesc': terp.get('name', ''),
'author': terp.get('author', 'Unknown'),
'maintainer': terp.get('maintainer', False),
'contributors': ', '.join(terp.get('contributors', [])) or False,
'website': terp.get('website', ''),
'license': terp.get('license', 'GPL-2'),
'certificate': terp.get('certificate') or None,
}
# update the list of available packages
def update_list(self, cr, uid, context={}):
res = [0, 0] # [update, add]
@@ -286,46 +329,31 @@ class module(osv.osv):
# iterate through installed modules and mark them as being so
for mod_name in addons.get_modules():
ids = self.search(cr, uid, [('name','=',mod_name)])
terp = self.get_module_info(mod_name)
values = self.get_values_from_terp(terp)
if ids:
id = ids[0]
mod = self.browse(cr, uid, id)
terp = self.get_module_info(mod_name)
if terp.get('installable', True) and mod.state == 'uninstallable':
self.write(cr, uid, id, {'state': 'uninstalled'})
if parse_version(terp.get('version', '')) > parse_version(mod.latest_version or ''):
self.write(cr, uid, id, { 'url': ''})
res[0] += 1
self.write(cr, uid, id, {
'description': terp.get('description', ''),
'shortdesc': terp.get('name', ''),
'author': terp.get('author', 'Unknown'),
'website': terp.get('website', ''),
'license': terp.get('license', 'GPL-2'),
'certificate': terp.get('certificate') or None,
})
self.write(cr, uid, id, values)
cr.execute('DELETE FROM ir_module_module_dependency WHERE module_id = %s', (id,))
self._update_dependencies(cr, uid, ids[0], terp.get('depends', []))
self._update_category(cr, uid, ids[0], terp.get('category', 'Uncategorized'))
continue
mod_path = addons.get_module_path(mod_name)
if mod_path:
terp = self.get_module_info(mod_name)
else:
mod_path = addons.get_module_path(mod_name)
if not mod_path:
continue
if not terp or not terp.get('installable', True):
continue
id = self.create(cr, uid, {
'name': mod_name,
'state': 'uninstalled',
'description': terp.get('description', ''),
'shortdesc': terp.get('name', ''),
'author': terp.get('author', 'Unknown'),
'website': terp.get('website', ''),
'license': terp.get('license', 'GPL-2'),
'certificate': terp.get('certificate') or None,
})
id = self.create(cr, uid, dict(name=mod_name, state='uninstalled', **values))
res[1] += 1
self._update_dependencies(cr, uid, id, terp.get('depends', []))
self._update_category(cr, uid, id, terp.get('category', 'Uncategorized'))
self._update_dependencies(cr, uid, id, terp.get('depends', []))
self._update_category(cr, uid, id, terp.get('category', 'Uncategorized'))
return res
@@ -352,14 +380,7 @@ class module(osv.osv):
except Exception, e:
raise orm.except_orm(_('Error'), _('Can not create the module file:\n %s') % (fname,))
terp = self.get_module_info(mod.name)
self.write(cr, uid, mod.id, {
'description': terp.get('description', ''),
'shortdesc': terp.get('name', ''),
'author': terp.get('author', 'Unknown'),
'website': terp.get('website', ''),
'license': terp.get('license', 'GPL-2'),
'certificate': terp.get('certificate') or None,
})
self.write(cr, uid, mod.id, self.get_values_from_terp(terp))
cr.execute('DELETE FROM ir_module_module_dependency ' \
'WHERE module_id = %s', (mod.id,))
self._update_dependencies(cr, uid, mod.id, terp.get('depends',
+4
View File
@@ -97,7 +97,9 @@
<field name="latest_version"/>
<field name="author" select="2"/>
<field name="website" select="2" widget="url"/>
<field name="maintainer" attrs="{'invisible': [('maintainer', '=', False)]}"/>
<field name="url" widget="url"/>
<field name="contributors" widget="char" attrs="{'invisible': [('contributors', '=', False)]}"/>
<field name="published_version"/>
<field name="license"/>
<field name="demo" readonly="1"/>
@@ -141,6 +143,8 @@
<field name="name"/>
<field name="shortdesc"/>
<field name="author"/>
<field name='maintainer'/>
<field name='contributors'/>
<field name="installed_version"/>
<field name="latest_version"/>
<field name="published_version"/>
+4 -7
View File
@@ -3,6 +3,7 @@
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
# Copyright (C) 2010 OpenERP s.a. (<http://openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@@ -56,7 +57,7 @@ class wizard_install_module(wizard.interface):
terp = mod_obj.get_module_info(module)
if not terp.get('installable', True):
continue
# XXX check if this code is correct...
fm = imp.find_module(module)
try:
@@ -65,12 +66,8 @@ class wizard_install_module(wizard.interface):
if fm[0]:
fm[0].close()
mod_id = mod_obj.create(cr, uid, {
'name': module,
'state': 'uninstalled',
'description': terp.get('description', ''),
'shortdesc': terp.get('name', ''),
'author': terp.get('author', 'Unknown')})
values = mod_obj.get_values_from_terp(terp)
mod_id = mod_obj.create(cr, uid, dict(name=module, state='uninstalled', **values))
dependencies = terp.get('depends', [])
for d in dependencies:
cr.execute('insert into ir_module_module_dependency (module_id,name) values (%s, %s)', (mod_id, d))
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<!-- Test count attribute for assertions -->
<data>
<assert
model="res.currency.rate"
search="[('currency_id', '=', ref('INR'))]"
count="3"
string="Rate entries for Indian rupee">
<test expr="True"/>
</assert>
</data>
</openerp>
+20
View File
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<!-- Test context attribute for unit tests -->
<data context="{'date': '2001-06-01'}">
<assert
model="res.currency"
id="INR"
string="Indian rupee rate in 2001">
<!-- date specified in data element's context -->
<test expr="str(rate)">0.631</test>
</assert>
<assert
model="res.currency"
id="INR"
string="Indian rupee rate in 2002"
context="{'date': '2002-06-01'}">
<test expr="str(rate)">0.632</test>
</assert>
</data>
</openerp>
+4
View File
@@ -32,6 +32,7 @@
<rng:attribute name="name" />
<rng:optional><rng:attribute name="id" /></rng:optional>
<rng:optional><rng:attribute name="eval" /></rng:optional>
<rng:optional><rng:attribute name="context" /></rng:optional>
<rng:choice>
<rng:empty />
<rng:oneOrMore>
@@ -51,6 +52,7 @@
<rng:optional><rng:attribute name="count" /></rng:optional>
<rng:optional><rng:attribute name="string" /></rng:optional>
<rng:optional><rng:attribute name="id" /></rng:optional>
<rng:optional><rng:attribute name="context" /></rng:optional>
<rng:optional><rng:attribute name="severity" /></rng:optional>
<rng:zeroOrMore>
<rng:element name="test">
@@ -70,6 +72,7 @@
<rng:optional><rng:attribute name="ref" /></rng:optional>
<rng:attribute name="action"/>
<rng:optional><rng:attribute name="uid"/></rng:optional>
<rng:optional><rng:attribute name="context" /></rng:optional>
<rng:choice>
<rng:empty/>
<rng:ref name="value"/>
@@ -205,6 +208,7 @@
<rng:define name="data">
<rng:element name="data">
<rng:optional><rng:attribute name="noupdate" /></rng:optional>
<rng:optional><rng:attribute name="context" /></rng:optional>
<rng:zeroOrMore>
<rng:choice>
<rng:text/>
+10 -10
View File
@@ -71,7 +71,8 @@ class expression(object):
if not reduce(lambda acc, val: acc and (self._is_operator(val) or self._is_leaf(val)), exp, True):
raise ValueError('Bad domain expression: %r' % (exp,))
self.__exp = exp
self.__tables = {} # used to store the table to use for the sql generation. key = index of the leaf
self.__field_tables = {} # used to store the table to use for the sql generation. key = index of the leaf
self.__all_tables = set()
self.__joins = []
self.__main_table = None # 'root' table. set by parse()
self.__DUMMY_LEAF = (1, '=', 1) # a dummy leaf that must not be parsed or sql generated
@@ -103,6 +104,7 @@ class expression(object):
return [(left, 'in', rg(ids, table, parent or table._parent_name))]
self.__main_table = table
self.__all_tables.add(table)
i = -1
while i + 1<len(self.__exp):
@@ -114,19 +116,17 @@ class expression(object):
working_table = table
main_table = table
fargs = left.split('.', 1)
index = i
if fargs[0] in table._inherit_fields:
while True:
field = main_table._columns.get(fargs[0], False)
if field:
working_table = main_table
self.__tables[i] = working_table
self.__field_tables[i] = working_table
break
working_table = main_table.pool.get(main_table._inherit_fields[fargs[0]][0])
if working_table not in self.__tables.values():
self.__joins.append(('%s.%s=%s.%s' % (working_table._table, 'id', main_table._table, main_table._inherits[working_table._name]), working_table._table))
self.__tables[index] = working_table
index += 1
if working_table not in self.__all_tables:
self.__joins.append('%s.%s=%s.%s' % (working_table._table, 'id', main_table._table, main_table._inherits[working_table._name]))
self.__all_tables.add(working_table)
main_table = working_table
field = working_table._columns.get(fargs[0], False)
@@ -433,7 +433,7 @@ class expression(object):
params = []
for i, e in reverse_enumerate(self.__exp):
if self._is_leaf(e, internal=True):
table = self.__tables.get(i, self.__main_table)
table = self.__field_tables.get(i, self.__main_table)
q, p = self.__leaf_to_sql(e, table)
params.insert(0, p)
stack.append(q)
@@ -447,13 +447,13 @@ class expression(object):
stack.append('(%s %s %s)' % (q1, ops[e], q2,))
query = ' AND '.join(reversed(stack))
joins = ' AND '.join(map(lambda j: j[0], self.__joins))
joins = ' AND '.join(self.__joins)
if joins:
query = '(%s) AND (%s)' % (joins, query)
return (query, flatten(params))
def get_tables(self):
return ['"%s"' % t._table for t in set(self.__tables.values()+[self.__main_table])]
return ['"%s"' % t._table for t in self.__all_tables]
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+12 -14
View File
@@ -3,6 +3,7 @@
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
# Copyright (C) 2010 OpenERP s.a. (<http://openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@@ -34,6 +35,7 @@ import socket
import re
from itertools import islice
import threading
from which import which
if sys.version_info[:2] < (2, 4):
@@ -112,23 +114,19 @@ def init_db(cr):
cr.commit()
def find_in_path(name):
if os.name == "nt":
sep = ';'
else:
sep = ':'
path = [dir for dir in os.environ['PATH'].split(sep)
if os.path.isdir(dir)]
for dir in path:
val = os.path.join(dir, name)
if os.path.isfile(val) or os.path.islink(val):
return val
return None
try:
return which(name)
except IOError:
return None
def find_pg_tool(name):
path = None
if config['pg_path'] and config['pg_path'] != 'None':
return os.path.join(config['pg_path'], name)
else:
return find_in_path(name)
path = config['pg_path']
try:
return which(name, path=path)
except IOError:
return None
def exec_pg_command(name, *args):
prog = find_pg_tool(name)
+153
View File
@@ -0,0 +1,153 @@
#!/usr/bin/env python
""" Which - locate a command
* adapted from Brian Curtin's http://bugs.python.org/file15381/shutil_which.patch
* see http://bugs.python.org/issue444582
* uses ``PATHEXT`` on Windows
* searches current directory before ``PATH`` on Windows,
but not before an explicitly passed path
* accepts both string or iterable for an explicitly passed path, or pathext
* accepts an explicitly passed empty path, or pathext (either '' or [])
* does not search ``PATH`` for files that have a path specified in their name already
* moved defpath and defpathext lists initialization to module level,
instead of initializing them on each function call
* changed interface: which_files() returns generator, which() returns first match,
or raises IOError(errno.ENOENT)
.. function:: which_files(file [, mode=os.F_OK | os.X_OK[, path=None[, pathext=None]]])
Return a generator which yields full paths in which the *file* name exists
in a directory that is part of the file name, or on *path*,
and has the given *mode*.
By default, *mode* matches an inclusive OR of os.F_OK and os.X_OK
- an existing executable file.
The *path* is, by default, the ``PATH`` variable on the platform,
or the string/iterable passed in as *path*.
In the event that a ``PATH`` variable is not found, :const:`os.defpath` is used.
On Windows, a current directory is searched before using the ``PATH`` variable,
but not before an explicitly passed *path*.
The *pathext* is only used on Windows to match files with given extensions appended as well.
It defaults to the ``PATHEXT`` variable, or the string/iterable passed in as *pathext*.
In the event that a ``PATHEXT`` variable is not found,
default value for Windows XP/Vista is used.
The command is always searched without extension first,
even when *pathext* is explicitly passed.
.. function:: which(file [, mode=os.F_OK | os.X_OK[, path=None[, pathext=None]]])
Return first match generated by which_files(file, mode, path, pathext),
or raise IOError(errno.ENOENT).
"""
__docformat__ = 'restructuredtext en'
__all__ = 'which which_files pathsep defpath defpathext F_OK R_OK W_OK X_OK'.split()
import sys
from os import access, defpath, pathsep, environ, F_OK, R_OK, W_OK, X_OK
from os.path import exists, dirname, split, join
windows = sys.platform.startswith('win')
defpath = environ.get('PATH', defpath).split(pathsep)
if windows:
defpath.insert(0, '.') # can insert without checking, when duplicates are removed
# given the quite usual mess in PATH on Windows, let's rather remove duplicates
seen = set()
defpath = [dir for dir in defpath if dir.lower() not in seen and not seen.add(dir.lower())]
del seen
defpathext = [''] + environ.get('PATHEXT',
'.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC').lower().split(pathsep)
else:
defpathext = ['']
def which_files(file, mode=F_OK | X_OK, path=None, pathext=None):
""" Locate a file in a path supplied as a part of the file name,
or the user's path, or a supplied path.
The function yields full paths (not necessarily absolute paths),
in which the given file name matches an existing file in a directory on the path.
>>> def test_which(expected, *args, **argd):
... result = list(which_files(*args, **argd))
... assert result == expected, 'which_files: %s != %s' % (result, expected)
...
... try:
... result = [ which(*args, **argd) ]
... except IOError:
... result = []
... assert result[:1] == expected[:1], 'which: %s != %s' % (result[:1], expected[:1])
>>> if windows: cmd = environ['COMSPEC']
>>> if windows: test_which([cmd], 'cmd')
>>> if windows: test_which([cmd], 'cmd.exe')
>>> if windows: test_which([cmd], 'cmd', path=dirname(cmd))
>>> if windows: test_which([cmd], 'cmd', pathext='.exe')
>>> if windows: test_which([cmd], cmd)
>>> if windows: test_which([cmd], cmd, path='<nonexistent>')
>>> if windows: test_which([cmd], cmd, pathext='<nonexistent>')
>>> if windows: test_which([cmd], cmd[:-4])
>>> if windows: test_which([cmd], cmd[:-4], path='<nonexistent>')
>>> if windows: test_which([], 'cmd', path='<nonexistent>')
>>> if windows: test_which([], 'cmd', pathext='<nonexistent>')
>>> if windows: test_which([], '<nonexistent>/cmd')
>>> if windows: test_which([], cmd[:-4], pathext='<nonexistent>')
>>> if not windows: sh = '/bin/sh'
>>> if not windows: test_which([sh], 'sh')
>>> if not windows: test_which([sh], 'sh', path=dirname(sh))
>>> if not windows: test_which([sh], 'sh', pathext='<nonexistent>')
>>> if not windows: test_which([sh], sh)
>>> if not windows: test_which([sh], sh, path='<nonexistent>')
>>> if not windows: test_which([sh], sh, pathext='<nonexistent>')
>>> if not windows: test_which([], 'sh', mode=W_OK) # not running as root, are you?
>>> if not windows: test_which([], 'sh', path='<nonexistent>')
>>> if not windows: test_which([], '<nonexistent>/sh')
"""
filepath, file = split(file)
if filepath:
path = (filepath,)
elif path is None:
path = defpath
elif isinstance(path, str):
path = path.split(pathsep)
if pathext is None:
pathext = defpathext
elif isinstance(pathext, str):
pathext = pathext.split(pathsep)
if not '' in pathext:
pathext.insert(0, '') # always check command without extension, even for custom pathext
for dir in path:
basepath = join(dir, file)
for ext in pathext:
fullpath = basepath + ext
if exists(fullpath) and access(fullpath, mode):
yield fullpath
def which(file, mode=F_OK | X_OK, path=None, pathext=None):
""" Locate a file in a path supplied as a part of the file name,
or the user's path, or a supplied path.
The function returns full path (not necessarily absolute path),
in which the given file name matches an existing file in a directory on the path,
or raises IOError(errno.ENOENT).
>>> # for doctest see which_files()
"""
try:
return iter(which_files(file, mode, path, pathext)).next()
except StopIteration:
try:
from errno import ENOENT
except ImportError:
ENOENT = 2
raise IOError(ENOENT, '%s not found' % (mode & X_OK and 'command' or 'file'), file)
if __name__ == '__main__':
import doctest
doctest.testmod()