sheety 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|