ki-repo 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,106 @@
1
+ # encoding: UTF-8
2
+
3
+ # Copyright 2012 Mikko Apo
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ module Ki
18
+ module DirectoryBaseModule
19
+ attr_chain :parent, :require
20
+
21
+ def init_from_path(path)
22
+ @path = path
23
+ end
24
+
25
+ def name
26
+ File.basename(@path)
27
+ end
28
+
29
+ def go(*path)
30
+ if path.empty?
31
+ self
32
+ else
33
+ path = File.join(path).split(File::Separator)
34
+ child = child(path.delete_at(0)).parent(self)
35
+ if path.empty?
36
+ child
37
+ else
38
+ child.go(*path)
39
+ end
40
+ end
41
+ end
42
+
43
+ def exists?(*sub_path)
44
+ File.exists?(go(*sub_path).path)
45
+ end
46
+
47
+ def mkdir(*path)
48
+ dest = go(*path)
49
+ if !dest.exists?
50
+ FileUtils.mkdir_p(dest.path)
51
+ end
52
+ dest
53
+ end
54
+
55
+ def path(*sub_paths)
56
+ new_path = [@path, sub_paths].flatten
57
+ if defined? @parent
58
+ new_path.unshift(@parent.path)
59
+ end
60
+ File.join(new_path)
61
+ end
62
+
63
+ def root
64
+ if defined? @parent
65
+ @parent.root
66
+ else
67
+ self
68
+ end
69
+ end
70
+
71
+ def root?
72
+ !defined? @parent
73
+ end
74
+
75
+ def ki_path(*sub_paths)
76
+ if defined? @parent
77
+ paths = [@parent.ki_path, @path]
78
+ else
79
+ paths = ["/"]
80
+ end
81
+ File.join([paths, sub_paths].flatten)
82
+ end
83
+
84
+ def child(name)
85
+ DirectoryBase.new(name)
86
+ end
87
+ end
88
+
89
+ class DirectoryBase
90
+ include DirectoryBaseModule
91
+
92
+ def initialize(path)
93
+ init_from_path(path)
94
+ end
95
+
96
+ def self.find!(path, *locations)
97
+ locations.each do |loc|
98
+ dest = loc.go(path)
99
+ if dest.exists?
100
+ return dest
101
+ end
102
+ end
103
+ raise "Could not find '#{path}' from '#{locations.map { |l| l.path }.join("', '")}'"
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,44 @@
1
+ # encoding: UTF-8
2
+
3
+ # Copyright 2012 Mikko Apo
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ module Ki
18
+ module RepositoryMethods
19
+ DirectoryWithChildrenInListFile.add_list_file(self, Repository::Repository)
20
+
21
+ # repositories are stored under repositories directory
22
+ class RepositoryListFile
23
+ undef :create_list_item
24
+ def create_list_item(item)
25
+ Repository::Repository.new("repositories/" + item).parent(parent).repository_id(item)
26
+ end
27
+ end
28
+
29
+ def finder
30
+ RepositoryFinder.new(self)
31
+ end
32
+
33
+ def version(*args, &block)
34
+ finder.version(*args, &block)
35
+ end
36
+ end
37
+
38
+ class KiHome < DirectoryBase
39
+ include RepositoryMethods
40
+ def self.ki_version
41
+ IO.read(File.expand_path(File.join(File.dirname(__FILE__),"../..", 'VERSION')))
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,153 @@
1
+ # encoding: UTF-8
2
+
3
+ # Copyright 2012 Mikko Apo
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ module Ki
18
+ require 'json'
19
+
20
+ # Base implementation for json files.
21
+ # * DirectoryBase takes a path argument where file will exist
22
+ # * Classes inheriting this file should implement json_default() to define default data object
23
+ # * cached_data loads data from disk when first accessed and edit_data modifies it
24
+ # * helper methods should access data through cached_data
25
+ class KiJSONFile < DirectoryBase
26
+ attr_chain :cached_data, -> { load_data_from_file }
27
+
28
+ # Loads latest data from file path. Does not update cached_data
29
+ def load_data_from_file(default=json_default)
30
+ KiJSONFile.load_json(path, default)
31
+ end
32
+
33
+ # Loads data from file path, makes it editable and saves data
34
+ def edit_data(&block)
35
+ @cached_data = load_data_from_file
36
+ block.call(self)
37
+ File.safe_write(path, JSON.pretty_generate(@cached_data))
38
+ @cached_data
39
+ end
40
+
41
+ # Saves data to file path. Does not update cached_data
42
+ def save(data=cached_data)
43
+ File.safe_write(path, JSON.pretty_generate(data))
44
+ end
45
+
46
+ def size
47
+ cached_data.size
48
+ end
49
+
50
+ def KiJSONFile.load_json(path, default=nil)
51
+ if File.exists?(path)
52
+ JSON.parse(IO.read(path))
53
+ else
54
+ default
55
+ end
56
+ end
57
+
58
+ def reset_cached_data
59
+ remove_instance_variable(:@cached_data)
60
+ end
61
+ end
62
+
63
+ # Base implementation for Json list file
64
+ class KiJSONListFile < KiJSONFile
65
+ include Enumerable
66
+ attr_chain :json_default, -> { Array.new }
67
+
68
+ def create_list_item(obj)
69
+ obj
70
+ end
71
+
72
+ def add_item(obj)
73
+ edit_data do
74
+ @cached_data << obj
75
+ end
76
+ create_list_item(obj)
77
+ end
78
+
79
+ def each(&block)
80
+ cached_data.each do |obj|
81
+ block.call(create_list_item(obj))
82
+ end
83
+ end
84
+ end
85
+
86
+ # Base implementation Json hash file
87
+ #
88
+ # Inheriting classes should implement their values using CachedMapDataAccessor
89
+ # class VersionMetadataFile < KiJSONHashFile {
90
+ # attr_chain :source, -> { Hash.new }, :accessor => CachedData
91
+ class KiJSONHashFile < KiJSONFile
92
+ include Enumerable
93
+ attr_chain :json_default, -> { Hash.new }
94
+
95
+ class CachedMapDataAccessor
96
+ def get(object, name)
97
+ object.cached_data[name.to_s]
98
+ end
99
+
100
+ def set(object, name, value)
101
+ object.cached_data[name.to_s] = value
102
+ end
103
+
104
+ def defined?(object, name)
105
+ object.cached_data.include?(name.to_s)
106
+ end
107
+ end
108
+
109
+ CachedData = CachedMapDataAccessor.new
110
+ end
111
+
112
+ # Helper method for creating list files.
113
+ class DirectoryWithChildrenInListFile
114
+ # Helper method for creating list files. When called on a class it extends the class with:
115
+ # * class extending KiJSONListFile which stores list items: class VersionListFile
116
+ # * method to load the file: versions()
117
+ # * method to load a specific item from list file: version(version_id, versions_list=versions)
118
+ def self.add_list_file(obj, clazz, name=nil)
119
+ stripped_class_name = clazz.name.split("::").last
120
+ class_name = clazz.name
121
+ list_class_name = "#{stripped_class_name}ListFile"
122
+ create_id_name = stripped_class_name.gsub(/.[A-Z]/) { |s| "#{s[0]}_#{s[1]}" }.downcase
123
+ if name.nil?
124
+ name = create_id_name
125
+ end
126
+ pluralized_name = "#{name}s".gsub(/ys$/, "ies")
127
+ new_methods = <<METHODS
128
+ class #{list_class_name} < KiJSONListFile
129
+ def create_list_item(#{name}_id)
130
+ i = #{class_name}.new(#{name}_id)
131
+ i.parent(parent)
132
+ i.#{create_id_name}_id(#{name}_id)
133
+ i
134
+ end
135
+ end
136
+
137
+ def #{pluralized_name}
138
+ #{list_class_name}.new("ki-#{pluralized_name}.json").parent(self)
139
+ end
140
+
141
+ def #{name}(#{name}_id, #{pluralized_name}_list=#{pluralized_name})
142
+ #{pluralized_name}_list.each do |c|
143
+ if c.#{name}_id == #{name}_id
144
+ return c
145
+ end
146
+ end
147
+ raise "#{class_name} '\#{#{name}_id}' not found"
148
+ end
149
+ METHODS
150
+ obj.class_eval(new_methods, __FILE__, (__LINE__ - new_methods.split("\n").size - 1))
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,91 @@
1
+ # encoding: UTF-8
2
+
3
+ # Copyright 2012 Mikko Apo
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ module Ki
18
+ module Repository
19
+ # Contains information about a version for one repository
20
+ # * Files: ki-version.json, ki-statuses.json, ki-reverse-dependencies.json
21
+ # @see Component
22
+ # @see VersionMetadataFile
23
+ # @see VersionStatusFile
24
+ # @see KiJSONListFile
25
+ class Version < DirectoryBase
26
+ attr_chain :version_id, :require
27
+ attr_chain :metadata, -> {VersionMetadataFile.new("ki-version.json").json_default("version_id" => version_id).parent(self)}
28
+
29
+ def statuses
30
+ VersionStatusFile.new("ki-statuses.json").parent(self)
31
+ end
32
+
33
+ def reverse_dependencies
34
+ KiJSONListFile.new("ki-reverse-dependencies.json").parent(self)
35
+ end
36
+
37
+ def action_usage
38
+
39
+ end
40
+ end
41
+
42
+ # Contains information about a component for one repository
43
+ # * Files: ki-versions.json, status_info.json
44
+ # @see Version
45
+ # @see Repository
46
+ class Component < DirectoryBase
47
+ attr_chain :component_id, :require
48
+ DirectoryWithChildrenInListFile.add_list_file(self, Version)
49
+
50
+ # Chronological list of versions in this component
51
+ class VersionListFile
52
+ undef create_list_item
53
+ def create_list_item(item)
54
+ id = item["id"]
55
+ Version.new(id).version_id(File.join(parent.component_id, id)).parent(parent)
56
+ end
57
+
58
+ def add_version(id, time=Time.now)
59
+ obj = {"id" => id, "time" => time}
60
+ edit_data do
61
+ @cached_data.unshift obj
62
+ end
63
+ create_list_item(obj)
64
+ end
65
+ end
66
+
67
+ # Status information file. Hash that defines information about status fields
68
+ # * list defines order of statuses {"maturity": ["alpha","beta","gamma"]}
69
+ def status_info
70
+ KiJSONHashFile.new("status_info.json").parent(self)
71
+ end
72
+ end
73
+
74
+ # Repository root
75
+ # * Files: ki-components.json
76
+ # @see Component
77
+ class Repository < DirectoryBase
78
+ attr_chain :repository_id, :require
79
+ DirectoryWithChildrenInListFile.add_list_file(self, Component)
80
+
81
+ # finds version matching the last part of version string, for example: my/component/1 looks for version named 1
82
+ # @see Component#version
83
+ def version(str)
84
+ args = str.split("/")
85
+ args.delete_at(-1)
86
+ component(args.join("/")).version(str)
87
+ end
88
+ end
89
+ end
90
+ end
91
+
@@ -0,0 +1,141 @@
1
+ # encoding: UTF-8
2
+
3
+ # Copyright 2012 Mikko Apo
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ module Ki
18
+ # Metadata
19
+ # * version_id String
20
+ # * build_info Hash. keys: source, tag, commiter. values=Strings
21
+ # * filesets Hash. keys: Sorted array of tags. values: List of File info Hash.
22
+ # * File info Hash. keys: path, size, executable, sha-1
23
+ # * dependencies List. values: Dependency Hash. keys: dependency_id, path, internal, dependency_operations
24
+ # * fileoperations
25
+ class VersionMetadataFile < KiJSONHashFile
26
+ attr_chain :version_id, :require, :accessor => CachedData
27
+ attr_chain :source, -> { Hash.new }, :accessor => CachedData
28
+ attr_chain :files, -> { Array.new }, :accessor => CachedData
29
+ attr_chain :operations, -> { Array.new }, :accessor => CachedData
30
+ attr_chain :dependencies, -> { Array.new }, :accessor => CachedData
31
+
32
+ def add_file_info(name, size, *args)
33
+ extra = (args.select { |arg| arg.kind_of?(Hash) }.size!(0..1).first or {})
34
+ tags = (args - [extra]).flatten.uniq
35
+ file_hash = {"path" => name, "size" => size}.merge(extra)
36
+ if tags.size > 0
37
+ file_hash["tags"]=tags
38
+ end
39
+ files << file_hash
40
+ end
41
+
42
+ # Comma separated list of dependency arguments
43
+ # * dependency parameters can be given in the hash
44
+ # TODO: version_id should be resolved through Version
45
+ def add_dependency(param_str, args={})
46
+ params = param_str.split(",")
47
+ version_id = params.delete_at(0)
48
+ dep_hash = {"version_id" => version_id}.merge(params.to_h("=")).merge(args)
49
+ if dep_hash["internal"]
50
+ dep_hash["internal"]=true
51
+ end
52
+ dep_hash.extend(DependencyMethods)
53
+ dependencies << dep_hash
54
+ dep_hash
55
+ end
56
+
57
+ def add_operation(args)
58
+ operations << args
59
+ end
60
+
61
+ def VersionMetadataFile.calculate_hashes(full_path, digester_ids)
62
+ digesters = {}
63
+ digester_ids.each do |h|
64
+ digesters[h] = KiCommand::KiExtensions.find!(File.join("/hashing", h)).digest
65
+ end
66
+ algos = digesters.values
67
+ File.open(full_path, "r") do |io|
68
+ while (!io.eof)
69
+ buf = io.readpartial(1024)
70
+ algos.each do |digester|
71
+ digester.update(buf)
72
+ end
73
+ end
74
+ end
75
+ digesters.each_pair do |h, digester|
76
+ digesters[h]=digester.hexdigest
77
+ end
78
+ digesters
79
+ end
80
+
81
+ # Processes all files from source that match patterns and for each file calculates hashes and stores tags based on default_parameters
82
+ def add_files(source, patterns, default_parameters={})
83
+ files_or_dirs = Array.wrap(patterns).map do |pattern|
84
+ Dir.glob(File.join(source, pattern))
85
+ end.flatten
86
+
87
+ files = files_or_dirs.map do |file_or_dir|
88
+ if File.directory?(file_or_dir)
89
+ Dir.glob(File.join(file_or_dir, "**/*"))
90
+ else
91
+ file_or_dir
92
+ end
93
+ end.flatten.sort
94
+
95
+ files.each do |file|
96
+ add_file(source, file, default_parameters)
97
+ end
98
+ self
99
+ end
100
+
101
+ def add_file(root, full_path, parameters)
102
+ stat = File.stat(full_path)
103
+ size = stat.size
104
+ extra = {}
105
+ if stat.executable?
106
+ extra["executable"]=true
107
+ end
108
+ if parameters["tags"]
109
+ tags = Array.wrap(parameters["tags"])
110
+ if tags && tags.size > 0
111
+ extra["tags"]= tags
112
+ end
113
+ end
114
+ if parameters["hashes"].nil?
115
+ parameters["hashes"]=["sha1"]
116
+ end
117
+ if parameters["hashes"].size > 0
118
+ extra.merge!(VersionMetadataFile.calculate_hashes(full_path, parameters["hashes"]))
119
+ end
120
+ add_file_info(full_path[root.size+1..-1], size, extra)
121
+ end
122
+ end
123
+
124
+ module DependencyMethods
125
+ attr_chain :operations, -> { Array.new }, :accessor => AttrChain::HashAccess
126
+
127
+ def add_operation(args)
128
+ operations << args
129
+ end
130
+ end
131
+
132
+ class VersionStatusFile < KiJSONListFile
133
+ def add_status(key, value, flags={})
134
+ add_item({"key" => key, "value" => value}.merge(flags))
135
+ end
136
+
137
+ def matching_statuses(key)
138
+ cached_data.select { |hash| hash["key"].match(key) }
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,42 @@
1
+ # encoding: UTF-8
2
+
3
+ # Copyright 2012 Mikko Apo
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require 'bundler/setup'
18
+
19
+ require_relative 'util/attr_chain'
20
+ require_relative 'util/exception_catcher'
21
+ require_relative 'util/ruby_extensions'
22
+ require_relative 'util/test'
23
+ require_relative 'util/service_registry'
24
+ require_relative 'util/hash'
25
+ require_relative 'util/hash_cache'
26
+ require_relative 'util/simple_optparse'
27
+
28
+ require_relative 'data_storage/dir_base'
29
+ require_relative 'data_storage/ki_json'
30
+ require_relative 'data_storage/repository'
31
+ require_relative 'data_storage/version_metadata'
32
+ require_relative 'data_storage/ki_home'
33
+
34
+ require_relative 'data_access/repository_info'
35
+ require_relative 'data_access/repository_finder'
36
+ require_relative 'data_access/version_helpers'
37
+ require_relative 'data_access/version_operations'
38
+ require_relative 'data_access/version_iterators'
39
+
40
+ require_relative 'cmd/cmd'
41
+ require_relative 'cmd/version_cmd'
42
+ require_relative 'cmd/user_pref_cmd'