solis 0.64.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.travis.yml +6 -0
  4. data/CODE_OF_CONDUCT.md +74 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +287 -0
  8. data/Rakefile +10 -0
  9. data/bin/console +14 -0
  10. data/bin/setup +8 -0
  11. data/examples/after_hooks.rb +24 -0
  12. data/examples/config.yml.template +15 -0
  13. data/examples/read_from_shacl.rb +22 -0
  14. data/examples/read_from_shacl_abv.rb +84 -0
  15. data/examples/read_from_sheet.rb +22 -0
  16. data/lib/solis/config_file.rb +91 -0
  17. data/lib/solis/error/cursor_error.rb +6 -0
  18. data/lib/solis/error/general_error.rb +6 -0
  19. data/lib/solis/error/invalid_attribute_error.rb +6 -0
  20. data/lib/solis/error/invalid_datatype_error.rb +6 -0
  21. data/lib/solis/error/not_found_error.rb +6 -0
  22. data/lib/solis/error/query_error.rb +6 -0
  23. data/lib/solis/error.rb +3 -0
  24. data/lib/solis/graph.rb +360 -0
  25. data/lib/solis/model.rb +565 -0
  26. data/lib/solis/options.rb +19 -0
  27. data/lib/solis/query/construct.rb +93 -0
  28. data/lib/solis/query/filter.rb +133 -0
  29. data/lib/solis/query/run.rb +97 -0
  30. data/lib/solis/query.rb +347 -0
  31. data/lib/solis/resource.rb +37 -0
  32. data/lib/solis/shape/data_types.rb +280 -0
  33. data/lib/solis/shape/reader/csv.rb +12 -0
  34. data/lib/solis/shape/reader/file.rb +16 -0
  35. data/lib/solis/shape/reader/sheet.rb +777 -0
  36. data/lib/solis/shape/reader/simple_sheets/sheet.rb +59 -0
  37. data/lib/solis/shape/reader/simple_sheets/worksheet.rb +173 -0
  38. data/lib/solis/shape/reader/simple_sheets.rb +40 -0
  39. data/lib/solis/shape.rb +189 -0
  40. data/lib/solis/sparql_adaptor.rb +318 -0
  41. data/lib/solis/store/sparql/client/query.rb +35 -0
  42. data/lib/solis/store/sparql/client.rb +41 -0
  43. data/lib/solis/version.rb +3 -0
  44. data/lib/solis.rb +13 -0
  45. data/solis.gemspec +50 -0
  46. metadata +304 -0
