Adrian Sampson avatar Adrian Sampson committed a642003

path_sep_replace config option

I also took this opportunity to move and rename util.santize_for_path to
library.format_for_path, which was long overdue.

Comments (0)

Files changed (5)

beets/config_default.yaml

     '[<>:"\?\*\|]': _
     '\.$': _
     '\s+$': ''
+path_sep_replace: _
 art_filename: cover
 
 plugins: []
 from beets.util import bytestring_path, syspath, normpath, samefile,\
     displayable_path
 from beets.util.functemplate import Template
+import beets
 
 MAX_FILENAME_LENGTH = 200
 
         return False
     return res is not None
 
+# Path element formatting for templating.
+def format_for_path(value, key=None, pathmod=None):
+    """Sanitize the value for inclusion in a path: replace separators
+    with _, etc. Doesn't guarantee that the whole path will be valid;
+    you should still call `util.sanitize_path` on the complete path.
+    """
+    pathmod = pathmod or os.path
+
+    if isinstance(value, basestring):
+        for sep in (pathmod.sep, pathmod.altsep):
+            if sep:
+                value = value.replace(
+                    sep,
+                    beets.config['path_sep_replace'].get(unicode),
+                )
+    elif key in ('track', 'tracktotal', 'disc', 'disctotal'):
+        # Pad indices with zeros.
+        value = u'%02i' % (value or 0)
+    elif key == 'year':
+        value = u'%04i' % (value or 0)
+    elif key in ('month', 'day'):
+        value = u'%02i' % (value or 0)
+    elif key == 'bitrate':
+        # Bitrate gets formatted as kbps.
+        value = u'%ikbps' % ((value or 0) // 1000)
+    elif key == 'samplerate':
+        # Sample rate formatted as kHz.
+        value = u'%ikHz' % ((value or 0) // 1000)
+    else:
+        value = unicode(value)
+
+    return value
+
 
 # Exceptions.
 
                 # From Item.
                 value = getattr(self, key)
             if sanitize:
-                value = util.sanitize_for_path(value, pathmod, key)
+                value = format_for_path(value, key, pathmod)
             mapping[key] = value
 
         # Additional fields in non-sanitized case.
         # Get values from plugins.
         for key, value in plugins.template_values(self).iteritems():
             if sanitize:
-                value = util.sanitize_for_path(value, pathmod, key)
+                value = format_for_path(value, key, pathmod)
             mapping[key] = value
 
         # Get template functions.
         if not isinstance(self._library.art_filename,Template):
             self._library.art_filename = Template(self._library.art_filename)
 
-        subpath = util.sanitize_path(util.sanitize_for_path(
+        subpath = util.sanitize_path(format_for_path(
             self.evaluate_template(self._library.art_filename)
         ))
         subpath = bytestring_path(subpath)
             return res
 
         # Flatten disambiguation value into a string.
-        disam_value = util.sanitize_for_path(getattr(album, disambiguator),
-                                             self.pathmod, disambiguator)
+        disam_value = format_for_path(getattr(album, disambiguator),
+                                      disambiguator, self.pathmod)
         res = u' [{0}]'.format(disam_value)
         self.lib._memotable[memokey] = res
         return res

beets/util/__init__.py

 
     return pathmod.join(*out)
 
-def sanitize_for_path(value, pathmod=None, key=None):
-    """Sanitize the value for inclusion in a path: replace separators
-    with _, etc. Doesn't guarantee that the whole path will be valid;
-    you should still call sanitize_path on the complete path.
-    """
-    pathmod = pathmod or os.path
-
-    if isinstance(value, basestring):
-        for sep in (pathmod.sep, pathmod.altsep):
-            if sep:
-                value = value.replace(sep, u'_')
-    elif key in ('track', 'tracktotal', 'disc', 'disctotal'):
-        # Pad indices with zeros.
-        value = u'%02i' % (value or 0)
-    elif key == 'year':
-        value = u'%04i' % (value or 0)
-    elif key in ('month', 'day'):
-        value = u'%02i' % (value or 0)
-    elif key == 'bitrate':
-        # Bitrate gets formatted as kbps.
-        value = u'%ikbps' % ((value or 0) // 1000)
-    elif key == 'samplerate':
-        # Sample rate formatted as kHz.
-        value = u'%ikHz' % ((value or 0) // 1000)
-    else:
-        value = unicode(value)
-
-    return value
-
 def str2bool(value):
     """Returns a boolean reflecting a human-entered string."""
     if value.lower() in ('yes', '1', 'true', 't', 'y'):

docs/changelog.rst

 * :doc:`/plugins/lastgenre`: A new configuration option lets you choose to
   retrieve artist-level tags as genres instead of album- or track-level tags.
   Thanks to Peter Fern and Peter Schnebel.
+* You can now customize the character substituted for path separators (e.g., /)
+  in filenames via ``path_sep_replace``. The default is an underscore. Use this
+  setting with caution.
 
 Other new stuff:
 
 
     def test_component_sanitize_replaces_separators(self):
         name = posixpath.join('a', 'b')
-        newname = util.sanitize_for_path(name, posixpath)
+        newname = beets.library.format_for_path(name, None, posixpath)
         self.assertNotEqual(name, newname)
 
     def test_component_sanitize_pads_with_zero(self):
-        name = util.sanitize_for_path(1, posixpath, 'track')
+        name = beets.library.format_for_path(1, 'track', posixpath)
         self.assertTrue(name.startswith('0'))
 
     def test_component_sanitize_uses_kbps_bitrate(self):
-        val = util.sanitize_for_path(12345, posixpath, 'bitrate')
+        val = beets.library.format_for_path(12345, 'bitrate', posixpath)
         self.assertEqual(val, u'12kbps')
 
     def test_component_sanitize_uses_khz_samplerate(self):
-        val = util.sanitize_for_path(12345, posixpath, 'samplerate')
+        val = beets.library.format_for_path(12345, 'samplerate', posixpath)
         self.assertEqual(val, u'12kHz')
 
     def test_artist_falls_back_to_albumartist(self):
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.