sapoci 0.1.4
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/CHANGELOG.md +21 -0
- data/LICENSE +21 -0
- data/README.md +14 -0
- data/lib/sapoci.rb +16 -0
- data/lib/sapoci/core_ext.rb +2 -0
- data/lib/sapoci/core_ext/blank.rb +59 -0
- data/lib/sapoci/core_ext/try.rb +11 -0
- data/lib/sapoci/document.rb +116 -0
- data/lib/sapoci/item.rb +139 -0
- metadata +102 -0
data/CHANGELOG.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# 0.1.4, release 2011-07-14
|
2
|
+
|
3
|
+
* Add various before_type_cast methods
|
4
|
+
|
5
|
+
# 0.1.3, release 2011-07-14
|
6
|
+
|
7
|
+
* Ignore whitespace on numeric fields
|
8
|
+
|
9
|
+
# 0.1.2, release 2011-07-05
|
10
|
+
|
11
|
+
* Use Nokogiri 1.5.0
|
12
|
+
|
13
|
+
# 0.1.1, release 2011-04-30
|
14
|
+
|
15
|
+
* Removed version from sources and put it into gemspec only
|
16
|
+
|
17
|
+
# 0.1.0, released 2011-04-29
|
18
|
+
|
19
|
+
* Added a gemspec
|
20
|
+
* Initial release on GitHub
|
21
|
+
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (c) 2007-2011 Oliver Eilhard
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
data/README.md
ADDED
data/lib/sapoci.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# Copyright (C) 2010 Oliver Eilhard
|
2
|
+
#
|
3
|
+
# This SAP OCI library is freely distributable under
|
4
|
+
# the terms of an MIT-style license.
|
5
|
+
# See COPYING or http://www.opensource.org/licenses/mit-license.php.
|
6
|
+
|
7
|
+
# This library is for parsing and emitting shopping cart data and
|
8
|
+
# behavior in SAP OCI style.
|
9
|
+
|
10
|
+
module SAPOCI
|
11
|
+
VERSION_40 = "4.0"
|
12
|
+
|
13
|
+
autoload :CoreExt, 'sapoci/core_ext.rb'
|
14
|
+
autoload :Document, 'sapoci/document.rb'
|
15
|
+
autoload :Item, 'sapoci/item.rb'
|
16
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
class Object #:nodoc:
|
2
|
+
unless respond_to?(:blank?)
|
3
|
+
def blank?
|
4
|
+
respond_to?(:empty?) ? empty? : !self
|
5
|
+
end
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class NilClass #:nodoc:
|
10
|
+
unless respond_to?(:blank?)
|
11
|
+
def blank?
|
12
|
+
true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class FalseClass #:nodoc:
|
18
|
+
unless respond_to?(:blank?)
|
19
|
+
def blank?
|
20
|
+
true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class TrueClass #:nodoc:
|
26
|
+
unless respond_to?(:blank?)
|
27
|
+
def blank?
|
28
|
+
false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Array #:nodoc:
|
34
|
+
unless respond_to?(:blank?)
|
35
|
+
alias_method :blank?, :empty?
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Hash #:nodoc:
|
40
|
+
unless respond_to?(:blank?)
|
41
|
+
alias_method :blank?, :empty?
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class String #:nodoc:
|
46
|
+
unless respond_to?(:blank?)
|
47
|
+
def blank?
|
48
|
+
self !~ /\S/
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class Numeric #:nodoc:
|
54
|
+
unless respond_to?(:blank?)
|
55
|
+
def blank?
|
56
|
+
false
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems' unless RUBY_VERSION >= '1.9'
|
4
|
+
require 'nokogiri'
|
5
|
+
require 'sapoci/core_ext'
|
6
|
+
|
7
|
+
module SAPOCI
|
8
|
+
# SAPOCI::Document is for parsing and emitting SAP OCI compliant
|
9
|
+
# data.
|
10
|
+
#
|
11
|
+
# Open a +Document+ by feeding it a string:
|
12
|
+
#
|
13
|
+
# doc = SAPOCI::Document.from_html("<html>...</html>")
|
14
|
+
#
|
15
|
+
# Open a +Document+ by parsing a Rails-/Rack compatible +Hash+:
|
16
|
+
#
|
17
|
+
# doc = SAPOCI::Document.from_params({ "NEW_ITEM-DESCRIPTION"=>{"1"=>"Standard Visitenkarte deutsch 200 St."} })
|
18
|
+
#
|
19
|
+
class Document
|
20
|
+
def initialize(items)
|
21
|
+
@items = items
|
22
|
+
end
|
23
|
+
|
24
|
+
# Create a new document from a HTML string.
|
25
|
+
def self.from_html(html)
|
26
|
+
html_doc = Nokogiri::HTML(html)
|
27
|
+
doc = Document.new(parse_html(html_doc))
|
28
|
+
yield doc if block_given?
|
29
|
+
doc
|
30
|
+
end
|
31
|
+
|
32
|
+
# Create a new document from a Rails-/Rack-compatible
|
33
|
+
# params hash.
|
34
|
+
def self.from_params(params)
|
35
|
+
doc = Document.new(parse_params(params))
|
36
|
+
yield doc if block_given?
|
37
|
+
doc
|
38
|
+
end
|
39
|
+
|
40
|
+
# All +Item+ instances.
|
41
|
+
attr_reader :items
|
42
|
+
|
43
|
+
# Returns all +items+ as HTML hidden field tags.
|
44
|
+
def to_html(options = {})
|
45
|
+
html = []
|
46
|
+
self.items.each do |item|
|
47
|
+
html << item.to_html(options)
|
48
|
+
end
|
49
|
+
html.join
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
# Parses a Nokogiri HTML document and returns
|
55
|
+
# +Item+ instances in an array.
|
56
|
+
def self.parse_html(doc)
|
57
|
+
items = {}
|
58
|
+
doc.xpath("//input[starts-with(@name, 'NEW_ITEM-')]").each do |item_node|
|
59
|
+
name = item_node.attribute("name")
|
60
|
+
if /NEW_ITEM-(\w+)\[(\d+)\]/.match(name)
|
61
|
+
property = $1
|
62
|
+
index = $2.to_i - 1
|
63
|
+
value = item_node.attribute("value").value
|
64
|
+
items[index] = Item.new(index) unless items[index]
|
65
|
+
items[index].send((property+'=').downcase.to_sym, value)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
items.inject([]) { |memo, (key, value)| memo << value }.sort_by(&:index)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Parses a Rails-/Rack-compatible params hash and returns
|
72
|
+
# +Item+ instances in an array.
|
73
|
+
def self.parse_params(params)
|
74
|
+
items = {}
|
75
|
+
(params || {}).each do |oci_name, oci_values|
|
76
|
+
if oci_name =~ /NEW_ITEM-/
|
77
|
+
# Parse anything but NEW_ITEM-LONGTEXT (which is special, see below)
|
78
|
+
oci_values.each do |index, value|
|
79
|
+
index = index.to_i - 1 rescue next
|
80
|
+
property = /NEW_ITEM-(\w+)/.match(oci_name)[1]
|
81
|
+
next if property =~ /LONGTEXT/
|
82
|
+
method = (property+'=').downcase.to_sym
|
83
|
+
items[index] = Item.new(index) unless items[index]
|
84
|
+
items[index].send(method, value) if items[index].respond_to?(method)
|
85
|
+
end if oci_values && oci_values.respond_to?(:each)
|
86
|
+
|
87
|
+
# LONGTEXT is a special case because it doesn't follow the conventions
|
88
|
+
# Format is:
|
89
|
+
# NEW_ITEM-LONTEXT_n:132[]
|
90
|
+
# But shops use other (invalid) formats as well (and we're ready to accept them):
|
91
|
+
# NEW_ITEM-LONGTEXT_n:132[n]
|
92
|
+
# NEW_ITEM_LONGTEXT_n:132
|
93
|
+
#
|
94
|
+
if /NEW_ITEM-LONGTEXT_(\d+):132/.match(oci_name)
|
95
|
+
index = $1.to_i - 1
|
96
|
+
if oci_values.is_a?(Array)
|
97
|
+
# NEW_ITEM-LONGTEXT_1:132[]
|
98
|
+
items[index] = Item.new(index) unless items[index]
|
99
|
+
items[index].longtext = oci_values.first
|
100
|
+
elsif oci_values.is_a?(String)
|
101
|
+
# NEW_ITEM-LONGTEXT_1:132 <= invalid but parsed!
|
102
|
+
items[index] = Item.new(index) unless items[index]
|
103
|
+
items[index].longtext = oci_values
|
104
|
+
elsif oci_values.is_a?(Hash)
|
105
|
+
# NEW_ITEM-LONGTEXT_1:132[1] <= invalid but parsed!
|
106
|
+
items[index] = Item.new(index) unless items[index]
|
107
|
+
items[index].longtext = oci_values.first.last
|
108
|
+
end
|
109
|
+
end if oci_values
|
110
|
+
end # oci_name =~ /NEW_ITEM-/
|
111
|
+
end
|
112
|
+
items.inject([]) { |memo, (key, value)| memo << value }.sort_by(&:index)
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
data/lib/sapoci/item.rb
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems' unless RUBY_VERSION >= '1.9'
|
4
|
+
require 'nokogiri'
|
5
|
+
require 'sapoci/core_ext'
|
6
|
+
require 'bigdecimal'
|
7
|
+
|
8
|
+
module SAPOCI
|
9
|
+
class Item
|
10
|
+
|
11
|
+
attr_reader :index
|
12
|
+
attr_accessor :description
|
13
|
+
attr_accessor :matnr
|
14
|
+
attr_accessor :quantity
|
15
|
+
attr_accessor :unit
|
16
|
+
attr_accessor :price
|
17
|
+
attr_accessor :currency
|
18
|
+
attr_accessor :priceunit
|
19
|
+
attr_accessor :leadtime
|
20
|
+
attr_accessor :longtext
|
21
|
+
attr_accessor :vendor
|
22
|
+
attr_accessor :vendormat
|
23
|
+
attr_accessor :manufactcode
|
24
|
+
attr_accessor :manufactmat
|
25
|
+
attr_accessor :matgroup
|
26
|
+
attr_accessor :service
|
27
|
+
attr_accessor :contract
|
28
|
+
attr_accessor :contract_item
|
29
|
+
attr_accessor :ext_quote_id
|
30
|
+
attr_accessor :ext_quote_item
|
31
|
+
attr_accessor :ext_product_id
|
32
|
+
attr_accessor :attachment
|
33
|
+
attr_accessor :attachment_title
|
34
|
+
attr_accessor :attachment_purpose
|
35
|
+
attr_accessor :ext_schema_type
|
36
|
+
attr_accessor :ext_category_id
|
37
|
+
attr_accessor :ext_category
|
38
|
+
attr_accessor :sld_sys_name
|
39
|
+
attr_accessor :cust_field1
|
40
|
+
attr_accessor :cust_field2
|
41
|
+
attr_accessor :cust_field3
|
42
|
+
attr_accessor :cust_field4
|
43
|
+
attr_accessor :cust_field5
|
44
|
+
|
45
|
+
# Initializes the item.
|
46
|
+
def initialize(index)
|
47
|
+
@index = index
|
48
|
+
end
|
49
|
+
|
50
|
+
def quantity
|
51
|
+
BigDecimal.new("0#{@quantity.to_s.strip}")
|
52
|
+
end
|
53
|
+
|
54
|
+
def quantity_before_type_cast
|
55
|
+
@quantity
|
56
|
+
end
|
57
|
+
|
58
|
+
def price
|
59
|
+
BigDecimal.new("0#{@price.to_s.strip}")
|
60
|
+
end
|
61
|
+
|
62
|
+
def price_before_type_cast
|
63
|
+
@price
|
64
|
+
end
|
65
|
+
|
66
|
+
def priceunit
|
67
|
+
BigDecimal.new("0#{@priceunit.to_s.strip}")
|
68
|
+
end
|
69
|
+
|
70
|
+
def priceunit_before_type_cast
|
71
|
+
@priceunit
|
72
|
+
end
|
73
|
+
|
74
|
+
def leadtime
|
75
|
+
@leadtime.to_i if @leadtime
|
76
|
+
end
|
77
|
+
|
78
|
+
def leadtime_before_type_cast
|
79
|
+
@leadtime
|
80
|
+
end
|
81
|
+
|
82
|
+
def service?
|
83
|
+
self.service == "X"
|
84
|
+
end
|
85
|
+
|
86
|
+
# Returns the item properties as HTML hidden field tags.
|
87
|
+
def to_html(options = {})
|
88
|
+
html = []
|
89
|
+
html << hidden_field_tag("DESCRIPTION", self.description) unless self.description.blank?
|
90
|
+
html << hidden_field_tag("MATNR", self.matnr) unless self.matnr.blank?
|
91
|
+
html << hidden_field_tag("QUANTITY", "%015.3f" % self.quantity)
|
92
|
+
html << hidden_field_tag("UNIT", self.unit) unless self.unit.blank?
|
93
|
+
html << hidden_field_tag("PRICE", "%015.3f" % self.price)
|
94
|
+
html << hidden_field_tag("CURRENCY", self.currency) unless self.currency.blank?
|
95
|
+
html << hidden_field_tag("PRICEUNIT", self.priceunit.to_i) if self.priceunit.to_i > 0
|
96
|
+
html << hidden_field_tag("LEADTIME", "%05d" % self.leadtime) if self.leadtime.to_i > 0
|
97
|
+
html << hidden_field_tag("VENDOR", self.vendor) unless self.vendor.blank?
|
98
|
+
html << hidden_field_tag("VENDORMAT", self.vendormat) unless self.vendormat.blank?
|
99
|
+
html << hidden_field_tag("MANUFACTCODE", self.manufactcode) unless self.manufactcode.blank?
|
100
|
+
html << hidden_field_tag("MANUFACTMAT", self.manufactmat) unless self.manufactmat.blank?
|
101
|
+
html << hidden_field_tag("MATGROUP", self.matgroup) unless self.matgroup.blank?
|
102
|
+
html << hidden_field_tag("SERVICE", "X") if self.service?
|
103
|
+
html << hidden_field_tag("CONTRACT", self.contract) unless self.contract.blank?
|
104
|
+
html << hidden_field_tag("CONTRACT_ITEM", self.contract_item) unless self.contract_item.blank?
|
105
|
+
html << hidden_field_tag("EXT_QUOTE_ID", self.ext_quote_id) unless self.ext_quote_id.blank?
|
106
|
+
html << hidden_field_tag("EXT_QUOTE_ITEM", self.ext_quote_item) unless self.ext_quote_item.blank?
|
107
|
+
html << hidden_field_tag("EXT_PRODUCT_ID", self.ext_product_id) unless self.ext_product_id.blank?
|
108
|
+
html << hidden_field_tag("ATTACHMENT", self.attachment) unless self.attachment.blank?
|
109
|
+
html << hidden_field_tag("ATTACHMENT_TITLE", self.attachment_title) unless self.attachment_title.blank?
|
110
|
+
html << hidden_field_tag("ATTACHMENT_PURPOSE", self.attachment_purpose) unless self.attachment_purpose.blank?
|
111
|
+
html << hidden_field_tag("EXT_SCHEMA_TYPE", self.ext_schema_type) unless self.ext_schema_type.blank?
|
112
|
+
html << hidden_field_tag("EXT_CATEGORY_ID", self.ext_category_id) unless self.ext_category_id.blank?
|
113
|
+
html << hidden_field_tag("EXT_CATEGORY", self.ext_category) unless self.ext_category.blank?
|
114
|
+
html << hidden_field_tag("SLD_SYS_NAME", self.sld_sys_name) unless self.sld_sys_name.blank?
|
115
|
+
html << hidden_field_tag("CUST_FIELD1", self.cust_field1) unless self.cust_field1.blank?
|
116
|
+
html << hidden_field_tag("CUST_FIELD2", self.cust_field2) unless self.cust_field2.blank?
|
117
|
+
html << hidden_field_tag("CUST_FIELD3", self.cust_field3) unless self.cust_field3.blank?
|
118
|
+
html << hidden_field_tag("CUST_FIELD4", self.cust_field4) unless self.cust_field4.blank?
|
119
|
+
html << hidden_field_tag("CUST_FIELD5", self.cust_field5) unless self.cust_field5.blank?
|
120
|
+
html << "<input type=\"hidden\" name=\"NEW_ITEM-LONGTEXT_#{index + 1}:132[]\" value=\"#{escape_html(self.longtext)}\" />" unless self.longtext.blank?
|
121
|
+
html.join
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
def hidden_field_tag(name, value, options = {})
|
127
|
+
"<input type=\"hidden\" name=\"NEW_ITEM-#{name}[#{index + 1}]\" value=\"#{escape_html(value)}\" />"
|
128
|
+
end
|
129
|
+
|
130
|
+
ESCAPE_HTML = { "&" => "&", "<" => "<", ">" => ">", "'" => "'", '"' => """, }
|
131
|
+
ESCAPE_HTML_PATTERN = Regexp.union(ESCAPE_HTML.keys)
|
132
|
+
|
133
|
+
# Shamelessly borrowed from Rack::Utils.escape_html(s)
|
134
|
+
def escape_html(s)
|
135
|
+
s.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] }
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
end
|
metadata
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sapoci
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.4
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Oliver Eilhard
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-08-18 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: &70261606168780 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70261606168780
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rdoc
|
27
|
+
requirement: &70261606168260 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '2.5'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70261606168260
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rake
|
38
|
+
requirement: &70261606167780 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 0.9.2
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70261606167780
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: nokogiri
|
49
|
+
requirement: &70261606166980 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.5.0
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70261606166980
|
58
|
+
description: Ruby library and Rails plugin for punchout via SAP OCI protocol.
|
59
|
+
email:
|
60
|
+
- oliver.eilhard@gmail.com
|
61
|
+
executables: []
|
62
|
+
extensions: []
|
63
|
+
extra_rdoc_files:
|
64
|
+
- CHANGELOG.md
|
65
|
+
- LICENSE
|
66
|
+
- README.md
|
67
|
+
files:
|
68
|
+
- lib/sapoci.rb
|
69
|
+
- lib/sapoci/core_ext.rb
|
70
|
+
- lib/sapoci/core_ext/blank.rb
|
71
|
+
- lib/sapoci/core_ext/try.rb
|
72
|
+
- lib/sapoci/document.rb
|
73
|
+
- lib/sapoci/item.rb
|
74
|
+
- CHANGELOG.md
|
75
|
+
- LICENSE
|
76
|
+
- README.md
|
77
|
+
homepage: http://github.com/meplato/sapoci
|
78
|
+
licenses: []
|
79
|
+
post_install_message:
|
80
|
+
rdoc_options:
|
81
|
+
- --charset=UTF-8
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ! '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
|
+
none: false
|
92
|
+
requirements:
|
93
|
+
- - ! '>='
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: 1.3.6
|
96
|
+
requirements: []
|
97
|
+
rubyforge_project:
|
98
|
+
rubygems_version: 1.8.7
|
99
|
+
signing_key:
|
100
|
+
specification_version: 3
|
101
|
+
summary: Ruby library and Rails plugin for punchout via SAP OCI protocol.
|
102
|
+
test_files: []
|