highland 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,12 @@
1
+ HighlandDB is a lightweight NoSQL database for custom Ruby based applications. It creates the database in the application directory, stores data in special highland files and utilizes asymptotically efficient algorythms for working with existing data.
2
+
3
+ ![Highland Logo](https://raw.github.com/mac-r/highland/master/logo.png)
4
+
5
+ [![Build Status](https://secure.travis-ci.org/mac-r/highland.png)](https://travis-ci.org/mac-r/highland)
6
+ [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/mac-r/highland)
7
+
8
+ ## Learn more about Highland:
9
+ [Installation](https://github.com/mac-r/highland/wiki/Installation) | [Querying](https://github.com/mac-r/highland/wiki/Querying) | [Making a Contribution](https://github.com/mac-r/highland/issues?milestone=&page=1&state=open)
10
+
11
+ ## Requirements:
12
+ ruby 1.9.3
data/bin/highland ADDED
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'gli'
4
+ include GLI::App
5
+ require 'rio'
6
+
7
+ ##
8
+ # This are console colors.
9
+ class Colors
10
+ COLOR1 = "\e[1;36;40m"
11
+ COLOR2 = "\e[1;35;40m"
12
+ NOCOLOR = "\e[0m"
13
+ RED = "\e[1;31;40m"
14
+ GREEN = "\e[1;32;40m"
15
+ DARKGREEN = "\e[0;32;40m"
16
+ YELLOW = "\e[1;33;40m"
17
+ DARKCYAN = "\e[0;36;40m"
18
+ end
19
+
20
+ class String
21
+ def color(color)
22
+ return color + self + Colors::NOCOLOR
23
+ end
24
+ end
25
+
26
+ ##
27
+ # This is an executable file,
28
+ # which includes all coomands available from the terminal.
29
+
30
+ ##
31
+ # Returns the version of Highland, further it should return
32
+ # the list of commands and examples of HighlandDB API.
33
+ command :help do |c|
34
+ c.action do |global_options,options,args|
35
+ puts "Highland v. 0.0.1"
36
+ end
37
+ end
38
+
39
+ ##
40
+ # Creates new database in the
41
+ command :init do |c|
42
+ c.action do |global_options,options,args|
43
+ puts "Highland: init called!".color(Colors::GREEN)
44
+ db_root = Dir::pwd + "/highland_db/"
45
+ puts db_root
46
+ exec_root = Dir::pwd
47
+ begin
48
+ Dir::mkdir(db_root)
49
+ puts "\tcreated highland root"
50
+ rescue
51
+ raise TypeError, "Highland: couldn't create the database!".color(Colors::RED)
52
+ end
53
+
54
+ # creating directories
55
+ Dir::mkdir("#{db_root}/db")
56
+ puts "\tprepared db folder"
57
+
58
+ # creating files
59
+ manifesto_content = "collections:\n"
60
+ File.open("#{db_root}/manifesto.yml", 'w') {|f| f.write(manifesto_content) }
61
+ puts "\tprepared Manifesto"
62
+ File.open("#{exec_root}/root", 'w') {|f| f.write("Highland dependency!") }
63
+ puts "\tprepared root file"
64
+
65
+ end
66
+ end
67
+
68
+ exit run(ARGV)
69
+
70
+
@@ -0,0 +1,201 @@
1
+ module Highland
2
+
3
+ ##
4
+ # CollectionMethods are mostly the API of Highland. Methods marked
5
+ # with "API:" are the ones, which are recommended to use day by day.
6
+ module CollectionMethods
7
+
8
+ ##
9
+ # Initializes collection (loads virtual hash and helper).
10
+ def init_collection(collection)
11
+ init_file(collection)
12
+ load_vhash(@file)
13
+ load_vhelper
14
+ end
15
+
16
+ ##
17
+ # API: Creates new element in collection.
18
+ # [example: your code] Users.create(:age => 26, :name => 'Chris')
19
+ # [example: what happens] Creates user in Users collection.
20
+ def create(*params)
21
+ id = rand(999999999999999999)
22
+ rec = {}
23
+ params[0].each_key do |key|
24
+ rec[key.to_s] ||= {}
25
+ rec[key.to_s]["type"] = params[0][key].class.to_s.downcase
26
+ rec[key.to_s]["value"] = params[0][key]
27
+ end
28
+ element = {id => rec}
29
+ insert_vhash(element)
30
+ insert_vhelper(element)
31
+ insert_shash(element)
32
+ return element
33
+ end
34
+
35
+ ##
36
+ # API: Returns array of elements according to the query.
37
+ # [example: your code #1] Users.where(:name => 'Helen', :age => 20)
38
+ # [example: your code #2] Users.where(:age => 20)
39
+ # [example: what happens] Will return Array of HighlandObjects, which
40
+ # fit the query constraints.
41
+ def where(*params)
42
+ hash = find_db(*params)
43
+ output = objectize(hash)
44
+ return output
45
+ end
46
+
47
+ ##
48
+ # API: Returns the first element according to the query.
49
+ # [example: your code #1] Users.first(:name => 'Bill', :age => 80)
50
+ # [example: your code #2] Users.first(:age => 80)
51
+ # [example: what happens] Will return the first HighlandObject, which
52
+ # fits the query constraints.
53
+ def first(*params)
54
+ where(*params).first
55
+ end
56
+
57
+ ##
58
+ # API: Returns all elements from the collection, you can also specify
59
+ # details of your request. Look at examples below
60
+ # [example: your code #1] Users.all(:name => 'Fake', :age => 20)
61
+ # [example: your code #2] Users.all(:age => 20)
62
+ # [example: your code #3] Users.all
63
+ # [example: what happens] Will return all HighlandObjects of collection
64
+ # or some of them according to the query.
65
+ def all(*params)
66
+ return where(*params) if params[0].class == Hash
67
+ return objectize(@vhash)
68
+ end
69
+
70
+ ##
71
+ # API: Allows you to find elements in collection. It's more powerful
72
+ # than "where" if you want to find several elements by id or by other keys.
73
+ # [example: your code #1] Users.find([id,id,...,id])
74
+ # [example: your code #2] Users.find(:age => [20,21,22])
75
+ # [example: your code #3] Users.find(:age => [28])
76
+ # [example: your code #4] Users.find(:age => 20)
77
+ # [example: what happens] Will return HighlandObjects
78
+ # according to the query.
79
+ def find(*params)
80
+ output = []
81
+ if params[0].class == Hash
82
+ params[0].each_key do |key|
83
+ [params[0][key]].flatten.each {|el| output = output + all(key => el)}
84
+ end
85
+ else
86
+ params.each {|id| output += where(id)}
87
+ end
88
+ return output
89
+ end
90
+
91
+ ##
92
+ # API: Define the key and it will return all elements from collection.
93
+ # If the order is not specified - it's ascending. Look at examples
94
+ # of possible syntax.
95
+ # [example: your code #1] Users.sort(:age)
96
+ # [example: your code #2] Users.sort(:age => "asc")
97
+ # [example: your code #3] Users.sort(:age => "desc")
98
+ # [example: what happens] Will return HighlandObjects
99
+ # according to the defined order.
100
+ def sort(*params)
101
+ column = (params[0].class == Hash)?(params[0].keys.first):(params[0])
102
+ sorted = if params[0].class == Hash and params[0][column] == "desc"
103
+ distinct(column).sort{|x,y| y <=> x}
104
+ else
105
+ distinct(column).sort
106
+ end
107
+ output = []
108
+ sorted.each {|s| @vhelper[column.to_s][s].each{|id| output += find(id)}}
109
+ return output
110
+ end
111
+
112
+
113
+
114
+
115
+
116
+
117
+ ##
118
+ # API: Define keys and it will return all elements from collection.
119
+ # If the order is not specified - it's ascending. Look at examples
120
+ # of possible syntax.
121
+ # [example: your code #1] Users.count(:name => 'Fake', :age => 20)
122
+ # [example: your code #2] Users.count(:name => 'Fake')
123
+ # [example: your code #3] Users.count(:age => 21)
124
+ # [example: your code #4] Users.count
125
+ # [example: what happens] Will return quantity of files
126
+ # according to the defined query.
127
+ def count(*params)
128
+ return find_db(*params).keys.length if params[0].class == Hash
129
+ return @vhash.keys.length
130
+ end
131
+
132
+ ##
133
+ # API: is a synonim for "count".
134
+ def size(*params)
135
+ count(*params)
136
+ end
137
+
138
+ ##
139
+ # API: Allows to get all values of a specified key inside collection.
140
+ # It's a good way to get all ids as well.
141
+ # [example: your code #1] Users.distinct(:name)
142
+ # [example: your code #2] Users.distinct(:id)
143
+ # [example: what happens] Will return all values of a specified key.
144
+ def distinct(column)
145
+ return @vhelper[column.to_s].keys if column.to_s != "id"
146
+ return @vhash.keys if column.to_s == "id"
147
+ end
148
+
149
+ ##
150
+ # API: Updates the element of collection.
151
+ # [example: your code] Users.update(:id => some_id,:age => 40, :name => "Kate")
152
+ # [example: what happens] Will update relative values for element with id equal "some_id".
153
+ def update(*params)
154
+ update_db(*params)
155
+ end
156
+
157
+ ##
158
+ # API: Removes the element of collection.
159
+ # [example: your code] Users.remove(:age => 25, :name => "Bob")
160
+ # [example: what happens] Removes the element from table according to the query.
161
+ def remove(*params)
162
+ delete(*params)
163
+ end
164
+
165
+ ##
166
+ # API: Clears the collection. Static and virtual hashes get empty.
167
+ # [example: your code] Users.clear
168
+ # [example: what happens] Now Users collection is empty.
169
+ def clear
170
+ clear_virtual
171
+ clear_static
172
+ reload_virtual
173
+ end
174
+
175
+ ##
176
+ # It's one of core methods, which works as a factory for HighlandObjects.
177
+ def objectize(hash)
178
+ output = []
179
+ build_object
180
+ hash.each_key do |id|
181
+ o = HighlandObject.new
182
+ o.instance_variable_set(:@id, id)
183
+ o.singleton_class.send(:define_method, :id) { @id }
184
+ hash[id].each_key do |key|
185
+ instance = "@#{key}"
186
+ o.instance_variable_set(instance.to_sym, hash[id][key]["value"])
187
+ o.singleton_class.send(:define_method, key) { eval(instance) }
188
+ end
189
+ output << o
190
+ end
191
+ return output
192
+ end
193
+
194
+ ##
195
+ # Objectize method helper.
196
+ def build_object
197
+ Object.const_set("HighlandObject", Class.new) unless defined?(HighlandObject)
198
+ end
199
+
200
+ end
201
+ end
@@ -0,0 +1,13 @@
1
+ class Hash
2
+ def remove!(*keys)
3
+ keys.each{|key| self.delete(key) }
4
+ self
5
+ end
6
+
7
+ def remove(*keys)
8
+ self.dup.remove!(*keys)
9
+ end
10
+ end
11
+
12
+ class HighlandObject < Array
13
+ end
@@ -0,0 +1,171 @@
1
+ require 'rio'
2
+ require File.join(File.dirname(__FILE__), "core_extensions" )
3
+
4
+ module Highland
5
+
6
+ ##
7
+ # DatabaseMethods are not the part of the user API. These methods
8
+ # are the fundamentals of the one. They work with static hashes and
9
+ # manage virtual ones. It's not recommended to work with these
10
+ # methods directly.
11
+ module DatabaseMethods
12
+
13
+ ##
14
+ # Defines @file instance variable.
15
+ def init_file(shash)
16
+ @file = shash
17
+ generate_hl if File.exists?(@file) == false
18
+ end
19
+
20
+ ##
21
+ # Creates static hash in the database folder.
22
+ def generate_hl
23
+ File.new(@file, "w")
24
+ end
25
+
26
+ ##
27
+ # Loads virtual hash.
28
+ def load_vhash(shash)
29
+ init_file(shash)
30
+ data = "{#{rio(@file).contents}}"
31
+ @vhash = eval(data)
32
+ end
33
+
34
+ ##
35
+ # Assigns the latest information about columns the @columns.
36
+ def update_columns
37
+ @columns = @vhelper.keys
38
+ end
39
+
40
+ ##
41
+ # Loads virtual helper.
42
+ def load_vhelper
43
+ @vhelper = {}
44
+ @vhash.each_key do |id|
45
+ @vhash[id].each_key do |column|
46
+ @vhelper[column] ||= {}
47
+ item = @vhash[id][column]["value"]
48
+ @vhelper[column][item] ||= []
49
+ @vhelper[column][item] << id
50
+ end
51
+ end
52
+ update_columns
53
+ end
54
+
55
+ ##
56
+ # Inserts hash element into the virtual hash.
57
+ def insert_vhash(element)
58
+ element.each_key do |key|
59
+ @vhash[key] = element[key]
60
+ end
61
+ end
62
+
63
+ ##
64
+ # Inserts hash element into the virtual helper.
65
+ def insert_vhelper(element)
66
+ element.each_key do |id|
67
+ element[id].each_key do |column|
68
+ @vhelper[column] ||= {}
69
+ item = element[id][column]["value"]
70
+ @vhelper[column][item] ||= []
71
+ @vhelper[column][item] << id
72
+ end
73
+ end
74
+ update_columns
75
+ end
76
+
77
+ ##
78
+ # Finds hash elements according to the query.
79
+ def find_db(*query)
80
+ if query[0].class == Fixnum
81
+ id = query[0]
82
+ output = {}
83
+ output[id] = @vhash[id] if @vhash[id] != nil
84
+ return output
85
+ end
86
+ if query[0].class == Hash
87
+ q = query[0]
88
+ ids = []
89
+ q.each_key do |column|
90
+ ids << @vhelper[column.to_s][q[column]]
91
+ end
92
+ res = ids.inject(ids.first){ |sim,cur| sim & cur }
93
+ return {} if res.class != Array or res == []
94
+ output = {}
95
+ res.each do |id|
96
+ next if @vhash[id] == nil
97
+ output[id] = @vhash[id]
98
+ end
99
+ return output
100
+ end
101
+ end
102
+
103
+ ##
104
+ # Inserts hash element into the static hash.
105
+ def insert_shash(element)
106
+ File.open(@file, 'a') do |file|
107
+ element.each_key do |id|
108
+ file.puts "#{id} => #{element[id]},"
109
+ end
110
+ end
111
+ end
112
+
113
+ ##
114
+ # Clears virtual hash and virtual helper.
115
+ def clear_virtual
116
+ @vhash, @vhelper = {}, {}
117
+ end
118
+
119
+ ##
120
+ # Reloads virtual hash and virtual helper.
121
+ def reload_virtual
122
+ clear_virtual
123
+ load_vhash(@file)
124
+ load_vhelper
125
+ end
126
+
127
+ ##
128
+ # Clears static hash (collection).
129
+ def clear_static
130
+ File.truncate(@file, 0)
131
+ end
132
+
133
+ ##
134
+ # Deletes element from collection.
135
+ def delete(*query)
136
+ find_db(*query).each_key do |id|
137
+ @vhash.remove!(id)
138
+ end
139
+ load_vhelper
140
+ clear_static
141
+ insert_shash(@vhash)
142
+ return true
143
+ end
144
+
145
+ ##
146
+ # Updates element.
147
+ def update_db(*query)
148
+ id = query[0][:id]
149
+ query[0].each_key do |key|
150
+ next if key == :id
151
+ @vhash[id][key.to_s]["value"] = query[0][key] if @vhash[id].has_key?(key.to_s)
152
+ end
153
+ load_vhelper
154
+ clear_static
155
+ insert_shash(@vhash)
156
+ return true
157
+ end
158
+
159
+ ##
160
+ # Returns virtual hash.
161
+ def return_vhash
162
+ @vhash
163
+ end
164
+
165
+ ##
166
+ # Returns virtual helper.
167
+ def return_vhelper
168
+ @vhelper
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,136 @@
1
+ require 'yaml'
2
+
3
+ module Highland
4
+
5
+ ##
6
+ # Environment is the initial set of methods, which gets loaded
7
+ # after you include highland in your file. It depends
8
+ # on the set of static files inside highland_db folder. If you
9
+ # write "require 'highland'" in your file and noothing works then
10
+ # it means that you didn't initialize your database in the folder
11
+ # of your application ($ highland init).
12
+ module Environment
13
+ self.extend Highland::Environment
14
+ @@klasses = []
15
+ @@stringklasses = []
16
+ @@db_path = ""
17
+
18
+ ##
19
+ # Load initializes the class variable @@db_path and loads collections.
20
+ # You shouldn't call this method. It's done automatically. If you want
21
+ # to specify the path to your database - write custom path in DB_PATH
22
+ # before "require 'highland'".
23
+ def load
24
+ @@db_path = find_root(Dir::pwd) if defined?(DB_PATH) == nil
25
+ @@db_path = DB_PATH if defined?(DB_PATH) == "constant"
26
+ load_collections
27
+ end
28
+
29
+ ##
30
+ # Build is an alternative for load method. Allows you to redefine class
31
+ # variable. It's not recommended to use this method.
32
+ def build(file)
33
+ @@db_path = find_root(expander(file)) if defined?(DB_PATH) == nil
34
+ load_collections
35
+ end
36
+
37
+ ##
38
+ # Gets the list of static collections, compares it with the manifesto.yml.
39
+ # If manifesto.yml doesn't include some static collections - this collections
40
+ # get deleted. If manifesto.yml has collections which do not exist then these
41
+ # collections are automatically created. After this - virtual hashes are loaded.
42
+ def load_collections
43
+ collections = get_collections
44
+ static = get_static
45
+ create_missing(collections, static)
46
+ delete_unused(collections, static)
47
+ classes = define_classes(collections)
48
+ load_virtuals(classes)
49
+ end
50
+
51
+ ##
52
+ # Gets names of collections from manifesto.yml.
53
+ def get_collections
54
+ return YAML.load_file(@@db_path + "manifesto.yml")["collections"]
55
+ end
56
+
57
+ ##
58
+ # Gets all static hashes (collections) from the database folder.
59
+ def get_static
60
+ return Dir[@@db_path + "db/*.hl"].map do |file|
61
+ File.basename(file,".hl").downcase
62
+ end
63
+ end
64
+
65
+ ##
66
+ # Creates all the missing static hashes, which are mentioned in manifesto.
67
+ def create_missing(collections, static)
68
+ collections.each do |c|
69
+ File.open(@@db_path + "db/#{c.downcase}.hl", "w") if static.include?(c.downcase) == false
70
+ end
71
+ end
72
+
73
+ ##
74
+ # Deletes all static collections, which are not mentioned in the manifesto.
75
+ def delete_unused(collections, static)
76
+ static.each do |c|
77
+ File.delete(@@db_path + "db/#{c.downcase}.hl") if collections.map(&:downcase).include?(c.downcase) == false
78
+ end
79
+ end
80
+
81
+ ##
82
+ # Creates classes for relevant collections and provides them with API.
83
+ def define_classes(collections)
84
+ collections.each do |collection|
85
+ if @@klasses.include?(collection) == false
86
+ new_class = Object.const_set(collection, Class.new)
87
+ new_class.extend Highland::CollectionMethods
88
+ new_class.extend Highland::DatabaseMethods
89
+ @@stringklasses << new_class
90
+ @@klasses << collection
91
+ end
92
+ end
93
+ return @@stringklasses
94
+ end
95
+
96
+ ##
97
+ # Loads virtual hash and virtual helper for each collection.
98
+ def load_virtuals(classes)
99
+ classes.each do |klass|
100
+ target = klass.to_s.downcase
101
+ klass.init_collection(@@db_path + "db/#{target}.hl")
102
+ end
103
+ end
104
+
105
+ ##
106
+ # Returns true if the input path is the application root.
107
+ def root?(path)
108
+ root_objects = ["gemfile", "procfile", "readme", "root"]
109
+ current_objects = Dir[path + "/*"].map do |file|
110
+ File.basename(file).downcase
111
+ end
112
+ dir = ""
113
+ current_objects.each do |co|
114
+ dir = (root_objects.include?(co) == true)? "ROOT" : "NOT ROOT"
115
+ break if dir == "ROOT"
116
+ end
117
+ return true if dir == "ROOT"
118
+ return false if dir == "NOT ROOT"
119
+ end
120
+
121
+ ##
122
+ # Expands the path of input file.
123
+ def expander(file)
124
+ return File.expand_path(File.dirname(file))
125
+ end
126
+
127
+ ##
128
+ # Recursive method, which tries to find the application root.
129
+ def find_root(path)
130
+ path = File.expand_path('..', path) if root?(path) == false
131
+ find_root(path) if root?(path) == false
132
+ return path + "/highland_db/" if root?(path) == true
133
+ end
134
+
135
+ end
136
+ end
@@ -0,0 +1,13 @@
1
+ require 'highland/environment'
2
+ require 'highland/collection_methods'
3
+ require 'highland/database_methods'
4
+
5
+ ##
6
+ # Highland is a lightweight NoSQL database. Enjoy!
7
+ module Highland
8
+ include Environment
9
+ include DatabaseMethods
10
+ include CollectionMethods
11
+ end
12
+
13
+ Highland::Environment.load
data/lib/highland.rb ADDED
@@ -0,0 +1 @@
1
+ require 'highland/highland'
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: highland
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Max Makarochkin
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-04 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rio
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: gli
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: Simple database initially built as a default Ajaila database, which can
47
+ be installed in less than a minute.
48
+ email: maxim.makarochkin@gmail.com
49
+ executables:
50
+ - highland
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - lib/highland.rb
55
+ - bin/highland
56
+ - lib/highland/collection_methods.rb
57
+ - lib/highland/core_extensions.rb
58
+ - lib/highland/database_methods.rb
59
+ - lib/highland/environment.rb
60
+ - lib/highland/highland.rb
61
+ - README.md
62
+ homepage: http://github.com/mac-r/highland
63
+ licenses: []
64
+ post_install_message:
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ! '>='
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ! '>='
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ requirements: []
81
+ rubyforge_project:
82
+ rubygems_version: 1.8.24
83
+ signing_key:
84
+ specification_version: 3
85
+ summary: Lightweight NoSQL database inside your Ruby app.
86
+ test_files: []