fileverse 0.1.2 → 0.1.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a14542329552682cfacbc80a77a520f6f67b8d879e68fc7ee901953039eddbaa
4
- data.tar.gz: 86d8f88902bf3dca295860636c073276322e0667524d0304a1db6e3de9c6527e
3
+ metadata.gz: b739dd6750e9f37a5720e9789800efa69c545abb67855d8f38630841f3c30870
4
+ data.tar.gz: d575e0eec8c760353b5b61d54f5e5e923cd6d261dd55d8a75533ed7079e7ecf4
5
5
  SHA512:
6
- metadata.gz: 64bfd38030920a3a5185ac8db6e82475392e32eb49e6a5d5b1a94c832aa914f9961a2e832f6b44608dfb71bf965e2ed8dd43cca56ebc6344089313fb4e87c1d1
7
- data.tar.gz: 104739b54efc776f2d71901cc9eac641104f4151165fe42288edd39de4641d99e4ea517177e975dff9c6f4d3025e9569f00fed29260c48e5311b2c6d635d66d6
6
+ metadata.gz: 5c64c1dca0991f50667bf048d74f88971b0611da8e309a9287f7bb679b8b8332c7cfc221e63eb4b411a6ce144a29243ee512a25c3f48e5f75173a25fe0e382fb
7
+ data.tar.gz: f551ff2f97acabd6299fea567425425261822361b789ef6065a966a25edaebc413c6938ad0db52068ba9d8d4c1a4527ae180176acb9ed38cef95f6ac0dbde98b
data/README.md CHANGED
@@ -12,14 +12,17 @@ A simple ruby cli tool for keeping different versions of a file.
12
12
 
13
13
  ## Commands / Shortcuts
14
14
  - snap {file_path} (s)
15
- - preview --<< {file_path} (p)
16
- - preview -->> {file_path} (p)
15
+ - preview --bwd {file_path} (p)
16
+ - preview --fwd {file_path} (p)
17
17
  - preview --name="" {file_path} (p)
18
18
  - preview --index=0 {file_path} (p)
19
19
  - reset {file_path} (x)
20
- - snapshots {file_path} (c)
20
+ - summary {file_path} (sm)
21
21
  - restore {file_path} (r)
22
22
 
23
+ ## Sample
24
+ ![Sample](./sample.gif)
25
+
23
26
  ## Development
24
27
 
25
28
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
data/lib/fileverse/cli.rb CHANGED
@@ -6,32 +6,32 @@ module Fileverse
6
6
  # CLI class
7
7
  class CLI < Thor
8
8
  desc "snap file content", "store current file content"
9
+ options template: :boolean, name: :string
9
10
  def snap(path)
10
11
  setup path
11
12
  @parser.parse
12
- @parser.add_snapshot(Files.read(@path))
13
+ @parser.add_snapshot(Files.read(@path), options[:name])
13
14
  Files.write_content(@path)
14
15
  Files.write_content(@hidden_path, @parser.to_writable_lines)
15
16
  end
16
17
  map "s" => "snap"
17
18
 
18
19
  desc "restore content", "restore content in the current cursor"
20
+ options template: :boolean, name: :string
19
21
  def restore(path)
20
22
  setup path
21
23
  @parser.parse
22
- Files.write_content(@path, @parser.cursor_content)
23
- @parser.remove_cursor_snapshot
24
- Files.write_content(@hidden_path, @parser.to_writable_lines)
24
+ options[:template] ? restore_template : restore_snapshot
25
25
  end
26
26
  map "r" => "restore"
27
27
 
28
28
  desc "preview snapshot", "preview snapshot at different index or name"
29
- options bwd: :boolean, fwd: :boolean, index: :numeric, name: :string
29
+ options bwd: :boolean, fwd: :boolean, index: :numeric, name: :string, template: :boolean
30
30
  def preview(path)
31
31
  setup path
32
32
  @parser.parse
33
33
  @previewer.parse
