sheety 0.1.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.
- checksums.yaml +7 -0
- data/lib/sheety.rb +49 -0
- data/lib/sheety/api.rb +99 -0
- data/lib/sheety/cell.rb +92 -0
- data/lib/sheety/children.rb +115 -0
- data/lib/sheety/feed.rb +86 -0
- data/lib/sheety/row.rb +87 -0
- data/lib/sheety/spreadsheet.rb +87 -0
- data/lib/sheety/worksheet.rb +94 -0
- metadata +85 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4d5c92ed5ae6bea6927c5e677aefe279464cf55f
|
4
|
+
data.tar.gz: d8dccac10a9bf260a14ab11f10f00c5f0b786297
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 47e44ab93c4dc032e79e8512622280da7aa9a233ef8387e3a425fc904c305d8508702ec0583ba957ea173ac837eb7f7a8fc0b6e539d6d5b13178a909e8a481e0
|
7
|
+
data.tar.gz: f0cc54c3d2a1c611c960af5b72d8ca0714d3c082bba4934d1a42a80a11bbf637c1d27d3ccf0dec1b2a947de8f9a77262a6a0797ad8fbce34625b0c1b863a29fb
|
data/lib/sheety.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
module Sheety
|
2
|
+
NEWLINE = "\n"
|
3
|
+
|
4
|
+
def self.length_s(val)
|
5
|
+
if val.respond_to?(:length) then val.length.to_s else "??" end
|
6
|
+
end
|
7
|
+
|
8
|
+
end
|
9
|
+
|
10
|
+
require_relative 'sheety/api'
|
11
|
+
require_relative 'sheety/cell'
|
12
|
+
require_relative 'sheety/row'
|
13
|
+
require_relative 'sheety/worksheet'
|
14
|
+
require_relative 'sheety/spreadsheet'
|
15
|
+
|
16
|
+
module Sheety
|
17
|
+
## Convenience Methods
|
18
|
+
def self.auth
|
19
|
+
return Api.inst.auth
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.sheets(force_refetch=false)
|
23
|
+
return Api.inst.sheets(force_refetch)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.sheets_where(constraints)
|
27
|
+
return Api.inst.sheets_where(constraints)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.find_sheet(constraints)
|
31
|
+
return Api.inst.find_sheet(constraints)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.sheets_except(constraints)
|
35
|
+
return Api.inst.sheets_except(constraints)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.sheets_except_any(constraints)
|
39
|
+
return Api.inst.sheets_except_any(constraints)
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.list_sheets
|
43
|
+
sheets.each_with_index do |sheet, index|
|
44
|
+
puts "Sheet #{index} - #{sheet}"
|
45
|
+
end
|
46
|
+
|
47
|
+
return nil
|
48
|
+
end
|
49
|
+
end
|
data/lib/sheety/api.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
require "google/api_client"
|
2
|
+
require 'xmlsimple'
|
3
|
+
|
4
|
+
require_relative 'children'
|
5
|
+
require_relative 'spreadsheet'
|
6
|
+
|
7
|
+
module Sheety
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
class Api
|
12
|
+
include Sheety::Children
|
13
|
+
|
14
|
+
URI_LIST = 'https://spreadsheets.google.com/feeds/spreadsheets/private/full'
|
15
|
+
URI_AUTH = 'https://www.googleapis.com/oauth2/v3/token'
|
16
|
+
|
17
|
+
def link(key) # for compat with ChildBearing
|
18
|
+
return key
|
19
|
+
end
|
20
|
+
|
21
|
+
atom_children :sheets, klass: Sheety::Spreadsheet, link: URI_LIST
|
22
|
+
|
23
|
+
def self.inst
|
24
|
+
if @@instance.nil?
|
25
|
+
@@instance = Sheety::Api.new
|
26
|
+
end
|
27
|
+
return @@instance
|
28
|
+
end
|
29
|
+
|
30
|
+
def auth(force=false)
|
31
|
+
if @access_token.blank? || force
|
32
|
+
data = { :grant_type => 'urn:ietf:params:oauth:grant-type:jwt-bearer', :assertion => @auth.to_jwt.to_s }
|
33
|
+
resp = HTTParty.post(URI_AUTH, { :body => data })
|
34
|
+
if Net::HTTPOK === resp.response
|
35
|
+
@access_token = resp['access_token']
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
return (@access_token ? self : nil)
|
40
|
+
end
|
41
|
+
|
42
|
+
def get_feed(uri)
|
43
|
+
return parse_response(HTTParty.get(uri, headers: get_headers))
|
44
|
+
end
|
45
|
+
|
46
|
+
def post_feed(uri, data)
|
47
|
+
return parse_response(HTTParty.post(uri, body: data, headers: post_headers))
|
48
|
+
end
|
49
|
+
|
50
|
+
def put_feed(uri, data)
|
51
|
+
return parse_response(HTTParty.put(uri, body: data, headers: put_headers))
|
52
|
+
end
|
53
|
+
|
54
|
+
def delete_feed(uri)
|
55
|
+
return parse_response(HTTParty.delete(uri, headers: delete_headers))
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def parse_response(resp)
|
61
|
+
begin
|
62
|
+
return XmlSimple.xml_in(resp.body, { 'KeyAttr' => 'name', 'keepnamespace' => true })
|
63
|
+
rescue
|
64
|
+
return resp
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def get_headers
|
69
|
+
return auth_headers
|
70
|
+
end
|
71
|
+
|
72
|
+
def post_headers
|
73
|
+
return put_headers
|
74
|
+
end
|
75
|
+
|
76
|
+
def put_headers
|
77
|
+
return auth_headers.merge('Content-Type' => 'application/atom+xml')
|
78
|
+
end
|
79
|
+
|
80
|
+
def delete_headers
|
81
|
+
return auth_headers
|
82
|
+
end
|
83
|
+
|
84
|
+
def auth_headers
|
85
|
+
return { 'Authorization' => "Bearer #{@access_token}" }
|
86
|
+
end
|
87
|
+
|
88
|
+
@@instance = nil
|
89
|
+
|
90
|
+
def initialize
|
91
|
+
@client = Google::APIClient.new(:application_name => 'Sheety', :application_version => "0.1.0")
|
92
|
+
@client.authorization= :google_app_default
|
93
|
+
@auth = @client.authorization
|
94
|
+
@auth.scope = 'https://spreadsheets.google.com/feeds'
|
95
|
+
|
96
|
+
return self
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
data/lib/sheety/cell.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
require_relative 'feed'
|
2
|
+
|
3
|
+
class Sheety::Cell < Sheety::Feed
|
4
|
+
COL_LETTERS = [nil, *('A'..'Z')]
|
5
|
+
|
6
|
+
attr_reader :row, :col, :input_value, :numeric_value, :display_value
|
7
|
+
|
8
|
+
def parse(entry)
|
9
|
+
super(entry)
|
10
|
+
cell = entry['gs:cell'][0]
|
11
|
+
|
12
|
+
@row = cell['row'].to_i
|
13
|
+
@col = cell['col'].to_i
|
14
|
+
@input_value = cell['inputValue']
|
15
|
+
@numeric_value = cell['numericValue'].to_f
|
16
|
+
@display_value = cell['content']
|
17
|
+
end
|
18
|
+
|
19
|
+
def value=(new_value)
|
20
|
+
if new_value != @input_value
|
21
|
+
@input_value = new_value
|
22
|
+
@numeric_value = nil
|
23
|
+
@display_value = nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def col=(c)
|
28
|
+
if @col.nil?
|
29
|
+
@col = c
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def row=(r)
|
34
|
+
if @row.nil?
|
35
|
+
@row = r
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def rc
|
40
|
+
return "R#{row}C#{col}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def as_xml
|
44
|
+
return [
|
45
|
+
"<entry xmlns=\"http://www.w3.org/2005/Atom\" xmlns:gs=\"http://schemas.google.com/spreadsheets/2006\">",
|
46
|
+
"<id>#{
|
47
|
+
if @id then
|
48
|
+
@id
|
49
|
+
else
|
50
|
+
@parent.link(Sheety::Worksheet::LINK_CELLS) + "/#{rc}"
|
51
|
+
end}</id>",
|
52
|
+
"<link rel=\"#{Sheety::Feed::LINK_EDIT}\" type=\"application/atom+xml\" href=\"#{
|
53
|
+
if link(LINK_EDIT) then
|
54
|
+
link(LINK_EDIT)
|
55
|
+
else
|
56
|
+
@parent.link(Sheety::Worksheet::LINK_CELLS) + "/#{rc}"
|
57
|
+
end}\"/>",
|
58
|
+
"<gs:cell row=\"#{row}\" col=\"#{col}\" inputValue=\"#{input_value}\"/>",
|
59
|
+
"</entry>",
|
60
|
+
].join(Sheety::NEWLINE)
|
61
|
+
end
|
62
|
+
|
63
|
+
def as_batch_xml
|
64
|
+
return [
|
65
|
+
"<entry>",
|
66
|
+
"<batch:id>#{title}</batch:id>",
|
67
|
+
"<batch:operation type=\"update\" />",
|
68
|
+
"<id>#{id}</id>",
|
69
|
+
"<link rel=\"#{LINK_EDIT}\" type=\"application/atom+xml\"",
|
70
|
+
"href=\"#{link(LINK_EDIT)}\"/>",
|
71
|
+
"<gs:cell row=\"#{@row}\" col=\"#{@col}\" inputValue=\"#{@input_value}\"/>",
|
72
|
+
"</entry>",
|
73
|
+
].join(Sheety::NEWLINE)
|
74
|
+
end
|
75
|
+
|
76
|
+
def save
|
77
|
+
uri = link(LINK_EDIT)
|
78
|
+
if uri
|
79
|
+
return Sheety::Api.inst.put_feed(uri, as_xml)
|
80
|
+
else
|
81
|
+
return Sheety::Api.inst.post_feed(@parent.link(Sheety::Worksheet::LINK_CELLS), as_xml)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def to_s
|
86
|
+
"<#{self.class}::#{object_id} #{rc} input='#{input_value}' numeric='#{numeric_value}' display='#{display_value}'>"
|
87
|
+
end
|
88
|
+
|
89
|
+
def inspect
|
90
|
+
to_s
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module Sheety::Children
|
2
|
+
def self.included base
|
3
|
+
base.extend ClassMethods
|
4
|
+
end
|
5
|
+
|
6
|
+
def _passes_constraint(i_val, c_val)
|
7
|
+
case c_val # Good Read: http://ruby.about.com/od/beginningruby/qt/On-Case-And-Class.htm
|
8
|
+
when Range
|
9
|
+
c_val.include? i_val
|
10
|
+
when Array
|
11
|
+
c_val.include? i_val
|
12
|
+
when Regexp
|
13
|
+
c_val =~ i_val
|
14
|
+
when String
|
15
|
+
c_val == i_val.to_s
|
16
|
+
else
|
17
|
+
c_val == i_val
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def _get_i_val(item, c_key, accessor=nil)
|
22
|
+
if accessor && item.respond_to?(accessor)
|
23
|
+
return item.send(accessor, c_key)
|
24
|
+
else
|
25
|
+
return item.try(c_key)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
module ClassMethods
|
30
|
+
def atom_children(symbol, options)
|
31
|
+
|
32
|
+
if options.blank?
|
33
|
+
raise ArgumentError.new("blank options #{options} are not valid options for bear_children")
|
34
|
+
end
|
35
|
+
|
36
|
+
if options[:klass].blank? || options[:klass].class != Class
|
37
|
+
raise ArgumentError.new("#{options} must have a :klass that is a Class, not a #{options[:klass].class}")
|
38
|
+
end
|
39
|
+
|
40
|
+
if options[:link].blank?
|
41
|
+
raise ArgumentError.new("#{options} must have a non-blank :link")
|
42
|
+
end
|
43
|
+
|
44
|
+
unless method_defined?(:link)
|
45
|
+
raise TypeError.new("#{self} must respond to :link to bear_children")
|
46
|
+
end
|
47
|
+
|
48
|
+
if options[:merge_links] && method_defined?(:add_links) == false
|
49
|
+
raise TypeError.new("#{self} must respond to :add_links to bear_children with mrege_links=true")
|
50
|
+
end
|
51
|
+
|
52
|
+
plural = symbol.to_s
|
53
|
+
singular = plural.singularize
|
54
|
+
|
55
|
+
inst_var_sym = :"@#{symbol}"
|
56
|
+
|
57
|
+
get_method = :"#{plural}"
|
58
|
+
new_method = :"new_#{singular}"
|
59
|
+
enum_method = :"_enumerate_#{plural}_by_method"
|
60
|
+
where_method = :"#{plural}_where"
|
61
|
+
except_method = :"#{plural}_except"
|
62
|
+
except_any_method = :"#{plural}_except_any"
|
63
|
+
find_first_method = :"find_#{singular}"
|
64
|
+
|
65
|
+
define_method(new_method) do |entry=nil|
|
66
|
+
options[:klass].new(self, entry)
|
67
|
+
end
|
68
|
+
|
69
|
+
define_method(get_method) do |force_refetch=false|
|
70
|
+
if instance_variable_get(inst_var_sym).nil? || force_refetch
|
71
|
+
list = Sheety::Api.inst.get_feed(link(options[:link])) # sort of a cyclic dependency, suboptimal
|
72
|
+
|
73
|
+
# TODO: Create a ListFeed Object so the links we get here don't need to be worried about collisions on link ids
|
74
|
+
add_links(list['link']) if !list['link'].blank? && options[:merge_links]
|
75
|
+
|
76
|
+
list = (list['entry'] || []).map do |entry|
|
77
|
+
method(new_method).call(entry)
|
78
|
+
end
|
79
|
+
|
80
|
+
instance_variable_set(inst_var_sym, list)
|
81
|
+
end
|
82
|
+
|
83
|
+
return instance_variable_get(inst_var_sym)
|
84
|
+
end
|
85
|
+
|
86
|
+
define_method(enum_method) do |constraints, method|
|
87
|
+
return method(get_method).call().send(method) do |item|
|
88
|
+
constraints.all? do |constraint|
|
89
|
+
_passes_constraint(_get_i_val(item, constraint[0], options[:accessor]), constraint[1])
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
define_method(where_method) do |constraints|
|
95
|
+
return method(enum_method).call(constraints, :find_all)
|
96
|
+
end
|
97
|
+
|
98
|
+
define_method(find_first_method) do |constraints|
|
99
|
+
return method(enum_method).call(constraints, :detect)
|
100
|
+
end
|
101
|
+
|
102
|
+
define_method(except_method) do |constraints|
|
103
|
+
return method(enum_method).call(constraints, :reject)
|
104
|
+
end
|
105
|
+
|
106
|
+
define_method(except_any_method) do |constraints|
|
107
|
+
return method(get_method).call().reject do |item|
|
108
|
+
constraints.any? do |constraint|
|
109
|
+
_passes_constraint(_get_i_val(item, constraint[0], options[:accessor]), constraint[1])
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
data/lib/sheety/feed.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
require_relative 'children'
|
2
|
+
|
3
|
+
class Sheety::Feed
|
4
|
+
include Sheety::Children
|
5
|
+
|
6
|
+
LINK_SELF = 'self'
|
7
|
+
LINK_EDIT = 'edit'
|
8
|
+
LINK_ALT = 'alternate'
|
9
|
+
|
10
|
+
attr_reader :id, :content, :updated, :parent
|
11
|
+
|
12
|
+
attr_accessor :title
|
13
|
+
|
14
|
+
def initialize(parent, entry=nil)
|
15
|
+
@parent = parent
|
16
|
+
@links = {}
|
17
|
+
unless entry.nil?
|
18
|
+
parse(entry)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def parse(entry)
|
23
|
+
return if entry.blank? || entry.length == 0
|
24
|
+
@title = Sheety::Feed.deref(entry, 'title', 0, 'content')
|
25
|
+
@id = Sheety::Feed.deref(entry, 'id', 0)
|
26
|
+
@content = Sheety::Feed.deref(entry, 'content', 'content')
|
27
|
+
@updated = Sheety::Feed.deref(entry, 'updated', 0)
|
28
|
+
@updated = DateTime.iso8601(@updated) if !@updated.blank?
|
29
|
+
add_links(Sheety::Feed.deref(entry ,'link'))
|
30
|
+
end
|
31
|
+
|
32
|
+
def link(key)
|
33
|
+
@links[key]
|
34
|
+
end
|
35
|
+
|
36
|
+
def as_xml
|
37
|
+
return "<entry></entry>"
|
38
|
+
end
|
39
|
+
|
40
|
+
protected
|
41
|
+
|
42
|
+
def add_links(links)
|
43
|
+
return if links.blank?
|
44
|
+
# Conflict prevention is here to preserve original values parsed from the entities.
|
45
|
+
# This mainly comes into play with the List Feed (a.k.a.: Rows) because we need the
|
46
|
+
# ability to add new ones.
|
47
|
+
links.each { |link_obj| @links[link_obj['rel']] = link_obj['href'] }
|
48
|
+
end
|
49
|
+
|
50
|
+
def link_edit
|
51
|
+
link(LINK_EDIT)
|
52
|
+
end
|
53
|
+
|
54
|
+
def link_add
|
55
|
+
parent.link(parent.class::LINK_ADD)
|
56
|
+
end
|
57
|
+
|
58
|
+
def save
|
59
|
+
result = if link_edit
|
60
|
+
Sheety::Api.inst.put_feed(link_edit, as_xml)
|
61
|
+
else
|
62
|
+
Sheety::Api.inst.post_feed(link_add, as_xml)
|
63
|
+
end
|
64
|
+
parse(result)
|
65
|
+
return result
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_s
|
69
|
+
"<#{self.class}::#{object_id}>"
|
70
|
+
end
|
71
|
+
|
72
|
+
def inspect
|
73
|
+
to_s
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def self.deref(data, *keys)
|
79
|
+
found = data
|
80
|
+
keys.each do |k|
|
81
|
+
next if found.blank?
|
82
|
+
found = found[k]
|
83
|
+
end
|
84
|
+
return found
|
85
|
+
end
|
86
|
+
end
|
data/lib/sheety/row.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require_relative 'feed'
|
2
|
+
|
3
|
+
class Sheety::Row < Sheety::Feed
|
4
|
+
def initialize(parent, entry=nil)
|
5
|
+
@attrs = {}
|
6
|
+
super(parent, entry)
|
7
|
+
end
|
8
|
+
|
9
|
+
def parse(entry)
|
10
|
+
super(entry)
|
11
|
+
entry.keys.each do |k|
|
12
|
+
if /\Agsx:/i =~ k
|
13
|
+
@attrs[k] = entry[k][0]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def value(key)
|
19
|
+
return @attrs['gsx:' + key.to_s]
|
20
|
+
end
|
21
|
+
|
22
|
+
alias_method :[], :value
|
23
|
+
|
24
|
+
def []=(key, val)
|
25
|
+
@attrs['gsx:' + key.to_s]=val
|
26
|
+
end
|
27
|
+
|
28
|
+
def put(hash)
|
29
|
+
hash.each { |kv| self[Sheety::Row.normalize_key(kv[0])] = kv[1] }
|
30
|
+
end
|
31
|
+
|
32
|
+
def as_xml
|
33
|
+
return [
|
34
|
+
'<entry xmlns="http://www.w3.org/2005/Atom" xmlns:gsx="http://schemas.google.com/spreadsheets/2006/extended">',
|
35
|
+
if !@id then
|
36
|
+
''
|
37
|
+
else
|
38
|
+
"<id>#{@id}</id>"
|
39
|
+
end,
|
40
|
+
if !@id then
|
41
|
+
''
|
42
|
+
else
|
43
|
+
"<updated>#{DateTime.now}</updated>"
|
44
|
+
end,
|
45
|
+
if !@id then
|
46
|
+
''
|
47
|
+
else
|
48
|
+
'<category scheme="http://schemas.google.com/spreadsheets/2006" term="http://schemas.google.com/spreadsheets/2006#list"/>'
|
49
|
+
end,
|
50
|
+
if !@id then
|
51
|
+
''
|
52
|
+
else
|
53
|
+
"<link rel=\"edit\" type=\"application/atom+xml\" href=\"#{@links[LINK_EDIT]}\" />"
|
54
|
+
end,
|
55
|
+
*(@attrs.map { |pair| "<#{pair[0]}>#{pair[1]}</#{pair[0]}>" }),
|
56
|
+
'</entry>',
|
57
|
+
].join(Sheety::NEWLINE)
|
58
|
+
end
|
59
|
+
|
60
|
+
def save
|
61
|
+
uri = link(LINK_EDIT)
|
62
|
+
if uri
|
63
|
+
return Sheety::Api.inst.put_feed(uri, as_xml)
|
64
|
+
else
|
65
|
+
return Sheety::Api.inst.post_feed(@parent.link(Sheety::Worksheet::LINK_POST), as_xml)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
alias_method :update, :save
|
70
|
+
|
71
|
+
# ProTip: Delete Rows in Reverse, otherwise the shift of deletion will cause unexpected behaviors
|
72
|
+
def delete
|
73
|
+
return Sheety::Api.inst.delete_feed(link(LINK_EDIT))
|
74
|
+
end
|
75
|
+
|
76
|
+
def to_s
|
77
|
+
"<#{self.class}::#{object_id} #{(@attrs.map { |kv| "#{kv[0].gsub('gsx:', '')}:#{kv[1]}" }).join(', ')}>"
|
78
|
+
end
|
79
|
+
|
80
|
+
def inspect
|
81
|
+
to_s
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.normalize_key(key)
|
85
|
+
key.to_s.gsub(/[^a-zA-Z0-9]/, '')
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require_relative 'feed'
|
2
|
+
require_relative 'cell'
|
3
|
+
require_relative 'row'
|
4
|
+
require_relative 'worksheet'
|
5
|
+
|
6
|
+
class Sheety::Spreadsheet < Sheety::Feed
|
7
|
+
LINK_WORKSHEETS = 'http://schemas.google.com/spreadsheets/2006#worksheetsfeed'
|
8
|
+
LINK_ADD = LINK_WORKSHEETS
|
9
|
+
|
10
|
+
atom_children :worksheets, klass: Sheety::Worksheet, link: LINK_WORKSHEETS
|
11
|
+
|
12
|
+
attr_reader :author_name, :author_email
|
13
|
+
|
14
|
+
def parse(entry)
|
15
|
+
super(entry)
|
16
|
+
@author_name = entry['author'][0]['name'][0]
|
17
|
+
@author_email = entry['author'][0]['email'][0]
|
18
|
+
end
|
19
|
+
|
20
|
+
def export_to_worksheet(data, options={})
|
21
|
+
if data.respond_to?(:as_json)
|
22
|
+
data = data.as_json
|
23
|
+
end
|
24
|
+
|
25
|
+
unless !data.blank? && Array === data
|
26
|
+
raise ArgumentError.new("data must be a non-blank array (or convertible by :as_json), got #{data}")
|
27
|
+
end
|
28
|
+
|
29
|
+
options = {title: 'worksheet', timestamp: true}.merge(options)
|
30
|
+
|
31
|
+
headers = data[0].keys
|
32
|
+
|
33
|
+
worksheet = new_worksheet
|
34
|
+
worksheet.col_count = headers.length
|
35
|
+
worksheet.row_count = 1 # we only need to have access to the header row for cells, other rows are inserted
|
36
|
+
worksheet.title = if options[:timestamp] then
|
37
|
+
"#{options[:title]}_#{Time.now.to_i.to_s}"
|
38
|
+
else
|
39
|
+
"#{options[:title]}"
|
40
|
+
end
|
41
|
+
worksheet.save # save that to create the ws
|
42
|
+
|
43
|
+
worksheet.cells # we need to fetch the link to save new cells to
|
44
|
+
|
45
|
+
headers.each_with_index do |key, index|
|
46
|
+
cell = worksheet.new_cell
|
47
|
+
cell.row = 1
|
48
|
+
cell.col = index + 1
|
49
|
+
cell.value = key.to_s
|
50
|
+
cell.save
|
51
|
+
end
|
52
|
+
|
53
|
+
worksheet.rows # Fetches the link we can save rows to
|
54
|
+
|
55
|
+
# We have to normalize the keys we display to a version without underscores or spaces or other bollocks
|
56
|
+
header_keys = Hash[headers.map { |k| [k, Row.normalize_key(k)] }]
|
57
|
+
|
58
|
+
# TODO: Finish this if I end up caring....
|
59
|
+
# dupes = (header_keys.values - header_keys.values.uniq)
|
60
|
+
#
|
61
|
+
# if dupes.length
|
62
|
+
#
|
63
|
+
# end
|
64
|
+
|
65
|
+
data.each do |datum|
|
66
|
+
row = worksheet.new_row
|
67
|
+
datum.each do |kv|
|
68
|
+
key, val = kv
|
69
|
+
header_key = header_keys[key]
|
70
|
+
row[header_key] = REXML::Text.normalize(val.to_s) unless val.blank?
|
71
|
+
end
|
72
|
+
row.save
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
## Serialization Methods
|
77
|
+
|
78
|
+
# TODO: as_xml
|
79
|
+
|
80
|
+
def to_s
|
81
|
+
"<Spreadsheet::#{object_id} '#{title}' by #{author_name} w/ #{Sheety.length_s(@worksheets)} Worksheets>"
|
82
|
+
end
|
83
|
+
|
84
|
+
def inspect
|
85
|
+
to_s
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require_relative 'feed'
|
2
|
+
require_relative 'cell'
|
3
|
+
require_relative 'row'
|
4
|
+
|
5
|
+
class Sheety::Worksheet < Sheety::Feed
|
6
|
+
LINK_ROWS = 'http://schemas.google.com/spreadsheets/2006#listfeed'
|
7
|
+
LINK_CELLS = 'http://schemas.google.com/spreadsheets/2006#cellsfeed'
|
8
|
+
LINK_VIZ = 'http://schemas.google.com/visualization/2008#visualizationApi'
|
9
|
+
LINK_CSV = 'http://schemas.google.com/spreadsheets/2006#exportcsv'
|
10
|
+
NATIVE_LINKS = [LINK_ROWS, LINK_CELLS, LINK_VIZ, LINK_CSV, LINK_SELF, LINK_EDIT]
|
11
|
+
# TODO: Put these in a ListFeed Object, because that's where they come from
|
12
|
+
LINK_POST = 'http://schemas.google.com/g/2005#post'
|
13
|
+
LINK_FEED = 'http://schemas.google.com/g/2005#feed'
|
14
|
+
|
15
|
+
attr_accessor :col_count, :row_count
|
16
|
+
|
17
|
+
atom_children :rows, klass: Sheety::Row, link: LINK_ROWS, merge_links: true, accessor: :value
|
18
|
+
atom_children :cells, klass: Sheety::Cell, link: LINK_CELLS
|
19
|
+
|
20
|
+
def parse(entry)
|
21
|
+
super(entry)
|
22
|
+
|
23
|
+
@col_count = Sheety::Feed.deref(entry, 'gs:colCount', 0).to_i
|
24
|
+
@row_count = Sheety::Feed.deref(entry, 'gs:rowCount', 0).to_i
|
25
|
+
end
|
26
|
+
|
27
|
+
def update_rows(data, key_key='key', value_key='value')
|
28
|
+
logs = {}
|
29
|
+
row_key_key = Sheety::Row.normalize_key(key_key)
|
30
|
+
row_value_key = Sheety::Row.normalize_key(value_key)
|
31
|
+
|
32
|
+
data.each do |kv|
|
33
|
+
case kv
|
34
|
+
when Array
|
35
|
+
key = kv[0].to_s
|
36
|
+
val = kv[1]
|
37
|
+
when Hash
|
38
|
+
key = kv[key_key].to_s
|
39
|
+
val = kv
|
40
|
+
else
|
41
|
+
raise ArgumentError("Unknown argument type: #{kv.class}")
|
42
|
+
end
|
43
|
+
|
44
|
+
row = find_row(row_key_key => key)
|
45
|
+
|
46
|
+
if row.nil?
|
47
|
+
row = new_row
|
48
|
+
row[row_key_key] = key
|
49
|
+
end
|
50
|
+
|
51
|
+
if Hash === val
|
52
|
+
row.put val
|
53
|
+
else
|
54
|
+
row[row_value_key] = val
|
55
|
+
end
|
56
|
+
|
57
|
+
resp = row.save
|
58
|
+
|
59
|
+
logs[key] = resp unless resp['id']
|
60
|
+
end
|
61
|
+
return logs
|
62
|
+
end
|
63
|
+
|
64
|
+
## Serialization Methods
|
65
|
+
|
66
|
+
def as_xml
|
67
|
+
return [
|
68
|
+
'<entry xmlns="http://www.w3.org/2005/Atom" xmlns:gs="http://schemas.google.com/spreadsheets/2006">',
|
69
|
+
if @id then
|
70
|
+
"<id>#{@id}</id>"
|
71
|
+
else
|
72
|
+
''
|
73
|
+
end,
|
74
|
+
"<category scheme=\"http://schemas.google.com/spreadsheets/2006\" term=\"http://schemas.google.com/spreadsheets/2006#worksheet\"/>",
|
75
|
+
"<title type=\"text\">#{title}</title>",
|
76
|
+
if link(LINK_EDIT) then
|
77
|
+
"<link rel=\"#{LINK_EDIT}\" type=\"application/atom+xml\" href=\"#{link(LINK_EDIT)}\"/>"
|
78
|
+
else
|
79
|
+
''
|
80
|
+
end,
|
81
|
+
"<gs:rowCount>#{row_count}</gs:rowCount>",
|
82
|
+
"<gs:colCount>#{col_count}</gs:colCount>",
|
83
|
+
"</entry>",
|
84
|
+
].join(Sheety::NEWLINE)
|
85
|
+
end
|
86
|
+
|
87
|
+
def to_s
|
88
|
+
"<Worksheet::#{object_id} '#{title}' (#{col_count} x #{row_count}) w/ #{Sheety.length_s(@rows)} Rows & #{Sheety.length_s(@cells)} Cells>"
|
89
|
+
end
|
90
|
+
|
91
|
+
def inspect
|
92
|
+
to_s
|
93
|
+
end
|
94
|
+
end
|
metadata
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sheety
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Blake Israel
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-06-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: xml-simple
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.1.5.pre.bisrael
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.1.5.pre.bisrael
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: google-api-client
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.8'
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: 0.8.6
|
37
|
+
type: :runtime
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - "~>"
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0.8'
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 0.8.6
|
47
|
+
description: An interface for manipulating Google Sheets in Ruby on Rails
|
48
|
+
email: blake@honkforhelp.com
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- lib/sheety.rb
|
54
|
+
- lib/sheety/api.rb
|
55
|
+
- lib/sheety/cell.rb
|
56
|
+
- lib/sheety/children.rb
|
57
|
+
- lib/sheety/feed.rb
|
58
|
+
- lib/sheety/row.rb
|
59
|
+
- lib/sheety/spreadsheet.rb
|
60
|
+
- lib/sheety/worksheet.rb
|
61
|
+
homepage: https://github.com/honkforhelp/sheety
|
62
|
+
licenses:
|
63
|
+
- MIT
|
64
|
+
metadata: {}
|
65
|
+
post_install_message:
|
66
|
+
rdoc_options: []
|
67
|
+
require_paths:
|
68
|
+
- lib
|
69
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
requirements: []
|
80
|
+
rubyforge_project:
|
81
|
+
rubygems_version: 2.2.2
|
82
|
+
signing_key:
|
83
|
+
specification_version: 4
|
84
|
+
summary: A Google Spreadsheets Gem
|
85
|
+
test_files: []
|