brn.nim 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. from std/os import join_path, nil
  2. from std/parseopt import nil
  3. from std/sugar import dup, collect, `=>`, `->`
  4. from std/osproc import wait_for_exit, nil
  5. from std/sequtils import to_seq, zip, filter, map_it, map
  6. from ./path import strip_paths
  7. from ./tmpdir import mktemp, mkdtemp
  8. from ./utils
  9. import die, warn, to_bool, to_int, all_uniq, exists, Exit, None
  10. type
  11. Config = object
  12. verbose: bool
  13. editor: string
  14. files: seq[string]
  15. proc get_config(): Config =
  16. var quiet: bool = false
  17. for kind, key, val in parseopt.getopt():
  18. case kind
  19. of parseopt.cmdArgument:
  20. result.files.add(key)
  21. of parseopt.cmdLongOption, parseopt.cmdShortOption:
  22. case key
  23. of "q", "quiet":
  24. quiet = true
  25. of "editor":
  26. if val:
  27. result.editor = val
  28. else:
  29. die("provide value for flag: ", key)
  30. else:
  31. die("invalid flag: ", key)
  32. of parseopt.cmdEnd: assert(false) # cannot happen
  33. result.verbose = not quiet
  34. if not result.editor:
  35. result.editor = os.get_env("EDITOR")
  36. if not result.editor:
  37. die "please, set editor throuth --editor flag or EDITOR env var"
  38. proc main(): int =
  39. let config = get_config()
  40. if not (config.files.len > 0):
  41. die "please provide some files to bulk rename"
  42. let dir = mkdtemp(prefix="brn.", suffix=".dir")
  43. try:
  44. let (fpath, file) = mktemp(tmpdir=dir, prefix="brn.", suffix=".files")
  45. try:
  46. let (prefix, files) = config.files.strip_paths
  47. if not files.all_uniq:
  48. die "found duplicate paths"
  49. discard file.dup(
  50. ((f: File) => (
  51. discard files.map do (i: string) -> None:
  52. f.write_line i
  53. )),
  54. flush_file,
  55. set_file_pos 0,
  56. )
  57. discard osproc.startProcess(
  58. command=config.editor,
  59. args=[fpath],
  60. options={
  61. osproc.poUsePath,
  62. osproc.poInteractive,
  63. osproc.poParentStreams,
  64. }
  65. ).wait_for_exit
  66. let new_files = file.lines.to_seq
  67. if new_files.len != files.len:
  68. die "different number of files before and after editing"
  69. for (old, new) in zip(files, new_files)
  70. .filter((x: tuple) => x[0] != x[1])
  71. .map((x: tuple) =>
  72. (prefix.join_path x[0], prefix.join_path x[1])):
  73. if not old.exists:
  74. warn "file did'n exists: " & old
  75. result = Exit.SomeErrors
  76. continue
  77. func `->`(o, n: auto): auto = o & " -> " & n
  78. try:
  79. os.move_file(old, new)
  80. if config.verbose: echo old -> new
  81. except OSError:
  82. warn "failed to rename: " & old -> new
  83. result = Exit.SomeErrors
  84. finally:
  85. file.close
  86. os.remove_file fpath
  87. finally:
  88. os.remove_dir dir
  89. when isMainModule: quit main()