@@ -0,0 +1,59 @@
1
+ class Sheet
2
+ include Enumerable
3
+ def initialize(worksheet)
4
+ @worksheet = worksheet
5
+ @last_sync = Time.now
6
+ end
7
+
8
+ def title
9
+ @worksheet.title
10
+ end
11
+
12
+ def each
13
+ @worksheet.rows(skip=1).each do |row|
14
+ yield make_record_from_row(row)
15
+ end
16
+ end
17
+
18
+ def header
19
+ @header ||= @worksheet.rows[0].map{|m| m.downcase.gsub(' ','_').gsub('#', '_hash').gsub('%','_percent')}
20
+ end
21
+
22
+ def [](index)
23
+ row_as_record(@worksheet.rows(skip=0)[index])
24
+ end
25
+
26
+ def count
27
+ @worksheet.num_rows - 1
28
+ end
29
+
30
+ private
31
+ def sync
32
+ if (Time.now.to_i - @last_sync.to_i) > 600
33
+ @worksheet.synchronize
34
+ end
35
+ end
36
+
37
+ def row_as_record(row)
38
+ sync
39
+ result = []
40
+ return result if row.nil?
41
+ if row && row.is_a?(Array) && row.first.is_a?(Array)
42
+ result = row.map do |m|
43
+ m.is_a?(Array) ? make_record_from_row(m) : m
44
+ end
45
+ else
46
+ result = make_record_from_row(row)
47
+ end
48
+ result
49
+ end
50
+
51
+ def make_record_from_row(row)
52
+ return nil if row.nil?
53
+ result = {}
54
+ row.each_index do |i|
55
+ result.store(self.header[i], row[i])
56
+ end
57
+ result
58
+ end
59
+ end
@@ -0,0 +1,173 @@
1
+ class Worksheet
2
+ attr_reader :properties, :title, :spreadsheet
3
+
4
+ def initialize(spreadsheet, properties)
5
+ @spreadsheet = spreadsheet
6
+ set_properties(properties)
7
+ @cells = nil
8
+ @input_values = nil
9
+ @numeric_values = nil
10
+ @modified = Set.new
11
+ end
12
+
13
+ def sheet_id
14
+ @properties.sheet_id
15
+ end
16
+
17
+ def gid
18
+ sheet_id.to_s
19
+ end
20
+
21
+ def [](*args)
22
+ (row, col) = parse_cell_args(args)
23
+ cells[[row, col]] || ''
24
+ end
25
+
26
+ def rows(skip = 0)
27
+ nc = num_cols
28
+ result = ((1 + skip)..num_rows).map do |row|
29
+ (1..nc).map { |col| self[row, col] }.freeze
30
+ end
31
+ result.freeze
32
+ end
33
+
34
+ def num_rows
35
+ reload_cells unless @cells
36
+ # Memoizes it because this can be bottle-neck.
37
+ # https://github.com/gimite/google-drive-ruby/pull/49
38
+ @num_rows ||=
39
+ @input_values
40
+ .reject { |(_r, _c), v| v.empty? }
41
+ .map { |(r, _c), _v| r }
42
+ .max ||
43
+ 0
44
+ end
45
+
46
+ # Column number of the right-most non-empty column.
47
+ def num_cols
48
+ reload_cells unless @cells
49
+ # Memoizes it because this can be bottle-neck.
50
+ # https://github.com/gimite/google-drive-ruby/pull/49
51
+ @num_cols ||=
52
+ @input_values
53
+ .reject { |(_r, _c), v| v.empty? }
54
+ .map { |(_r, c), _v| c }
55
+ .max ||
56
+ 0
57
+ end
58
+
59
+ private
60
+ def parse_cell_args(args)
61
+ if args.size == 1 && args[0].is_a?(String)
62
+ cell_name_to_row_col(args[0])
63
+ elsif args.size == 2 && args[0].is_a?(Integer) && args[1].is_a?(Integer)
64
+ if args[0] >= 1 && args[1] >= 1
65
+ args
66
+ else
67
+ raise(
68
+ ArgumentError,
69
+ format(
70
+ 'Row/col must be >= 1 (1-origin), but are %d/%d',
71
+ args[0], args[1]
72
+ )
73
+ )
74
+ end
75
+ else
76
+ raise(
77
+ ArgumentError,
78
+ format(
79
+ "Arguments must be either one String or two Integer's, but are %p",
80
+ args
81
+ )
82
+ )
83
+ end
84
+ end
85
+
86
+ def cell_name_to_row_col(cell_name)
87
+ unless cell_name.is_a?(String)
88
+ raise(
89
+ ArgumentError, format('Cell name must be a string: %p', cell_name)
90
+ )
91
+ end
92
+ unless cell_name.upcase =~ /^([A-Z]+)(\d+)$/
93
+ raise(
94
+ ArgumentError,
95
+ format(
96
+ 'Cell name must be only letters followed by digits with no ' \
97
+ 'spaces in between: %p',
98
+ cell_name
99
+ )
100
+ )
101
+ end
102
+ col = 0
103
+ Regexp.last_match(1).each_byte do |b|
104
+ # 0x41: "A"
105
+ col = col * 26 + (b - 0x41 + 1)
106
+ end
107
+ row = Regexp.last_match(2).to_i
108
+ [row, col]
109
+ end
110
+
111
+ def cells
112
+ reload_cells unless @cells
113
+ @cells
114
+ end
115
+
116
+ def set_properties(properties)
117
+ @properties = properties
118
+ @title = @remote_title = properties.title
119
+ @index = properties.index
120
+ if properties.grid_properties.nil?
121
+ @max_rows = @max_cols = 0
122
+ else
123
+ @max_rows = properties.grid_properties.row_count
124
+ @max_cols = properties.grid_properties.column_count
125
+ end
126
+ @meta_modified = false
127
+ end
128
+
129
+ def reload_cells
130
+ response =
131
+ @spreadsheet.sheets_service.get_spreadsheet(
132
+ @spreadsheet.id,
133
+ ranges: "'%s'" % @remote_title,
134
+ fields: 'sheets.data.rowData.values(formattedValue,userEnteredValue,effectiveValue)'
135
+ )
136
+ update_cells_from_api_sheet(response.sheets[0])
137
+ end
138
+
139
+ def update_cells_from_api_sheet(api_sheet)
140
+ rows_data = api_sheet.data[0].row_data || []
141
+
142
+ @num_rows = rows_data.size
143
+ @num_cols = 0
144
+ @cells = {}
145
+ @input_values = {}
146
+ @numeric_values = {}
147
+
148
+ rows_data.each_with_index do |row_data, r|
149
+ next if !row_data.values
150
+ @num_cols = row_data.values.size if row_data.values.size > @num_cols
151
+ row_data.values.each_with_index do |cell_data, c|
152
+ k = [r + 1, c + 1]
153
+ @cells[k] = cell_data.formatted_value || ''
154
+ @input_values[k] = extended_value_to_str(cell_data.user_entered_value)
155
+ @numeric_values[k] =
156
+ cell_data.effective_value && cell_data.effective_value.number_value ?
157
+ cell_data.effective_value.number_value.to_f : nil
158
+ end
159
+ end
160
+
161
+ @modified.clear
162
+ end
163
+
164
+ def extended_value_to_str(extended_value)
165
+ return '' if !extended_value
166
+ value =
167
+ extended_value.number_value ||
168
+ extended_value.string_value ||
169
+ extended_value.bool_value ||
170
+ extended_value.formula_value
171
+ value.to_s
172
+ end
173
+ end
@@ -0,0 +1,40 @@
1
+ require 'google/apis/sheets_v4'
2
+ require_relative 'simple_sheets/sheet'
3
+ require_relative 'simple_sheets/worksheet'
4
+
5
+ class SimpleSheets
6
+ attr_accessor :key
7
+ attr_reader :sheets_service, :id
8
+
9
+ def initialize(key, spreadsheet_id)
10
+ @id = spreadsheet_id
11
+ @key = key
12
+ @sheets_service = Google::Apis::SheetsV4::SheetsService.new
13
+ @sheets_service.key = @key
14
+ end
15
+
16
+ def worksheets
17
+ spreadsheet_api = @sheets_service.get_spreadsheet(@id, fields: 'sheets.properties')
18
+ spreadsheet_api.sheets.map { |s| Worksheet.new(self, s.properties) }
19
+ #TODO: catch not found
20
+ rescue Google::Apis::ClientError => e
21
+ case e.status_code
22
+ when 404
23
+ raise "Sheet with id #{@id} NOT FOUND"
24
+ else
25
+ raise "An error occured reading sheet with id #{@id}. HTTP status code = #{e.status_code}, reason = '#{e.header.reason_phrase}'"
26
+ end
27
+ rescue Exception => e
28
+ raise e
29
+ end
30
+
31
+ def worksheet_by_title(title)
32
+ worksheets.find { |ws| ws.title == title }
33
+ end
34
+
35
+ def worksheet_by_sheet_id(sheet_id)
36
+ sheet_id = sheet_id.to_i
37
+ worksheets.find { |ws| ws.sheet_id == sheet_id }
38
+ end
39
+
40
+ end
@@ -0,0 +1,189 @@
1
+ require_relative 'shape/reader/file'
2
+ require_relative 'shape/reader/sheet'
3
+ require_relative 'shape/data_types'
4
+
5
+ module Solis
6
+ module Shape
7
+ def self.from_graph(graph)
8
+ class << self
9
+ def parse_graph(graph)
10
+ shapes = {}
11
+ #puts query.execute(graph).to_csv
12
+
13
+ query.execute(graph) do |solution|
14
+ parse_solution(shapes, solution)
15
+ end
16
+
17
+ # shapes = add_missing_attributes(shapes)
18
+ shapes
19
+ rescue Solis::Error::GeneralError => e
20
+ raise "Unable to parse shapes: #{e.message}"
21
+ end
22
+
23
+ def lookup_datatype(datatype, node)
24
+ if datatype =~ /^http:\/\/www.w3.org\/2001\/XMLSchema#/
25
+ case datatype
26
+ when /^http:\/\/www.w3.org\/2001\/XMLSchema#anyURI/
27
+ :string
28
+ when /http:\/\/www.w3.org\/2001\/XMLSchema#duration/
29
+ :duration
30
+ when /http:\/\/www.w3.org\/2001\/XMLSchema#integer/
31
+ :integer
32
+ when /http:\/\/www.w3.org\/2001\/XMLSchema#int/
33
+ :integer
34
+ when /http:\/\/www.w3.org\/2001\/XMLSchema#dateTime/
35
+ :datetime
36
+ when /http:\/\/www.w3.org\/2001\/XMLSchema#date/
37
+ :date
38
+ when /http:\/\/www.w3.org\/2001\/XMLSchema#time/
39
+ :time
40
+ when /http:\/\/www.w3.org\/2001\/XMLSchema#gYear/
41
+ :year
42
+ when /http:\/\/www.w3.org\/2001\/XMLSchema#string/
43
+ :string
44
+ when /http:\/\/www.w3.org\/2001\/XMLSchema#boolean/
45
+ :boolean
46
+ when /http:\/\/www.w3.org\/2001\/XMLSchema#float/
47
+ :float
48
+ when /http:\/\/www.w3.org\/2001\/XMLSchema#double/
49
+ :double
50
+ else
51
+ #puts datatype.split('#').last.to_sym
52
+ :string
53
+ end
54
+ elsif datatype =~ /http:\/\/schema.org\//
55
+ case datatype
56
+ when /http:\/\/schema.org\/temporalCoverage/
57
+ :temporal_coverage
58
+ else
59
+ #puts datatype.split('#').last.to_sym
60
+ :string
61
+ end
62
+ elsif datatype =~ /http:\/\/www.w3.org\/2006\/time/
63
+ case datatype
64
+ when /http:\/\/www.w3.org\/2006\/time#DateTimeInterval/
65
+ :datetime_interval
66
+ else
67
+ #puts datatype.split('#').last.to_sym
68
+ :string
69
+ end
70
+ elsif datatype.nil? && node.is_a?(RDF::URI)
71
+ node.value.split('/').last.gsub(/Shape$/, '').to_sym
72
+ elsif datatype =~ /^http:\/\/www.w3.org\/1999\/02\/22-rdf-syntax-ns/
73
+ case datatype
74
+ when /http:\/\/www.w3.org\/1999\/02\/22-rdf-syntax-ns#langString/
75
+ :lang_string
76
+ when /http:\/\/www.w3.org\/1999\/02\/22-rdf-syntax-ns#JSON/
77
+ :json
78
+ else
79
+ :string
80
+ end
81
+ else
82
+ :string
83
+ end
84
+ end
85
+
86
+ def parse_solution(shapes, solution)
87
+ shape_rdf = solution.targetClass
88
+ shape_node = solution.targetNode if solution.bound?(:targetNode)
89
+ shape_name = solution.className.value
90
+ attribute_name = solution.attributeName.value if solution.bound?(:attributeName)
91
+ comment = solution.comment.value if solution.bound?(:comment)
92
+ attribute_rdf = solution.attributePath.value if solution.bound?(:attributePath)
93
+ attribute_datatype_rdf = solution.attributeDatatype.value if solution.bound?(:attributeDatatype)
94
+ attribute_min_count = solution.bound?(:attributeMinCount) ? solution.attributeMinCount.value.to_i : 0
95
+ attribute_max_count = solution.bound?(:attributeMaxCount) ? solution.attributeMaxCount.value.to_i : nil
96
+ attribute_node_kind = solution.attributeNodeKind if solution.bound?(:attributeNodeKind)
97
+ attribute_node = solution.attributeNode if solution.bound?(:attributeNode)
98
+ attribute_class = solution.attributeClass if solution.bound?(:attributeClass)
99
+ attribute_comment = solution.attributeComment if solution.bound?(:attributeComment)
100
+ # if solution.bound?(:attributeOr)
101
+ # pp solution
102
+ # end
103
+
104
+ attribute_node = attribute_class if attribute_name && attribute_datatype_rdf.nil? && attribute_node.nil?
105
+
106
+ shape = shapes.key?(shape_name) ? shapes[shape_name] : { target_class: nil, target_node: nil, attributes: {} }
107
+ shape[:target_class] = shape_rdf
108
+ shape[:target_node] = shape_node
109
+ shape[:comment] = comment
110
+
111
+ shape[:attributes][attribute_name] = {
112
+ path: attribute_rdf,
113
+ datatype_rdf: attribute_datatype_rdf,
114
+ datatype: lookup_datatype(attribute_datatype_rdf, attribute_node),
115
+ mincount: attribute_min_count,
116
+ maxcount: attribute_max_count,
117
+ node: attribute_node,
118
+ node_kind: attribute_node_kind,
119
+ class: attribute_class,
120
+ comment: attribute_comment
121
+ }
122
+
123
+ shape[:attributes].delete_if { |k, _| k.nil? }
124
+ shapes[shape_name] = shape
125
+ end
126
+
127
+ def query
128
+ SPARQL.parse %(
129
+ PREFIX sh: <http://www.w3.org/ns/shacl#>
130
+ PREFIX rdfv: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
131
+
132
+ SELECT ?targetClass ?targetNode ?comment ?className ?attributePath ?attributeName ?attributeDatatype
133
+ ?attributeMinCount ?attributeMaxCount ?attributeOr ?attributeClass
134
+ ?attributeNode ?attributeNodeKind ?attributeComment ?o
135
+ WHERE {
136
+
137
+ ?s a sh:NodeShape;
138
+ sh:targetClass ?targetClass ;
139
+ sh:node ?targetNode ;
140
+ sh:description ?comment ;
141
+ sh:name ?className .
142
+ OPTIONAL{ ?s sh:property ?attributes .
143
+ ?attributes sh:name ?attributeName ;
144
+ sh:path ?attributePath ;
145
+ OPTIONAL{ ?attributes sh:datatype ?attributeDatatype } .
146
+ OPTIONAL{ ?attributes sh:minCount ?attributeMinCount } .
147
+ OPTIONAL{ ?attributes sh:maxCount ?attributeMaxCount } .
148
+ OPTIONAL{ ?attributes sh:or ?attributeOr } .
149
+ OPTIONAL{ ?attributes sh:class ?attributeClass } .
150
+ OPTIONAL{ ?attributes sh:nodeKind ?attributeNodeKind } .
151
+ OPTIONAL{ ?attributes sh:node ?attributeNode } .
152
+ OPTIONAL{ ?attributes sh:description ?attributeComment } .
153
+ }.
154
+ }
155
+ )
156
+ end
157
+
158
+ def add_missing_attributes(shapes)
159
+ shapes.each do |shape|
160
+ if shape[1].is_a?(Hash)
161
+ graph_name = shape[1][:target_class].value.split(shape[1][:target_class].path).first
162
+ attributes = shape[1][:attributes]
163
+ unless attributes.key?('id')
164
+ if shape[1][:target_class] == shape[1][:target_node]
165
+ attributes['id'] = {
166
+ "path": "#{graph_name}/id",
167
+ "datatype_rdf": "http://www.w3.org/2001/XMLSchema#string",
168
+ "datatype": "string",
169
+ "mincount": 1,
170
+ "maxcount": 1,
171
+ "node": nil,
172
+ "node_kind": nil,
173
+ "class": nil,
174
+ "comment": RDF::Literal.new("UUID", datatype: RDF::XSD.string)
175
+ }
176
+
177
+ shape[1][:attributes] = attributes
178
+ end
179
+ end
180
+ end
181
+ end
182
+ shapes
183
+ end
184
+ end
185
+
186
+ parse_graph(graph)
187
+ end
188
+ end
189
+ end