renamr 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +25 -0
- data/README.md +53 -0
- data/bin/renamr +12 -0
- data/lib/renamr.rb +7 -0
- data/lib/renamr/action.rb +19 -0
- data/lib/renamr/ascii_validator.rb +18 -0
- data/lib/renamr/auto_localization.rb +20 -0
- data/lib/renamr/char.rb +18 -0
- data/lib/renamr/configurator.rb +97 -0
- data/lib/renamr/downcase.rb +15 -0
- data/lib/renamr/existence.rb +45 -0
- data/lib/renamr/factory.rb +57 -0
- data/lib/renamr/manual_localization.rb +28 -0
- data/lib/renamr/omit.rb +21 -0
- data/lib/renamr/point.rb +25 -0
- data/lib/renamr/prepend.rb +21 -0
- data/lib/renamr/prepend_date.rb +23 -0
- data/lib/renamr/remove.rb +26 -0
- data/lib/renamr/renamer.rb +59 -0
- data/lib/renamr/reporter.rb +67 -0
- data/lib/renamr/rutoen.rb +32 -0
- data/lib/renamr/substitute.rb +23 -0
- data/lib/renamr/trim.rb +19 -0
- data/lib/renamr/truncate.rb +28 -0
- data/lib/renamr/utils.rb +47 -0
- data/lib/renamr/version.rb +8 -0
- data/renamr.gemspec +28 -0
- metadata +142 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ebae244a34e6b7498de4aa841b1d09d2dde748e553f6100553551fcc8a88c6e4
|
4
|
+
data.tar.gz: 1fa08e2c6001b46f9675f07c142de7a226efe1fbbba18554c71ed4ba8f9b0138
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4e94c4ed1320cd5652cbe4c6ce85852225ce9a6dca6daeb945d0ae499d191a585fff21f65344bd02b7733522baa23a690e72cfa2162ec14bfad16bb1c661a139
|
7
|
+
data.tar.gz: 570c8406c091a60895232d26f1c19cbf5add10e891250e672f02b1074c350e004e4ba646e3801662c8855bb09222d58f743d35c4f78640c2fab6f385d2127b3d
|
data/LICENSE
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
BSD 2-Clause License
|
2
|
+
|
3
|
+
Copyright (c) 2020, David Rabkin
|
4
|
+
All rights reserved.
|
5
|
+
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
8
|
+
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
10
|
+
list of conditions and the following disclaimer.
|
11
|
+
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
14
|
+
and/or other materials provided with the distribution.
|
15
|
+
|
16
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
17
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
18
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
19
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
20
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
21
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
22
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
23
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
24
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
25
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# Renamr
|
2
|
+
File and directory names organiser
|
3
|
+
|
4
|
+
* [About](#about)
|
5
|
+
* [Installation](#installation)
|
6
|
+
* [Usage](#usage)
|
7
|
+
* [License](#license)
|
8
|
+
|
9
|
+
## About
|
10
|
+
Hi, I'm [David Rabkin](https://www.rabkin.co.il). I created this tool to
|
11
|
+
orginize multiple files.
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
The tool is designed to work on macOS, GNU/Linux, Windows, Unix-like OS. It is
|
15
|
+
packaged as a Gem and require Ruby version 2.4 or later. See «[Installing
|
16
|
+
Ruby](https://www.ruby-lang.org/en/documentation/installation/)» if you don't
|
17
|
+
have the proper version on your platform.
|
18
|
+
|
19
|
+
Use this command to install:
|
20
|
+
|
21
|
+
gem install renamr
|
22
|
+
|
23
|
+
### Updating
|
24
|
+
Use this command to update the package:
|
25
|
+
|
26
|
+
gem update renamr
|
27
|
+
|
28
|
+
### Requirements
|
29
|
+
There are no requirements.
|
30
|
+
|
31
|
+
## Usage
|
32
|
+
renamr [options]
|
33
|
+
-a, --act Real renaming.
|
34
|
+
-r, --rec Passes recursively.
|
35
|
+
-l, --lim Limits name length.
|
36
|
+
-m, --mod Prepends modification time.
|
37
|
+
-d, --dir dir Directory to rename.
|
38
|
+
-s, --src src A string to substitute.
|
39
|
+
-t, --dst dst A string to replace to.
|
40
|
+
-p, --pre pre A string to prepend to.
|
41
|
+
-w, --wid wid Width of the table.
|
42
|
+
-c, --cut pos,len Removes symbols from pos.
|
43
|
+
|
44
|
+
### Example
|
45
|
+
|
46
|
+
renamr -d <source>
|
47
|
+
|
48
|
+
It renames all files in `source` by default pattern: 26 English letters,
|
49
|
+
10 numbers, minus for spaces and other symbols.
|
50
|
+
|
51
|
+
## License
|
52
|
+
Transcode is copyright [David Rabkin](http://www.rabkin.co.il/) and
|
53
|
+
available under a [2-Claus BSD license](https://github.com/rdavid/renamr/blob/master/LICENSE).
|
data/bin/renamr
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# vi:ts=2 sw=2 tw=79 et lbr wrap
|
5
|
+
# Copyright 2018-present David Rabkin
|
6
|
+
# This script renames files in given directory by specific rules.
|
7
|
+
|
8
|
+
require 'pidfile'
|
9
|
+
require_relative '../lib/renamr'
|
10
|
+
|
11
|
+
PidFile.new
|
12
|
+
Renamr::Renamer.new.do
|
data/lib/renamr.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# vi:ts=2 sw=2 tw=79 et lbr wrap
|
4
|
+
# Copyright 2018-present David Rabkin
|
5
|
+
|
6
|
+
module Renamr
|
7
|
+
# An interface for actions implementation.
|
8
|
+
class Action
|
9
|
+
def do(src)
|
10
|
+
raise "Undefined method Action.do is called with #{src}."
|
11
|
+
end
|
12
|
+
|
13
|
+
def set(src) end
|
14
|
+
|
15
|
+
def p2m(src)
|
16
|
+
src.tr('.', '-')
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# vi:ts=2 sw=2 tw=79 et lbr wrap
|
4
|
+
# Copyright 2018-present David Rabkin
|
5
|
+
|
6
|
+
require_relative 'action'
|
7
|
+
|
8
|
+
module Renamr
|
9
|
+
# Checks if the resulted string has only ASCII symbols.
|
10
|
+
class ASCIIValidatorAction < Action
|
11
|
+
def do(src)
|
12
|
+
ascii = src.chars.select(&:ascii_only?).join
|
13
|
+
raise "String #{src} has non-ASCII symbols." if src != ascii
|
14
|
+
|
15
|
+
src
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# vi:ts=2 sw=2 tw=79 et lbr wrap
|
4
|
+
# Copyright 2018-present David Rabkin
|
5
|
+
|
6
|
+
require 'i18n'
|
7
|
+
require_relative 'action'
|
8
|
+
|
9
|
+
module Renamr
|
10
|
+
# Automatic localization.
|
11
|
+
class AutoLocalizationAction < Action
|
12
|
+
def initialize
|
13
|
+
I18n.config.available_locales = :en
|
14
|
+
end
|
15
|
+
|
16
|
+
def do(src)
|
17
|
+
I18n.transliterate(src).tr('?', '')
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/renamr/char.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# vi:ts=2 sw=2 tw=79 et lbr wrap
|
4
|
+
# Copyright 2018-present David Rabkin
|
5
|
+
|
6
|
+
require 'set'
|
7
|
+
require_relative 'action'
|
8
|
+
|
9
|
+
module Renamr
|
10
|
+
# All special symbols besides some (., &, $) are replaced by minus.
|
11
|
+
class CharAction < Action
|
12
|
+
SYM = ' (){},~–\'![]_#@=„“”"`—+‘’;·‡«»%…'.chars.to_set.freeze
|
13
|
+
|
14
|
+
def do(src)
|
15
|
+
src.chars.map { |s| SYM.include?(s) ? '-' : s }.join
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# vi:ts=2 sw=2 tw=79 et lbr wrap
|
4
|
+
# Copyright 2018-present David Rabkin
|
5
|
+
|
6
|
+
require 'optparse'
|
7
|
+
|
8
|
+
module Renamr
|
9
|
+
# Handles input parameters.
|
10
|
+
class Configurator
|
11
|
+
DIC = [
|
12
|
+
['-a', '--act', 'Real renaming.', :act],
|
13
|
+
['-r', '--rec', 'Passes recursively.', :rec],
|
14
|
+
['-l', '--lim', 'Limits name length.', :lim],
|
15
|
+
['-m', '--mod', 'Prepends modification time.', :mod],
|
16
|
+
['-d', '--dir dir', 'Directory to rename.', :dir],
|
17
|
+
['-s', '--src src', 'A string to substitute.', :src],
|
18
|
+
['-t', '--dst dst', 'A string to replace to.', :dst],
|
19
|
+
['-p', '--pre pre', 'A string to prepend to.', :pre],
|
20
|
+
['-w', '--wid wid', 'Width of the table.', :wid]
|
21
|
+
].freeze
|
22
|
+
|
23
|
+
def initialize
|
24
|
+
@options = {}
|
25
|
+
OptionParser.new do |o|
|
26
|
+
o.banner = "Usage: #{File.basename($PROGRAM_NAME)} [options]."
|
27
|
+
DIC.each { |f, p, d, k| o.on(f, p, d) { |i| @options[k] = i } }
|
28
|
+
o.on('-c', '--cut pos,len', Array, 'Removes symbols from pos.') do |l|
|
29
|
+
@options[:pos] = l[0]
|
30
|
+
@options[:len] = l[1]
|
31
|
+
end
|
32
|
+
end.parse!
|
33
|
+
validate
|
34
|
+
end
|
35
|
+
|
36
|
+
def validate
|
37
|
+
if dir.nil?
|
38
|
+
@options[:dir] = Dir.pwd
|
39
|
+
else
|
40
|
+
raise "No such directory: #{dir}." unless File.directory?(dir)
|
41
|
+
|
42
|
+
@options[:dir] = File.expand_path(dir)
|
43
|
+
end
|
44
|
+
raise "Width of the table should exeeds 14 symbols: #{wid}." if wid < 15
|
45
|
+
end
|
46
|
+
|
47
|
+
def act?
|
48
|
+
@options[:act]
|
49
|
+
end
|
50
|
+
|
51
|
+
def rec?
|
52
|
+
@options[:rec]
|
53
|
+
end
|
54
|
+
|
55
|
+
def lim?
|
56
|
+
@options[:lim]
|
57
|
+
end
|
58
|
+
|
59
|
+
def mod?
|
60
|
+
@options[:mod]
|
61
|
+
end
|
62
|
+
|
63
|
+
def dir
|
64
|
+
@options[:dir]
|
65
|
+
end
|
66
|
+
|
67
|
+
def src
|
68
|
+
@options[:src]
|
69
|
+
end
|
70
|
+
|
71
|
+
def dst
|
72
|
+
@options[:dst]
|
73
|
+
end
|
74
|
+
|
75
|
+
def pre
|
76
|
+
@options[:pre]
|
77
|
+
end
|
78
|
+
|
79
|
+
def pos
|
80
|
+
@options[:pos]
|
81
|
+
end
|
82
|
+
|
83
|
+
def len
|
84
|
+
@options[:len]
|
85
|
+
end
|
86
|
+
|
87
|
+
def wid
|
88
|
+
if @options[:wid].nil?
|
89
|
+
# Reads current terminal width.
|
90
|
+
wid = `tput cols`
|
91
|
+
wid.to_s.empty? ? 79 : wid.to_i
|
92
|
+
else
|
93
|
+
@options[:wid].to_i
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# vi:ts=2 sw=2 tw=79 et lbr wrap
|
4
|
+
# Copyright 2018-present David Rabkin
|
5
|
+
|
6
|
+
require_relative 'action'
|
7
|
+
|
8
|
+
module Renamr
|
9
|
+
# All names should be downcased.
|
10
|
+
class DowncaseAction < Action
|
11
|
+
def do(src)
|
12
|
+
src.downcase
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# vi:ts=2 sw=2 tw=79 et lbr wrap
|
4
|
+
# Copyright 2018-present David Rabkin
|
5
|
+
|
6
|
+
require_relative 'action'
|
7
|
+
|
8
|
+
module Renamr
|
9
|
+
# Adds number from 0 to 9 in case of file existence.
|
10
|
+
class ExistenceAction < Action
|
11
|
+
ITERATION = 10
|
12
|
+
|
13
|
+
def initialize(dir, lim)
|
14
|
+
raise 'dir cannot be nil.' if dir.nil?
|
15
|
+
raise 'lim cannot be nil.' if lim.nil?
|
16
|
+
|
17
|
+
@dir = dir
|
18
|
+
@lim = lim
|
19
|
+
end
|
20
|
+
|
21
|
+
def do(src) # rubocop:disable MethodLength, CyclomaticComplexity, AbcSize
|
22
|
+
raise 'ExistenceAction needs original file name.' if @src.nil?
|
23
|
+
return src if src == @src
|
24
|
+
return src unless File.exist?(File.join(@dir, src))
|
25
|
+
|
26
|
+
if src.length == @lim
|
27
|
+
ext = File.extname(src)
|
28
|
+
src = src[0..@lim - ext.length - ITERATION.to_s.length + 1]
|
29
|
+
src << ext
|
30
|
+
end
|
31
|
+
nme = File.basename(src, '.*')
|
32
|
+
nme = '' if nme.length == 1
|
33
|
+
ext = File.extname(src)
|
34
|
+
(0..ITERATION).each do |i|
|
35
|
+
n = File.join(@dir, nme + i.to_s + ext)
|
36
|
+
return n unless File.exist?(n)
|
37
|
+
end
|
38
|
+
raise "Unable to compose a new name: #{src}."
|
39
|
+
end
|
40
|
+
|
41
|
+
def set(src)
|
42
|
+
@src = src
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# vi:ts=2 sw=2 tw=79 et lbr wrap
|
4
|
+
# Copyright 2018-present David Rabkin
|
5
|
+
|
6
|
+
require_relative 'ascii_validator'
|
7
|
+
require_relative 'auto_localization'
|
8
|
+
require_relative 'char'
|
9
|
+
require_relative 'downcase'
|
10
|
+
require_relative 'existence'
|
11
|
+
require_relative 'manual_localization'
|
12
|
+
require_relative 'omit'
|
13
|
+
require_relative 'point'
|
14
|
+
require_relative 'prepend'
|
15
|
+
require_relative 'prepend_date'
|
16
|
+
require_relative 'remove'
|
17
|
+
require_relative 'rutoen'
|
18
|
+
require_relative 'substitute'
|
19
|
+
require_relative 'trim'
|
20
|
+
require_relative 'truncate'
|
21
|
+
|
22
|
+
module Renamr
|
23
|
+
# Produces actions for certain directories.
|
24
|
+
class ActionsFactory
|
25
|
+
LIMIT = 143 # Synology eCryptfs limitation.
|
26
|
+
|
27
|
+
def initialize(cfg)
|
28
|
+
@cfg = cfg
|
29
|
+
end
|
30
|
+
|
31
|
+
def produce(dir) # rubocop:disable MethodLength, AbcSize
|
32
|
+
if @cfg.lim?
|
33
|
+
[
|
34
|
+
OmitAction.new(LIMIT),
|
35
|
+
TruncateAction.new(LIMIT)
|
36
|
+
]
|
37
|
+
else
|
38
|
+
[
|
39
|
+
PointAction.new(dir), # Should be the first.
|
40
|
+
@cfg.pos.nil? ? nil : RemoveAction.new(@cfg.pos, @cfg.len),
|
41
|
+
@cfg.src.nil? ? nil : SubstituteAction.new(@cfg.src, @cfg.dst),
|
42
|
+
ManualLocalizationAction.new,
|
43
|
+
DowncaseAction.new,
|
44
|
+
CharAction.new,
|
45
|
+
RuToEnAction.new,
|
46
|
+
AutoLocalizationAction.new,
|
47
|
+
@cfg.mod? ? PrependDateAction.new(dir) : nil,
|
48
|
+
@cfg.pre.nil? ? nil : PrependAction.new(@cfg.pre),
|
49
|
+
ASCIIValidatorAction.new,
|
50
|
+
TrimAction.new,
|
51
|
+
TruncateAction.new(LIMIT),
|
52
|
+
ExistenceAction.new(dir, LIMIT)
|
53
|
+
].compact
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# vi:ts=2 sw=2 tw=79 et lbr wrap
|
4
|
+
# Copyright 2018-present David Rabkin
|
5
|
+
|
6
|
+
require_relative 'action'
|
7
|
+
|
8
|
+
module Renamr
|
9
|
+
# Manual localization.
|
10
|
+
class ManualLocalizationAction < Action
|
11
|
+
SRC = 'ÀÁÂÃÄÅàáâãäåĀāĂ㥹ÇçĆćĈĉĊċČčÐðĎďĐđ'\
|
12
|
+
'ÈÉÊËèéêëĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħÌÍÎÏ'\
|
13
|
+
'ìíîïĨĩĪīĬĭĮįİıĴĵĶķĸĹĺĻļĽľĿŀŁłÑñŃńŅ'\
|
14
|
+
'ņŇňʼnŊŋÒÓÔÕÖØòóôõöøŌōŎŏŐőŔŕŖŗŘřŚśŜŝ'\
|
15
|
+
'ŞşŠšſŢţŤťŦŧÙÚÛÜùúûüŨũŪūŬŭŮůŰűŲųŴŵÝ'\
|
16
|
+
'ýÿŶŷŸŹźŻżŽž'
|
17
|
+
DST = 'AAAAAAaaaaaaAaAaAaCcCcCcCcCcDdDdDd'\
|
18
|
+
'EEEEeeeeEeEeEeEeEeGgGgGgGgHhHhIIII'\
|
19
|
+
'iiiiIiIiIiIiIiJjKkkLlLlLlLlLlNnNnN'\
|
20
|
+
'nNnnNnOOOOOOooooooOoOoOoRrRrRrSsSs'\
|
21
|
+
'SsSssTtTtTtUUUUuuuuUuUuUuUuUuUuWwY'\
|
22
|
+
'yyYyYZzZzZz'
|
23
|
+
|
24
|
+
def do(src)
|
25
|
+
src.tr(SRC, DST)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/renamr/omit.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# vi:ts=2 sw=2 tw=79 et lbr wrap
|
4
|
+
# Copyright 2018-present David Rabkin
|
5
|
+
|
6
|
+
require_relative 'action'
|
7
|
+
|
8
|
+
module Renamr
|
9
|
+
# Omits file names shorter than limit.
|
10
|
+
class OmitAction < Action
|
11
|
+
def initialize(lim)
|
12
|
+
raise 'lim cannot be nil.' if lim.nil?
|
13
|
+
|
14
|
+
@lim = lim
|
15
|
+
end
|
16
|
+
|
17
|
+
def do(src)
|
18
|
+
src.length < @lim ? nil : src
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/renamr/point.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# vi:ts=2 sw=2 tw=79 et lbr wrap
|
4
|
+
# Copyright 2018-present David Rabkin
|
5
|
+
|
6
|
+
require_relative 'action'
|
7
|
+
|
8
|
+
module Renamr
|
9
|
+
# All points besides extention are replaced by minus.
|
10
|
+
class PointAction < Action
|
11
|
+
def initialize(dir)
|
12
|
+
raise 'dir cannot be nil.' if dir.nil?
|
13
|
+
|
14
|
+
@dir = dir
|
15
|
+
end
|
16
|
+
|
17
|
+
def do(src)
|
18
|
+
if File.file?(File.join(@dir, src))
|
19
|
+
p2m(File.basename(src, '.*')) << File.extname(src)
|
20
|
+
else
|
21
|
+
p2m(src)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# vi:ts=2 sw=2 tw=79 et lbr wrap
|
4
|
+
# Copyright 2018-present David Rabkin
|
5
|
+
|
6
|
+
require_relative 'action'
|
7
|
+
|
8
|
+
module Renamr
|
9
|
+
# Prepends user patter.
|
10
|
+
class PrependAction < Action
|
11
|
+
def initialize(pat)
|
12
|
+
raise 'pat cannot be nil.' if pat.nil?
|
13
|
+
|
14
|
+
@pat = pat
|
15
|
+
end
|
16
|
+
|
17
|
+
def do(src)
|
18
|
+
src.prepend(@pat)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# vi:ts=2 sw=2 tw=79 et lbr wrap
|
4
|
+
# Copyright 2018-present David Rabkin
|
5
|
+
|
6
|
+
require_relative 'action'
|
7
|
+
|
8
|
+
module Renamr
|
9
|
+
# Prepends file modification datestamp.
|
10
|
+
class PrependDateAction < Action
|
11
|
+
def initialize(dir)
|
12
|
+
@dir = dir
|
13
|
+
end
|
14
|
+
|
15
|
+
def do(src)
|
16
|
+
src.prepend(File.mtime(File.join(@dir, @src)).strftime('%Y%m%d-'))
|
17
|
+
end
|
18
|
+
|
19
|
+
def set(src)
|
20
|
+
@src = src
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# vi:ts=2 sw=2 tw=79 et lbr wrap
|
4
|
+
# Copyright 2018-present David Rabkin
|
5
|
+
|
6
|
+
require_relative 'action'
|
7
|
+
|
8
|
+
module Renamr
|
9
|
+
# Removes symbols between left and right positions.
|
10
|
+
class RemoveAction < Action
|
11
|
+
def initialize(pos, len)
|
12
|
+
raise 'len cannot bi nil.' if len.nil?
|
13
|
+
raise 'pos cannot be nil.' if pos.nil?
|
14
|
+
raise 'pos has to be positive.' unless pos.to_i.positive?
|
15
|
+
|
16
|
+
@pos = pos.to_i
|
17
|
+
@len = len.to_i
|
18
|
+
end
|
19
|
+
|
20
|
+
def do(src)
|
21
|
+
return src[@len..-1] if @pos == 1
|
22
|
+
|
23
|
+
src[0..@pos - 1] + src[@pos + @len..-1]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# vi:ts=2 sw=2 tw=79 et lbr wrap
|
4
|
+
# Copyright 2018-present David Rabkin
|
5
|
+
|
6
|
+
require 'fileutils'
|
7
|
+
require_relative 'configurator'
|
8
|
+
require_relative 'factory'
|
9
|
+
require_relative 'reporter'
|
10
|
+
|
11
|
+
module Renamr
|
12
|
+
# Renames file by certain rules.
|
13
|
+
class Renamer
|
14
|
+
def initialize
|
15
|
+
@cfg = Configurator.new
|
16
|
+
@fac = ActionsFactory.new(@cfg)
|
17
|
+
end
|
18
|
+
|
19
|
+
def move(dir, dat) # rubocop:disable MethodLength, AbcSize
|
20
|
+
rep = Reporter.new(File.expand_path(dir))
|
21
|
+
dat.each do |src, dst|
|
22
|
+
if src == dst
|
23
|
+
rep.add(File.basename(src), '')
|
24
|
+
next
|
25
|
+
end
|
26
|
+
begin
|
27
|
+
FileUtils.mv(src, dst) if @cfg.act?
|
28
|
+
rep.add(File.basename(src), File.basename(dst))
|
29
|
+
rescue StandardError => e
|
30
|
+
rep.add(File.basename(src), e)
|
31
|
+
puts e.backtrace.join("\n\t")
|
32
|
+
.sub("\n\t", ": #{e}#{e.class ? " (#{e.class})" : ''}\n\t")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
rep.do
|
36
|
+
end
|
37
|
+
|
38
|
+
def do_dir(dir) # rubocop:disable MethodLength, CyclomaticComplexity, AbcSize
|
39
|
+
raise "No such directory: #{dir}." unless File.directory?(dir)
|
40
|
+
|
41
|
+
dat = []
|
42
|
+
act = @fac.produce(dir)
|
43
|
+
(Dir.entries(dir) - ['.', '..']).sort.each do |nme|
|
44
|
+
src = File.join(dir, nme)
|
45
|
+
do_dir(src) if @cfg.rec? && File.directory?(src)
|
46
|
+
act.each { |a| a.set(nme) }
|
47
|
+
act.each { |a| break if (nme = a.do(nme)).nil? }
|
48
|
+
dat << [src, File.join(dir, nme)] unless nme.nil?
|
49
|
+
end
|
50
|
+
move(dir, dat) if dat.any?
|
51
|
+
end
|
52
|
+
|
53
|
+
def do
|
54
|
+
Reporter.init(@cfg.act?, @cfg.wid)
|
55
|
+
do_dir(@cfg.dir)
|
56
|
+
Reporter.final
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# vi:ts=2 sw=2 tw=79 et lbr wrap
|
4
|
+
# Copyright 2018-present David Rabkin
|
5
|
+
|
6
|
+
require 'terminal-table'
|
7
|
+
require_relative 'action'
|
8
|
+
require_relative 'utils'
|
9
|
+
|
10
|
+
module Renamr
|
11
|
+
# Formats and prints output data.
|
12
|
+
class Reporter
|
13
|
+
@@tim = Timer.new
|
14
|
+
@@sta = { moved: 0, unaltered: 0, failed: 0 }
|
15
|
+
|
16
|
+
def self.init(act, wid)
|
17
|
+
@@act = act
|
18
|
+
@@tbl = wid
|
19
|
+
@@ttl = wid - 4
|
20
|
+
@@str = (wid - 7) / 2
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(dir)
|
24
|
+
@dir = dir
|
25
|
+
@row = []
|
26
|
+
end
|
27
|
+
|
28
|
+
def add(lhs, rhs)
|
29
|
+
if rhs.is_a?(StandardError)
|
30
|
+
tag = :failed
|
31
|
+
rhs = "#{rhs.message} (#{rhs.class})"
|
32
|
+
elsif rhs == ''
|
33
|
+
tag = :unaltered
|
34
|
+
else
|
35
|
+
tag = :moved
|
36
|
+
end
|
37
|
+
@@sta[tag] += 1
|
38
|
+
@row << [Utils.trim(lhs, @@str), Utils.trim(rhs, @@str)]
|
39
|
+
end
|
40
|
+
|
41
|
+
def do
|
42
|
+
puts Terminal::Table.new(
|
43
|
+
title: Utils.trim(@dir, @@ttl),
|
44
|
+
headings: [
|
45
|
+
{ value: 'src', alignment: :center },
|
46
|
+
{ value: 'dst', alignment: :center }
|
47
|
+
],
|
48
|
+
rows: @row,
|
49
|
+
style: { width: @@tbl }
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.stat_out
|
54
|
+
out = ''
|
55
|
+
@@sta.each do |k, v|
|
56
|
+
out += ' ' + v.to_s + ' ' + k.to_s + ',' if v.positive?
|
57
|
+
end
|
58
|
+
out.chop
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.final
|
62
|
+
msg = "#{@@act ? 'Real' : 'Test'}:#{stat_out} in #{@@tim.read}."
|
63
|
+
msg = Utils.trim(msg, @@ttl)
|
64
|
+
puts "| #{msg}#{' ' * (@@ttl - msg.length)} |\n+-#{'-' * @@ttl}-+"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# vi:ts=2 sw=2 tw=79 et lbr wrap
|
4
|
+
# Copyright 2018-present David Rabkin
|
5
|
+
|
6
|
+
require_relative 'action'
|
7
|
+
|
8
|
+
module Renamr
|
9
|
+
# Transliterates Russian to English.
|
10
|
+
class RuToEnAction < Action
|
11
|
+
MSC = {
|
12
|
+
'ё' => 'jo',
|
13
|
+
'ж' => 'zh',
|
14
|
+
'ц' => 'tz',
|
15
|
+
'ч' => 'ch',
|
16
|
+
'ш' => 'sh',
|
17
|
+
'щ' => 'szh',
|
18
|
+
'ю' => 'ju',
|
19
|
+
'я' => 'ya',
|
20
|
+
'$' => '-usd-',
|
21
|
+
'№' => '-num-',
|
22
|
+
'&' => '-and-'
|
23
|
+
}.freeze
|
24
|
+
SRC = 'абвгдезийклмнопрстуфхъыьэ'.chars.freeze
|
25
|
+
DST = 'abvgdeziyklmnoprstufh y e'.chars.freeze
|
26
|
+
DIC = SRC.zip(DST).to_h.merge(MSC).freeze
|
27
|
+
|
28
|
+
def do(src)
|
29
|
+
src.chars.map { |c| DIC[c].nil? ? c : DIC[c] }.collect(&:strip).join
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# vi:ts=2 sw=2 tw=79 et lbr wrap
|
4
|
+
# Copyright 2018-present David Rabkin
|
5
|
+
|
6
|
+
require_relative 'action'
|
7
|
+
|
8
|
+
module Renamr
|
9
|
+
# Substitutes a string with a string.
|
10
|
+
class SubstituteAction < Action
|
11
|
+
def initialize(src, dst)
|
12
|
+
raise 'src cannot be nil.' if src.nil?
|
13
|
+
|
14
|
+
# The action works after PointAction. All points are replaces with minus.
|
15
|
+
@src = p2m(src)
|
16
|
+
@dst = dst.nil? ? '-' : p2m(dst)
|
17
|
+
end
|
18
|
+
|
19
|
+
def do(src)
|
20
|
+
src.gsub(@src, @dst)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/renamr/trim.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# vi:ts=2 sw=2 tw=79 et lbr wrap
|
4
|
+
# Copyright 2018-present David Rabkin
|
5
|
+
|
6
|
+
require_relative 'action'
|
7
|
+
|
8
|
+
module Renamr
|
9
|
+
# Replaces multiple minuses to single. Trims minuses.
|
10
|
+
class TrimAction < Action
|
11
|
+
def do(src)
|
12
|
+
src.gsub!(/-+/, '-')
|
13
|
+
src.gsub!('-.', '.')
|
14
|
+
src.gsub!('.-', '.')
|
15
|
+
src.gsub!(/^-|-$/, '') unless src == '-'
|
16
|
+
src
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# vi:ts=2 sw=2 tw=79 et lbr wrap
|
4
|
+
# Copyright 2018-present David Rabkin
|
5
|
+
|
6
|
+
require_relative 'action'
|
7
|
+
|
8
|
+
module Renamr
|
9
|
+
# Limits file length.
|
10
|
+
class TruncateAction < Action
|
11
|
+
def initialize(lim)
|
12
|
+
raise 'lim cannot be nil.' if lim.nil?
|
13
|
+
|
14
|
+
@lim = lim
|
15
|
+
end
|
16
|
+
|
17
|
+
def do(src)
|
18
|
+
return src unless src.length > @lim
|
19
|
+
|
20
|
+
ext = File.extname(src)
|
21
|
+
len = ext.length
|
22
|
+
dst = len >= @lim ? ext[0..@lim - 1] : src[0..@lim - 1 - len] << ext
|
23
|
+
dst.gsub!(/-$/, '')
|
24
|
+
dst.gsub!('-.', '.')
|
25
|
+
dst
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/renamr/utils.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# vi:ts=2 sw=2 tw=79 et lbr wrap
|
4
|
+
# Copyright 2018-present David Rabkin
|
5
|
+
|
6
|
+
# All methods are static.
|
7
|
+
class Utils
|
8
|
+
class << self
|
9
|
+
SEP = '~'
|
10
|
+
def trim(src, lim)
|
11
|
+
return src if src.length <= lim
|
12
|
+
|
13
|
+
beg = fin = (lim - SEP.length) / 2
|
14
|
+
beg -= 1 if lim.odd?
|
15
|
+
src[0..beg] + SEP + src[-fin..-1]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns string with humanized time interval.
|
21
|
+
class Timer
|
22
|
+
DIC = [
|
23
|
+
[60, :seconds, :second],
|
24
|
+
[60, :minutes, :minute],
|
25
|
+
[24, :hours, :hour],
|
26
|
+
[1000, :days, :day]
|
27
|
+
].freeze
|
28
|
+
|
29
|
+
def initialize
|
30
|
+
@sta = Time.now
|
31
|
+
end
|
32
|
+
|
33
|
+
def read
|
34
|
+
humanize(Time.now - @sta)
|
35
|
+
end
|
36
|
+
|
37
|
+
def humanize(sec)
|
38
|
+
return 'less than a second' if sec < 1
|
39
|
+
|
40
|
+
DIC.map do |cnt, nms, nm1|
|
41
|
+
next if sec <= 0
|
42
|
+
|
43
|
+
sec, n = sec.divmod(cnt)
|
44
|
+
"#{n.to_i} #{n.to_i != 1 ? nms : nm1}"
|
45
|
+
end.compact.reverse.join(' ')
|
46
|
+
end
|
47
|
+
end
|
data/renamr.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/lib')
|
4
|
+
|
5
|
+
require 'renamr'
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = 'renamr'
|
9
|
+
s.version = Renamr::VERSION
|
10
|
+
s.required_ruby_version = '>= 2.4'
|
11
|
+
s.summary = 'File and directory names organiser.'
|
12
|
+
s.description = <<-HERE
|
13
|
+
Renamr organises multiple files and directories.
|
14
|
+
HERE
|
15
|
+
s.license = 'BSD-2-Clause'
|
16
|
+
s.author = 'David Rabkin'
|
17
|
+
s.email = 'pub@rabkin.co.il'
|
18
|
+
s.homepage = 'https://github.com/rdavid/renamr'
|
19
|
+
s.files = Dir['{bin,lib}/**/*'] + Dir['[A-Z]*'] + ['renamr.gemspec']
|
20
|
+
s.executables = ['renamr']
|
21
|
+
s.extra_rdoc_files = ['LICENSE', 'README.md']
|
22
|
+
s.require_paths = ['lib']
|
23
|
+
s.add_runtime_dependency 'fileutils', '1.4.1'
|
24
|
+
s.add_runtime_dependency 'i18n', '1.8.3'
|
25
|
+
s.add_runtime_dependency 'pidfile', '0.3.0'
|
26
|
+
s.add_runtime_dependency 'terminal-table', '1.8.0'
|
27
|
+
s.add_development_dependency 'rubocop', '0.85.1'
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: renamr
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- David Rabkin
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-06-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: fileutils
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.4.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.4.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: i18n
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.8.3
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.8.3
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: pidfile
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.3.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.3.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: terminal-table
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.8.0
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.8.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rubocop
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.85.1
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.85.1
|
83
|
+
description: " Renamr organises multiple files and directories.\n"
|
84
|
+
email: pub@rabkin.co.il
|
85
|
+
executables:
|
86
|
+
- renamr
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files:
|
89
|
+
- LICENSE
|
90
|
+
- README.md
|
91
|
+
files:
|
92
|
+
- LICENSE
|
93
|
+
- README.md
|
94
|
+
- bin/renamr
|
95
|
+
- lib/renamr.rb
|
96
|
+
- lib/renamr/action.rb
|
97
|
+
- lib/renamr/ascii_validator.rb
|
98
|
+
- lib/renamr/auto_localization.rb
|
99
|
+
- lib/renamr/char.rb
|
100
|
+
- lib/renamr/configurator.rb
|
101
|
+
- lib/renamr/downcase.rb
|
102
|
+
- lib/renamr/existence.rb
|
103
|
+
- lib/renamr/factory.rb
|
104
|
+
- lib/renamr/manual_localization.rb
|
105
|
+
- lib/renamr/omit.rb
|
106
|
+
- lib/renamr/point.rb
|
107
|
+
- lib/renamr/prepend.rb
|
108
|
+
- lib/renamr/prepend_date.rb
|
109
|
+
- lib/renamr/remove.rb
|
110
|
+
- lib/renamr/renamer.rb
|
111
|
+
- lib/renamr/reporter.rb
|
112
|
+
- lib/renamr/rutoen.rb
|
113
|
+
- lib/renamr/substitute.rb
|
114
|
+
- lib/renamr/trim.rb
|
115
|
+
- lib/renamr/truncate.rb
|
116
|
+
- lib/renamr/utils.rb
|
117
|
+
- lib/renamr/version.rb
|
118
|
+
- renamr.gemspec
|
119
|
+
homepage: https://github.com/rdavid/renamr
|
120
|
+
licenses:
|
121
|
+
- BSD-2-Clause
|
122
|
+
metadata: {}
|
123
|
+
post_install_message:
|
124
|
+
rdoc_options: []
|
125
|
+
require_paths:
|
126
|
+
- lib
|
127
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '2.4'
|
132
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
133
|
+
requirements:
|
134
|
+
- - ">="
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: '0'
|
137
|
+
requirements: []
|
138
|
+
rubygems_version: 3.1.4
|
139
|
+
signing_key:
|
140
|
+
specification_version: 4
|
141
|
+
summary: File and directory names organiser.
|
142
|
+
test_files: []
|