34
- update_preview_content
34
+ @previewer.preview_content = preview_content
35
35
  Files.write_content(@path, @previewer.to_writable_lines)
36
36
  Files.write_content(@hidden_path, @parser.to_writable_lines)
37
37
  end
@@ -49,24 +49,46 @@ module Fileverse
49
49
  end
50
50
  map "x" => "reset"
51
51
 
52
+ desc "summary", "return all the summary of snapshots"
53
+ def summary(path)
54
+ setup path
55
+ @parser.parse_head
56
+ puts @parser.summary
57
+ end
58
+ map "sm" => "summary"
59
+
52
60
  private
53
61
 
54
62
  def setup(path)
55
63
  @path = Files.expand_path(path)
56
- @hidden_path = Files.expand_hidden_path(path)
57
- @parser = Parser::Header.new(@hidden_path)
64
+ @hidden_path = options[:template] ? Files.template_path : Files.expand_hidden_path(path)
65
+ @parser = Parser.new(@hidden_path)
58
66
  @previewer = Previewer.new(@path)
59
67
  end
60
68
 
61
- def update_preview_content
62
- if options[:bwd]
63
- @parser.decrement_cursor
69
+ def restore_snapshot
70
+ Files.write_content(@path, @parser.cursor_content)
71
+ @parser.remove_cursor_snapshot
72
+ Files.write_content(@hidden_path, @parser.to_writable_lines)
73
+ end
74
+
75
+ def restore_template
76
+ template_content = @parser.snapshot_content_by_name(options[:name])
77
+ return if template_content.nil?
78
+
79
+ Files.write_content(@path, template_content)
80
+ end
81
+
82
+ def preview_content
83
+ if options[:name]
84
+ @parser.snapshot_content_by_name(options[:name])
85
+ elsif options[:bwd]
86
+ @parser.snapshot_content_backward
64
87
  elsif options[:fwd]
65
- @parser.increment_cursor
88
+ @parser.snapshot_content_forward
66
89
  elsif options[:index]
67
- @parser.cursor = options[:index]
90
+ @parser.snapshot_content_by_index(options[:index])
68
91
  end
69
- @previewer.preview_content = @parser.cursor_content
70
92
  end
71
93
  end
72
94
  end
@@ -22,5 +22,9 @@ module Fileverse
22
22
  full_path = File.expand_path path, Dir.pwd
23
23
  File.open(full_path, "w") { |file| file.puts content }
24
24
  end
25
+
26
+ def template_path
27
+ File.expand_path ".fileverse.template", Dir.home
28
+ end
25
29
  end
26
30
  end
