solis 0.64.0

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.
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