io 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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