@@ -1,260 +1,280 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fileverse
4
- module Parser
5
- # Fileverse::Parser::Header : is the main parser for the storage file.
6
- # Lets take a storage file example:
7
- #
8
- # == Storage file Example content
9
- #
10
- # <######0
11
- # template>shot> 6 ~> 9
12
- # 9 ~> 12
13
- # 12 ~> 15
14
- # 15 ~> 18
15
- # ######>
16
- # // New file
17
- # // Write here
18
- # // When you can
19
- # File 1
20
- # Content 1 of file 1
21
- # Content 2 of file 1
22
- # File 2
23
- # Content 1 of file 2
24
- # Content 2 of file 2
25
- # File 3
26
- # Content 1 of file 3
27
- # Content 2 of file 3
28
- #
29
- #
30
- # == Example end
31
- #
32
- # It uses the head section which is what is between "<######{cursor}" and "######>".
33
- # It starts with "templates" and then "files". Each has a range which is represented like "{start} ~> {stop}".
34
- # templates are represented as "template>{name}>{range}". While files are just represented as "{range}".
35
- # so the above example will parse as follows:
36
- #
37
- # - template>shot> 6 ~> 9
38
- #
39
- # =======================================
40
- # | // New file
41
- # | // Write here
42
- # | // When you can
43
- # ======================================
44
- #
45
- # - 9 ~> 12
46
- #
47
- # =======================================
48
- # | File 1
49
- # | Content 1 of file 1
50
- # | Content 2 of file 1
51
- # ======================================
52
- #
53
- # - 12 ~> 15
54
- #
55
- # =======================================
56
- # | File 2
57
- # | Content 1 of file 2
58
- # | Content 2 of file 2
59
- # ======================================
60
- #
61
- # - 15 ~> 18
62
- #
63
- # =======================================
64
- # | File 3
65
- # | Content 1 of file 3
66
- # | Content 2 of file 3
67
- # ======================================
68
- #
69
- class Header # rubocop:disable Metrics/ClassLength
70
- START_TAG = "<######"
71
- CLOSE_TAG = "######>"
72
-
73
- attr_reader :path, :cursor, :line_index
74
-
75
- def initialize(path)
76
- @path = path
77
- @iterator = File.foreach(path, chomp: true)
78
- @line_index = 0
79
- @cursor = -1
80
- @snapshots = []
81
- @templates = []
82
- end
4
+ # Fileverse::Parser : is the main parser for the storage file.
5
+ # Lets take a storage file example:
6
+ #
7
+ # == Storage file Example content
8
+ #
9
+ # <######0
10
+ # shot> 6 ~> 9
11
+ # 9 ~> 12
12
+ # 12 ~> 15
13
+ # 15 ~> 18
14
+ # ######>
15
+ # // New file
16
+ # // Write here
17
+ # // When you can
18
+ # File 1
19
+ # Content 1 of file 1
20
+ # Content 2 of file 1
21
+ # File 2
22
+ # Content 1 of file 2
23
+ # Content 2 of file 2
24
+ # File 3
25
+ # Content 1 of file 3
26
+ # Content 2 of file 3
27
+ #
28
+ #
29
+ # == Example end
30
+ #
31
+ # It uses the head section which is what is between "<######{cursor}" and "######>" as the table to parse the file.
32
+ # Each line is a range represented like "{name}> {start} ~> {stop}". the "{name}>" part is optional.
33
+ # so the above example will parse as follows:
34
+ #
35
+ # - shot> 6 ~> 9
36
+ #
37
+ # =======================================
38
+ # | // New file
39
+ # | // Write here
40
+ # | // When you can
41
+ # ======================================
42
+ #
43
+ # - 9 ~> 12
44
+ #
45
+ # =======================================
46
+ # | File 1
47
+ # | Content 1 of file 1
48
+ # | Content 2 of file 1
49
+ # ======================================
50
+ #
51
+ # - 12 ~> 15
52
+ #
53
+ # =======================================
54
+ # | File 2
55
+ # | Content 1 of file 2
56
+ # | Content 2 of file 2
57
+ # ======================================
58
+ #
59
+ # - 15 ~> 18
60
+ #
61
+ # =======================================
62
+ # | File 3
63
+ # | Content 1 of file 3
64
+ # | Content 2 of file 3
65
+ # ======================================
66
+ #
67
+ class Parser # rubocop:disable Metrics/ClassLength
68
+ START_TAG = "<######"
69
+ CLOSE_TAG = "######>"
70
+
71
+ attr_reader :path, :cursor, :line_index
72
+
73
+ def initialize(path)
74
+ @path = path
75
+ @iterator = File.foreach(path, chomp: true)
76
+ @line_index = 0
77
+ @cursor = -1
78
+ @snapshots = []
79
+ end
83
80
 
84
- def parse
85
- return unless File.exist?(@path) && !peek_line.nil?
81
+ def parse
82
+ return unless File.exist?(@path) && !peek_line.nil?
86
83
 
87
- verify_first_header
88
- parse_header_template_lines
89
- parse_header_snapshot_lines
90
- parse_snapshots
84
+ verify_first_header
85
+ parse_header_snapshot_lines
86
+ parse_snapshots
91
87
 
92
- raise CorruptFormat, " Content remains after parsing." unless peek_line.nil?
93
- end
88
+ raise CorruptFormat, " Content remains after parsing." unless peek_line.nil?
89
+ end
94
90
 
