dm-keeper-adapter 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.
- data/.gitignore +6 -0
- data/README.rdoc +40 -0
- data/Rakefile +29 -0
- data/dm-keeper-adapter.gemspec +27 -0
- data/lib/dm-keeper-adapter.rb +78 -0
- data/lib/dm-keeper-adapter/create.rb +21 -0
- data/lib/dm-keeper-adapter/delete.rb +12 -0
- data/lib/dm-keeper-adapter/misc.rb +9 -0
- data/lib/dm-keeper-adapter/read.rb +168 -0
- data/lib/dm-keeper-adapter/update.rb +12 -0
- data/lib/dm-keeper-adapter/version.rb +8 -0
- data/lib/keeper/container.rb +58 -0
- data/lib/keeper/feature.rb +31 -0
- data/lib/keeper/product.rb +14 -0
- data/lib/keeper/productline.rb +7 -0
- data/lib/keeper/relation.rb +13 -0
- data/lib/keeper/relationtree.rb +20 -0
- data/lib/keeper/valuelist.rb +99 -0
- data/lib/property/xml.rb +18 -0
- data/script/README.rdoc +12 -0
- data/script/generate_class.rb +10 -0
- data/script/genfeatureclass.rb +52 -0
- data/script/show-containers.rb +22 -0
- data/test/helper.rb +3 -0
- data/test/test_get_feature.rb +19 -0
- data/test/test_get_relationtree.rb +19 -0
- metadata +125 -0
data/.gitignore
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
= dm-keeper-adapter
|
2
|
+
|
3
|
+
A DataMapper adapter for features.opensuse.org
|
4
|
+
|
5
|
+
== Usage
|
6
|
+
|
7
|
+
This adapter allows easy access to the openSUSE feature database using
|
8
|
+
DataMapper. It supports features, relationtrees, productlines, and
|
9
|
+
products.
|
10
|
+
|
11
|
+
features.opensuse.org is the web frontend, the database server
|
12
|
+
provides an xquery API at https://keeper.novell.com/sxkeeper, which is
|
13
|
+
used by this adapter - hence the name.
|
14
|
+
|
15
|
+
== Install
|
16
|
+
|
17
|
+
Install it with
|
18
|
+
|
19
|
+
gem install dm-keeper-adapter
|
20
|
+
|
21
|
+
== Code
|
22
|
+
|
23
|
+
require 'rubygems'
|
24
|
+
require 'dm-core'
|
25
|
+
require 'dm-keeper-adapter'
|
26
|
+
|
27
|
+
DataMapper::Logger.new($stdout, :debug)
|
28
|
+
|
29
|
+
# Retrieves credentials from ~/.oscrc if exists
|
30
|
+
# Otherwise add user:pass@ before keeper.novell.com
|
31
|
+
keeper = DataMapper.setup(:default, :adapter => 'keeper', :url => 'https://keeper.novell.com/sxkeeper')
|
32
|
+
|
33
|
+
require 'keeper/feature'
|
34
|
+
DataMapper.finalize
|
35
|
+
|
36
|
+
f = Feature.get(311545)
|
37
|
+
|
38
|
+
== TODO
|
39
|
+
|
40
|
+
Real DataMapper queries mapped to XPath
|
data/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rake/testtask'
|
3
|
+
|
4
|
+
extra_docs = ['README*', 'TODO*', 'CHANGELOG*']
|
5
|
+
|
6
|
+
task :default => [:test]
|
7
|
+
|
8
|
+
Rake::TestTask.new do |t|
|
9
|
+
t.libs << File.expand_path('../test', __FILE__)
|
10
|
+
t.libs << File.expand_path('../', __FILE__)
|
11
|
+
t.test_files = FileList['test/test*.rb']
|
12
|
+
t.verbose = true
|
13
|
+
end
|
14
|
+
|
15
|
+
begin
|
16
|
+
require 'yard'
|
17
|
+
YARD::Rake::YardocTask.new(:doc) do |t|
|
18
|
+
t.files = ['lib/**/*.rb', *extra_docs]
|
19
|
+
t.options = ['--no-private']
|
20
|
+
end
|
21
|
+
rescue LoadError
|
22
|
+
STDERR.puts "Install yard if you want prettier docs"
|
23
|
+
require 'rdoc/task'
|
24
|
+
Rake::RDocTask.new(:doc) do |rdoc|
|
25
|
+
rdoc.rdoc_dir = "doc"
|
26
|
+
rdoc.title = "dm-keeper-adapter #{DataMapper::Adapters::KeeperAdapter::VERSION}"
|
27
|
+
extra_docs.each { |ex| rdoc.rdoc_files.include ex }
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "dm-keeper-adapter/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "dm-keeper-adapter"
|
7
|
+
s.version = DataMapper::Adapters::KeeperAdapter::VERSION
|
8
|
+
|
9
|
+
s.platform = Gem::Platform::RUBY
|
10
|
+
s.authors = ["Klaus Kämpf"]
|
11
|
+
s.email = ["kkaempf@suse.de"]
|
12
|
+
s.homepage = ""
|
13
|
+
s.summary = %q{A datamapper adapter for FATE (aka keeper.novell.com)}
|
14
|
+
s.description = %q{Use it in Ruby applications to access FATE}
|
15
|
+
|
16
|
+
# get credentials from ~/.oscrc
|
17
|
+
s.add_dependency("inifile", ["~> 0.4.1"])
|
18
|
+
# parse xml response
|
19
|
+
s.add_dependency("nokogiri", ["~> 1.4"])
|
20
|
+
|
21
|
+
s.rubyforge_project = "dm-keeper-adapter"
|
22
|
+
|
23
|
+
s.files = `git ls-files`.split("\n")
|
24
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
25
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
26
|
+
s.require_paths = ["lib"]
|
27
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'dm-core'
|
3
|
+
require 'dm-core/adapters/abstract_adapter'
|
4
|
+
|
5
|
+
require "dm-keeper-adapter/create"
|
6
|
+
require "dm-keeper-adapter/read"
|
7
|
+
require "dm-keeper-adapter/update"
|
8
|
+
require "dm-keeper-adapter/delete"
|
9
|
+
require "dm-keeper-adapter/misc"
|
10
|
+
|
11
|
+
|
12
|
+
module DataMapper
|
13
|
+
class Property
|
14
|
+
autoload :XML, "property/xml"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
module DataMapper::Adapters
|
20
|
+
|
21
|
+
class KeeperAdapter < AbstractAdapter
|
22
|
+
OSCRC_CREDENTIALS = "https://api.opensuse.org"
|
23
|
+
|
24
|
+
def initialize(name, options)
|
25
|
+
super
|
26
|
+
require 'net/https'
|
27
|
+
require 'uri'
|
28
|
+
@uri = URI.parse(options[:url])
|
29
|
+
@username = options[:username]
|
30
|
+
@password = options[:password]
|
31
|
+
unless @username && @password
|
32
|
+
require 'inifile'
|
33
|
+
oscrc = IniFile.new(File.join(ENV['HOME'], '.oscrc'))
|
34
|
+
if oscrc.has_section?(OSCRC_CREDENTIALS)
|
35
|
+
@username = oscrc[OSCRC_CREDENTIALS]['user']
|
36
|
+
@password = oscrc[OSCRC_CREDENTIALS]['pass']
|
37
|
+
raise "No .oscrc credentials for keeper.novell.com" unless @username && @password
|
38
|
+
end
|
39
|
+
end
|
40
|
+
@connection = Net::HTTP.new( @uri.host, @uri.port )
|
41
|
+
# from http://www.rubyinside.com/nethttp-cheat-sheet-2940.html
|
42
|
+
@connection.use_ssl = true if @uri.scheme == "https"
|
43
|
+
@connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
def get(path)
|
48
|
+
# STDERR.puts "Get #{@uri.path} + #{path}"
|
49
|
+
request = Net::HTTP::Get.new @uri.path + path
|
50
|
+
request.basic_auth @username, @password
|
51
|
+
response = @connection.request request
|
52
|
+
raise ArgumentError unless response
|
53
|
+
raise( RuntimeError, "Server returned #{response.code}" ) unless response.code.to_i == 200
|
54
|
+
# $stderr.puts "#{response.inspect}"
|
55
|
+
# response.each { |k,v| $stderr.puts "#{k}: #{v}" }
|
56
|
+
# $stderr.puts "Encoding >#{response['content-encoding']}<"
|
57
|
+
# check
|
58
|
+
# content-type: text/xml;charset=UTF-8
|
59
|
+
# content-encoding: gzip
|
60
|
+
body = response.body
|
61
|
+
if response['content-encoding'] == "gzip"
|
62
|
+
require 'zlib'
|
63
|
+
require 'stringio'
|
64
|
+
# http://stackoverflow.com/questions/1361892/how-to-decompress-gzip-string-in-ruby
|
65
|
+
body = Zlib::GzipReader.new(StringIO.new(body)).read
|
66
|
+
end
|
67
|
+
case response['content-type']
|
68
|
+
when /xml/
|
69
|
+
Nokogiri::XML body
|
70
|
+
when /html/
|
71
|
+
Nokogiri::HTML body
|
72
|
+
else
|
73
|
+
raise TypeError, "Unknown content-type #{response['content-type']}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# create.rb
|
2
|
+
module DataMapper::Adapters
|
3
|
+
class KeeperAdapter < AbstractAdapter
|
4
|
+
def create(resources)
|
5
|
+
table_groups = group_resources_by_table(resources)
|
6
|
+
table_groups.each do |table, resources|
|
7
|
+
# make
|
8
|
+
# class User
|
9
|
+
# property :id, Serial
|
10
|
+
# end
|
11
|
+
# work
|
12
|
+
resources.each do |resource|
|
13
|
+
initialize_serial(resource,
|
14
|
+
worksheet_record_cound(table)+1)
|
15
|
+
post_resource_to_worksheet(resource,table)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
resources.size
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# delete.rb
|
2
|
+
module DataMapper::Adapters
|
3
|
+
class KeeperAdapter < AbstractAdapter
|
4
|
+
def delete(collection)
|
5
|
+
each_resource_with_edit_url(collection) do |resource, edit_url|
|
6
|
+
connection.delete(edit_url, 'If-Match' => "*")
|
7
|
+
end
|
8
|
+
# return count
|
9
|
+
collection.size
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
# read.rb
|
2
|
+
module DataMapper::Adapters
|
3
|
+
class KeeperAdapter < AbstractAdapter
|
4
|
+
require 'nokogiri'
|
5
|
+
def read(query)
|
6
|
+
records = records_for(query)
|
7
|
+
# query.filter_records(records)
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
# taken from https://github.com/whoahbot/dm-redis-adapter/
|
12
|
+
|
13
|
+
##
|
14
|
+
# Retrieves records for a particular model.
|
15
|
+
#
|
16
|
+
# @param [DataMapper::Query] query
|
17
|
+
# The query used to locate the resources
|
18
|
+
#
|
19
|
+
# @return [Array]
|
20
|
+
# An array of hashes of all of the records for a particular model
|
21
|
+
#
|
22
|
+
# @api private
|
23
|
+
def records_for(query)
|
24
|
+
# $stderr.puts "records_for(#{query})"
|
25
|
+
# $stderr.puts "records_for(#{query.inspect})"
|
26
|
+
records = []
|
27
|
+
if query.conditions.nil?
|
28
|
+
# return all
|
29
|
+
else
|
30
|
+
query.conditions.operands.each do |operand|
|
31
|
+
if operand.is_a?(DataMapper::Query::Conditions::OrOperation)
|
32
|
+
operand.each do |op|
|
33
|
+
records = records + perform_query(query, op)
|
34
|
+
end
|
35
|
+
else
|
36
|
+
records = perform_query(query, operand)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
records
|
41
|
+
end #def
|
42
|
+
|
43
|
+
##
|
44
|
+
# Find records that match have a matching value
|
45
|
+
#
|
46
|
+
# @param [DataMapper::Query] query
|
47
|
+
# The query used to locate the resources
|
48
|
+
#
|
49
|
+
# @param [DataMapper::Operation] the operation for the query
|
50
|
+
#
|
51
|
+
# @return [Array]
|
52
|
+
# An array of hashes of all of the records for a particular model
|
53
|
+
#
|
54
|
+
# @api private
|
55
|
+
def perform_query(query, operand)
|
56
|
+
# $stderr.puts "perform_query(#{query},#{operand})"
|
57
|
+
records = []
|
58
|
+
|
59
|
+
if operand.is_a?(DataMapper::Query::Conditions::NotOperation)
|
60
|
+
subject = operand.first.subject
|
61
|
+
value = operand.first.value
|
62
|
+
elsif operand.subject.is_a?(DataMapper::Associations::ManyToOne::Relationship)
|
63
|
+
subject = operand.subject.child_key.first
|
64
|
+
value = operand.value[operand.subject.parent_key.first.name]
|
65
|
+
else
|
66
|
+
subject = operand.subject
|
67
|
+
value = operand.value
|
68
|
+
end
|
69
|
+
|
70
|
+
if subject.is_a?(DataMapper::Associations::ManyToOne::Relationship)
|
71
|
+
subject = subject.child_key.first
|
72
|
+
end
|
73
|
+
|
74
|
+
# typical queries
|
75
|
+
#
|
76
|
+
# ?query=/feature[
|
77
|
+
# productcontext[
|
78
|
+
# not (status[done or rejected or duplicate or unconfirmed])
|
79
|
+
# ]
|
80
|
+
# and
|
81
|
+
# actor[
|
82
|
+
# (person/userid='kkaempf@novell.com' or person/email='kkaempf@novell.com' or person/fullname='kkaempf@novell.com')
|
83
|
+
# and
|
84
|
+
# role='projectmanager'
|
85
|
+
# ]
|
86
|
+
# ]
|
87
|
+
#
|
88
|
+
# "/#{container}[actor/role='infoprovider']
|
89
|
+
#
|
90
|
+
# query=/feature[title='Foo%20bar%20baz']
|
91
|
+
#
|
92
|
+
# query=/feature[contains(title,'Foo')]
|
93
|
+
# query=/feature[contains(title,'Foo')]/title
|
94
|
+
# query=/feature[contains(title,'Foo')]/@k:id
|
95
|
+
#
|
96
|
+
|
97
|
+
# $stderr.puts "perform_query(subject #{subject.inspect},#{value.inspect})"
|
98
|
+
container = query.model.to_s.downcase
|
99
|
+
if query.model.key.include?(subject)
|
100
|
+
# get single <feature>
|
101
|
+
records << node_to_record(query.model, get("/#{container}/#{value}").root)
|
102
|
+
else
|
103
|
+
# query, get <collection>[<object><feature>...]*
|
104
|
+
xpath = "/#{container}["
|
105
|
+
# ...
|
106
|
+
xpath << "]"
|
107
|
+
collection = get("/#{container}?query=#{xpath}")
|
108
|
+
collection.xpath("/#{container}").each do |feature|
|
109
|
+
records << node_to_record(query.model, feature)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
records
|
114
|
+
end # def
|
115
|
+
|
116
|
+
##
|
117
|
+
# Convert feature (as xml) into record (as hash of key/value pairs)
|
118
|
+
#
|
119
|
+
# @param [Nokogiri::XML::Node] node
|
120
|
+
# A node
|
121
|
+
#
|
122
|
+
# @return [Hash]
|
123
|
+
# A hash of all of the properties for a particular record
|
124
|
+
#
|
125
|
+
# @api private
|
126
|
+
def node_to_record(model, node)
|
127
|
+
record = { }
|
128
|
+
xpathmap = model.xpathmap rescue { }
|
129
|
+
xmlnamespaces = model.xmlnamespaces rescue nil
|
130
|
+
# $stderr.puts "node_to_record(#{model}:#{node.class})"
|
131
|
+
model.properties.each do |property|
|
132
|
+
xpath = xpathmap[property.name] || property.name
|
133
|
+
key = property.name.to_s
|
134
|
+
children = node.xpath("./#{xpath}", xmlnamespaces)
|
135
|
+
# $stderr.puts "Property found: #{property.inspect}"
|
136
|
+
case children.size
|
137
|
+
when 0: next
|
138
|
+
when 1: value = children.text
|
139
|
+
else
|
140
|
+
value = children.to_xml
|
141
|
+
end
|
142
|
+
# $stderr.puts "Key #{key}, Value #{value} <#{property.class}>"
|
143
|
+
case property
|
144
|
+
when DataMapper::Property::Date
|
145
|
+
require 'parsedate'
|
146
|
+
record[key] = Time.utc(ParseDate.parsedate(value))
|
147
|
+
when DataMapper::Property::Integer
|
148
|
+
record[key] = value.to_s
|
149
|
+
when DataMapper::Property::String
|
150
|
+
record[key] = value.to_s
|
151
|
+
else
|
152
|
+
raise TypeError, "#{property} unsupported"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
model.relationships.each do |rel|
|
156
|
+
# $stderr.puts "Rel ? #{rel.inspect}"
|
157
|
+
children = node.xpath("./#{rel.child_model_name.downcase}")
|
158
|
+
value = []
|
159
|
+
while n = children.shift
|
160
|
+
value << node_to_record(rel.child_model, n)
|
161
|
+
end
|
162
|
+
record[rel.name.to_s] = value
|
163
|
+
# $stderr.puts "#{rel.name} -> #{value.inspect}"
|
164
|
+
end
|
165
|
+
record
|
166
|
+
end
|
167
|
+
end # class
|
168
|
+
end # module
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# update.rb
|
2
|
+
module DataMapper::Adapters
|
3
|
+
class KeeperAdapter < AbstractAdapter
|
4
|
+
def update(attributes, collection)
|
5
|
+
each_resource_with_edit_url(collection) do |resource, edit_url|
|
6
|
+
put_updated_resource(edit_url, resource)
|
7
|
+
end
|
8
|
+
# return count
|
9
|
+
collection.size
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require "dm-keeper-adapter"
|
2
|
+
require "nokogiri"
|
3
|
+
|
4
|
+
module Keeper
|
5
|
+
class ContainerEntry
|
6
|
+
attr_reader :name, :description, :size, :postmaster
|
7
|
+
#
|
8
|
+
# parse
|
9
|
+
# <tr class="title">
|
10
|
+
# <td colspan="2"><b>product</b></td></tr>
|
11
|
+
# <tr><td><b>Description:</b></td><td>Container for storing product definitions from NPP.</td></tr>
|
12
|
+
# <tr><td><b>Documents:</b></td><td>#325</td></tr>
|
13
|
+
# <tr><td><b>Update notification class: </b></td><td>null</td></tr>
|
14
|
+
# <tr><td><b>Read-only: </b></td><td>false</td></tr>
|
15
|
+
# <tr><td><b>Notification debug: </b></td><td>false</td></tr>
|
16
|
+
# <tr><td><b>Notification postmaster: </b></td><td>tschmidt@suse.de</td></tr>
|
17
|
+
#
|
18
|
+
def initialize node
|
19
|
+
# $stderr.puts "ContainerEntry(#{node})"
|
20
|
+
@name = node.xpath("./td/b").text
|
21
|
+
while node = node.next do # next <tr>
|
22
|
+
break if node['class']
|
23
|
+
key = value = nil
|
24
|
+
node.children.each do |child| # iterate over <td>
|
25
|
+
if child.child.name == "b" # <td><b>
|
26
|
+
key = child.child.text
|
27
|
+
else
|
28
|
+
value = child.text
|
29
|
+
end
|
30
|
+
if key && value
|
31
|
+
case key
|
32
|
+
when "Description:": @description = value
|
33
|
+
when "Documents:": @size = value.to_i
|
34
|
+
when "Notification postmaster: ": @postmaster = value
|
35
|
+
end
|
36
|
+
key = value = nil
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
def to_s
|
42
|
+
"#{@name}: #{@description}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
class Container
|
46
|
+
def initialize adapter
|
47
|
+
@adapter = adapter
|
48
|
+
@entries = []
|
49
|
+
node = adapter.get "/info"
|
50
|
+
node.xpath("//tr[@class='title']").each do |node|
|
51
|
+
@entries << ContainerEntry.new(node)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
def each
|
55
|
+
@entries.each { |e| yield e }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
#
|
2
|
+
# Feature for datamapper
|
3
|
+
#
|
4
|
+
require 'rubygems'
|
5
|
+
require 'dm-core'
|
6
|
+
|
7
|
+
class Feature
|
8
|
+
include DataMapper::Resource
|
9
|
+
|
10
|
+
def self.xpathmap
|
11
|
+
{ :id => "@k:id", :milestone => "productcontext/milestone/name", :actors => "actor",
|
12
|
+
:requester => "actor[role='requester']/person/email",
|
13
|
+
:productmgr => "actor[role='productmanager']/person/email",
|
14
|
+
:projectmgr => "actor[role='projectmanager']/person/email",
|
15
|
+
:engmgr => "actor[role='teamleader']/person/email",
|
16
|
+
:developer => "actor[role='developer']/person/email"
|
17
|
+
}
|
18
|
+
end
|
19
|
+
def self.xmlnamespaces
|
20
|
+
{ "k" => "http://inttools.suse.de/sxkeeper/schema/keeper" }
|
21
|
+
end
|
22
|
+
|
23
|
+
property :id, Integer, :key => true
|
24
|
+
property :title, String
|
25
|
+
property :requester, String
|
26
|
+
property :productmgr, String
|
27
|
+
property :projectmgr, String
|
28
|
+
property :engmgr, String
|
29
|
+
property :developer, String
|
30
|
+
property :milestone, String
|
31
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'dm-core'
|
3
|
+
class Product
|
4
|
+
include DataMapper::Resource
|
5
|
+
property :id, Integer, :key => true
|
6
|
+
has 1, :productline
|
7
|
+
property :fatename, String
|
8
|
+
property :shortname, String
|
9
|
+
property :longname, String
|
10
|
+
property :bugzillaname, String
|
11
|
+
has n, :milestone
|
12
|
+
has n, :component
|
13
|
+
property :releasedate, Date
|
14
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'dm-core'
|
3
|
+
class Relation
|
4
|
+
include DataMapper::Resource
|
5
|
+
def self.xpathmap
|
6
|
+
{ :target => "@target", :sort_position => "@sortPosition" }
|
7
|
+
end
|
8
|
+
property :target, Integer, :key => true
|
9
|
+
property :sort_position, Integer
|
10
|
+
has 1, :parent, self
|
11
|
+
belongs_to :relation, :required => false
|
12
|
+
belongs_to :relationtree, :required => false
|
13
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'dm-core'
|
3
|
+
require 'dm-types'
|
4
|
+
|
5
|
+
require 'keeper/relation'
|
6
|
+
|
7
|
+
class Relationtree
|
8
|
+
include DataMapper::Resource
|
9
|
+
def self.xpathmap
|
10
|
+
{ :id => "@k:id" }
|
11
|
+
end
|
12
|
+
def self.xmlnamespaces
|
13
|
+
{ "k" => "http://inttools.suse.de/sxkeeper/schema/keeper" }
|
14
|
+
end
|
15
|
+
property :id, Integer, :key => true
|
16
|
+
property :title, String
|
17
|
+
property :description, String
|
18
|
+
# has n, :relations
|
19
|
+
property :relations, Csv
|
20
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Keeper
|
2
|
+
class Containers
|
3
|
+
|
4
|
+
def initialize(name, options)
|
5
|
+
super
|
6
|
+
require 'net/https'
|
7
|
+
require 'uri'
|
8
|
+
@uri = URI.parse(options[:url])
|
9
|
+
@username = options[:username]
|
10
|
+
@password = options[:password]
|
11
|
+
@connection = Net::HTTP.new( @uri.host, @uri.port )
|
12
|
+
# from http://www.rubyinside.com/nethttp-cheat-sheet-2940.html
|
13
|
+
@connection.use_ssl = true if @uri.scheme == "https"
|
14
|
+
@connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
def categories
|
19
|
+
@categories ||= values_of "list_categories", "/value"
|
20
|
+
end
|
21
|
+
def partners
|
22
|
+
@partners ||= values_of "list_partner", "/value"
|
23
|
+
end
|
24
|
+
def roles
|
25
|
+
@roles ||= values_of "list_roles"
|
26
|
+
end
|
27
|
+
def viewparts
|
28
|
+
@viewparts ||= values_of("list_view_parts")
|
29
|
+
end
|
30
|
+
def xmlfields
|
31
|
+
@xmlfields ||= values_of("list_xmlfields")
|
32
|
+
end
|
33
|
+
|
34
|
+
def get(path)
|
35
|
+
# $stderr.puts "Get #{@uri.path} + #{path}"
|
36
|
+
request = Net::HTTP::Get.new @uri.path + path
|
37
|
+
request.basic_auth @username, @password
|
38
|
+
response = @connection.request request
|
39
|
+
raise ArgumentError unless response
|
40
|
+
raise( RuntimeError, "Server returned #{response.code}" ) unless response.code.to_i == 200
|
41
|
+
# $stderr.puts "#{response.inspect}"
|
42
|
+
# response.each { |k,v| $stderr.puts "#{k}: #{v}" }
|
43
|
+
# $stderr.puts "Encoding >#{response['content-encoding']}<"
|
44
|
+
# check
|
45
|
+
# content-type: text/xml;charset=UTF-8
|
46
|
+
# content-encoding: gzip
|
47
|
+
raise TypeError unless response['content-type'] =~ /xml/
|
48
|
+
body = response.body
|
49
|
+
if response['content-encoding'] == "gzip"
|
50
|
+
require 'zlib'
|
51
|
+
require 'stringio'
|
52
|
+
# http://stackoverflow.com/questions/1361892/how-to-decompress-gzip-string-in-ruby
|
53
|
+
body = Zlib::GzipReader.new(StringIO.new(body)).read
|
54
|
+
end
|
55
|
+
Nokogiri::XML body
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
def values_of id, detail = nil
|
60
|
+
# STDERR.puts "----------------------\nextract_values #{id}\n#-------------------"
|
61
|
+
|
62
|
+
if detail
|
63
|
+
result = []
|
64
|
+
else
|
65
|
+
result = {}
|
66
|
+
end
|
67
|
+
|
68
|
+
valuelist.xpath("//valuelist[@id='#{id}']/item#{detail}").each do |node|
|
69
|
+
if detail
|
70
|
+
# node = <value>foo</value>
|
71
|
+
result << node.text
|
72
|
+
else
|
73
|
+
# node = <item><value>...</value><property name='...'>...</property>
|
74
|
+
# with multiple properties
|
75
|
+
props = {}
|
76
|
+
# puts "Node #{node}"
|
77
|
+
# puts "Value #{node.xpath('./value').text}"
|
78
|
+
node.xpath("./property").each do |prop|
|
79
|
+
# puts "prop #{prop}"
|
80
|
+
# puts "name #{prop['name']}"
|
81
|
+
# puts "content #{prop.content}"
|
82
|
+
props[prop['name']] = prop.text
|
83
|
+
end
|
84
|
+
# puts "Props #{props.inspect}"
|
85
|
+
result[node.xpath("./value").text] = props
|
86
|
+
end
|
87
|
+
end
|
88
|
+
raise RuntimeError if result.empty?
|
89
|
+
result
|
90
|
+
end
|
91
|
+
|
92
|
+
def valuelist
|
93
|
+
@valuelist ||= get "/valuelist"
|
94
|
+
raise RuntimeError if @valuelist.nil?
|
95
|
+
@valuelist
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
data/lib/property/xml.rb
ADDED
data/script/README.rdoc
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
= Sample scripts
|
2
|
+
|
3
|
+
keeper.novell.com also provides structure information about the
|
4
|
+
entities stored in its database.
|
5
|
+
|
6
|
+
== generate_class.rb
|
7
|
+
generate_class.rb is a sample Ruby script showing how to use this
|
8
|
+
information to generate Ruby classes for use with DataMapper.
|
9
|
+
|
10
|
+
== show-containers.rb
|
11
|
+
show-containers.rb displays information about the containers (aka
|
12
|
+
database tables) provides by keeper.novell.com
|
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/bin/env ruby
|
2
|
+
#
|
3
|
+
# Generate/update class definition for 'Feature'
|
4
|
+
|
5
|
+
require 'lib/dm-keeper-adapter'
|
6
|
+
|
7
|
+
adapter = DataMapper.setup(:default, :adapter => 'keeper',
|
8
|
+
:url => 'https://keeper.novell.com/sxkeeper')
|
9
|
+
|
10
|
+
adapter.update_featureclass "feature.rb"
|
@@ -0,0 +1,52 @@
|
|
1
|
+
#
|
2
|
+
# generate or update the class definition for 'Feature'
|
3
|
+
#
|
4
|
+
def update_featureclass path
|
5
|
+
partsrevision = 0
|
6
|
+
xmlfieldsrevision = 0
|
7
|
+
if File.exists? path
|
8
|
+
require path
|
9
|
+
partsrevision = Feature.partsrevision
|
10
|
+
xmlfieldsrevision = Feature.xmlfieldsrevision
|
11
|
+
end
|
12
|
+
current_partsrevision = revision_of('list_view_parts').to_i
|
13
|
+
current_xmlfieldsrevision = revision_of('list_xmlfields').to_i
|
14
|
+
if partsrevision < current_partsrevision || xmlfieldsrevision < current_xmlfieldsrevision
|
15
|
+
# class needs regeneration
|
16
|
+
File.open path, "w+" do |f|
|
17
|
+
f.puts("# File generated by dm-keeper-adapter on #{Time.new.asctime}")
|
18
|
+
f.puts("require 'rubygems'")
|
19
|
+
f.puts("require 'dm-core'")
|
20
|
+
f.puts("class Feature")
|
21
|
+
f.puts(" def Feature.partsrevision")
|
22
|
+
f.puts(" #{current_partsrevision}")
|
23
|
+
f.puts(" end")
|
24
|
+
f.puts(" def Feature.xmlfieldsrevision")
|
25
|
+
f.puts(" #{current_xmlfieldsrevision}")
|
26
|
+
f.puts(" end")
|
27
|
+
f.puts(" include DataMapper::Resource\n")
|
28
|
+
f.puts(" property :id, Integer, :key => true")
|
29
|
+
properties = []
|
30
|
+
viewparts.each_key do |k|
|
31
|
+
n = k.tr(" ","_").downcase
|
32
|
+
properties << n
|
33
|
+
f.puts(" property :#{n}, String")
|
34
|
+
end
|
35
|
+
xmlfields.each_key do |k|
|
36
|
+
n = k.tr(" ","_").downcase
|
37
|
+
next if n == '.'
|
38
|
+
next if properties.include? n
|
39
|
+
f.puts(" property :#{n}, String")
|
40
|
+
end
|
41
|
+
f.puts("end")
|
42
|
+
end
|
43
|
+
else
|
44
|
+
puts "#{path} is up to date"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def revision_of id
|
49
|
+
node = valuelist.xpath("//valuelist[@id='#{id}']").first
|
50
|
+
node.attribute_with_ns('revision', 'http://inttools.suse.de/sxkeeper/schema/keeper').value
|
51
|
+
end
|
52
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
#
|
2
|
+
# show-containers.rb
|
3
|
+
#
|
4
|
+
# Demo script to extract available 'containers' (aka database tables)
|
5
|
+
# from FATE (keeper.suse.de) using DataMapper
|
6
|
+
#
|
7
|
+
|
8
|
+
$: << File.join(File.dirname(__FILE__), "..", "dm-keeper-adapter", "lib")
|
9
|
+
|
10
|
+
require 'rubygems'
|
11
|
+
require 'dm-core'
|
12
|
+
require 'keeper/container'
|
13
|
+
|
14
|
+
DataMapper::Logger.new($stdout, :debug)
|
15
|
+
keeper = DataMapper.setup(:default, :adapter => 'keeper',
|
16
|
+
:url => 'https://keeper.novell.com/sxkeeper')
|
17
|
+
|
18
|
+
c = Keeper::Container.new keeper
|
19
|
+
c.each do |e|
|
20
|
+
puts e
|
21
|
+
end
|
22
|
+
|
data/test/helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'helper')
|
2
|
+
|
3
|
+
class Get_bug_test < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def test_get_bug
|
6
|
+
DataMapper::Logger.new(STDOUT, :debug)
|
7
|
+
keeper = DataMapper.setup(:default,
|
8
|
+
:adapter => 'keeper',
|
9
|
+
:url => 'https://keeper.novell.com/sxkeeper')
|
10
|
+
|
11
|
+
require 'keeper/feature'
|
12
|
+
DataMapper.finalize
|
13
|
+
|
14
|
+
feature = Feature.get(312814)
|
15
|
+
assert feature
|
16
|
+
puts "Feature #{feature.inspect}"
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'helper')
|
2
|
+
|
3
|
+
class Get_relationtree_test < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def test_get_relationtree
|
6
|
+
DataMapper::Logger.new(STDOUT, :debug)
|
7
|
+
keeper = DataMapper.setup(:default,
|
8
|
+
:adapter => 'keeper',
|
9
|
+
:url => 'https://keeper.novell.com/sxkeeper')
|
10
|
+
|
11
|
+
require 'keeper/relationtree'
|
12
|
+
DataMapper.finalize
|
13
|
+
|
14
|
+
tree = Relationtree.get(1137)
|
15
|
+
assert tree
|
16
|
+
puts "Relationtree #{tree.inspect}"
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dm-keeper-adapter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- "Klaus K\xC3\xA4mpf"
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-09-27 00:00:00 +02:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: inifile
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 13
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
- 4
|
33
|
+
- 1
|
34
|
+
version: 0.4.1
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: nokogiri
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 7
|
46
|
+
segments:
|
47
|
+
- 1
|
48
|
+
- 4
|
49
|
+
version: "1.4"
|
50
|
+
type: :runtime
|
51
|
+
version_requirements: *id002
|
52
|
+
description: Use it in Ruby applications to access FATE
|
53
|
+
email:
|
54
|
+
- kkaempf@suse.de
|
55
|
+
executables: []
|
56
|
+
|
57
|
+
extensions: []
|
58
|
+
|
59
|
+
extra_rdoc_files: []
|
60
|
+
|
61
|
+
files:
|
62
|
+
- .gitignore
|
63
|
+
- README.rdoc
|
64
|
+
- Rakefile
|
65
|
+
- dm-keeper-adapter.gemspec
|
66
|
+
- lib/dm-keeper-adapter.rb
|
67
|
+
- lib/dm-keeper-adapter/create.rb
|
68
|
+
- lib/dm-keeper-adapter/delete.rb
|
69
|
+
- lib/dm-keeper-adapter/misc.rb
|
70
|
+
- lib/dm-keeper-adapter/read.rb
|
71
|
+
- lib/dm-keeper-adapter/update.rb
|
72
|
+
- lib/dm-keeper-adapter/version.rb
|
73
|
+
- lib/keeper/container.rb
|
74
|
+
- lib/keeper/feature.rb
|
75
|
+
- lib/keeper/product.rb
|
76
|
+
- lib/keeper/productline.rb
|
77
|
+
- lib/keeper/relation.rb
|
78
|
+
- lib/keeper/relationtree.rb
|
79
|
+
- lib/keeper/valuelist.rb
|
80
|
+
- lib/property/xml.rb
|
81
|
+
- script/README.rdoc
|
82
|
+
- script/generate_class.rb
|
83
|
+
- script/genfeatureclass.rb
|
84
|
+
- script/show-containers.rb
|
85
|
+
- test/helper.rb
|
86
|
+
- test/test_get_feature.rb
|
87
|
+
- test/test_get_relationtree.rb
|
88
|
+
has_rdoc: true
|
89
|
+
homepage: ""
|
90
|
+
licenses: []
|
91
|
+
|
92
|
+
post_install_message:
|
93
|
+
rdoc_options: []
|
94
|
+
|
95
|
+
require_paths:
|
96
|
+
- lib
|
97
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
98
|
+
none: false
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
hash: 3
|
103
|
+
segments:
|
104
|
+
- 0
|
105
|
+
version: "0"
|
106
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
107
|
+
none: false
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
hash: 3
|
112
|
+
segments:
|
113
|
+
- 0
|
114
|
+
version: "0"
|
115
|
+
requirements: []
|
116
|
+
|
117
|
+
rubyforge_project: dm-keeper-adapter
|
118
|
+
rubygems_version: 1.5.2
|
119
|
+
signing_key:
|
120
|
+
specification_version: 3
|
121
|
+
summary: A datamapper adapter for FATE (aka keeper.novell.com)
|
122
|
+
test_files:
|
123
|
+
- test/helper.rb
|
124
|
+
- test/test_get_feature.rb
|
125
|
+
- test/test_get_relationtree.rb
|