renamr 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.
@@ -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).
@@ -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
@@ -0,0 +1,7 @@
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 'renamr/renamer'
7
+ require_relative 'renamr/version'
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,8 @@
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
+ VERSION = '1.0.2'
8
+ end
@@ -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: []