95
- def snapshot_count
96
- @snapshots.length
97
- end
91
+ def snapshot_count
92
+ @snapshots.length
93
+ end
98
94
 
99
- def add_snapshot(content)
100
- last_snapshot = @snapshots[-1]
101
- start = last_snapshot&.stop || 3
102
- snapshot = Snapshot.new(start, start + content.length)
103
- snapshot.content = content
104
- last_snapshot&.next_snapshot = snapshot
105
- @snapshots.push(snapshot)
106
- reset
107
- end
95
+ def add_snapshot(content, name = nil)
96
+ return if name && update_named_snapshot(name, content)
108
97
 
109
- def cursor_content
110
- @snapshots[@cursor]&.content || []
111
- end
98
+ prev_snapshot = @snapshots[-1]
99
+ start = prev_snapshot&.stop || 3
100
+ snapshot = Snapshot.new(start, start + content.length, name)
101
+ snapshot.content = content
102
+ prev_snapshot&.next_snapshot = snapshot
103
+ @snapshots.push(snapshot)
104
+ reset
105
+ end
112
106
 
113
- def remove_cursor_snapshot
114
- snapshot = @snapshots[@cursor]
115
- return unless snapshot
107
+ def cursor_content
108
+ raise InvalidCursorPointer if @cursor.negative? || @cursor > @snapshots.length
116
109
 
117
- snapshot_before = @snapshots[@cursor - 1]
118
- snapshot_before.next_snapshot = snapshot.next_snapshot if snapshot_before
119
- @snapshots = @snapshots[0, @cursor].concat(@snapshots[@cursor + 1..])
120
- reset
121
- end
110
+ @snapshots[@cursor].content
111
+ end
122
112
 
123
- def to_writable_lines
124
- [*head_lines, *template_lines, *snapshot_lines]
125
- end
113
+ def snapshot_content_by_name(name)
114
+ find_named_snapshot(name)&.content
115
+ end
126
116
 
127
- def increment_cursor
128
- raise InvalidCursorPointer if @cursor + 1 >= @snapshots.length
117
+ def snapshot_content_by_index(index)
118
+ raise InvalidCursorPointer if index.negative? || index > @snapshots.length
129
119
 
130
- @cursor += 1
131
- end
120
+ @cursor = value
121
+ cursor_content
122
+ end
132
123
 
133
- def decrement_cursor
134
- raise InvalidCursorPointer if (@cursor - 1).negative?
124
+ def snapshot_content_backward
125
+ decrement_cursor
126
+ cursor_content
127
+ end
135
128
 
136
- @cursor -= 1
137
- end
129
+ def snapshot_content_forward
130
+ increment_cursor
131
+ cursor_content
132
+ end
138
133
 
139
- def cursor=(value)
140
- raise InvalidCursorPointer if value.negative? || value > @snapshots.length
134
+ def remove_cursor_snapshot
135
+ snapshot = @snapshots[@cursor]
136
+ return unless snapshot
141
137
 
142
- @cursor = value
143
- end
138
+ snapshot_before = @snapshots[@cursor - 1]
139
+ snapshot_before.next_snapshot = snapshot.next_snapshot if snapshot_before
140
+ @snapshots = @snapshots[0, @cursor].concat(@snapshots[@cursor + 1..])
141
+ reset
142
+ end
144
143
 
145
- def reset
146
- @cursor = @snapshots.length - 1
147
- @snapshots[0]&.update_start @snapshots.length + 2
148
- end
144
+ def to_writable_lines
145
+ [*head_lines, *snapshot_lines]
146
+ end
149
147
 
150
- private
148
+ def reset
149
+ @cursor = @snapshots.length - 1
150
+ @snapshots[0]&.update_start @snapshots.length + 2
151
+ end
151
152
 
152
- def verify_first_header
153
- first_line = next_line
154
- /\A<\#{6}(?<cursor>-?\d+)\z/ =~ first_line
155
- raise CorruptFormat, " Error parsing header" unless cursor
153
+ def parse_head
154
+ return unless File.exist?(@path) && !peek_line.nil?
156
155
 
