file_transactions 0.1.0
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/lib/file_transactions.rb +17 -0
- data/lib/file_transactions/base_command.rb +103 -0
- data/lib/file_transactions/change_file_command.rb +38 -0
- data/lib/file_transactions/create_directory_command.rb +47 -0
- data/lib/file_transactions/create_file_command.rb +31 -0
- data/lib/file_transactions/delete_file_command.rb +34 -0
- data/lib/file_transactions/error.rb +6 -0
- data/lib/file_transactions/move_file_command.rb +26 -0
- data/lib/file_transactions/transaction.rb +60 -0
- data/lib/file_transactions/version.rb +5 -0
- metadata +57 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: bbdef21f6b3d981705c6a0ed618d90abbaee1e1f4d2e50f4c1233390a809584c
|
|
4
|
+
data.tar.gz: 0da72546f7cf4014ecaeb3101a169be1b7977b59d694deeaf3961c9ff0a21e1b
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 8dec98fc1b74aa2187e0ac76abd6b6b4a605327c5541e5aed0a9fbebf17783c4b4c0aae19bf3314f5aa3557eaded940ed59d9dd22d8d4722e966e22f1f7ca3d8
|
|
7
|
+
data.tar.gz: f8cbfc09aaee6af0fab0d3e783eb5f7d5a2818806cc657b1f40d639d618ac6ada0150b168558f4a989d4d52036f48710034ba26259e1006061a4baa0cbdd5365
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "file_transactions/version"
|
|
4
|
+
require 'file_transactions/error'
|
|
5
|
+
require 'file_transactions/base_command'
|
|
6
|
+
require 'file_transactions/change_file_command'
|
|
7
|
+
require 'file_transactions/create_directory_command'
|
|
8
|
+
require 'file_transactions/create_file_command'
|
|
9
|
+
require 'file_transactions/delete_file_command'
|
|
10
|
+
require 'file_transactions/move_file_command'
|
|
11
|
+
require 'file_transactions/transaction'
|
|
12
|
+
|
|
13
|
+
module FileTransactions
|
|
14
|
+
def self.transaction(&block)
|
|
15
|
+
Transaction.run(&block)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FileTransactions
|
|
4
|
+
# A Base class that all commands must inherit from.
|
|
5
|
+
#
|
|
6
|
+
# This class provides all the necessary methods/hooks to make it possible to
|
|
7
|
+
# group commands together and/or nested inside transactions (and other
|
|
8
|
+
# commands).
|
|
9
|
+
class BaseCommand
|
|
10
|
+
def self.execute(*args, &block)
|
|
11
|
+
new(*args, &block).tap { |cmd| cmd.execute }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Execute the command. This will trigger the following methods:
|
|
15
|
+
# * #before
|
|
16
|
+
# * #execute!
|
|
17
|
+
# * #after
|
|
18
|
+
def execute
|
|
19
|
+
scope = Transaction.scope
|
|
20
|
+
scope&.register self
|
|
21
|
+
prepare
|
|
22
|
+
run_before
|
|
23
|
+
run_excecute.tap { run_after }
|
|
24
|
+
rescue StandardError
|
|
25
|
+
self.failure_state = state
|
|
26
|
+
raise
|
|
27
|
+
ensure
|
|
28
|
+
Transaction.scope = scope
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Undo the changes made from a previous call to #execute. All previouly
|
|
32
|
+
# executed commands will be undone in reverse order.
|
|
33
|
+
def undo
|
|
34
|
+
raise Error, "Cannot undo #{self.class} which hasn't been executed" unless executed?
|
|
35
|
+
|
|
36
|
+
sub_commands[:after].reverse_each(&:undo)
|
|
37
|
+
|
|
38
|
+
ret = undo! unless failure_state == :before
|
|
39
|
+
sub_commands[:before].reverse_each(&:undo)
|
|
40
|
+
ret
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# This registers a nested command. This method is called whever a command
|
|
44
|
+
# is executed and should not be called manually.
|
|
45
|
+
def register(command)
|
|
46
|
+
sub_commands[state] << command
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Returns true of false depending on if the commands has been executed.
|
|
50
|
+
def executed?
|
|
51
|
+
!!executed
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Returns true if the command has been unsuccessfully executed, otherwise false.
|
|
55
|
+
def failed?
|
|
56
|
+
!!failure_state
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
attr_accessor :state, :executed, :failure_state
|
|
62
|
+
|
|
63
|
+
def prepare
|
|
64
|
+
Transaction.scope = self
|
|
65
|
+
self.executed = true
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def sub_commands
|
|
69
|
+
@sub_commands ||= {
|
|
70
|
+
before: [],
|
|
71
|
+
exec: [],
|
|
72
|
+
after: [],
|
|
73
|
+
}
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def run_before
|
|
77
|
+
self.state = :before
|
|
78
|
+
before
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def run_excecute
|
|
82
|
+
self.state = :exec
|
|
83
|
+
execute!
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def run_after
|
|
87
|
+
self.state = :after
|
|
88
|
+
after
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def before; end
|
|
92
|
+
|
|
93
|
+
def execute!
|
|
94
|
+
raise NotImplementedError, "#{self.clas} must implement #execute"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def undo!
|
|
98
|
+
raise NotImplementedError, "#{self.clas} must implement #undo"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def after; end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'tmpdir'
|
|
5
|
+
|
|
6
|
+
module FileTransactions
|
|
7
|
+
class ChangeFileCommand < BaseCommand
|
|
8
|
+
attr_reader :name, :block
|
|
9
|
+
|
|
10
|
+
def initialize(name, &block)
|
|
11
|
+
@name = name
|
|
12
|
+
@block = block
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def before
|
|
18
|
+
CreateFileCommand.execute(tmp_name) do
|
|
19
|
+
FileUtils.copy name, tmp_name
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def execute!
|
|
24
|
+
value = block.call(name)
|
|
25
|
+
return unless value.is_a? String
|
|
26
|
+
|
|
27
|
+
File.open(name, 'w') { |f| f.write(value) }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def undo!
|
|
31
|
+
FileUtils.copy tmp_name, name
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def tmp_name
|
|
35
|
+
@tmp_name ||= File.join(Dir.mktmpdir, File.basename(name))
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
|
|
5
|
+
module FileTransactions
|
|
6
|
+
class CreateDirectoryCommand < BaseCommand
|
|
7
|
+
attr_reader :name
|
|
8
|
+
|
|
9
|
+
# Create new command for creating directories. N
|
|
10
|
+
#
|
|
11
|
+
# ==== Attributes
|
|
12
|
+
#
|
|
13
|
+
# * +name+ - The name of the directory to be created
|
|
14
|
+
#
|
|
15
|
+
# ==== Examples
|
|
16
|
+
#
|
|
17
|
+
# # Pass in the new directory name to ::new
|
|
18
|
+
# cmd1 = CreateDirectoryCommand.new('directory_name')
|
|
19
|
+
#
|
|
20
|
+
# # The new directory may be a path of multiple non exsting directories (like `mkdir -p`)
|
|
21
|
+
# cmd2 = CreateDirectoryCommand.new('non_existing_dir1/non_existing_dir2/new_dir')
|
|
22
|
+
def initialize(name)
|
|
23
|
+
@name = name
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def execute!
|
|
29
|
+
dir = name
|
|
30
|
+
|
|
31
|
+
until Dir.exist? dir
|
|
32
|
+
directories.unshift dir
|
|
33
|
+
dir = File.dirname dir
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
directories.each { |dir| Dir.mkdir dir }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def undo!
|
|
40
|
+
directories.reverse_each { |dir| Dir.unlink dir }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def directories
|
|
44
|
+
@directories ||= []
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FileTransactions
|
|
4
|
+
class CreateFileCommand < BaseCommand
|
|
5
|
+
attr_reader :name, :block
|
|
6
|
+
|
|
7
|
+
def initialize(name, &block)
|
|
8
|
+
@name = name
|
|
9
|
+
@block = block
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def before
|
|
15
|
+
dir = File.dirname(name)
|
|
16
|
+
return if Dir.exist? dir
|
|
17
|
+
CreateDirectoryCommand.execute(dir)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def execute!
|
|
21
|
+
value = block.call(name)
|
|
22
|
+
return unless value.is_a? String
|
|
23
|
+
|
|
24
|
+
File.open(name, 'w') { |f| f.write(value) }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def undo!
|
|
28
|
+
File.unlink name
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'tmpdir'
|
|
5
|
+
|
|
6
|
+
module FileTransactions
|
|
7
|
+
class DeleteFileCommand < BaseCommand
|
|
8
|
+
attr_reader :name, :block
|
|
9
|
+
|
|
10
|
+
def initialize(name)
|
|
11
|
+
@name = name
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def before
|
|
17
|
+
CreateFileCommand.execute(tmp_name) do
|
|
18
|
+
FileUtils.copy name, tmp_name
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def execute!
|
|
23
|
+
File.delete name
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def undo!
|
|
27
|
+
FileUtils.copy tmp_name, name
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def tmp_name
|
|
31
|
+
@tmp_name ||= File.join(Dir.mktmpdir, File.basename(name))
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FileTransactions
|
|
4
|
+
class MoveFileCommand < BaseCommand
|
|
5
|
+
attr_reader :from, :to
|
|
6
|
+
|
|
7
|
+
def initialize(from:, to:)
|
|
8
|
+
@from = from
|
|
9
|
+
@to = to
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def before
|
|
15
|
+
CreateDirectoryCommand.execute(File.dirname(to))
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def execute!
|
|
19
|
+
File.rename from, to
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def undo!
|
|
23
|
+
File.rename to, from
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FileTransactions
|
|
4
|
+
class Transaction
|
|
5
|
+
class << self
|
|
6
|
+
def run(&block)
|
|
7
|
+
new(&block).__send__(:run)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def scope=(scope)
|
|
11
|
+
Thread.current['FT.scope'] = scope
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def scope
|
|
15
|
+
Thread.current['FT.scope']
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def initialize(&block)
|
|
21
|
+
raise Error, 'A block must be given' unless block_given?
|
|
22
|
+
|
|
23
|
+
@block = block
|
|
24
|
+
@commands = []
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def register(command)
|
|
28
|
+
commands << command
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def rollback
|
|
32
|
+
return if backrolled?
|
|
33
|
+
|
|
34
|
+
commands.reverse_each(&:undo)
|
|
35
|
+
self.backrolled = true
|
|
36
|
+
end
|
|
37
|
+
alias undo rollback
|
|
38
|
+
|
|
39
|
+
def backrolled?
|
|
40
|
+
!!backrolled
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
attr_reader :block, :commands
|
|
46
|
+
attr_accessor :backrolled
|
|
47
|
+
|
|
48
|
+
def run
|
|
49
|
+
scope = Transaction.scope
|
|
50
|
+
scope&.register self
|
|
51
|
+
Transaction.scope = self
|
|
52
|
+
block.call
|
|
53
|
+
rescue StandardError => e
|
|
54
|
+
rollback
|
|
55
|
+
raise unless Rollback === e
|
|
56
|
+
ensure
|
|
57
|
+
Transaction.scope = scope
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: file_transactions
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Sammy Henningsson
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2020-06-18 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: |
|
|
14
|
+
A set of file operation that can be undone or wrapped in a
|
|
15
|
+
transaction. If the transaction is rolled back then all file
|
|
16
|
+
operations will be undone.
|
|
17
|
+
email:
|
|
18
|
+
- sammy.henningsson@gmail.com
|
|
19
|
+
executables: []
|
|
20
|
+
extensions: []
|
|
21
|
+
extra_rdoc_files: []
|
|
22
|
+
files:
|
|
23
|
+
- lib/file_transactions.rb
|
|
24
|
+
- lib/file_transactions/base_command.rb
|
|
25
|
+
- lib/file_transactions/change_file_command.rb
|
|
26
|
+
- lib/file_transactions/create_directory_command.rb
|
|
27
|
+
- lib/file_transactions/create_file_command.rb
|
|
28
|
+
- lib/file_transactions/delete_file_command.rb
|
|
29
|
+
- lib/file_transactions/error.rb
|
|
30
|
+
- lib/file_transactions/move_file_command.rb
|
|
31
|
+
- lib/file_transactions/transaction.rb
|
|
32
|
+
- lib/file_transactions/version.rb
|
|
33
|
+
homepage: https://github.com/sammyhenningsson/file_transactions
|
|
34
|
+
licenses:
|
|
35
|
+
- MIT
|
|
36
|
+
metadata:
|
|
37
|
+
homepage_uri: https://github.com/sammyhenningsson/file_transactions
|
|
38
|
+
post_install_message:
|
|
39
|
+
rdoc_options: []
|
|
40
|
+
require_paths:
|
|
41
|
+
- lib
|
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '2.5'
|
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
48
|
+
requirements:
|
|
49
|
+
- - ">="
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: '0'
|
|
52
|
+
requirements: []
|
|
53
|
+
rubygems_version: 3.0.3
|
|
54
|
+
signing_key:
|
|
55
|
+
specification_version: 4
|
|
56
|
+
summary: Transactions for file operations
|
|
57
|
+
test_files: []
|