ki-repo 0.1.0 → 0.1.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,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'