157
- @cursor = cursor.to_i
158
- end
156
+ verify_first_header
157
+ parse_header_snapshot_lines
158
+ end
159
159
 
160
- def parse_header_template_lines
161
- loop do
162
- break unless /\A\s*template>(?<name>\w+)>\s*(?<start>\d+)\s*~>\s*(?<stop>\d+)\s*\z/ =~ peek_line
160
+ private
163
161
 
164
- next_line
165
- @templates.push(Snapshot.new(start.to_i, stop.to_i, name))
166
- end
167
- end
162
+ def update_named_snapshot(name, content)
163
+ snapshot = find_named_snapshot(name)
164
+ return false unless snapshot
168
165
 
169
- def parse_header_snapshot_lines
170
- loop do
171
- line = next_line
172
- unless /\A\s*(?<start>\d+)\s*~>\s*(?<stop>\d+)\s*\z/ =~ line
173
- break if line == CLOSE_TAG
166
+ snapshot.content = content
167
+ reset
168
+ true
169
+ end
174
170
 
175
- raise CorruptFormat
176
- end
177
- raise CorruptFormat if stop.to_i < start.to_i
171
+ def find_named_snapshot(name)
172
+ @snapshots.find { |snapshot| snapshot.name == name }
173
+ end
178
174
 
179
- @snapshots.push Snapshot.new(start.to_i, stop.to_i)
180
- end
181
- end
175
+ def decrement_cursor
176
+ raise InvalidCursorPointer if (@cursor - 1).negative?
177
+
178
+ @cursor -= 1
179
+ end
180
+
181
+ def increment_cursor
182
+ raise InvalidCursorPointer if @cursor + 1 >= @snapshots.length
182
183
 
183
- def parse_snapshots
184
- last_snap = nil
185
- [*@templates, *@snapshots].each do |snap|
186
- raise CorruptFormat, " Wrong indexing in header." if line_index != snap.start
184
+ @cursor += 1
185
+ end
186
+
187
+ def verify_first_header
188
+ first_line = next_line
189
+ /\A<\#{6}(?<cursor>-?\d+)\z/ =~ first_line
190
+ raise CorruptFormat, " Error parsing header" unless cursor
191
+
192
+ @cursor = cursor.to_i
193
+ end
187
194
 
188
- snap.content = parse_snap_content(snap)
195
+ def parse_header_snapshot_lines
196
+ loop do
197
+ line = next_line
198
+ unless /\A\s*(?<name>\w*>)?\s*(?<start>\d+)\s*~>\s*(?<stop>\d+)\s*\z/ =~ line
199
+ break if line == CLOSE_TAG
189
200
 
190
- last_snap&.next_snapshot = snap
191
- last_snap = snap
201
+ raise CorruptFormat, "Unknow format found."
192
202
  end
193
- end
203
+ raise CorruptFormat, "Start index cannot be larger than stop." if stop.to_i < start.to_i
194
204
 
195
- def parse_snap_content(snap)
196
- result = []
197
- (snap.stop - snap.start).times { result.push next_line }
198
- result
205
+ @snapshots.push Snapshot.new(start.to_i, stop.to_i, name&.chomp(">"))
199
206
  end
207
+ end
200
208
 
201
- def next_line
202
- @line_index += 1
203
- @iterator.next
204
- rescue StopIteration
205
- raise CorruptFormat, " No content to parse."
206
- end
209
+ def parse_snapshots
210
+ last_snap = nil
211
+ [*@snapshots].each do |snap|
212
+ raise CorruptFormat, " Wrong indexing in header." if line_index != snap.start
207
213
 
208
- def peek_line
209
- @iterator.peek
210
- rescue StopIteration
211
- nil
212
- end
214
+ snap.content = parse_snap_content(snap)
213
215
 
