xxx_rename 0.0.1

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.
Files changed (97) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/codeql-analysis.yml +42 -0
  3. data/.github/workflows/ruby.yml +44 -0
  4. data/.gitignore +12 -0
  5. data/.rspec +3 -0
  6. data/.rubocop.yml +41 -0
  7. data/.ruby-version +1 -0
  8. data/Gemfile +6 -0
  9. data/Gemfile.lock +174 -0
  10. data/README.md +319 -0
  11. data/Rakefile +12 -0
  12. data/bin/console +23 -0
  13. data/bin/install +22 -0
  14. data/bin/setup +8 -0
  15. data/codecov.yml +2 -0
  16. data/docs/DEVELOPMENT.md +42 -0
  17. data/exe/xxx_rename +12 -0
  18. data/lib/xxx_rename/actions/base_action.rb +20 -0
  19. data/lib/xxx_rename/actions/log_new_filename.rb +40 -0
  20. data/lib/xxx_rename/actions/resolver.rb +32 -0
  21. data/lib/xxx_rename/actions/stash_app_post_movie.rb +62 -0
  22. data/lib/xxx_rename/actors_helper.rb +117 -0
  23. data/lib/xxx_rename/cli.rb +211 -0
  24. data/lib/xxx_rename/client.rb +110 -0
  25. data/lib/xxx_rename/constants.rb +96 -0
  26. data/lib/xxx_rename/contract/config_contract.rb +241 -0
  27. data/lib/xxx_rename/contract/config_generator.rb +207 -0
  28. data/lib/xxx_rename/contract/file_rename_op_contract.rb +54 -0
  29. data/lib/xxx_rename/contract/types.rb +10 -0
  30. data/lib/xxx_rename/core_extensions/string.rb +39 -0
  31. data/lib/xxx_rename/data/base.rb +34 -0
  32. data/lib/xxx_rename/data/config.rb +97 -0
  33. data/lib/xxx_rename/data/file_rename_op.rb +42 -0
  34. data/lib/xxx_rename/data/file_rename_op_datastore.rb +111 -0
  35. data/lib/xxx_rename/data/naughty_america_database.rb +22 -0
  36. data/lib/xxx_rename/data/query_interface.rb +78 -0
  37. data/lib/xxx_rename/data/scene_data.rb +71 -0
  38. data/lib/xxx_rename/data/scene_datastore.rb +401 -0
  39. data/lib/xxx_rename/data/site_config.rb +84 -0
  40. data/lib/xxx_rename/data/types.rb +13 -0
  41. data/lib/xxx_rename/errors.rb +28 -0
  42. data/lib/xxx_rename/file_scanner.rb +49 -0
  43. data/lib/xxx_rename/file_utilities.rb +38 -0
  44. data/lib/xxx_rename/filename_generator.rb +173 -0
  45. data/lib/xxx_rename/integrations/base.rb +20 -0
  46. data/lib/xxx_rename/integrations/stash_app.rb +316 -0
  47. data/lib/xxx_rename/log.rb +26 -0
  48. data/lib/xxx_rename/migration_client.rb +139 -0
  49. data/lib/xxx_rename/processed_file.rb +203 -0
  50. data/lib/xxx_rename/search.rb +166 -0
  51. data/lib/xxx_rename/site_client_matcher.rb +299 -0
  52. data/lib/xxx_rename/site_clients/adult_time.rb +31 -0
  53. data/lib/xxx_rename/site_clients/algolia_common.rb +48 -0
  54. data/lib/xxx_rename/site_clients/algolia_v2.rb +181 -0
  55. data/lib/xxx_rename/site_clients/babes.rb +15 -0
  56. data/lib/xxx_rename/site_clients/base.rb +61 -0
  57. data/lib/xxx_rename/site_clients/blacked.rb +12 -0
  58. data/lib/xxx_rename/site_clients/blacked_raw.rb +12 -0
  59. data/lib/xxx_rename/site_clients/brazzers.rb +15 -0
  60. data/lib/xxx_rename/site_clients/configuration.rb +55 -0
  61. data/lib/xxx_rename/site_clients/digital_playground.rb +15 -0
  62. data/lib/xxx_rename/site_clients/elegant_angel.rb +168 -0
  63. data/lib/xxx_rename/site_clients/errors.rb +103 -0
  64. data/lib/xxx_rename/site_clients/evil_angel.rb +59 -0
  65. data/lib/xxx_rename/site_clients/goodporn.rb +109 -0
  66. data/lib/xxx_rename/site_clients/jules_jordan.rb +22 -0
  67. data/lib/xxx_rename/site_clients/jules_jordan_media.rb +175 -0
  68. data/lib/xxx_rename/site_clients/manuel_ferrara.rb +24 -0
  69. data/lib/xxx_rename/site_clients/mg_premium.rb +247 -0
  70. data/lib/xxx_rename/site_clients/mofos.rb +15 -0
  71. data/lib/xxx_rename/site_clients/naughty_america.rb +272 -0
  72. data/lib/xxx_rename/site_clients/nfbusty.rb +84 -0
  73. data/lib/xxx_rename/site_clients/query_generator/base.rb +89 -0
  74. data/lib/xxx_rename/site_clients/query_generator/evil_angel.rb +36 -0
  75. data/lib/xxx_rename/site_clients/query_generator/goodporn.rb +27 -0
  76. data/lib/xxx_rename/site_clients/query_generator/mg_premium.rb +26 -0
  77. data/lib/xxx_rename/site_clients/query_generator/naughty_america.rb +24 -0
  78. data/lib/xxx_rename/site_clients/query_generator/stash_db.rb +21 -0
  79. data/lib/xxx_rename/site_clients/query_generator/vixen.rb +27 -0
  80. data/lib/xxx_rename/site_clients/query_generator/whale.rb +39 -0
  81. data/lib/xxx_rename/site_clients/reality_kings.rb +14 -0
  82. data/lib/xxx_rename/site_clients/stash_db.rb +257 -0
  83. data/lib/xxx_rename/site_clients/tushy.rb +12 -0
  84. data/lib/xxx_rename/site_clients/tushy_raw.rb +12 -0
  85. data/lib/xxx_rename/site_clients/twistys.rb +15 -0
  86. data/lib/xxx_rename/site_clients/vixen.rb +12 -0
  87. data/lib/xxx_rename/site_clients/vixen_media.rb +130 -0
  88. data/lib/xxx_rename/site_clients/whale.rb +106 -0
  89. data/lib/xxx_rename/site_clients/wicked.rb +52 -0
  90. data/lib/xxx_rename/site_clients/x_empire.rb +51 -0
  91. data/lib/xxx_rename/site_clients/zero_tolerance.rb +36 -0
  92. data/lib/xxx_rename/utils.rb +81 -0
  93. data/lib/xxx_rename/version.rb +5 -0
  94. data/lib/xxx_rename.rb +60 -0
  95. data/output.png +0 -0
  96. data/xxx_rename.gemspec +42 -0
  97. metadata +411 -0
