io 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.
@@ -0,0 +1,147 @@
1
+ module Vfs
2
+ class Entry
3
+ attr_reader :driver, :path, :path_cache
4
+
5
+ def initialize *args
6
+ if args.size == 1 and args.first.is_a? Entry
7
+ entry = args.first
8
+ @path_cache = entry.path_cache
9
+ @driver, @path = entry.driver, entry.path
10
+ else
11
+ driver, path = *args
12
+ @path_cache = Path.new path
13
+ @driver, @path = driver, path_cache.to_s
14
+ end
15
+ raise "driver not defined!" unless self.driver
16
+ end
17
+
18
+ #
19
+ # Navigation
20
+ #
21
+ def parent
22
+ Dir.new(driver, path_cache + '..')
23
+ end
24
+
25
+
26
+ #
27
+ # Transformations
28
+ #
29
+ def dir path = nil
30
+ if path
31
+ new_path = path_cache + path
32
+ Dir.new driver, new_path
33
+ else
34
+ Dir.new self
35
+ end
36
+ end
37
+ alias_method :to_dir, :dir
38
+
39
+ def file path = nil
40
+ if path
41
+ new_path = path_cache + path
42
+ File.new driver, new_path
43
+ else
44
+ File.new self
45
+ end
46
+ end
47
+ alias_method :to_file, :file
48
+
49
+ def entry path = nil
50
+ entry = if path
51
+ new_path = path_cache + path
52
+ klass = new_path.probably_dir? ? Dir : UniversalEntry
53
+ klass.new driver, new_path
54
+ else
55
+ UniversalEntry.new self
56
+ end
57
+ EntryProxy.new entry
58
+ end
59
+ alias_method :to_entry, :entry
60
+
61
+
62
+ #
63
+ # Attributes
64
+ #
65
+ def get attr_name = nil
66
+ attrs = driver.open{driver.attributes(path)}
67
+ (attr_name and attrs) ? attrs[attr_name] : attrs
68
+ end
69
+
70
+ def set options
71
+ # TODO2 set attributes
72
+ not_implemented
73
+ end
74
+
75
+ def dir?; !!get(:dir) end
76
+ def file?; !!get(:file) end
77
+ def created_at; get :created_at end
78
+ def updated_at; get :updated_at end
79
+
80
+
81
+ #
82
+ # Miscellaneous
83
+ #
84
+ def name
85
+ path_cache.name
86
+ end
87
+
88
+ def tmp &block
89
+ driver.open do
90
+ if block
91
+ driver.tmp do |path|
92
+ block.call Dir.new(driver, path)
93
+ end
94
+ else
95
+ Dir.new driver, driver.tmp
96
+ end
97
+ end
98
+ end
99
+
100
+ def local?
101
+ driver.local?
102
+ end
103
+
104
+
105
+ #
106
+ # Utils
107
+ #
108
+ def inspect
109
+ "#{driver}#{':' unless driver.to_s.empty?}#{path}"
110
+ end
111
+ alias_method :to_s, :inspect
112
+
113
+ def == other
114
+ return false unless other.is_a? Entry
115
+ driver == other.driver and path == other.path
116
+ end
117
+
118
+ def hash
119
+ driver.hash + path.hash
120
+ end
121
+
122
+ def eql? other
123
+ return false unless other.class == self.class
124
+ driver.eql?(other.driver) and path.eql?(other.path)
125
+ end
126
+
127
+ protected
128
+ def destroy_entry first = :file, second = :dir
129
+ driver.open do
130
+ begin
131
+ driver.send :"delete_#{first}", path
132
+ rescue StandardError => e
133
+ attrs = get
134
+ if attrs and attrs[first]
135
+ # some unknown error
136
+ raise e
137
+ elsif attrs and attrs[second]
138
+ driver.send :"delete_#{second}", path
139
+ else
140
+ # do nothing, entry already not exist
141
+ end
142
+ end
143
+ end
144
+ self
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,154 @@
1
+ module Vfs
2
+ class File < Entry
3
+ #
4
+ # Attributes
5
+ #
6
+ alias_method :exist?, :file?
7
+
8
+
9
+ #
10
+ # CRUD
11
+ #
12
+ def read options = {}, &block
13
+ options[:bang] = true unless options.include? :bang
14
+ driver.open do
15
+ begin
16
+ if block
17
+ driver.read_file path, &block
18
+ else
19
+ data = ""
20
+ driver.read_file(path){|buff| data << buff}
21
+ data
22
+ end
23
+ rescue StandardError => e
24
+ raise Vfs::Error, "can't read Dir #{self}!" if dir.exist?
25
+ attrs = get
26
+ if attrs and attrs[:file]
27
+ # unknown internal error
28
+ raise e
29
+ elsif attrs and attrs[:dir]
30
+ raise Error, "You are trying to read Dir '#{self}' as if it's a File!"
31
+ else
32
+ if options[:bang]
33
+ raise Error, "file #{self} not exist!"
34
+ else
35
+ block ? block.call('') : ''
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ # def content options = {}
43
+ # read options
44
+ # end
45
+
46
+ def create options = {}
47
+ write '', options
48
+ self
49
+ end
50
+
51
+ def write *args, &block
52
+ if block
53
+ options = args.first || {}
54
+ else
55
+ data, options = *args
56
+ options ||= {}
57
+ end
58
+ raise "can't do :override and :append at the same time!" if options[:override] and options[:append]
59
+
60
+ driver.open do
61
+ try = 0
62
+ begin
63
+ try += 1
64
+ if block
65
+ driver.write_file(path, options[:append], &block)
66
+ else
67
+ driver.write_file(path, options[:append]){|writer| writer.write data}
68
+ end
69
+ rescue StandardError => error
70
+ parent = self.parent
71
+ if entry.exist?
72
+ entry.destroy
73
+ elsif !parent.exist?
74
+ parent.create(options)
75
+ else
76
+ # unknown error
77
+ raise error
78
+ end
79
+
80
+ try < 2 ? retry : raise(error)
81
+ end
82
+ end
83
+ self
84
+ end
85
+
86
+ def append *args, &block
87
+ options = (args.last.is_a?(Hash) && args.pop) || {}
88
+ options[:append] = true
89
+ write(*(args << options), &block)
90
+ end
91
+
92
+ def update options = {}, &block
93
+ data = read options
94
+ write block.call(data), options
95
+ end
96
+
97
+ def destroy
98
+ destroy_entry
99
+ end
100
+
101
+
102
+ #
103
+ # Transfers
104
+ #
105
+ def copy_to to, options = {}
106
+ raise Error, "you can't copy to itself" if self == to
107
+
108
+ target = if to.is_a? File
109
+ to
110
+ elsif to.is_a? Dir
111
+ to.file #(name)
112
+ elsif to.is_a? UniversalEntry
113
+ to.file
114
+ else
115
+ raise "can't copy to unknown Entry!"
116
+ end
117
+
118
+ target.write options do |writer|
119
+ read(options){|buff| writer.write buff}
120
+ end
121
+
122
+ target
123
+ end
124
+
125
+ def move_to to
126
+ copy_to to
127
+ destroy
128
+ to
129
+ end
130
+
131
+
132
+ #
133
+ # Extra Stuff
134
+ #
135
+ def render *args
136
+ require 'tilt'
137
+
138
+ args.unshift Object.new if args.size == 1 and args.first.is_a?(Hash)
139
+
140
+ template = Tilt.new(path){read}
141
+ template.render *args
142
+ end
143
+
144
+ def size; get :size end
145
+
146
+ def basename
147
+ ::File.basename(name, File.extname(name))
148
+ end
149
+
150
+ def extension
151
+ ::File.extname(name).sub(/^\./, '')
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,24 @@
1
+ module Vfs
2
+ class UniversalEntry < Entry
3
+ #
4
+ # Attributes
5
+ #
6
+ def exist?
7
+ !!get
8
+ end
9
+
10
+
11
+ def copy_to to, options = {}
12
+ from = file? ? to_file : to_dir
13
+ from.copy_to to, options
14
+ end
15
+
16
+
17
+ #
18
+ # CRUD
19
+ #
20
+ def destroy
21
+ destroy_entry
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,42 @@
1
+ #
2
+ # It allows dynamically (magically) switching between UniversalEntry/Dir/File
3
+ #
4
+ module Vfs
5
+ class EntryProxy < BasicObject
6
+ attr_reader :_target
7
+
8
+ def initialize entry
9
+ raise 'something wrong happening here!' if entry.respond_to?(:proxy?) and entry.proxy?
10
+ self._target = entry
11
+ end
12
+
13
+ def proxy?
14
+ true
15
+ end
16
+
17
+ protected :==, :equal?, :!, :!=
18
+ protected
19
+ attr_writer :_target
20
+
21
+ def respond_to? m
22
+ super or
23
+ ::Vfs::UniversalEntry.method_defined?(m) or
24
+ ::Vfs::Dir.method_defined?(m) or
25
+ ::Vfs::File.method_defined?(m)
26
+ end
27
+
28
+ def method_missing m, *a, &b
29
+ unless _target.respond_to? m
30
+ if ::Vfs::UniversalEntry.method_defined? m
31
+ self.target = _target.entry
32
+ elsif ::Vfs::Dir.method_defined? m
33
+ self._target = _target.dir
34
+ elsif ::Vfs::File.method_defined? m
35
+ self._target = _target.file
36
+ end
37
+ end
38
+
39
+ _target.send m, *a, &b
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,4 @@
1
+ module Vfs
2
+ class Error < StandardError
3
+ end
4
+ end
@@ -0,0 +1,30 @@
1
+ class String
2
+ def to_entry_on driver = nil
3
+ path = self
4
+ driver ||= Vfs.default_driver
5
+
6
+ path = "./#{path}" unless path =~ /^[\/\.\~]/
7
+ Vfs::Entry.new(driver, path).entry
8
+ end
9
+ alias_method :to_entry, :to_entry_on
10
+
11
+ def to_file_on driver = nil
12
+ to_entry_on(driver).file
13
+ end
14
+ alias_method :to_file, :to_file_on
15
+
16
+ def to_dir_on driver = nil
17
+ to_entry_on(driver).dir
18
+ end
19
+ alias_method :to_dir, :to_dir_on
20
+ end
21
+
22
+ class File
23
+ def to_entry
24
+ path.to_entry
25
+ end
26
+
27
+ def to_file
28
+ path.to_file
29
+ end
30
+ end
@@ -0,0 +1,125 @@
1
+ module Vfs
2
+ class Path < String
3
+ def initialize path = '/', options = {}
4
+ if options[:skip_normalization]
5
+ super path
6
+ @probably_dir = options[:probably_dir]
7
+ else
8
+ Path.validate! path
9
+ path, probably_dir = Path.normalize_to_string path
10
+ raise "invalid path '#{path}' (you are outside of the root)!" unless path
11
+ super path
12
+ @probably_dir = probably_dir
13
+ end
14
+ end
15
+
16
+ def + path = ''
17
+ path = path.to_s
18
+ Path.validate! path, false
19
+
20
+ if Path.absolute?(path)
21
+ Path.normalize path
22
+ elsif path.empty?
23
+ self
24
+ else
25
+ Path.normalize "#{self}#{'/' unless self == '/'}#{path}"
26
+ end
27
+ end
28
+
29
+ def parent
30
+ self + '..'
31
+ end
32
+
33
+ def probably_dir?
34
+ !!@probably_dir
35
+ end
36
+
37
+ def name
38
+ unless @name
39
+ root = self[0..0]
40
+ @name ||= split('/').last || root
41
+ end
42
+ @name
43
+ end
44
+
45
+ class << self
46
+ def absolute? path
47
+ path =~ /^[\/~\/]|^\.$|^\.\//
48
+ end
49
+
50
+ def valid? path, forbid_relative = true, &block
51
+ result, err = if forbid_relative and !absolute?(path)
52
+ [false, "path must be started with '/', or '.'"]
53
+ elsif path =~ /.+\/~$|.+\/$|\/\.$/
54
+ [false, "path can't be ended with '/', '/~', or '/.'"]
55
+ elsif path =~ /\/\/|\/~\/|\/\.\//
56
+ [false, "path can't include '/./', '/~/', '//' combinations!"]
57
+ # elsif path =~ /.+[~]|\/\.\//
58
+ # [false, "'~', or '.' can be present only at the begining of string"]
59
+ else
60
+ [true, nil]
61
+ end
62
+
63
+ block.call err if block and !result and err
64
+ result
65
+ end
66
+
67
+ def normalize path
68
+ path, probably_dir = normalize_to_string path
69
+ unless path
70
+ nil
71
+ else
72
+ Path.new(path, skip_normalization: true, probably_dir: probably_dir)
73
+ end
74
+ end
75
+
76
+ def validate! path, forbid_relative = true
77
+ valid?(path, forbid_relative){|error| raise "invalid path '#{path}' (#{error})!"}
78
+ end
79
+
80
+ def normalize_to_string path
81
+ root = path[0..0]
82
+ result, probably_dir = [], false
83
+
84
+ parts = path.split('/')[1..-1]
85
+ if parts
86
+ parts.each do |part|
87
+ if part == '..' and root != '.'
88
+ return nil, false unless result.size > 0
89
+ result.pop
90
+ probably_dir ||= true
91
+ # elsif part == '.'
92
+ # # do nothing
93
+ else
94
+ result << part
95
+ probably_dir &&= false
96
+ end
97
+ end
98
+ end
99
+ normalized_path = result.join('/')
100
+
101
+ probably_dir ||= true if normalized_path.empty?
102
+
103
+ return "#{root}#{'/' unless root == '/' or normalized_path.empty?}#{normalized_path}", probably_dir
104
+ end
105
+ end
106
+
107
+ # protected
108
+ # def delete_dir_mark
109
+ # path = path.to_s.sub(%r{/$}, '')
110
+ # end
111
+ #
112
+ #
113
+ # def root_path? path
114
+ # path =~ /^[#{ROOT_SYMBOLS}]$/
115
+ # end
116
+ #
117
+ # def split_path path
118
+ # path.split(/#{ROOT_SYMBOLS}/)
119
+ # end
120
+ #
121
+ # def dir_mark? path
122
+ # path =~ %r{/$}
123
+ # end
124
+ end
125
+ end