We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Fileupload removes underscores from filenames
There doesn't seem to be any reason to forbid underscores in filenames, especially since munge allows them elsewhere. Looks like someone just forgot to add an underscore to the replace function. Have a PR all ready to go with fix and updated test, just need to know what branch to submit it to ;)


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `ckan/lib/munge.py`
Content:
```
1 # Note these functions are similar to, but separate from name/title mungers
2 # found in the ckanext importer. That one needs to be stable to prevent
3 # packages changing name on reimport, but these ones can be changed and
4 # improved.
5 
6 import re
7 import os.path
8 
9 from ckan import model
10 
11 
12 def munge_name(name):
13     '''Munges the package name field in case it is not to spec.'''
14     # substitute non-ascii characters
15     if isinstance(name, unicode):
16         name = substitute_ascii_equivalents(name)
17     # separators become dashes
18     name = re.sub('[ .:/]', '-', name)
19     # take out not-allowed characters
20     name = re.sub('[^a-zA-Z0-9-_]', '', name).lower()
21     # keep it within the length spec
22     name = _munge_to_length(name, model.PACKAGE_NAME_MIN_LENGTH,
23                             model.PACKAGE_NAME_MAX_LENGTH)
24     return name
25 
26 
27 def munge_title_to_name(name):
28     '''Munge a package title into a package name.'''
29     # substitute non-ascii characters
30     if isinstance(name, unicode):
31         name = substitute_ascii_equivalents(name)
32     # convert spaces and separators
33     name = re.sub('[ .:/]', '-', name)
34     # take out not-allowed characters
35     name = re.sub('[^a-zA-Z0-9-_]', '', name).lower()
36     # remove doubles
37     name = re.sub('--', '-', name)
38     # remove leading or trailing hyphens
39     name = name.strip('-')
40     # if longer than max_length, keep last word if a year
41     max_length = model.PACKAGE_NAME_MAX_LENGTH - 5
42     # (make length less than max, in case we need a few for '_' chars
43     # to de-clash names.)
44     if len(name) > max_length:
45         year_match = re.match('.*?[_-]((?:\d{2,4}[-/])?\d{2,4})$', name)
46         if year_match:
47             year = year_match.groups()[0]
48             name = '%s-%s' % (name[:(max_length-len(year)-1)], year)
49         else:
50             name = name[:max_length]
51     name = _munge_to_length(name, model.PACKAGE_NAME_MIN_LENGTH,
52                             model.PACKAGE_NAME_MAX_LENGTH)
53     return name
54 
55 
56 def substitute_ascii_equivalents(text_unicode):
57     # Method taken from: http://code.activestate.com/recipes/251871/
58     """
59     This takes a UNICODE string and replaces Latin-1 characters with something
60     equivalent in 7-bit ASCII. It returns a plain ASCII string. This function
61     makes a best effort to convert Latin-1 characters into ASCII equivalents.
62     It does not just strip out the Latin-1 characters. All characters in the
63     standard 7-bit ASCII range are preserved. In the 8th bit range all the
64     Latin-1 accented letters are converted to unaccented equivalents. Most
65     symbol characters are converted to something meaningful. Anything not
66     converted is deleted.
67     """
68     char_mapping = {
69         0xc0: 'A', 0xc1: 'A', 0xc2: 'A', 0xc3: 'A', 0xc4: 'A', 0xc5: 'A',
70         0xc6: 'Ae', 0xc7: 'C',
71         0xc8: 'E', 0xc9: 'E', 0xca: 'E', 0xcb: 'E',
72         0xcc: 'I', 0xcd: 'I', 0xce: 'I', 0xcf: 'I',
73         0xd0: 'Th', 0xd1: 'N',
74         0xd2: 'O', 0xd3: 'O', 0xd4: 'O', 0xd5: 'O', 0xd6: 'O', 0xd8: 'O',
75         0xd9: 'U', 0xda: 'U', 0xdb: 'U', 0xdc: 'U',
76         0xdd: 'Y', 0xde: 'th', 0xdf: 'ss',
77         0xe0: 'a', 0xe1: 'a', 0xe2: 'a', 0xe3: 'a', 0xe4: 'a', 0xe5: 'a',
78         0xe6: 'ae', 0xe7: 'c',
79         0xe8: 'e', 0xe9: 'e', 0xea: 'e', 0xeb: 'e',
80         0xec: 'i', 0xed: 'i', 0xee: 'i', 0xef: 'i',
81         0xf0: 'th', 0xf1: 'n',
82         0xf2: 'o', 0xf3: 'o', 0xf4: 'o', 0xf5: 'o', 0xf6: 'o', 0xf8: 'o',
83         0xf9: 'u', 0xfa: 'u', 0xfb: 'u', 0xfc: 'u',
84         0xfd: 'y', 0xfe: 'th', 0xff: 'y',
85         #0xa1: '!', 0xa2: '{cent}', 0xa3: '{pound}', 0xa4: '{currency}',
86         #0xa5: '{yen}', 0xa6: '|', 0xa7: '{section}', 0xa8: '{umlaut}',
87         #0xa9: '{C}', 0xaa: '{^a}', 0xab: '<<', 0xac: '{not}',
88         #0xad: '-', 0xae: '{R}', 0xaf: '_', 0xb0: '{degrees}',
89         #0xb1: '{+/-}', 0xb2: '{^2}', 0xb3: '{^3}', 0xb4:"'",
90         #0xb5: '{micro}', 0xb6: '{paragraph}', 0xb7: '*', 0xb8: '{cedilla}',
91         #0xb9: '{^1}', 0xba: '{^o}', 0xbb: '>>',
92         #0xbc: '{1/4}', 0xbd: '{1/2}', 0xbe: '{3/4}', 0xbf: '?',
93         #0xd7: '*', 0xf7: '/'
94     }
95 
96     r = ''
97     for char in text_unicode:
98         if ord(char) in char_mapping:
99             r += char_mapping[ord(char)]
100         elif ord(char) >= 0x80:
101             pass
102         else:
103             r += str(char)
104     return r
105 
106 
107 def munge_tag(tag):
108     tag = substitute_ascii_equivalents(tag)
109     tag = tag.lower().strip()
110     tag = re.sub(r'[^a-zA-Z0-9\- ]', '', tag).replace(' ', '-')
111     tag = _munge_to_length(tag, model.MIN_TAG_LENGTH, model.MAX_TAG_LENGTH)
112     return tag
113 
114 
115 def munge_filename_legacy(filename):
116     ''' Tidies a filename. NB: deprecated
117 
118     Unfortunately it mangles any path or filename extension, so is deprecated.
119     It needs to remain unchanged for use by group_dictize() and
120     Upload.update_data_dict() because if this routine changes then group images
121     uploaded previous to the change may not be viewable.
122     '''
123     filename = substitute_ascii_equivalents(filename)
124     filename = filename.strip()
125     filename = re.sub(r'[^a-zA-Z0-9.\- ]', '', filename).replace(' ', '-')
126     filename = _munge_to_length(filename, 3, 100)
127     return filename
128 
129 
130 def munge_filename(filename):
131     ''' Tidies a filename
132 
133     Keeps the filename extension (e.g. .csv).
134     Strips off any path on the front.
135     '''
136 
137     # just get the filename ignore the path
138     path, filename = os.path.split(filename)
139     # clean up
140     filename = substitute_ascii_equivalents(filename)
141     filename = filename.lower().strip()
142     filename = re.sub(r'[^a-zA-Z0-9. -]', '', filename).replace(' ', '-')
143     # resize if needed but keep extension
144     name, ext = os.path.splitext(filename)
145     # limit overly long extensions
146     if len(ext) > 21:
147         ext = ext[:21]
148     # max/min size
149     ext_length = len(ext)
150     name = _munge_to_length(name, max(3 - ext_length, 1), 100 - ext_length)
151     filename = name + ext
152 
153     return filename
154 
155 
156 def _munge_to_length(string, min_length, max_length):
157     '''Pad/truncates a string'''
158     if len(string) < min_length:
159         string += '_' * (min_length - len(string))
160     if len(string) > max_length:
161         string = string[:max_length]
162     return string
163
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/ckan/lib/munge.py b/ckan/lib/munge.py
--- a/ckan/lib/munge.py
+++ b/ckan/lib/munge.py
@@ -139,7 +139,7 @@
     # clean up
     filename = substitute_ascii_equivalents(filename)
     filename = filename.lower().strip()
-    filename = re.sub(r'[^a-zA-Z0-9. -]', '', filename).replace(' ', '-')
+    filename = re.sub(r'[^a-zA-Z0-9_. -]', '', filename).replace(' ', '-')
     # resize if needed but keep extension
     name, ext = os.path.splitext(filename)
     # limit overly long extensions

{"golden_diff": "diff --git a/ckan/lib/munge.py b/ckan/lib/munge.py\n--- a/ckan/lib/munge.py\n+++ b/ckan/lib/munge.py\n@@ -139,7 +139,7 @@\n     # clean up\n     filename = substitute_ascii_equivalents(filename)\n     filename = filename.lower().strip()\n-    filename = re.sub(r'[^a-zA-Z0-9. -]', '', filename).replace(' ', '-')\n+    filename = re.sub(r'[^a-zA-Z0-9_. -]', '', filename).replace(' ', '-')\n     # resize if needed but keep extension\n     name, ext = os.path.splitext(filename)\n     # limit overly long extensions\n", "issue": "Fileupload removes underscores from filenames\nThere doesn't seem to be any reason to forbid underscores in filenames, especially since munge allows them elsewhere. Looks like someone just forgot to add an underscore to the replace function. Have a PR all ready to go with fix and updated test, just need to know what branch to submit it to ;)\n\n", "before_files": [{"content": "# Note these functions are similar to, but separate from name/title mungers\n# found in the ckanext importer. That one needs to be stable to prevent\n# packages changing name on reimport, but these ones can be changed and\n# improved.\n\nimport re\nimport os.path\n\nfrom ckan import model\n\n\ndef munge_name(name):\n    '''Munges the package name field in case it is not to spec.'''\n    # substitute non-ascii characters\n    if isinstance(name, unicode):\n        name = substitute_ascii_equivalents(name)\n    # separators become dashes\n    name = re.sub('[ .:/]', '-', name)\n    # take out not-allowed characters\n    name = re.sub('[^a-zA-Z0-9-_]', '', name).lower()\n    # keep it within the length spec\n    name = _munge_to_length(name, model.PACKAGE_NAME_MIN_LENGTH,\n                            model.PACKAGE_NAME_MAX_LENGTH)\n    return name\n\n\ndef munge_title_to_name(name):\n    '''Munge a package title into a package name.'''\n    # substitute non-ascii characters\n    if isinstance(name, unicode):\n        name = substitute_ascii_equivalents(name)\n    # convert spaces and separators\n    name = re.sub('[ .:/]', '-', name)\n    # take out not-allowed characters\n    name = re.sub('[^a-zA-Z0-9-_]', '', name).lower()\n    # remove doubles\n    name = re.sub('--', '-', name)\n    # remove leading or trailing hyphens\n    name = name.strip('-')\n    # if longer than max_length, keep last word if a year\n    max_length = model.PACKAGE_NAME_MAX_LENGTH - 5\n    # (make length less than max, in case we need a few for '_' chars\n    # to de-clash names.)\n    if len(name) > max_length:\n        year_match = re.match('.*?[_-]((?:\\d{2,4}[-/])?\\d{2,4})$', name)\n        if year_match:\n            year = year_match.groups()[0]\n            name = '%s-%s' % (name[:(max_length-len(year)-1)], year)\n        else:\n            name = name[:max_length]\n    name = _munge_to_length(name, model.PACKAGE_NAME_MIN_LENGTH,\n                            model.PACKAGE_NAME_MAX_LENGTH)\n    return name\n\n\ndef substitute_ascii_equivalents(text_unicode):\n    # Method taken from: http://code.activestate.com/recipes/251871/\n    \"\"\"\n    This takes a UNICODE string and replaces Latin-1 characters with something\n    equivalent in 7-bit ASCII. It returns a plain ASCII string. This function\n    makes a best effort to convert Latin-1 characters into ASCII equivalents.\n    It does not just strip out the Latin-1 characters. All characters in the\n    standard 7-bit ASCII range are preserved. In the 8th bit range all the\n    Latin-1 accented letters are converted to unaccented equivalents. Most\n    symbol characters are converted to something meaningful. Anything not\n    converted is deleted.\n    \"\"\"\n    char_mapping = {\n        0xc0: 'A', 0xc1: 'A', 0xc2: 'A', 0xc3: 'A', 0xc4: 'A', 0xc5: 'A',\n        0xc6: 'Ae', 0xc7: 'C',\n        0xc8: 'E', 0xc9: 'E', 0xca: 'E', 0xcb: 'E',\n        0xcc: 'I', 0xcd: 'I', 0xce: 'I', 0xcf: 'I',\n        0xd0: 'Th', 0xd1: 'N',\n        0xd2: 'O', 0xd3: 'O', 0xd4: 'O', 0xd5: 'O', 0xd6: 'O', 0xd8: 'O',\n        0xd9: 'U', 0xda: 'U', 0xdb: 'U', 0xdc: 'U',\n        0xdd: 'Y', 0xde: 'th', 0xdf: 'ss',\n        0xe0: 'a', 0xe1: 'a', 0xe2: 'a', 0xe3: 'a', 0xe4: 'a', 0xe5: 'a',\n        0xe6: 'ae', 0xe7: 'c',\n        0xe8: 'e', 0xe9: 'e', 0xea: 'e', 0xeb: 'e',\n        0xec: 'i', 0xed: 'i', 0xee: 'i', 0xef: 'i',\n        0xf0: 'th', 0xf1: 'n',\n        0xf2: 'o', 0xf3: 'o', 0xf4: 'o', 0xf5: 'o', 0xf6: 'o', 0xf8: 'o',\n        0xf9: 'u', 0xfa: 'u', 0xfb: 'u', 0xfc: 'u',\n        0xfd: 'y', 0xfe: 'th', 0xff: 'y',\n        #0xa1: '!', 0xa2: '{cent}', 0xa3: '{pound}', 0xa4: '{currency}',\n        #0xa5: '{yen}', 0xa6: '|', 0xa7: '{section}', 0xa8: '{umlaut}',\n        #0xa9: '{C}', 0xaa: '{^a}', 0xab: '<<', 0xac: '{not}',\n        #0xad: '-', 0xae: '{R}', 0xaf: '_', 0xb0: '{degrees}',\n        #0xb1: '{+/-}', 0xb2: '{^2}', 0xb3: '{^3}', 0xb4:\"'\",\n        #0xb5: '{micro}', 0xb6: '{paragraph}', 0xb7: '*', 0xb8: '{cedilla}',\n        #0xb9: '{^1}', 0xba: '{^o}', 0xbb: '>>',\n        #0xbc: '{1/4}', 0xbd: '{1/2}', 0xbe: '{3/4}', 0xbf: '?',\n        #0xd7: '*', 0xf7: '/'\n    }\n\n    r = ''\n    for char in text_unicode:\n        if ord(char) in char_mapping:\n            r += char_mapping[ord(char)]\n        elif ord(char) >= 0x80:\n            pass\n        else:\n            r += str(char)\n    return r\n\n\ndef munge_tag(tag):\n    tag = substitute_ascii_equivalents(tag)\n    tag = tag.lower().strip()\n    tag = re.sub(r'[^a-zA-Z0-9\\- ]', '', tag).replace(' ', '-')\n    tag = _munge_to_length(tag, model.MIN_TAG_LENGTH, model.MAX_TAG_LENGTH)\n    return tag\n\n\ndef munge_filename_legacy(filename):\n    ''' Tidies a filename. NB: deprecated\n\n    Unfortunately it mangles any path or filename extension, so is deprecated.\n    It needs to remain unchanged for use by group_dictize() and\n    Upload.update_data_dict() because if this routine changes then group images\n    uploaded previous to the change may not be viewable.\n    '''\n    filename = substitute_ascii_equivalents(filename)\n    filename = filename.strip()\n    filename = re.sub(r'[^a-zA-Z0-9.\\- ]', '', filename).replace(' ', '-')\n    filename = _munge_to_length(filename, 3, 100)\n    return filename\n\n\ndef munge_filename(filename):\n    ''' Tidies a filename\n\n    Keeps the filename extension (e.g. .csv).\n    Strips off any path on the front.\n    '''\n\n    # just get the filename ignore the path\n    path, filename = os.path.split(filename)\n    # clean up\n    filename = substitute_ascii_equivalents(filename)\n    filename = filename.lower().strip()\n    filename = re.sub(r'[^a-zA-Z0-9. -]', '', filename).replace(' ', '-')\n    # resize if needed but keep extension\n    name, ext = os.path.splitext(filename)\n    # limit overly long extensions\n    if len(ext) > 21:\n        ext = ext[:21]\n    # max/min size\n    ext_length = len(ext)\n    name = _munge_to_length(name, max(3 - ext_length, 1), 100 - ext_length)\n    filename = name + ext\n\n    return filename\n\n\ndef _munge_to_length(string, min_length, max_length):\n    '''Pad/truncates a string'''\n    if len(string) < min_length:\n        string += '_' * (min_length - len(string))\n    if len(string) > max_length:\n        string = string[:max_length]\n    return string\n", "path": "ckan/lib/munge.py"}], "after_files": [{"content": "# Note these functions are similar to, but separate from name/title mungers\n# found in the ckanext importer. That one needs to be stable to prevent\n# packages changing name on reimport, but these ones can be changed and\n# improved.\n\nimport re\nimport os.path\n\nfrom ckan import model\n\n\ndef munge_name(name):\n    '''Munges the package name field in case it is not to spec.'''\n    # substitute non-ascii characters\n    if isinstance(name, unicode):\n        name = substitute_ascii_equivalents(name)\n    # separators become dashes\n    name = re.sub('[ .:/]', '-', name)\n    # take out not-allowed characters\n    name = re.sub('[^a-zA-Z0-9-_]', '', name).lower()\n    # keep it within the length spec\n    name = _munge_to_length(name, model.PACKAGE_NAME_MIN_LENGTH,\n                            model.PACKAGE_NAME_MAX_LENGTH)\n    return name\n\n\ndef munge_title_to_name(name):\n    '''Munge a package title into a package name.'''\n    # substitute non-ascii characters\n    if isinstance(name, unicode):\n        name = substitute_ascii_equivalents(name)\n    # convert spaces and separators\n    name = re.sub('[ .:/]', '-', name)\n    # take out not-allowed characters\n    name = re.sub('[^a-zA-Z0-9-_]', '', name).lower()\n    # remove doubles\n    name = re.sub('--', '-', name)\n    # remove leading or trailing hyphens\n    name = name.strip('-')\n    # if longer than max_length, keep last word if a year\n    max_length = model.PACKAGE_NAME_MAX_LENGTH - 5\n    # (make length less than max, in case we need a few for '_' chars\n    # to de-clash names.)\n    if len(name) > max_length:\n        year_match = re.match('.*?[_-]((?:\\d{2,4}[-/])?\\d{2,4})$', name)\n        if year_match:\n            year = year_match.groups()[0]\n            name = '%s-%s' % (name[:(max_length-len(year)-1)], year)\n        else:\n            name = name[:max_length]\n    name = _munge_to_length(name, model.PACKAGE_NAME_MIN_LENGTH,\n                            model.PACKAGE_NAME_MAX_LENGTH)\n    return name\n\n\ndef substitute_ascii_equivalents(text_unicode):\n    # Method taken from: http://code.activestate.com/recipes/251871/\n    \"\"\"\n    This takes a UNICODE string and replaces Latin-1 characters with something\n    equivalent in 7-bit ASCII. It returns a plain ASCII string. This function\n    makes a best effort to convert Latin-1 characters into ASCII equivalents.\n    It does not just strip out the Latin-1 characters. All characters in the\n    standard 7-bit ASCII range are preserved. In the 8th bit range all the\n    Latin-1 accented letters are converted to unaccented equivalents. Most\n    symbol characters are converted to something meaningful. Anything not\n    converted is deleted.\n    \"\"\"\n    char_mapping = {\n        0xc0: 'A', 0xc1: 'A', 0xc2: 'A', 0xc3: 'A', 0xc4: 'A', 0xc5: 'A',\n        0xc6: 'Ae', 0xc7: 'C',\n        0xc8: 'E', 0xc9: 'E', 0xca: 'E', 0xcb: 'E',\n        0xcc: 'I', 0xcd: 'I', 0xce: 'I', 0xcf: 'I',\n        0xd0: 'Th', 0xd1: 'N',\n        0xd2: 'O', 0xd3: 'O', 0xd4: 'O', 0xd5: 'O', 0xd6: 'O', 0xd8: 'O',\n        0xd9: 'U', 0xda: 'U', 0xdb: 'U', 0xdc: 'U',\n        0xdd: 'Y', 0xde: 'th', 0xdf: 'ss',\n        0xe0: 'a', 0xe1: 'a', 0xe2: 'a', 0xe3: 'a', 0xe4: 'a', 0xe5: 'a',\n        0xe6: 'ae', 0xe7: 'c',\n        0xe8: 'e', 0xe9: 'e', 0xea: 'e', 0xeb: 'e',\n        0xec: 'i', 0xed: 'i', 0xee: 'i', 0xef: 'i',\n        0xf0: 'th', 0xf1: 'n',\n        0xf2: 'o', 0xf3: 'o', 0xf4: 'o', 0xf5: 'o', 0xf6: 'o', 0xf8: 'o',\n        0xf9: 'u', 0xfa: 'u', 0xfb: 'u', 0xfc: 'u',\n        0xfd: 'y', 0xfe: 'th', 0xff: 'y',\n        #0xa1: '!', 0xa2: '{cent}', 0xa3: '{pound}', 0xa4: '{currency}',\n        #0xa5: '{yen}', 0xa6: '|', 0xa7: '{section}', 0xa8: '{umlaut}',\n        #0xa9: '{C}', 0xaa: '{^a}', 0xab: '<<', 0xac: '{not}',\n        #0xad: '-', 0xae: '{R}', 0xaf: '_', 0xb0: '{degrees}',\n        #0xb1: '{+/-}', 0xb2: '{^2}', 0xb3: '{^3}', 0xb4:\"'\",\n        #0xb5: '{micro}', 0xb6: '{paragraph}', 0xb7: '*', 0xb8: '{cedilla}',\n        #0xb9: '{^1}', 0xba: '{^o}', 0xbb: '>>',\n        #0xbc: '{1/4}', 0xbd: '{1/2}', 0xbe: '{3/4}', 0xbf: '?',\n        #0xd7: '*', 0xf7: '/'\n    }\n\n    r = ''\n    for char in text_unicode:\n        if ord(char) in char_mapping:\n            r += char_mapping[ord(char)]\n        elif ord(char) >= 0x80:\n            pass\n        else:\n            r += str(char)\n    return r\n\n\ndef munge_tag(tag):\n    tag = substitute_ascii_equivalents(tag)\n    tag = tag.lower().strip()\n    tag = re.sub(r'[^a-zA-Z0-9\\- ]', '', tag).replace(' ', '-')\n    tag = _munge_to_length(tag, model.MIN_TAG_LENGTH, model.MAX_TAG_LENGTH)\n    return tag\n\n\ndef munge_filename_legacy(filename):\n    ''' Tidies a filename. NB: deprecated\n\n    Unfortunately it mangles any path or filename extension, so is deprecated.\n    It needs to remain unchanged for use by group_dictize() and\n    Upload.update_data_dict() because if this routine changes then group images\n    uploaded previous to the change may not be viewable.\n    '''\n    filename = substitute_ascii_equivalents(filename)\n    filename = filename.strip()\n    filename = re.sub(r'[^a-zA-Z0-9.\\- ]', '', filename).replace(' ', '-')\n    filename = _munge_to_length(filename, 3, 100)\n    return filename\n\n\ndef munge_filename(filename):\n    ''' Tidies a filename\n\n    Keeps the filename extension (e.g. .csv).\n    Strips off any path on the front.\n    '''\n\n    # just get the filename ignore the path\n    path, filename = os.path.split(filename)\n    # clean up\n    filename = substitute_ascii_equivalents(filename)\n    filename = filename.lower().strip()\n    filename = re.sub(r'[^a-zA-Z0-9_. -]', '', filename).replace(' ', '-')\n    # resize if needed but keep extension\n    name, ext = os.path.splitext(filename)\n    # limit overly long extensions\n    if len(ext) > 21:\n        ext = ext[:21]\n    # max/min size\n    ext_length = len(ext)\n    name = _munge_to_length(name, max(3 - ext_length, 1), 100 - ext_length)\n    filename = name + ext\n\n    return filename\n\n\ndef _munge_to_length(string, min_length, max_length):\n    '''Pad/truncates a string'''\n    if len(string) < min_length:\n        string += '_' * (min_length - len(string))\n    if len(string) > max_length:\n        string = string[:max_length]\n    return string\n", "path": "ckan/lib/munge.py"}]}