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