README.rst 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. Aka - Rename files in complicated ways easily
  2. =============================================
  3. Abstract
  4. --------
  5. This package provides a command line utility called ``aka`` for swiftly renaming (or copying) files using Python code.
  6. This makes it easy to rename files even when the changes you are making are quite complicated. It always
  7. renames files in two passes to avoid collisions; it tries to detect miscellaneous errors in advance; and
  8. if errors occur underways it will put you in an emergency mode to resolve the problem or roll back changes.
  9. It also provides the functions ``aka.rename`` and ``aka.copy``, which is the underlying interface.
  10. The problem being solved
  11. ------------------------
  12. Lets say you have a directory with the files ``File0``, ``File1``, and ``File2``. Then some people comes along and complains
  13. (rightly or wrongly) that the numbering starts at zero. So you decide to write a program to rename all those files, but a
  14. problem arises. You cannot do it in any order you like, you have to start with ``File2 -> File3`` in order to avoid conflicts.
  15. It'd be nice to just write a function that knows how to change the names of individual files and let another program sort out the rest.
  16. This is what ``aka.rename`` is about:
  17. .. code-block:: pycon
  18. >>> import aka
  19. >>> from contex import rules
  20. >>> def machine(fn):
  21. return rules(r'File(\d+)', {1: lambda digit: int(digit) + 1}).apply(fn)
  22. >>> machine('File42')
  23. 'File43'
  24. >>> aka.rename(machine)
  25. Actions to be taken (simplified; doesn't show the temporary stage):
  26. /home/uglemat/Documents/File0 -> /home/uglemat/Documents/File1
  27. /home/uglemat/Documents/File1 -> /home/uglemat/Documents/File2
  28. /home/uglemat/Documents/File2 -> /home/uglemat/Documents/File3
  29. Target directories:
  30. /home/uglemat/Documents
  31. The files will be renamed as shown above (in two passes though, in order to avoid
  32. collisions). This program searched for name conflicts in all target directories
  33. and did not find any. If errors do pop up, you'll be taken to an emergency mode
  34. where you can roll back changes. Continue? [N/y]: y
  35. Renaming /home/uglemat/Documents/File0 -> /tmp/aka_maok91r8/File0
  36. Renaming /home/uglemat/Documents/File1 -> /tmp/aka_maok91r8/File1
  37. Renaming /home/uglemat/Documents/File2 -> /tmp/aka_maok91r8/File2
  38. Renaming /tmp/aka_maok91r8/File0 -> /home/uglemat/Documents/File1
  39. Renaming /tmp/aka_maok91r8/File1 -> /home/uglemat/Documents/File2
  40. Renaming /tmp/aka_maok91r8/File2 -> /home/uglemat/Documents/File3
  41. True
  42. I used `contex.rules <https://pypi.python.org/pypi/contex/>`_ to manipulate the string, but you can do whatever you like inside ``machine``, you
  43. just need to return the new name of the file.
  44. By default it renames files in the current working directory, but that can be changed with the ``location`` argument to ``aka.rename``. ``aka.copy``
  45. is basically the same, it just copies files instead. Read the docstrings of those functions to learn the details.
  46. Command line utility
  47. --------------------
  48. That's all fine and dandy, but when you just have some files and you need to rename them, you want to do it with a command line utility. This is the basics:
  49. .. code-block:: bash
  50. $ aka --help
  51. Useful information ...
  52. $ aka -p 'fn+".jpg"'
  53. That will add a ".jpg" suffix to all files in the working directory. But lets do what we did above with ``aka.rename``:
  54. .. code-block:: bash
  55. $ aka -p 'rules(r"File(\d+)", {1: lambda digit: int(digit) + 1})'
  56. The expression after ``-p`` doesn't need to be a new filename, it can also be a unary callable (like ``machine`` above) that returns the new filename.
  57. That is why the example above works; ``contex.rules`` returns a callable. If you want to copy instead of rename, just add in the ``-c`` option:
  58. .. code-block:: bash
  59. $ aka -c -p 'rules(r"File(\d+)", {1: lambda digit: int(digit) + 1})'
  60. -- COPYING FILES IN . --
  61. ERROR: /home/uglemat/Documents/File1 -> /home/uglemat/Documents/File2 is a conflict!
  62. ERROR: /home/uglemat/Documents/File2 -> /home/uglemat/Documents/File3 is a conflict!
  63. Aborting...
  64. Err, yes, that won't work, of course. Good thing ``aka`` detects naming conflicts in advance!
  65. More complicated renaming schemes
  66. ---------------------------------
  67. That's great, but what if it's not a simple one-liner? Then you need to create a new file,
  68. write some python code, launch the python interpreter, import the stuff you need... It's cumbersome, which is why ``aka`` can help with that:
  69. .. code-block:: bash
  70. $ aka -e emacs
  71. This will launch emacs and take you to a temporary file which looks kind of like this:
  72. .. code-block:: python
  73. import re
  74. from os.path import join
  75. from contex import rules
  76. # Directories in which to perform changes:
  77. # /home/uglemat/Documents
  78. def rename(fn, dirname):
  79. return fn
  80. Your job is to complete ``rename``, and when you exit the editor it will do the job (after asking you if you want to continue).
  81. Lets do something more advanced, say you have lots of files in ``~/Documents/files`` of the format ``File<num>`` and you want to split
  82. them into the folders ``odd`` and ``even``, like this:
  83. .. code-block:: bash
  84. ~/Documents/files $ for i in {0..20}; do touch "File$i"; done
  85. ~/Documents/files $ ls
  86. File0 File1 File10 File11 File12 File13 File14 File15 File16 File17 File18 File19 File2 File20 File3 File4 File5 File6 File7 File8 File9
  87. ~/Documents/files $ mkdir odd even
  88. There is a slight problem in that you can't rename ``odd`` and ``even``, but they are in the same directory. You just
  89. got to make sure that the rename function returns a falsy value for those filenames (btw, aka treats directories like files and
  90. will rename them too). Lets go to the editor with ``aka -e 'emacs -nw'`` and write this:
  91. .. code-block:: python
  92. import re
  93. from os.path import join
  94. from contex import rules
  95. # Directories in which to perform changes:
  96. # /home/uglemat/Documents/files
  97. def rename(fn, dirname):
  98. match = re.search(r'\d+', fn)
  99. if match:
  100. digit = int(match.group(0))
  101. return join('even' if even(digit) else 'odd', fn)
  102. def even(d):
  103. return (d % 2) == 0
  104. The directories ``odd`` and ``even`` doesn't match, so ``rename`` returns ``None`` for those names and thus they are ignored, and
  105. the code above works as expected:
  106. .. code-block:: shell-session
  107. ~/Documents/files $ aka -e 'emacs -nw'
  108. running $ emacs -nw +9:14 /tmp/aka_3uvuyn8c.py
  109. Aka: Proceed? [Y/n]: y
  110. -- RENAMING FILES IN . --
  111. Actions to be taken (simplified; doesn't show the temporary stage):
  112. /home/uglemat/Documents/files/File3 -> /home/uglemat/Documents/files/odd/File3
  113. /home/uglemat/Documents/files/File18 -> /home/uglemat/Documents/files/even/File18
  114. /home/uglemat/Documents/files/File13 -> /home/uglemat/Documents/files/odd/File13
  115. ...
  116. Target directories:
  117. /home/uglemat/Documents/files/odd
  118. /home/uglemat/Documents/files/even
  119. The files will be renamed as shown above (in two passes though, in order to avoid
  120. collisions). This program searched for name conflicts in all target directories
  121. and did not find any. If errors do pop up, you'll be taken to an emergency mode
  122. where you can roll back changes. Continue? [N/y]: y
  123. Renaming /home/uglemat/Documents/files/File3 -> /tmp/aka_st72r5jp/File3
  124. Renaming /home/uglemat/Documents/files/File18 -> /tmp/aka_st72r5jp/File18
  125. Renaming /home/uglemat/Documents/files/File13 -> /tmp/aka_st72r5jp/File13
  126. ...
  127. Renaming /tmp/aka_st72r5jp/File3 -> /home/uglemat/Documents/files/odd/File3
  128. Renaming /tmp/aka_st72r5jp/File18 -> /home/uglemat/Documents/files/even/File18
  129. Renaming /tmp/aka_st72r5jp/File13 -> /home/uglemat/Documents/files/odd/File13
  130. ~/Documents/files $ ls *
  131. even:
  132. File0 File10 File12 File14 File16 File18 File2 File20 File4 File6 File8
  133. odd:
  134. File1 File11 File13 File15 File17 File19 File3 File5 File7 File9
  135. Rollbacks
  136. ---------
  137. To test the rollback feature of ``aka``, lets first launch this command:
  138. .. code-block:: shell-session
  139. $ aka -p 'rules(r"File(\d+)", {1: lambda digit: int(digit) + 1})'
  140. -- RENAMING FILES IN . --
  141. Actions to be taken (simplified; doesn't show the temporary stage):
  142. /home/uglemat/Documents/File3 -> /home/uglemat/Documents/File4
  143. /home/uglemat/Documents/File1 -> /home/uglemat/Documents/File2
  144. /home/uglemat/Documents/File2 -> /home/uglemat/Documents/File3
  145. Target directories:
  146. /home/uglemat/Documents
  147. The files will be renamed as shown above (in two passes though, in order to avoid
  148. collisions). This program searched for name conflicts in all target directories
  149. and did not find any. If errors do pop up, you'll be taken to an emergency mode
  150. where you can roll back changes. Continue? [N/y]:
  151. Now it's waiting for confirmation from the user. So we have time to do some sabotage in another shell:
  152. .. code-block:: bash
  153. $ touch File4
  154. $ ls
  155. File1 File2 File3 File4
  156. In the first shell, lets enter ``y`` to see how it fails:
  157. .. code-block:: shell-session
  158. Renaming /home/uglemat/Documents/File3 -> /tmp/aka_1ozr4w4b/File3
  159. Renaming /home/uglemat/Documents/File1 -> /tmp/aka_1ozr4w4b/File1
  160. Renaming /home/uglemat/Documents/File2 -> /tmp/aka_1ozr4w4b/File2
  161. Renaming /tmp/aka_1ozr4w4b/File3 -> /home/uglemat/Documents/File4
  162. EMERGENCY MODE: File /home/uglemat/Documents/File4 already exists!
  163. ERROR: Error happened when trying to rename /tmp/aka_1ozr4w4b/File3 -> /home/uglemat/Documents/File4
  164. What should the program do?
  165. retry : try again (presumably you've fixed something in the meantime)
  166. rollback : attempt to undo changes (except for the ones previously continue'd)
  167. showroll : show which actions will be taken if you choose `rollback`
  168. exit : exit the program
  169. continue : ignore the error and move on
  170. >
  171. Oh my, looks like think didn't go as planned. You're now in the emergency prompt of ``aka``. You can easily fix the problem
  172. by deleting ``File4`` and entering ``retry``, but that's boring. Let's first see what happens when you select ``continue``:
  173. .. code-block:: shell-session
  174. > continue
  175. Renaming /tmp/aka_1ozr4w4b/File1 -> /home/uglemat/Documents/File2
  176. Renaming /tmp/aka_1ozr4w4b/File2 -> /home/uglemat/Documents/File3
  177. LOST FILES IN TEMP DIR: '/tmp/aka_1ozr4w4b'
  178. $ ls /tmp/aka_1ozr4w4b
  179. File3
  180. It's not very nice that it just left the file in the temp dir. ``continue`` is probably rarely a good option. Lets be more sophisticated
  181. and choose ``rollback``:
  182. .. code-block:: shell-session
  183. > showroll
  184. Rollback actions:
  185. /tmp/aka_1ozr4w4b/File2 -> /home/uglemat/Documents/File2
  186. /tmp/aka_1ozr4w4b/File1 -> /home/uglemat/Documents/File1
  187. /tmp/aka_1ozr4w4b/File3 -> /home/uglemat/Documents/File3
  188. What should the program do?
  189. retry : try again (presumably you've fixed something in the meantime)
  190. rollback : attempt to undo changes (except for the ones previously continue'd)
  191. showroll : show which actions will be taken if you choose `rollback`
  192. exit : exit the program
  193. continue : ignore the error and move on
  194. > rollback
  195. Rollback renaming /tmp/aka_1ozr4w4b/File2 -> /home/uglemat/Documents/File2
  196. Rollback renaming /tmp/aka_1ozr4w4b/File1 -> /home/uglemat/Documents/File1
  197. Rollback renaming /tmp/aka_1ozr4w4b/File3 -> /home/uglemat/Documents/File3
  198. $ ls
  199. File1 File2 File3 File4
  200. Rollback will "undo" all previous actions, in the reverse order that they were "done'd". If you use the ``--copy`` option then the undoing
  201. consists of deleting files already copied. If any of the rollback actions fails, then ``aka`` will ignore it and try to undo as much as possible.
  202. Installing
  203. ----------
  204. ``aka`` works only in Python 3.
  205. Install with ``$ pip3 install aka``. You might want to replace ``pip3`` with ``pip``, depending on how your system is configured.
  206. Windows Compatability
  207. ---------------------
  208. I developed this program on GNU/Linux, but it should be working on Windows as well. It understands that filenames are
  209. case insensitive on Windows when checking for naming conflicts, yet the case sensitivity is preserved when the actual renames are done.
  210. Developing
  211. ----------
  212. Aka has some tests. Run ``$ nosetests`` or
  213. ``$ python3 setup.py test`` to run the tests. The code is hosted at https://notabug.org/Uglemat/aka
  214. You can install in development mode with ``$ python3 setup.py develop``, then your changes to aka will take effect immediately. Launch the same command with the ``--uninstall`` option to (kind of) remove.
  215. License
  216. -------
  217. The code is licensed under the GNU General Public License 3 or later.
  218. This README file is public domain.