data/bin/console ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require_relative "../lib/xxx_rename"
6
+
7
+ def reload!(print: true)
8
+ puts "Reloading ..." if print
9
+ # Main project directory.
10
+ root_dir = File.expand_path("..", __dir__)
11
+ # Directories within the project that should be reloaded.
12
+ reload_dirs = %w[lib]
13
+ # Loop through and reload every file in all relevant project directories.
14
+ reload_dirs.each do |dir|
15
+ Dir.glob("#{root_dir}/#{dir}/**/*.rb").each { |f| load(f) }
16
+ end
17
+ # Return true when complete.
18
+ true
19
+ end
20
+
21
+ require "irb"
22
+
23
+ IRB.start(__FILE__)
data/bin/install ADDED
@@ -0,0 +1,22 @@
1
+ #!/bin/bash
2
+
3
+ # check if ruby is installed and version is above 2.7
4
+ if [ -x "$(command -v ruby)" ]; then
5
+ ruby_version=$(ruby -v | awk '{print $2}')
6
+ if [ "$(echo $ruby_version | cut -d '.' -f 1)" -ge 2 ] && [ "$(echo $ruby_version | cut -d '.' -f 2)" -ge 7 ]; then
7
+ echo "Uninstalling any previously installed versions"
8
+ gem uninstall -a -x xxx_rename
9
+ echo "Building gem"
10
+ rake build
11
+ gem_path=$(ls -t ./pkg/*.gem | head -1)
12
+ echo "Installing xxx_rename"
13
+ gem install --local $gem_path
14
+ echo "Done"
15
+ else
16
+ echo "Ruby version is $ruby_version, please upgrade to 2.7 or higher"
17
+ exit 1
18
+ fi
19
+ else
20
+ echo "Ruby is not installed, please install ruby 2.7 or higher"
21
+ exit 1
22
+ fi
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/codecov.yml ADDED
@@ -0,0 +1,2 @@
1
+ ignore:
2
+ - "spec/**/*"
@@ -0,0 +1,42 @@
1
+ # Building from Source
2
+
3
+ ## Pre-requisites
4
+
5
+ The CLI is written entirely in [Ruby](https://www.ruby-lang.org/en/). The
6
+ supported version is 2.7.
7
+
8
+ You can also choose to use any env managers like
9
+ [rbenv](https://github.com/rbenv/rbenv), [rvm](https://rvm.io/),
10
+ [asdf](https://asdf-vm.com/), [chruby](https://github.com/postmodern/chruby),
11
+ etc.
12
+
13
+ To clone the repository, follow the steps below:
14
+
15
+ ```bash
16
+ brew install ruby@2.7 # Install ruby (This only works for MacOS/Linux)
17
+ git clone https://github.com/c477y/xxx_rename.git
18
+ cd xxx_rename
19
+ ```
20
+
21
+ ## Environment
22
+
23
+ I cannot vouch for Windows, as this CLI is tested to work on Linux and MacOS,
24
+ but the test coverage should allow you to test for any errors.
25
+
26
+ ## Build
27
+
28
+ Run the setup script to build the gem and its dependencies.
29
+
30
+ ```bash
31
+ ./bin/setup
32
+ ```
33
+
34
+ To install the gem to your local machine.
35
+
36
+ ```bash
37
+ ./bin/install
38
+ ```
39
+
40
+ ## Run tests
41
+
42
+ Run the `rake` command to run the tests and lint the project using Rubocop.
data/exe/xxx_rename ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # frozen_string_literal: true
4
+
5
+ $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
6
+
7
+ $pwd = Dir.pwd # rubocop:disable Style/GlobalVars
8
+
9
+ require "thor"
10
+ require "xxx_rename"
11
+
12
+ XxxRename::Cli.start ARGV
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module XxxRename
4
+ module Actions
5
+ class BaseAction
6
+ attr_reader :config
7
+
8
+ def initialize(config)
9
+ @config = config
10
+ end
11
+
12
+ # @param [String] _dir
13
+ # @param [String] _file
14
+ # @param [XxxRename::Search::SearchResult] _search_result
15
+ def perform(_dir, _file, _search_result)
16
+ raise "Not Implemented"
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "xxx_rename/actions/base_action"
4
+
5
+ module XxxRename
6
+ module Actions
7
+ class LogNewFilename < BaseAction
8
+ def perform(_dir, file, search_result)
9
+ new_filename = FilenameGenerator.generate_with_multi_formats!(
10
+ search_result.scene_data,
11
+ File.extname(file),
12
+ config.prefix_hash,
13
+ *output_patterns(search_result.site_client)
14
+ )
15
+
16
+ XxxRename.logger.info "[RENAME OPERATION]".colorize(:blue)
17
+ XxxRename.logger.info "\t#{"ORIGINAL:".colorize(:light_magenta)} #{file}"
18
+ XxxRename.logger.info "\t#{"NEW: ".colorize(:green)} #{new_filename}"
19
+
20
+ config.output_recorder.create!(search_result.scene_data, file, new_filename, Dir.pwd)
21
+ rescue FilenameGenerationError => e
22
+ XxxRename.logger.error "#{"[RENAME OPERATION ERROR]".colorize(:red)} #{e.message}"
23
+ nil
24
+ rescue Contract::FileRenameOpValidationFailure => e
25
+ XxxRename.logger.error "[RENAME OPERATION VALIDATION ERROR] #{e.message}"
26
+ nil
27
+ end
28
+
29
+ #
30
+ # Return a list of patterns that will be used to generate the new filename
31
+ # Individual site client formats take precedence over global formats
32
+ #
33
+ # @param [XxxRename::SiteClients::Base] site_client
34
+ # @return [Array[String]]
35
+ def output_patterns(site_client)
36
+ site_client.site_config.output_format.push(*config.global.output_format)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "xxx_rename/actions/log_new_filename"
4
+ require "xxx_rename/actions/stash_app_post_movie"
5
+
6
+ module XxxRename
7
+ module Actions
8
+ class Resolver
9
+ def initialize(config)
10
+ @config = config
11
+ end
12
+
13
+ def resolve!(action)
14
+ action = action.to_sym
15
+ case action
16
+ when :sync_to_stash
17
+ actions_klass_hash.fetch(action, StashAppPostMovie.new(@config))
18
+ when :log_rename_op
19
+ actions_klass_hash.fetch(action, LogNewFilename.new(@config))
20
+ else
21
+ raise Errors::FatalError, "Unknown action #{action}"
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def actions_klass_hash
28
+ @actions_klass_hash ||= {}
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "xxx_rename/actions/base_action"
4
+ require "xxx_rename/integrations/stash_app"
5
+
6
+ module XxxRename
7
+ module Actions
8
+ class StashAppPostMovie < BaseAction
9
+ attr_reader :stash, :config
10
+
11
+ def initialize(config)
12
+ super(config)
13
+ @stash = Integrations::StashApp.new(config)
14
+ @stash.setup_credentials!
15
+ end
16
+
17
+ def perform(dir, file, search_result)
18
+ scene_data = search_result.scene_data
19
+ return if scene_data.movie.nil?
20
+
21
+ post_movie!(dir, file, scene_data)
22
+ rescue XxxRename::Integrations::StashAPIError => e
23
+ XxxRename.logger.error e.message
24
+ end
25
+
26
+ private
27
+
28
+ def post_movie!(_dir, file, scene_data)
29
+ scene = stash.fetch_scene(file)
30
+ if scene.nil?
31
+ XxxRename.logger.warn "[FILE MISSING ON STASH] #{file}"
32
+ return
33
+ end
34
+
35
+ if scene["movies"].find { |x| x.dig("movie", "name") == scene_data.movie.name }
36
+ XxxRename.logger.info "#{"[STASH APP MOVIE NOT UPDATED]".colorize(:yellow)} #{file}"
37
+ return
38
+ end
39
+
40
+ studio_id = studio_id(scene_data.movie.studio)
41
+ XxxRename.logger.warn "#{"[NO STUDIO ON STASH]".colorize(:light_red)} #{scene_data.movie.studio}" if studio_id.nil?
42
+
43
+ existing_movie = stash.fetch_movie(scene_data.movie.name)
44
+ if existing_movie.nil?
45
+ XxxRename.logger.info "#{"[STASH MOVIE CREATE]".colorize(:green)} #{scene_data.movie.name}"
46
+ movie = stash.create_movie(scene_data, studio_id)
47
+ stash.update_scene(scene["id"], movie["id"])
48
+ else
49
+ XxxRename.logger.info "#{"[STASH MOVIE ASSIGN]".colorize(:green)} #{scene_data.movie.name}"
50
+ stash.update_scene(scene["id"], existing_movie["id"])
51
+ end
52
+ rescue SocketError => e
53
+ XxxRename.logger.error "#{"[SOCKET ERROR #post_movie!]".colorize(:red)} #{e.message}"
54
+ nil
55
+ end
56
+
57
+ def studio_id(name)
58
+ stash.fetch_studio(name)&.[]("id")
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "singleton"
4
+
5
+ module XxxRename
6
+ class ActorsHelper
7
+ include Singleton
8
+
9
+ def matcher(matcher = nil)
10
+ raise Errors::FatalError, "#{self.class.name} initialised without matcher" if @matcher.nil? && matcher.nil?
11
+
12
+ @matcher ||= matcher
13
+ end
14
+
15
+ def female_actors
16
+ @female_actors ||= {}
17
+ end
18
+
19
+ def male_actors
20
+ @male_actors ||= {}
21
+ end
22
+
23
+ def append_female(actor_hash)
24
+ female_actors[actor_hash["compressed_name"]] = actor_hash["name"] if female_actors[actor_hash["compressed_name"]].nil?
25
+ end
26
+
27
+ def append_male(actor_hash)
28
+ male_actors[actor_hash["compressed_name"]] = actor_hash["name"] if male_actors[actor_hash["compressed_name"]].nil?
29
+ end
30
+
31
+ def male?(actor)
32
+ processed!(actor)
33
+ male_actors.key? actor.normalize
34
+ rescue Errors::UnprocessedEntity
35
+ false
36
+ end
37
+
38
+ def female?(actor)
39
+ processed!(actor)
40
+ female_actors.key? actor.normalize
41
+ rescue Errors::UnprocessedEntity
42
+ false
43
+ end
44
+
45
+ def processed?(actor)
46
+ processed!(actor)
47
+ rescue Errors::UnprocessedEntity
48
+ false
49
+ end
50
+
51
+ def processed!(actor)
52
+ return true if female_actors.key?(actor.normalize) || male_actors.key?(actor.normalize)
53
+
54
+ raise Errors::UnprocessedEntity, actor
55
+ end
56
+
57
+ def auto_fetch!(actor)
58
+ details = fetch_actor_details.details(actor)
59
+
60
+ raise Errors::UnprocessedEntity, actor if details.nil?
61
+
62
+ details["gender"].downcase == "female" ? append_female(details) : append_male(details)
63
+ nil
64
+ end
65
+
66
+ def auto_fetch(actor)
67
+ auto_fetch!(actor)
68
+ rescue Errors::UnprocessedEntity => e
69
+ XxxRename.logger.warn "Unable to fetch details for #{e.message}"
70
+ nil
71
+ end
72
+
73
+ private
74
+
75
+ def fetch_actor_details
76
+ @fetch_actor_details ||= FetchActorDetails.new(matcher)
77
+ end
78
+ end
79
+
80
+ class FetchActorDetails
81
+ # @param [XxxRename::SiteClientMatcher] matcher
82
+ def initialize(matcher)
83
+ @matcher = matcher
84
+ end
85
+
86
+ # @param [String] actor Search string for actor
87
+ # @return [nil, Hash] Hash of details or nil if not found
88
+ def details(actor)
89
+ clients.each do |client|
90
+ details = client.actor_details(actor)
91
+ next if details.nil?
92
+
93
+ XxxRename.logger.debug "#{client.class.name.split("::").last} matched actor #{actor} as #{details["gender"]}"
94
+ details.tap do |h|
95
+ h["compressed_name"] = details["name"].normalize
96
+ h["gender"] = details["gender"].normalize
97
+ end
98
+ return details
99
+ end
100
+ nil
101
+ end
102
+
103
+ private
104
+
105
+ attr_reader :matcher
106
+
107
+ def clients
108
+ [
109
+ matcher.initialise_site_client(:stash),
110
+ matcher.initialise_site_client(:brazzers),
111
+ matcher.initialise_site_client(:wicked),
112
+ matcher.initialise_site_client(:reality_kings),
113
+ matcher.initialise_site_client(:evil_angel)
114
+ ].compact
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,211 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+
5
+ module XxxRename
6
+ class Cli < Thor
7
+ SUPPORTED_SITES = %w[
8
+ adult_time
9
+ babes
10
+ blacked
11
+ blacked_raw
12
+ brazzers
13
+ digital_playground
14
+ elegant_angel
15
+ evil_angel
16
+ goodporn
17
+ jules_jordan
18
+ manuel_ferrara
19
+ mofos
20
+ naughty_america
21
+ nf_busty
22
+ reality_kings
23
+ stash
24
+ tushy
25
+ tushy_raw
26
+ twistys
27
+ vixen
28
+ whale_media
29
+ wicked
30
+ x_empire
31
+ zero_tolerance
32
+ ].freeze
33
+
34
+ def self.exit_on_failure?
35
+ true
36
+ end
37
+
38
+ desc "version", "Print the CLI version"
39
+ def version
40
+ require_relative "version"
41
+ puts "v#{XxxRename::VERSION}"
42
+ end
43
+ map %w[--version -v] => :version
44
+
45
+ long_desc <<-LONGDESC
46
+ Scan files and generate metadata
47
+
48
+ For first time users, run the command without any flags to generate a config
49
+ file in $HOME/.config/xxx_rename
50
+
51
+ $ xxx_rename generate
52
+
53
+ The cli will look for a config file in these three places in order:
54
+
55
+ * --config (This takes precedence over everything)
56
+
57
+ * $HOME/.config/xxx_rename
58
+
59
+ * HOME/xxx_rename
60
+
61
+ Examples
62
+
63
+ # Scan all files in a given directory and its sub-directories
64
+
65
+ $ xxx_rename generate . --nested
66
+
67
+ # Force the cli to use `brazzers` to match a file
68
+
69
+ $ xxx_rename generate . --verbose --override_site=brazzers
70
+
71
+ # Generate a migrations file to rename the matched files
72
+
73
+ $ xxx_rename generate . --actions=log_rename_op
74
+ LONGDESC
75
+ desc "generate FILE|FOLDER", "Rename a file or all file(s) inside a given directory"
76
+ option :config, alias: :c, type: :string, required: false, desc: "path to config file"
77
+ option :verbose, alias: :v, type: :boolean, default: false, desc: "enable verbose logging"
78
+ option :override_site, alias: :s, type: :string, required: false, desc: "force use an override site",
79
+ enum: SUPPORTED_SITES
80
+ option :nested, type: :boolean, default: false, desc: "recursively search for all files in the given directory"
81
+ option :force_refresh_datastore, type: :boolean, default: false, desc: "force site client to fetch all scenes, if implemented"
82
+ option :actions, alias: :a, type: :string, default: [], desc: "action to perform on a successful match",
83
+ enum: %w[sync_to_stash log_rename_op], repeatable: true
84
+ option :force_refresh, type: :boolean, default: false, desc: "force match scenes from original sites"
85
+ option :checkpoint, type: :string, required: false, desc: "skip all iterations until check-pointed file is matched"
86
+ def generate(object)
87
+ XxxRename.logger(verbose: options["verbose"])
88
+ config = Contract::ConfigGenerator.new(options).generate!
89
+ client = Client.new(config,
90
+ verbose: options["verbose"],
91
+ override_site: options["override_site"]&.to_sym,
92
+ nested: options["nested"],
93
+ checkpoint: options["checkpoint"])
94
+ client.generate(object)
95
+ rescue Interrupt
96
+ print "Exiting...\n".colorize(:green)
97
+ rescue Errors::FatalError => e
98
+ XxxRename.logger.fatal "#{e.class} #{e.message}".colorize(:red)
99
+ e.backtrace&.each { |x| XxxRename.logger.debug x }
100
+ exit 1
101
+ rescue StandardError => e
102
+ XxxRename.logger.fatal "CLI ran into an unexpected error. Report this on https://github.com/c477y/xxx_rename/issues/new"
103
+ XxxRename.logger.fatal "#{e.class} #{e.message}".colorize(:red)
104
+ e.backtrace&.each { |x| XxxRename.logger.fatal x }
105
+ exit 1
106
+ end
107
+
108
+ long_desc <<~LONGDESC
109
+ WARNING: This is a destructive operation as it will rename files
110
+ Run this on a small subset to be sure and run it at your own risk
111
+
112
+ Rename files based on operations listed in a migration file.
113
+
114
+ All rename files are located in your `generated_files_dir` directory.
115
+ Migration files are plain YAML files of format 'output_YYYYMMDDHHMM.yml'
116
+
117
+ Pass a migration file using option --version YYYYMMDDHHMM
118
+ If you want to apply a migration file that you have just created,
119
+ pass the --version as "latest". Or don't pass the --version
120
+ flag and the CLI will use the latest version by default.
121
+
122
+ Migration files have the following format:
123
+
124
+ ---
125
+ # 0 means the migration is not applied
126
+
127
+ # 1 means the migration is applied
128
+
129
+ # This flag prevents applying a migration that has already been applied
130
+
131
+ ___MIGRATION_STATUS___: 0
132
+
133
+ # All operations are stored as an array
134
+
135
+ ___RENAME_ACTIONS___:
136
+
137
+ # DO NOT MANIPULATE AN ARRAY ITEM! Doing so can result in unexpected
138
+
139
+ # behaviour. You can remove an operation from the list entirely, but
140
+
141
+ # the recommended way is to discard this migration completely,
142
+
143
+ # modify the `output_format` for your file in the config and run the
144
+
145
+ # generate command again
146
+
147
+ - !ruby/object:XxxRename::Data::FileRenameOp
148
+
149
+ attributes:
150
+
151
+ :key: eab204175567d39202c1df5895e443be # DO NOT MODIFY THIS#{" "}
152
+
153
+ :directory: "/ABSOLUTE/DIRECTORY/TOFILE"
154
+
155
+ :source_filename: ORIGINAL_FILENAME.MP4
156
+
157
+ :output_filename: NEW_FILENAME.MP4
158
+
159
+ :mtime: 2000-01-01 00:00:00.000000000 +00:00
160
+
161
+ Example Usage:
162
+
163
+ $ xxx_rename migrate --version=202301131252
164
+
165
+ $ xxx_rename migrate
166
+ LONGDESC
167
+ desc "migrate --version=VERSION", "Apply a rename migration file"
168
+ option :config, alias: :c, type: :string, required: false, desc: "path to config file"
169
+ option :version, type: :string, default: "latest", desc: "Name of migration file to apply"
170
+ def migrate
171
+ config = Contract::ConfigGenerator.new(options.slice("config")).generate!
172
+ MigrationClient.new(config, options["version"]).apply
173
+ rescue Interrupt
174
+ print "Exiting...\n".colorize(:green)
175
+ rescue Errors::FatalError => e
176
+ XxxRename.logger.fatal "#{e.class} #{e.message}".colorize(:red)
177
+ e.backtrace&.each { |x| XxxRename.logger.debug x }
178
+ exit 1
179
+ rescue StandardError => e
180
+ XxxRename.logger.fatal "CLI ran into an unexpected error. Report this on https://github.com/c477y/xxx_rename/issues/new"
181
+ XxxRename.logger.fatal "#{e.class} #{e.message}".colorize(:red)
182
+ e.backtrace&.each { |x| XxxRename.logger.fatal x }
183
+ exit 1
184
+ end
185
+
186
+ long_desc <<-LONGDESC
187
+ Reverse the actions taken by the `migrate` command
188
+
189
+ Read the help command for more information
190
+ $ xxx_rename help migrate
191
+ LONGDESC
192
+ desc "rollback --version=VERSION", "Rollback a migration"
193
+ option :config, alias: :c, type: :string, required: false, desc: "path to config file"
194
+ option :version, type: :string, default: "latest", desc: "Name of migration file to apply"
195
+ def rollback
196
+ config = Contract::ConfigGenerator.new(options.slice("config")).generate!
197
+ MigrationClient.new(config, options["version"]).rollback
198
+ rescue Interrupt
199
+ print "Exiting...\n".colorize(:green)
200
+ rescue Errors::FatalError => e
201
+ XxxRename.logger.fatal "#{e.class} #{e.message}".colorize(:red)
202
+ e.backtrace&.each { |x| XxxRename.logger.debug x }
203
+ exit 1
204
+ rescue StandardError => e
205
+ XxxRename.logger.fatal "CLI ran into an unexpected error. Report this on https://github.com/c477y/xxx_rename/issues/new"
206
+ XxxRename.logger.fatal "#{e.class} #{e.message}".colorize(:red)
207
+ e.backtrace&.each { |x| XxxRename.logger.fatal x }
208
+ exit 1
209
+ end
210
+ end
211
+ end