reen 1.0.0 → 1.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.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/bin/reen +1 -82
- data/lib/reen/application/reen_cli.rb +85 -0
- data/lib/reen/application/renamer.rb +67 -0
- data/lib/reen/{change.rb → domain/change.rb} +5 -22
- data/lib/reen/{changes.rb → domain/changes.rb} +0 -5
- data/lib/reen/{path_entry_list.rb → domain/path_entry_list.rb} +3 -0
- data/lib/reen/{changes_file.rb → infrastructure/changes_file.rb} +2 -0
- data/lib/reen/version.rb +1 -1
- data/lib/reen.rb +7 -6
- data/reen.gemspec +35 -0
- data/sig/{reenrb.rbs → reen.rbs} +1 -1
- metadata +14 -13
- data/CLAUDE.feature-numbered-lists.md +0 -145
- data/lib/reen/reen.rb +0 -46
- /data/lib/reen/{path_entry.rb → domain/path_entry.rb} +0 -0
- /data/lib/reen/{actions → infrastructure/actions}/delete.rb +0 -0
- /data/lib/reen/{actions → infrastructure/actions}/force_delete.rb +0 -0
- /data/lib/reen/{actions → infrastructure/actions}/nothing.rb +0 -0
- /data/lib/reen/{actions → infrastructure/actions}/rename.rb +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d84332445053fed05a469540f848b194b7ddc94374b5ae42e805e84681e53f9b
|
|
4
|
+
data.tar.gz: 966507f2ef7e0d07c273c758dd9242535acba82414876376d5f73432a4868c54
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 245fbb2118dd45f596c4794f9e59df822a7bf91ce1c5dc38d7ecfdf356d6ca4333cf8360b490e3d01ff184c741069934b499b3d61f7792b26f9733624427cfb2
|
|
7
|
+
data.tar.gz: 6c4e07c278d6ba23a9930f0f04f334004b80caba6bdb53389f2f495b5f4ccbf8e15ce46c7f9d883967700b423791cb1dc2dffc5b1fb39be3a9300a59fc6d3c77
|
data/README.md
CHANGED
|
@@ -103,7 +103,7 @@ Use Reen programmatically using the `reen` gem. In the example below, we specify
|
|
|
103
103
|
require 'reen'
|
|
104
104
|
|
|
105
105
|
glob = Dir.glob("*")
|
|
106
|
-
reen = Reen::
|
|
106
|
+
reen = Reen::Renamer.new(editor: nil)
|
|
107
107
|
|
|
108
108
|
reen.execute(glob) do |file|
|
|
109
109
|
# Rename LICENSE.txt -> LICENSE.md (gsub works on the path portion)
|
data/bin/reen
CHANGED
|
@@ -3,90 +3,9 @@
|
|
|
3
3
|
|
|
4
4
|
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
|
|
5
5
|
require "reen"
|
|
6
|
-
require "optparse"
|
|
7
|
-
|
|
8
|
-
# Reen command line application
|
|
9
|
-
class ReenCLI
|
|
10
|
-
EDITOR_MSG = "Editor not set in $VISUAL or $EDITOR -- please set one of those environment variables"
|
|
11
|
-
Options = Struct.new(:editor, :review) do
|
|
12
|
-
def initialize(editor: nil, review: false)
|
|
13
|
-
editor ||= ENV["VISUAL"] || ENV["EDITOR"] # rubocop:disable Style/FetchEnvVar
|
|
14
|
-
super(editor, review)
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
attr_reader :options, :files
|
|
19
|
-
|
|
20
|
-
def initialize(args)
|
|
21
|
-
@options = Options.new
|
|
22
|
-
optparse = OptionParser.new { |parser| setup_options(parser) }
|
|
23
|
-
optparse.parse!(args, into: @options)
|
|
24
|
-
|
|
25
|
-
exit_with_msg(EDITOR_MSG) unless options.editor
|
|
26
|
-
|
|
27
|
-
@files = args.empty? ? Dir.glob("*") : args
|
|
28
|
-
rescue OptionParser::InvalidOption => e
|
|
29
|
-
puts "#{e.message}\n\n"
|
|
30
|
-
exit_with_msg(optparse)
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def setup_options(parser) # rubocop:disable Metrics/MethodLength
|
|
34
|
-
parser.banner = "Usage: reen files [options]"
|
|
35
|
-
parser.version = Reen::VERSION
|
|
36
|
-
|
|
37
|
-
parser.on("-e", "--editor [EDITOR]", "Use EDITOR (default: $EDITOR)") do |ed|
|
|
38
|
-
@options.editor = ed || ENV["EDITOR"] # rubocop:disable Style/FetchEnvVar
|
|
39
|
-
end
|
|
40
|
-
parser.on("-v", "--visual [EDITOR]", "Use EDITOR (default: $VISUAL)") do |ed|
|
|
41
|
-
@options.editor = ed || ENV["VISUAL"] # rubocop:disable Style/FetchEnvVar
|
|
42
|
-
end
|
|
43
|
-
parser.on("-r", "--review", "Require review and confirmation of changes")
|
|
44
|
-
|
|
45
|
-
parser.on("-h", "--help", "Show help for options") do
|
|
46
|
-
exit_with_msg(parser)
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def exit_with_msg(message)
|
|
51
|
-
puts message
|
|
52
|
-
exit(false)
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def review_changes
|
|
56
|
-
puts
|
|
57
|
-
puts @requests.change_requested.summarize
|
|
58
|
-
print "\nContinue? (y/n) "
|
|
59
|
-
confirmation = %w[y yes].include?($stdin.gets.chomp.downcase)
|
|
60
|
-
exit_with_msg("Nothing changed") unless confirmation
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def check_inputs
|
|
64
|
-
@files = files.empty? ? Dir.glob("*") : files
|
|
65
|
-
exit_with_msg(EDITOR_MSG) unless options.editor
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def user_review?
|
|
69
|
-
@options.review && @requests.changes_requested?
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def call
|
|
73
|
-
@requests = Reen::Reen.new(editor: options.editor).request(files)
|
|
74
|
-
review_changes if user_review?
|
|
75
|
-
|
|
76
|
-
changes = @requests
|
|
77
|
-
.execute_all
|
|
78
|
-
.change_requested
|
|
79
|
-
|
|
80
|
-
if user_review? && changes.all_executed?
|
|
81
|
-
puts "Changes made"
|
|
82
|
-
else
|
|
83
|
-
puts changes.summarize
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
6
|
|
|
88
7
|
begin
|
|
89
|
-
ReenCLI.new(ARGV).call
|
|
8
|
+
Reen::ReenCLI.new(ARGV).call
|
|
90
9
|
rescue Reen::Error => e
|
|
91
10
|
puts "#{e.message}\n"
|
|
92
11
|
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "optparse"
|
|
4
|
+
|
|
5
|
+
module Reen
|
|
6
|
+
# Reen command line application
|
|
7
|
+
class ReenCLI
|
|
8
|
+
EDITOR_MSG = "Editor not set in $VISUAL or $EDITOR -- please set one of those environment variables"
|
|
9
|
+
Options = Struct.new(:editor, :review) do
|
|
10
|
+
def initialize(editor: nil, review: false)
|
|
11
|
+
editor ||= ENV["VISUAL"] || ENV["EDITOR"] # rubocop:disable Style/FetchEnvVar
|
|
12
|
+
super(editor, review)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
attr_reader :options, :files
|
|
17
|
+
|
|
18
|
+
def initialize(args)
|
|
19
|
+
@options = Options.new
|
|
20
|
+
optparse = OptionParser.new { |parser| setup_options(parser) }
|
|
21
|
+
optparse.parse!(args, into: @options)
|
|
22
|
+
|
|
23
|
+
exit_with_msg(EDITOR_MSG) unless options.editor
|
|
24
|
+
|
|
25
|
+
@files = args.empty? ? Dir.glob("*") : args
|
|
26
|
+
rescue OptionParser::InvalidOption => e
|
|
27
|
+
puts "#{e.message}\n\n"
|
|
28
|
+
exit_with_msg(optparse)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def setup_options(parser) # rubocop:disable Metrics/MethodLength
|
|
32
|
+
parser.banner = "Usage: reen files [options]"
|
|
33
|
+
parser.version = Reen::VERSION
|
|
34
|
+
|
|
35
|
+
parser.on("-e", "--editor [EDITOR]", "Use EDITOR (default: $EDITOR)") do |ed|
|
|
36
|
+
@options.editor = ed || ENV["EDITOR"] # rubocop:disable Style/FetchEnvVar
|
|
37
|
+
end
|
|
38
|
+
parser.on("-v", "--visual [EDITOR]", "Use EDITOR (default: $VISUAL)") do |ed|
|
|
39
|
+
@options.editor = ed || ENV["VISUAL"] # rubocop:disable Style/FetchEnvVar
|
|
40
|
+
end
|
|
41
|
+
parser.on("-r", "--review", "Require review and confirmation of changes")
|
|
42
|
+
|
|
43
|
+
parser.on("-h", "--help", "Show help for options") do
|
|
44
|
+
exit_with_msg(parser)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def exit_with_msg(message)
|
|
49
|
+
puts message
|
|
50
|
+
exit(false)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def review_changes
|
|
54
|
+
puts
|
|
55
|
+
puts @changes.summarize
|
|
56
|
+
print "\nContinue? (y/n) "
|
|
57
|
+
confirmation = %w[y yes].include?($stdin.gets.chomp.downcase)
|
|
58
|
+
exit_with_msg("Nothing changed") unless confirmation
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def check_inputs
|
|
62
|
+
@files = files.empty? ? Dir.glob("*") : files
|
|
63
|
+
exit_with_msg(EDITOR_MSG) unless options.editor
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def user_wants_review?
|
|
67
|
+
@options.review && @requests.changes_requested?
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def call
|
|
71
|
+
@service = Renamer.new(editor: options.editor)
|
|
72
|
+
@requests = @service.request(files)
|
|
73
|
+
@changes = @requests.change_requested
|
|
74
|
+
|
|
75
|
+
review_changes if user_wants_review?
|
|
76
|
+
@service.execute_changes
|
|
77
|
+
|
|
78
|
+
if user_wants_review? && @changes.all_executed?
|
|
79
|
+
puts "Changes made"
|
|
80
|
+
else
|
|
81
|
+
puts @changes.summarize
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../infrastructure/actions/delete"
|
|
4
|
+
require_relative "../infrastructure/actions/force_delete"
|
|
5
|
+
require_relative "../infrastructure/actions/rename"
|
|
6
|
+
require_relative "../infrastructure/actions/nothing"
|
|
7
|
+
|
|
8
|
+
module Reen
|
|
9
|
+
# Renames pattern of files with given editor
|
|
10
|
+
# Examples:
|
|
11
|
+
# Reen::Renamer.new(editor: "code -w").call("spec/fixtures/example/*")
|
|
12
|
+
# Reen::Renamer.new(editor: nil).call("spec/fixtures/example/*") { ... }
|
|
13
|
+
class Renamer
|
|
14
|
+
DEL_ERROR = "Do not remove any file/folder names (no changes made)"
|
|
15
|
+
|
|
16
|
+
ACTION_HANDLER = {
|
|
17
|
+
Change::CHANGE::NONE => Actions::DoNothing,
|
|
18
|
+
Change::CHANGE::DELETE => Actions::Delete,
|
|
19
|
+
Change::CHANGE::FORCE_DELETE => Actions::ForceDelete,
|
|
20
|
+
Change::CHANGE::RENAME => Actions::Rename
|
|
21
|
+
}.freeze
|
|
22
|
+
|
|
23
|
+
attr_reader :changes
|
|
24
|
+
|
|
25
|
+
def initialize(editor: "emacs", options: {})
|
|
26
|
+
@editor = editor
|
|
27
|
+
@options = options
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def request(original_list, &block)
|
|
31
|
+
@entry_list = PathEntryList.new(original_list)
|
|
32
|
+
changed_list = ChangesFile.new(@entry_list).allow_changes(@editor, &block)
|
|
33
|
+
|
|
34
|
+
raise(Error, DEL_ERROR) if changed_list.size != @entry_list.count
|
|
35
|
+
|
|
36
|
+
@changes = detect_changes(@entry_list, changed_list)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def execute(original_list, &block)
|
|
40
|
+
@changes ||= request(original_list, &block)
|
|
41
|
+
execute_changes
|
|
42
|
+
@changes
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def execute_changes
|
|
46
|
+
@changes.list.each do |change|
|
|
47
|
+
next if change.not_accepted?
|
|
48
|
+
|
|
49
|
+
error = ACTION_HANDLER[change.change].new(change.original, change.requested).call
|
|
50
|
+
error ? change.mark_failed(error) : change.mark_executed
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def detect_changes(entry_list, changed_list)
|
|
57
|
+
changed_by_number = PathEntryList.from_numbered(changed_list)
|
|
58
|
+
entries = entry_list.to_a
|
|
59
|
+
|
|
60
|
+
changes = changed_by_number.map do |number, revised|
|
|
61
|
+
Change.new(entries[number - 1], revised)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
Changes.new(changes)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "actions/delete"
|
|
4
|
-
require_relative "actions/force_delete"
|
|
5
|
-
require_relative "actions/rename"
|
|
6
|
-
require_relative "actions/nothing"
|
|
7
|
-
|
|
8
3
|
module Reen
|
|
9
4
|
# Change to an orignal file
|
|
10
5
|
class Change
|
|
@@ -29,13 +24,6 @@ module Reen
|
|
|
29
24
|
FOLDER = :folder
|
|
30
25
|
end
|
|
31
26
|
|
|
32
|
-
ACTION_HANDLER = {
|
|
33
|
-
CHANGE::NONE => Actions::DoNothing,
|
|
34
|
-
CHANGE::DELETE => Actions::Delete,
|
|
35
|
-
CHANGE::FORCE_DELETE => Actions::ForceDelete,
|
|
36
|
-
CHANGE::RENAME => Actions::Rename
|
|
37
|
-
}.freeze
|
|
38
|
-
|
|
39
27
|
CHANGES_DESC = {
|
|
40
28
|
CHANGE::NONE => "Nothing",
|
|
41
29
|
CHANGE::DELETE => "Deleting",
|
|
@@ -87,18 +75,13 @@ module Reen
|
|
|
87
75
|
end
|
|
88
76
|
end
|
|
89
77
|
|
|
90
|
-
def
|
|
91
|
-
return self if not_accepted?
|
|
92
|
-
|
|
78
|
+
def mark_executed
|
|
93
79
|
@status = STATUS::EXECUTED
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if error
|
|
97
|
-
@status = STATUS::FAILED
|
|
98
|
-
@reason = error
|
|
99
|
-
end
|
|
80
|
+
end
|
|
100
81
|
|
|
101
|
-
|
|
82
|
+
def mark_failed(reason)
|
|
83
|
+
@status = STATUS::FAILED
|
|
84
|
+
@reason = reason
|
|
102
85
|
end
|
|
103
86
|
|
|
104
87
|
# Predicates
|
|
@@ -27,6 +27,9 @@ module Reen
|
|
|
27
27
|
end
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
+
# Parses "[NN] path" lines into { number => path } hash.
|
|
31
|
+
# Preserves line order (Ruby hash insertion order), so callers get editor order.
|
|
32
|
+
# Silently skips lines that don't match the [NN] pattern.
|
|
30
33
|
def self.from_numbered(lines)
|
|
31
34
|
lines.each_with_object({}) do |line, hash|
|
|
32
35
|
match = line.match(/^\[(\d+)\] (.*)/)
|
|
@@ -24,6 +24,8 @@ module Reen
|
|
|
24
24
|
@list_file.close
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
# Returns only valid "[NN] path" entry lines (comments and blanks stripped).
|
|
28
|
+
# Line order reflects the user's editor arrangement.
|
|
27
29
|
def allow_changes(editor, &block)
|
|
28
30
|
await_editor(editor) if editor
|
|
29
31
|
@list = File.read(path).split("\n").map(&:strip)
|
data/lib/reen/version.rb
CHANGED
data/lib/reen.rb
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "reen/version"
|
|
4
|
-
require_relative "reen/path_entry"
|
|
5
|
-
require_relative "reen/path_entry_list"
|
|
6
|
-
require_relative "reen/
|
|
7
|
-
require_relative "reen/
|
|
8
|
-
require_relative "reen/
|
|
9
|
-
require_relative "reen/
|
|
4
|
+
require_relative "reen/domain/path_entry"
|
|
5
|
+
require_relative "reen/domain/path_entry_list"
|
|
6
|
+
require_relative "reen/domain/change"
|
|
7
|
+
require_relative "reen/domain/changes"
|
|
8
|
+
require_relative "reen/infrastructure/changes_file"
|
|
9
|
+
require_relative "reen/application/renamer"
|
|
10
|
+
require_relative "reen/application/reen_cli"
|
|
10
11
|
|
|
11
12
|
module Reen
|
|
12
13
|
class Error < StandardError; end
|
data/reen.gemspec
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/reen/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "reen"
|
|
7
|
+
spec.version = Reen::VERSION
|
|
8
|
+
spec.authors = ["Soumya Ray"]
|
|
9
|
+
spec.email = ["soumya.ray@gmail.com"]
|
|
10
|
+
|
|
11
|
+
spec.summary = "Renames or deletes a pattern of files using your favorite editor"
|
|
12
|
+
spec.description = "Renames or deletes a pattern of files using your favorite editor"
|
|
13
|
+
spec.homepage = "https://github.com/soumyaray/reen"
|
|
14
|
+
spec.license = "MIT"
|
|
15
|
+
spec.required_ruby_version = ">= 3.0"
|
|
16
|
+
|
|
17
|
+
# spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'"
|
|
18
|
+
|
|
19
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
20
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
|
21
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
|
22
|
+
# spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
|
|
23
|
+
|
|
24
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
25
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
|
26
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
spec.bindir = "bin"
|
|
30
|
+
spec.executables << "reen"
|
|
31
|
+
spec.require_paths = ["lib"]
|
|
32
|
+
|
|
33
|
+
# For more information and examples about making a new gem, check out our
|
|
34
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
|
35
|
+
end
|
data/sig/{reenrb.rbs → reen.rbs}
RENAMED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: reen
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Soumya Ray
|
|
@@ -21,25 +21,26 @@ files:
|
|
|
21
21
|
- ".markdownlint.json"
|
|
22
22
|
- ".rubocop.yml"
|
|
23
23
|
- CHANGELOG.md
|
|
24
|
-
- CLAUDE.feature-numbered-lists.md
|
|
25
24
|
- Gemfile
|
|
26
25
|
- LICENSE.txt
|
|
27
26
|
- README.md
|
|
28
27
|
- Rakefile
|
|
29
28
|
- bin/reen
|
|
30
29
|
- lib/reen.rb
|
|
31
|
-
- lib/reen/
|
|
32
|
-
- lib/reen/
|
|
33
|
-
- lib/reen/
|
|
34
|
-
- lib/reen/
|
|
35
|
-
- lib/reen/
|
|
36
|
-
- lib/reen/
|
|
37
|
-
- lib/reen/
|
|
38
|
-
- lib/reen/
|
|
39
|
-
- lib/reen/
|
|
40
|
-
- lib/reen/
|
|
30
|
+
- lib/reen/application/reen_cli.rb
|
|
31
|
+
- lib/reen/application/renamer.rb
|
|
32
|
+
- lib/reen/domain/change.rb
|
|
33
|
+
- lib/reen/domain/changes.rb
|
|
34
|
+
- lib/reen/domain/path_entry.rb
|
|
35
|
+
- lib/reen/domain/path_entry_list.rb
|
|
36
|
+
- lib/reen/infrastructure/actions/delete.rb
|
|
37
|
+
- lib/reen/infrastructure/actions/force_delete.rb
|
|
38
|
+
- lib/reen/infrastructure/actions/nothing.rb
|
|
39
|
+
- lib/reen/infrastructure/actions/rename.rb
|
|
40
|
+
- lib/reen/infrastructure/changes_file.rb
|
|
41
41
|
- lib/reen/version.rb
|
|
42
|
-
-
|
|
42
|
+
- reen.gemspec
|
|
43
|
+
- sig/reen.rbs
|
|
43
44
|
homepage: https://github.com/soumyaray/reen
|
|
44
45
|
licenses:
|
|
45
46
|
- MIT
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
# Numbered List Prefixes for Editor
|
|
2
|
-
|
|
3
|
-
> **IMPORTANT**: This plan must be kept up-to-date at all times. Assume context can be cleared at any time — this file is the single source of truth for the current state of this work. Update this plan before and after task and subtask implementations.
|
|
4
|
-
|
|
5
|
-
## Branch
|
|
6
|
-
|
|
7
|
-
`feature-numbered-lists`
|
|
8
|
-
|
|
9
|
-
## Goal
|
|
10
|
-
|
|
11
|
-
Add numbered prefixes (e.g., `[01]`, `[002]`) to the file list shown in the editor so users can freely reorder entries without losing track of which original file each line corresponds to. Matching changes back to originals uses the number, not line position. Also fix the dash-operator ambiguity by requiring a space after `-`/`--` to distinguish delete operations from literal filenames starting with dashes. Introduce `PathEntry` and `PathEntryList` domain objects to replace raw path strings — `PathEntry` wraps a file path and answers filesystem queries, `PathEntryList` is a collection that owns numbered serialization for the editor.
|
|
12
|
-
|
|
13
|
-
## Strategy: Vertical Slice
|
|
14
|
-
|
|
15
|
-
1. **Refactor** — Introduce `PathEntry`/`PathEntryList` domain objects, migrate existing code (all tests pass)
|
|
16
|
-
2. **Fix** — Require space after `-`/`--` for delete operators (dash ambiguity fix)
|
|
17
|
-
3. **Feature** — Add numbered prefixes and number-based matching
|
|
18
|
-
4. **Verify** — Manual test with real editor confirms behavior
|
|
19
|
-
|
|
20
|
-
## Current State
|
|
21
|
-
|
|
22
|
-
- [x] Plan created
|
|
23
|
-
- [x] Slice 0: PathEntry/PathEntryList refactor
|
|
24
|
-
- [x] Slice 1: Dash-operator ambiguity fix
|
|
25
|
-
- [x] Slice 2: Numbered prefixes
|
|
26
|
-
- [x] Slice 3: Number-based matching
|
|
27
|
-
- [x] Slice 4: Documentation
|
|
28
|
-
- [ ] Slice 5: Verification
|
|
29
|
-
|
|
30
|
-
## Key Findings
|
|
31
|
-
|
|
32
|
-
- `Change` currently owns filesystem queries (`request_dir?`, `request_file?`, `request_empty_dir?`, `request_full_dir?`) by checking `@original` path directly. These should move to `PathEntry`.
|
|
33
|
-
- `Change` will hold an `PathEntry` instead of a raw `@original` string. `Change#original` delegates to `PathEntry#path`.
|
|
34
|
-
- `PathEntryList` wraps an array of `PathEntry` objects. Constructed from `Dir.glob` results (array of strings). Owns numbered serialization (writing `[NN] path` lines) and parsing (stripping prefixes, returning number-keyed entries).
|
|
35
|
-
- `ChangesFile#initialize` currently writes `requested_list.join("\n")` — will instead take an `PathEntryList` and call its serialization.
|
|
36
|
-
- `ChangesFile#allow_changes` reads back lines, strips comments/blanks — will delegate prefix parsing to `PathEntryList`.
|
|
37
|
-
- `Reen#request` does positional matching via `original_list.zip(changed_list)` — changes to number-based matching via `PathEntryList`.
|
|
38
|
-
- `Reen#request` raises if list sizes differ (line deletion guard) — still valid with numbered approach.
|
|
39
|
-
- `Change#extract_request` parses operator prefixes (`-`, `--`) greedily, which means renaming to `-` or `--` is impossible. Fix: require a space after `-`/`--` to trigger delete.
|
|
40
|
-
- Tests manipulate `file.list` directly (mock editor) — the list exposed to the block will include number prefixes so test blocks simulate what a real user sees/edits.
|
|
41
|
-
- Number width is auto-sized: `format("%0#{width}d")` where `width = list.size.to_s.length`.
|
|
42
|
-
|
|
43
|
-
## Questions
|
|
44
|
-
|
|
45
|
-
- ~~Should `Change` know about number prefixes?~~ No — `ChangesFile` handles writing and stripping prefixes. `Change` and `Reen` see clean filenames. `Reen#compare_lists` changes from positional zip to number-keyed matching.
|
|
46
|
-
- ~~Should dashes go before or after number prefixes?~~ After. Dashes are operators on the filename, numbers are stable anchors: `[01] - file` = delete, `[01] -file` = rename to `-file`. Require space after `-`/`--` to distinguish.
|
|
47
|
-
- [ ] Should reordering be reflected in the output summary? (Probably not needed for v1.)
|
|
48
|
-
|
|
49
|
-
## Scope
|
|
50
|
-
|
|
51
|
-
**In scope**:
|
|
52
|
-
|
|
53
|
-
- `PathEntry` (`lib/reenrb/path_entry.rb`): wraps a file path, provides `file?`, `dir?`, `empty_dir?`, `full_dir?`, `path`
|
|
54
|
-
- `PathEntryList` (`lib/reenrb/path_entry_list.rb`): collection of `PathEntry`, constructed from string array, owns numbered serialization/parsing
|
|
55
|
-
- `Change`: refactor to hold `PathEntry` instead of raw string, delegate filesystem queries to `PathEntry`
|
|
56
|
-
- `Change#extract_request`: require space after `-`/`--` for delete operators (fixes dash-name ambiguity)
|
|
57
|
-
- `ChangesFile`: use `PathEntryList` for writing/reading, numbered prefixes, updated instructions
|
|
58
|
-
- `Reen`: accept `PathEntryList` (or build one from string array), number-based matching
|
|
59
|
-
- Tests for: `PathEntry`, `PathEntryList`, dash-space fix, prefix formatting/stripping, reordered matching
|
|
60
|
-
|
|
61
|
-
**Out of scope**:
|
|
62
|
-
|
|
63
|
-
- Reporting reorder information to the user
|
|
64
|
-
- Any CLI flag to disable numbering
|
|
65
|
-
- `Changes` refactor (stays as-is, wraps array of `Change`)
|
|
66
|
-
- File/folder creation via unnumbered lines (touch/mkdir) — future feature, separate branch
|
|
67
|
-
|
|
68
|
-
## Tasks
|
|
69
|
-
|
|
70
|
-
> **Strict TDD cycle**: For each slice — (1) run full suite to confirm green baseline, (2) write failing tests (RED), (3) run tests to confirm they fail, (4) implement (GREEN), (5) run full suite to confirm all pass. **Check off each step immediately after completing it.**
|
|
71
|
-
|
|
72
|
-
### Slice 0: Introduce PathEntry and PathEntryList domain objects (refactor)
|
|
73
|
-
|
|
74
|
-
- [x] 0.0 Run full suite — confirm green baseline (11 tests, 0 failures)
|
|
75
|
-
- [x] 0.1 RED — Write failing tests in `spec/spec_path_entry.rb`:
|
|
76
|
-
- [x] 0.1a `PathEntry` wraps a path and delegates `file?`, `dir?`, `empty_dir?`, `full_dir?`
|
|
77
|
-
- [x] 0.1b `PathEntryList` constructs from array of strings, iterates as `PathEntry` objects
|
|
78
|
-
- [x] 0.1c `PathEntryList#paths` returns array of path strings
|
|
79
|
-
- [x] 0.2 Run new tests — confirm RED (6 errors, uninitialized constant)
|
|
80
|
-
- [x] 0.3 GREEN — Implement `PathEntry` (`lib/reenrb/path_entry.rb`)
|
|
81
|
-
- [x] 0.4 GREEN — Implement `PathEntryList` (`lib/reenrb/path_entry_list.rb`)
|
|
82
|
-
- [x] 0.5 Run new tests — confirm GREEN (6 runs, 27 assertions, 0 failures)
|
|
83
|
-
- [x] 0.6 Refactor `Change` to hold `PathEntry`, delegate filesystem queries
|
|
84
|
-
- [x] 0.7 Refactor `Reen` and `ChangesFile` to accept/use `PathEntryList`
|
|
85
|
-
- [x] 0.8 Run full suite — confirm all tests pass (17 runs, 58 assertions, 0 failures)
|
|
86
|
-
|
|
87
|
-
### Slice 1: Fix dash-operator ambiguity in Change
|
|
88
|
-
|
|
89
|
-
- [x] 1.0 Run full suite — confirm green baseline (17 runs, 0 failures)
|
|
90
|
-
- [x] 1.1 RED — Write failing tests in `spec/spec_change.rb`:
|
|
91
|
-
- [x] 1.1a `- filename` (with space) triggers delete
|
|
92
|
-
- [x] 1.1b `-filename` (no space) triggers rename to `-filename`
|
|
93
|
-
- [x] 1.1c `-- filename` (with space) triggers force delete
|
|
94
|
-
- [x] 1.1d `--filename` (no space) triggers rename to `--filename`
|
|
95
|
-
- [x] 1.2 Run new tests — confirm RED (1.1b and 1.1d fail as expected)
|
|
96
|
-
- [x] 1.3 GREEN — Updated `Change#extract_request` regex: `^(--|-)·(?<name>.*)` with fallback `^()(?<name>.*)`
|
|
97
|
-
- [x] 1.4 Run full suite — confirm GREEN (21 runs, 64 assertions, 0 failures)
|
|
98
|
-
|
|
99
|
-
### Slice 2: Numbered prefixes in PathEntryList and ChangesFile
|
|
100
|
-
|
|
101
|
-
- [x] 2.0 Run full suite — confirm green baseline (21 runs, 0 failures)
|
|
102
|
-
- [x] 2.1 RED — Write failing tests:
|
|
103
|
-
- [x] 2.1a `PathEntryList#to_numbered` serializes as `[NN] path` lines (auto-sized width)
|
|
104
|
-
- [x] 2.1b `PathEntryList.from_numbered` parses `[NN] path` lines back to number-keyed entries
|
|
105
|
-
- [x] 2.1c (deferred to Slice 4 — documentation)
|
|
106
|
-
- [x] 2.2 Run new tests — confirm RED (3 errors, undefined method `to_numbered`)
|
|
107
|
-
- [x] 2.3 GREEN — Implement `PathEntryList#to_numbered` and `PathEntryList.from_numbered`
|
|
108
|
-
- [x] 2.4 GREEN — Update `ChangesFile` to use numbered serialization
|
|
109
|
-
- [x] 2.5 GREEN — Update `INSTRUCTIONS` constant with reorder/number/dash-space guidance
|
|
110
|
-
- [x] 2.6 Run full suite — deferred to Slice 3 (existing tests broke as expected, fixed there)
|
|
111
|
-
|
|
112
|
-
### Slice 3: Number-based matching in Reen
|
|
113
|
-
|
|
114
|
-
- [x] 3.0 (combined with Slice 2 — baseline was 21 runs before numbered output broke existing tests)
|
|
115
|
-
- [x] 3.1 RED — Write failing tests in `spec/spec_reorder.rb`:
|
|
116
|
-
- [x] 3.1a `Reen#request` correctly matches reordered entries to originals by number (reverse, rename+move)
|
|
117
|
-
- [x] 3.1b `Reen#request` still raises error when lines are removed
|
|
118
|
-
- [x] 3.2 Run new tests — confirm RED (3.1a failed, 3.1b passed)
|
|
119
|
-
- [x] 3.3 GREEN — Changed `Reen#compare_lists` to use `PathEntryList.from_numbered` for number-keyed matching
|
|
120
|
-
- [x] 3.4 GREEN — Updated existing tests: delete/force-delete use `.sub("] ", "] - ")` pattern
|
|
121
|
-
- [x] 3.5 Run full suite — confirm all pass (27 runs, 110 assertions, 0 failures)
|
|
122
|
-
|
|
123
|
-
### Slice 4: Documentation
|
|
124
|
-
|
|
125
|
-
- [x] 4.1 Update README.md: documented numbered prefixes, dash-space syntax, reordering, programmatic examples
|
|
126
|
-
- [x] 4.2 CLI help in `bin/reen` — no changes needed (uses banner/options only, editing syntax is in INSTRUCTIONS)
|
|
127
|
-
- [x] 4.3 `ChangesFile::INSTRUCTIONS` — updated in Slice 2.5, verified
|
|
128
|
-
|
|
129
|
-
### Slice 5: Verification
|
|
130
|
-
|
|
131
|
-
- [x] 5.1 Run full suite (`bundle exec rake`) — 29 tests, 115 assertions, 0 failures. RuboCop: only pre-existing offenses, no new ones.
|
|
132
|
-
- [ ] 5.2 Manual verification with real editor (user to perform)
|
|
133
|
-
|
|
134
|
-
## Completed
|
|
135
|
-
|
|
136
|
-
- Slice 0: PathEntry/PathEntryList refactor — `PathEntry` wraps paths with filesystem queries, `PathEntryList` is an Enumerable collection. `Change` delegates to `PathEntry`. `Reen` and `ChangesFile` accept `PathEntryList`. All 17 tests pass.
|
|
137
|
-
- Slice 1: Dash-operator ambiguity fix — `Change#extract_request` now requires space after `-`/`--` for delete operators. `-file` is rename to `-file`, `- file` is delete. All 21 tests pass.
|
|
138
|
-
- Slice 2: Numbered prefixes — `PathEntryList#to_numbered` and `.from_numbered` implemented. `ChangesFile` writes `[NN] path` lines and updated `INSTRUCTIONS`.
|
|
139
|
-
- Slice 3: Number-based matching — `Reen#compare_lists` uses `from_numbered` for number-keyed matching instead of positional zip. Reordering works. All 27 tests pass (110 assertions).
|
|
140
|
-
- Slice 4: Documentation — README updated with numbered prefixes, dash-space syntax, reordering docs, programmatic examples. CLI help unchanged (banner only). INSTRUCTIONS verified.
|
|
141
|
-
- Slice 5: Verification — 29 tests, 115 assertions, 0 failures. No new RuboCop offenses. Manual editor test pending.
|
|
142
|
-
|
|
143
|
-
---
|
|
144
|
-
|
|
145
|
-
Last updated: 2026-03-10
|
data/lib/reen/reen.rb
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Reen
|
|
4
|
-
# Renames pattern of files with given editor
|
|
5
|
-
# Examples:
|
|
6
|
-
# Reen::Reen.new(editor: "code -w").call("spec/fixtures/example/*")
|
|
7
|
-
# Reen::Reen.new(editor: nil).call("spec/fixtures/example/*") { ... }
|
|
8
|
-
class Reen
|
|
9
|
-
DEL_ERROR = "Do not remove any file/folder names (no changes made)"
|
|
10
|
-
|
|
11
|
-
attr_reader :changes
|
|
12
|
-
|
|
13
|
-
def initialize(editor: "emacs", options: {})
|
|
14
|
-
@editor = editor
|
|
15
|
-
@options = options
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def request(original_list, &block)
|
|
19
|
-
@entry_list = PathEntryList.new(original_list)
|
|
20
|
-
changed_list = ChangesFile.new(@entry_list).allow_changes(@editor, &block)
|
|
21
|
-
|
|
22
|
-
raise(Error, DEL_ERROR) if changed_list.size != @entry_list.count
|
|
23
|
-
|
|
24
|
-
@changes = compare_lists(@entry_list, changed_list)
|
|
25
|
-
.then { |change_array| Changes.new(change_array) }
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def execute(original_list, &block)
|
|
29
|
-
@changes ||= request(original_list, &block)
|
|
30
|
-
@changes = @changes.execute_all
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
private
|
|
34
|
-
|
|
35
|
-
def compare_lists(entry_list, changed_list)
|
|
36
|
-
changed_by_number = PathEntryList.from_numbered(changed_list)
|
|
37
|
-
entries = entry_list.to_a
|
|
38
|
-
|
|
39
|
-
entries.each_with_index.map do |entry, i|
|
|
40
|
-
number = i + 1
|
|
41
|
-
revised = changed_by_number[number]
|
|
42
|
-
Change.new(entry, revised)
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
end
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|