214
- def head_lines
215
- [
216
- "#{START_TAG}#{cursor}",
217
- *@templates.map { |template| "template>#{template.name}> #{template.start} ~> #{template.stop}" },
218
- *@snapshots.map { |snap| "#{snap.start} ~> #{snap.stop}" },
219
- CLOSE_TAG
220
- ]
216
+ last_snap&.next_snapshot = snap
217
+ last_snap = snap
221
218
  end
219
+ end
222
220
 
223
- def template_lines
224
- @templates.map(&:content).flatten
225
- end
221
+ def parse_snap_content(snap)
222
+ result = []
223
+ (snap.stop - snap.start).times { result.push next_line }
224
+ result
225
+ end
226
226
 
227
- def snapshot_lines
228
- @snapshots.map(&:content).flatten
229
- end
227
+ def next_line
228
+ @line_index += 1
229
+ @iterator.next
230
+ rescue StopIteration
231
+ raise CorruptFormat, " No content to parse."
232
+ end
230
233
 
231
- # Snapshot for each file
232
- class Snapshot
233
- attr_reader :start, :stop, :name, :content
234
- attr_accessor :next_snapshot
234
+ def peek_line
235
+ @iterator.peek
236
+ rescue StopIteration
237
+ nil
238
+ end
235
239
 
236
- def initialize(start, stop, name = nil)
237
- @start = start
238
- @stop = stop
239
- @name = name
240
- end
240
+ def head_lines
241
+ [
242
+ "#{START_TAG}#{cursor}",
243
+ *@snapshots.map { |snap| "#{snap.name ? "#{snap.name}>" : ""}#{snap.start} ~> #{snap.stop}" },
244
+ CLOSE_TAG
245
+ ]
246
+ end
241
247
 
242
- def content=(value)
243
- @content = value
244
- update_stop
245
- end
248
+ def snapshot_lines
249
+ @snapshots.map(&:content).flatten
250
+ end
246
251
 
247
- def update_start(new_start)
248
- @start = new_start
249
- update_stop
250
- end
252
+ # Snapshot for each file
253
+ class Snapshot
254
+ attr_reader :start, :stop, :name, :content
255
+ attr_accessor :next_snapshot
251
256
 
252
- private
257
+ def initialize(start, stop, name = nil)
258
+ @start = start
259
+ @stop = stop
260
+ @name = name
261
+ end
253
262
 
254
- def update_stop
255
- @stop = start + content.length
256
- next_snapshot&.update_start @stop
257
- end
263
+ def content=(value)
264
+ @content = value
265
+ update_stop
266
+ end
267
+
268
+ def update_start(new_start)
269
+ @start = new_start
270
+ update_stop
271
+ end
272
+
273
+ private
274
+
275
+ def update_stop
276
+ @stop = start + content.length
277
+ next_snapshot&.update_start @stop
258
278
  end
259
279
  end
260
280
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fileverse
4
- VERSION = "0.1.2"
4
+ VERSION = "0.1.4"
5
5
  end
data/lib/fileverse.rb CHANGED
@@ -7,12 +7,6 @@ require_relative "fileverse/parser"
7
7
  require_relative "fileverse/previewer"
8
8
 
9
9
  # Parent module
10
- module Fileverse
11
- def self.create_hidden_file(path)
12
- File.open(path, "w") do |writer|
13
- writer.write(inital_header)
14
- end
15
- end
16
- end
10
+ module Fileverse; end
17
11
 
18
12
  require_relative "fileverse/cli"
data/sample.gif ADDED
Binary file
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fileverse
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Unegbu Kingsley
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-04-02 00:00:00.000000000 Z
11
+ date: 2025-04-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -49,6 +49,7 @@ files:
49
49
  - lib/fileverse/parser.rb
50
50
  - lib/fileverse/previewer.rb
51
51
  - lib/fileverse/version.rb
52
+ - sample.gif
52
53
  - sig/fileverse.rbs
53
54
  homepage: https://github.com/urchmaney/fileverse
54
55
  licenses: