dcparker-shopify 0.1.9 → 0.2.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.
- data/CHANGELOG +4 -1
- data/Manifest +13 -1
- data/README.textile +19 -0
- data/lib/shopify.rb +46 -13
- data/lib/shopify/extlib.rb +9 -0
- data/lib/shopify/extlib/assertions.rb +8 -0
- data/lib/shopify/extlib/class.rb +98 -0
- data/lib/shopify/extlib/hash.rb +327 -0
- data/lib/shopify/extlib/hook.rb +366 -0
- data/lib/shopify/extlib/inflection.rb +436 -0
- data/lib/shopify/extlib/logger.rb +202 -0
- data/lib/shopify/extlib/object.rb +162 -0
- data/lib/shopify/extlib/pathname.rb +15 -0
- data/lib/shopify/extlib/rubygems.rb +38 -0
- data/lib/shopify/extlib/string.rb +32 -0
- data/lib/shopify/extlib/time.rb +41 -0
- data/lib/shopify/support.rb +29 -46
- data/shopify.gemspec +10 -8
- metadata +35 -8
- data/README +0 -9
@@ -0,0 +1,202 @@
|
|
1
|
+
require "time" # httpdate
|
2
|
+
# ==== Public Merb Logger API
|
3
|
+
#
|
4
|
+
# To replace an existing logger with a new one:
|
5
|
+
# Merb::Logger.set_log(log{String, IO},level{Symbol, String})
|
6
|
+
#
|
7
|
+
# Available logging levels are
|
8
|
+
# Merb::Logger::{ Fatal, Error, Warn, Info, Debug }
|
9
|
+
#
|
10
|
+
# Logging via:
|
11
|
+
# Merb.logger.fatal(message<String>,&block)
|
12
|
+
# Merb.logger.error(message<String>,&block)
|
13
|
+
# Merb.logger.warn(message<String>,&block)
|
14
|
+
# Merb.logger.info(message<String>,&block)
|
15
|
+
# Merb.logger.debug(message<String>,&block)
|
16
|
+
#
|
17
|
+
# Logging with autoflush:
|
18
|
+
# Merb.logger.fatal!(message<String>,&block)
|
19
|
+
# Merb.logger.error!(message<String>,&block)
|
20
|
+
# Merb.logger.warn!(message<String>,&block)
|
21
|
+
# Merb.logger.info!(message<String>,&block)
|
22
|
+
# Merb.logger.debug!(message<String>,&block)
|
23
|
+
#
|
24
|
+
# Flush the buffer to
|
25
|
+
# Merb.logger.flush
|
26
|
+
#
|
27
|
+
# Remove the current log object
|
28
|
+
# Merb.logger.close
|
29
|
+
#
|
30
|
+
# ==== Private Merb Logger API
|
31
|
+
#
|
32
|
+
# To initialize the logger you create a new object, proxies to set_log.
|
33
|
+
# Merb::Logger.new(log{String, IO},level{Symbol, String})
|
34
|
+
module Extlib # :nodoc:all
|
35
|
+
|
36
|
+
class << self
|
37
|
+
attr_accessor :logger
|
38
|
+
end
|
39
|
+
|
40
|
+
class Logger
|
41
|
+
|
42
|
+
attr_accessor :level
|
43
|
+
attr_accessor :delimiter
|
44
|
+
attr_accessor :auto_flush
|
45
|
+
attr_reader :buffer
|
46
|
+
attr_reader :log
|
47
|
+
attr_reader :init_args
|
48
|
+
|
49
|
+
# ==== Notes
|
50
|
+
# Ruby (standard) logger levels:
|
51
|
+
# :fatal:: An unhandleable error that results in a program crash
|
52
|
+
# :error:: A handleable error condition
|
53
|
+
# :warn:: A warning
|
54
|
+
# :info:: generic (useful) information about system operation
|
55
|
+
# :debug:: low-level information for developers
|
56
|
+
Levels =
|
57
|
+
{
|
58
|
+
:fatal => 7,
|
59
|
+
:error => 6,
|
60
|
+
:warn => 4,
|
61
|
+
:info => 3,
|
62
|
+
:debug => 0
|
63
|
+
}
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
# Readies a log for writing.
|
68
|
+
#
|
69
|
+
# ==== Parameters
|
70
|
+
# log<IO, String>:: Either an IO object or a name of a logfile.
|
71
|
+
def initialize_log(log)
|
72
|
+
close if @log # be sure that we don't leave open files laying around.
|
73
|
+
|
74
|
+
if log.respond_to?(:write)
|
75
|
+
@log = log
|
76
|
+
elsif File.exist?(log)
|
77
|
+
@log = open(log, (File::WRONLY | File::APPEND))
|
78
|
+
@log.sync = true
|
79
|
+
else
|
80
|
+
FileUtils.mkdir_p(File.dirname(log)) unless File.directory?(File.dirname(log))
|
81
|
+
@log = open(log, (File::WRONLY | File::APPEND | File::CREAT))
|
82
|
+
@log.sync = true
|
83
|
+
@log.write("#{Time.now.httpdate} #{delimiter} info #{delimiter} Logfile created\n")
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
public
|
88
|
+
|
89
|
+
# To initialize the logger you create a new object, proxies to set_log.
|
90
|
+
#
|
91
|
+
# ==== Parameters
|
92
|
+
# *args:: Arguments to create the log from. See set_logs for specifics.
|
93
|
+
def initialize(*args)
|
94
|
+
@init_args = args
|
95
|
+
set_log(*args)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Replaces an existing logger with a new one.
|
99
|
+
#
|
100
|
+
# ==== Parameters
|
101
|
+
# log<IO, String>:: Either an IO object or a name of a logfile.
|
102
|
+
# log_level<~to_sym>::
|
103
|
+
# The log level from, e.g. :fatal or :info. Defaults to :error in the
|
104
|
+
# production environment and :debug otherwise.
|
105
|
+
# delimiter<String>::
|
106
|
+
# Delimiter to use between message sections. Defaults to " ~ ".
|
107
|
+
# auto_flush<Boolean>::
|
108
|
+
# Whether the log should automatically flush after new messages are
|
109
|
+
# added. Defaults to false.
|
110
|
+
def set_log(log, log_level = nil, delimiter = " ~ ", auto_flush = false)
|
111
|
+
if log_level && Levels[log_level.to_sym]
|
112
|
+
@level = Levels[log_level.to_sym]
|
113
|
+
elsif Merb.environment == "production"
|
114
|
+
@level = Levels[:warn]
|
115
|
+
else
|
116
|
+
@level = Levels[:debug]
|
117
|
+
end
|
118
|
+
@buffer = []
|
119
|
+
@delimiter = delimiter
|
120
|
+
@auto_flush = auto_flush
|
121
|
+
|
122
|
+
initialize_log(log)
|
123
|
+
|
124
|
+
Merb.logger = self
|
125
|
+
end
|
126
|
+
|
127
|
+
# Flush the entire buffer to the log object.
|
128
|
+
def flush
|
129
|
+
return unless @buffer.size > 0
|
130
|
+
@log.write(@buffer.slice!(0..-1).to_s)
|
131
|
+
end
|
132
|
+
|
133
|
+
# Close and remove the current log object.
|
134
|
+
def close
|
135
|
+
flush
|
136
|
+
@log.close if @log.respond_to?(:close) && !@log.tty?
|
137
|
+
@log = nil
|
138
|
+
end
|
139
|
+
|
140
|
+
# Appends a message to the log. The methods yield to an optional block and
|
141
|
+
# the output of this block will be appended to the message.
|
142
|
+
#
|
143
|
+
# ==== Parameters
|
144
|
+
# string<String>:: The message to be logged. Defaults to nil.
|
145
|
+
#
|
146
|
+
# ==== Returns
|
147
|
+
# String:: The resulting message added to the log file.
|
148
|
+
def <<(string = nil)
|
149
|
+
message = ""
|
150
|
+
message << delimiter
|
151
|
+
message << string if string
|
152
|
+
message << "\n" unless message[-1] == ?\n
|
153
|
+
@buffer << message
|
154
|
+
flush if @auto_flush
|
155
|
+
|
156
|
+
message
|
157
|
+
end
|
158
|
+
alias :push :<<
|
159
|
+
|
160
|
+
# Generate the logging methods for Merb.logger for each log level.
|
161
|
+
Levels.each_pair do |name, number|
|
162
|
+
class_eval <<-LEVELMETHODS, __FILE__, __LINE__
|
163
|
+
|
164
|
+
# Appends a message to the log if the log level is at least as high as
|
165
|
+
# the log level of the logger.
|
166
|
+
#
|
167
|
+
# ==== Parameters
|
168
|
+
# string<String>:: The message to be logged. Defaults to nil.
|
169
|
+
#
|
170
|
+
# ==== Returns
|
171
|
+
# self:: The logger object for chaining.
|
172
|
+
def #{name}(message = nil)
|
173
|
+
self << message if #{number} >= level
|
174
|
+
self
|
175
|
+
end
|
176
|
+
|
177
|
+
# Appends a message to the log if the log level is at least as high as
|
178
|
+
# the log level of the logger. The bang! version of the method also auto
|
179
|
+
# flushes the log buffer to disk.
|
180
|
+
#
|
181
|
+
# ==== Parameters
|
182
|
+
# string<String>:: The message to be logged. Defaults to nil.
|
183
|
+
#
|
184
|
+
# ==== Returns
|
185
|
+
# self:: The logger object for chaining.
|
186
|
+
def #{name}!(message = nil)
|
187
|
+
self << message if #{number} >= level
|
188
|
+
flush if #{number} >= level
|
189
|
+
self
|
190
|
+
end
|
191
|
+
|
192
|
+
# ==== Returns
|
193
|
+
# Boolean:: True if this level will be logged by this logger.
|
194
|
+
def #{name}?
|
195
|
+
#{number} >= level
|
196
|
+
end
|
197
|
+
LEVELMETHODS
|
198
|
+
end
|
199
|
+
|
200
|
+
end
|
201
|
+
|
202
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
class Object # :nodoc:all
|
2
|
+
# Extracts the singleton class, so that metaprogramming can be done on it.
|
3
|
+
#
|
4
|
+
# @return <Class> The meta class.
|
5
|
+
#
|
6
|
+
# @example [Setup]
|
7
|
+
# class MyString < String; end
|
8
|
+
#
|
9
|
+
# MyString.instance_eval do
|
10
|
+
# define_method :foo do
|
11
|
+
# puts self
|
12
|
+
# end
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# MyString.meta_class.instance_eval do
|
16
|
+
# define_method :bar do
|
17
|
+
# puts self
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# def String.add_meta_var(var)
|
22
|
+
# self.meta_class.instance_eval do
|
23
|
+
# define_method var do
|
24
|
+
# puts "HELLO"
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# @example
|
30
|
+
# MyString.new("Hello").foo #=> "Hello"
|
31
|
+
# @example
|
32
|
+
# MyString.new("Hello").bar
|
33
|
+
# #=> NoMethodError: undefined method `bar' for "Hello":MyString
|
34
|
+
# @example
|
35
|
+
# MyString.foo
|
36
|
+
# #=> NoMethodError: undefined method `foo' for MyString:Class
|
37
|
+
# @example
|
38
|
+
# MyString.bar
|
39
|
+
# #=> MyString
|
40
|
+
# @example
|
41
|
+
# String.bar
|
42
|
+
# #=> NoMethodError: undefined method `bar' for String:Class
|
43
|
+
# @example
|
44
|
+
# MyString.add_meta_var(:x)
|
45
|
+
# MyString.x #=> HELLO
|
46
|
+
#
|
47
|
+
# @details [Description of Examples]
|
48
|
+
# As you can see, using #meta_class allows you to execute code (and here,
|
49
|
+
# define a method) on the metaclass itself. It also allows you to define
|
50
|
+
# class methods that can be run on subclasses, and then be able to execute
|
51
|
+
# code on the metaclass of the subclass (here MyString).
|
52
|
+
#
|
53
|
+
# In this case, we were able to define a class method (add_meta_var) on
|
54
|
+
# String that was executable by the MyString subclass. It was then able to
|
55
|
+
# define a method on the subclass by adding it to the MyString metaclass.
|
56
|
+
#
|
57
|
+
# For more information, you can check out _why's excellent article at:
|
58
|
+
# http://whytheluckystiff.net/articles/seeingMetaclassesClearly.html
|
59
|
+
def meta_class() class << self; self end end
|
60
|
+
|
61
|
+
# @param name<String> The name of the constant to get, e.g. "Merb::Router".
|
62
|
+
#
|
63
|
+
# @return <Object> The constant corresponding to the name.
|
64
|
+
def full_const_get(name)
|
65
|
+
list = name.split("::")
|
66
|
+
list.shift if list.first.blank?
|
67
|
+
obj = self
|
68
|
+
list.each do |x|
|
69
|
+
# This is required because const_get tries to look for constants in the
|
70
|
+
# ancestor chain, but we only want constants that are HERE
|
71
|
+
obj = obj.const_defined?(x) ? obj.const_get(x) : obj.const_missing(x)
|
72
|
+
end
|
73
|
+
obj
|
74
|
+
end
|
75
|
+
|
76
|
+
# @param name<String> The name of the constant to get, e.g. "Merb::Router".
|
77
|
+
# @param value<Object> The value to assign to the constant.
|
78
|
+
#
|
79
|
+
# @return <Object> The constant corresponding to the name.
|
80
|
+
def full_const_set(name, value)
|
81
|
+
list = name.split("::")
|
82
|
+
toplevel = list.first.blank?
|
83
|
+
list.shift if toplevel
|
84
|
+
last = list.pop
|
85
|
+
obj = list.empty? ? Object : Object.full_const_get(list.join("::"))
|
86
|
+
obj.const_set(last, value) if obj && !obj.const_defined?(last)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Defines module from a string name (e.g. Foo::Bar::Baz)
|
90
|
+
# If module already exists, no exception raised.
|
91
|
+
#
|
92
|
+
# @param name<String> The name of the full module name to make
|
93
|
+
#
|
94
|
+
# @return <NilClass>
|
95
|
+
def make_module(str)
|
96
|
+
mod = str.split("::")
|
97
|
+
current_module = self
|
98
|
+
mod.each do |x|
|
99
|
+
unless current_module.const_defined?(x)
|
100
|
+
current_module.class_eval "module #{x}; end"
|
101
|
+
end
|
102
|
+
current_module = current_module.const_get(x)
|
103
|
+
end
|
104
|
+
current_module
|
105
|
+
end
|
106
|
+
|
107
|
+
# @param duck<Symbol, Class, Array> The thing to compare the object to.
|
108
|
+
#
|
109
|
+
# @note
|
110
|
+
# The behavior of the method depends on the type of duck as follows:
|
111
|
+
# Symbol:: Check whether the object respond_to?(duck).
|
112
|
+
# Class:: Check whether the object is_a?(duck).
|
113
|
+
# Array::
|
114
|
+
# Check whether the object quacks_like? at least one of the options in the
|
115
|
+
# array.
|
116
|
+
#
|
117
|
+
# @return <TrueClass, FalseClass>
|
118
|
+
# True if the object quacks like duck.
|
119
|
+
def quacks_like?(duck)
|
120
|
+
case duck
|
121
|
+
when Symbol
|
122
|
+
self.respond_to?(duck)
|
123
|
+
when Class
|
124
|
+
self.is_a?(duck)
|
125
|
+
when Array
|
126
|
+
duck.any? {|d| self.quacks_like?(d) }
|
127
|
+
else
|
128
|
+
false
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Override this in a child if it cannot be dup'ed
|
133
|
+
#
|
134
|
+
# @return <Object>
|
135
|
+
def try_dup
|
136
|
+
self.dup
|
137
|
+
end
|
138
|
+
|
139
|
+
# @param arrayish<#include?> Container to check, to see if it includes the object.
|
140
|
+
# @param *more<Array>:: additional args, will be flattened into arrayish
|
141
|
+
#
|
142
|
+
# @return <TrueClass, FalseClass>
|
143
|
+
# True if the object is included in arrayish (+ more)
|
144
|
+
#
|
145
|
+
# @example 1.in?([1,2,3]) #=> true
|
146
|
+
# @example 1.in?(1,2,3) #=> true
|
147
|
+
def in?(arrayish,*more)
|
148
|
+
arrayish = more.unshift(arrayish) unless more.empty?
|
149
|
+
arrayish.include?(self)
|
150
|
+
end
|
151
|
+
|
152
|
+
# Add instance_variable_defined? for backward compatibility
|
153
|
+
# @param variable<Symbol, String>
|
154
|
+
#
|
155
|
+
# @return <TrueClass, FalseClass>
|
156
|
+
# True if the object has the given instance variable defined
|
157
|
+
unless respond_to?(:instance_variable_defined?)
|
158
|
+
def instance_variable_defined?(variable)
|
159
|
+
instance_variables.include?(variable.to_s)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class Pathname # :nodoc:all
|
2
|
+
# Append path segments and expand to absolute path
|
3
|
+
#
|
4
|
+
# file = Pathname(Dir.pwd) / "subdir1" / :subdir2 / "filename.ext"
|
5
|
+
#
|
6
|
+
# @param [Pathname, String, #to_s] path path segment to concatenate with receiver
|
7
|
+
#
|
8
|
+
# @return [Pathname]
|
9
|
+
# receiver with _path_ appended and expanded to an absolute path
|
10
|
+
#
|
11
|
+
# @api public
|
12
|
+
def /(path)
|
13
|
+
(self + path).expand_path
|
14
|
+
end
|
15
|
+
end # class Pathname
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# this is a temporary workaround until rubygems Does the Right thing here
|
2
|
+
require 'rubygems'
|
3
|
+
module Gem # :nodoc:all
|
4
|
+
class SourceIndex
|
5
|
+
|
6
|
+
# This is resolved in 1.1
|
7
|
+
if Version.new(RubyGemsVersion) < Version.new("1.1")
|
8
|
+
|
9
|
+
# Overwrite this so that a gem of the same name and version won't push one
|
10
|
+
# from the gems directory out entirely.
|
11
|
+
#
|
12
|
+
# @param gem_spec<Gem::Specification> The specification of the gem to add.
|
13
|
+
def add_spec(gem_spec)
|
14
|
+
unless gem_spec.instance_variable_get("@loaded_from") &&
|
15
|
+
@gems[gem_spec.full_name].is_a?(Gem::Specification) &&
|
16
|
+
@gems[gem_spec.full_name].installation_path ==
|
17
|
+
File.join(defined?(Merb) && Merb.respond_to?(:root) ? Merb.root : Dir.pwd,"gems")
|
18
|
+
|
19
|
+
@gems[gem_spec.full_name] = gem_spec
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
class Specification
|
28
|
+
|
29
|
+
# Overwrite this so that gems in the gems directory get preferred over gems
|
30
|
+
# from any other location. If there are two gems of different versions in
|
31
|
+
# the gems directory, the later one will load as usual.
|
32
|
+
#
|
33
|
+
# @return <Array[Array]> The object used for sorting gem specs.
|
34
|
+
def sort_obj
|
35
|
+
[@name, installation_path == File.join(defined?(Merb) && Merb.respond_to?(:root) ? Merb.root : Dir.pwd,"gems") ? 1 : -1, @version.to_ints, @new_platform == Gem::Platform::RUBY ? -1 : 1]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require "pathname"
|
2
|
+
|
3
|
+
class String # :nodoc:all
|
4
|
+
##
|
5
|
+
# Convert to snake case.
|
6
|
+
#
|
7
|
+
# "FooBar".snake_case #=> "foo_bar"
|
8
|
+
# "HeadlineCNNNews".snake_case #=> "headline_cnn_news"
|
9
|
+
# "CNN".snake_case #=> "cnn"
|
10
|
+
#
|
11
|
+
# @return [String] Receiver converted to snake case.
|
12
|
+
#
|
13
|
+
# @api public
|
14
|
+
def snake_case
|
15
|
+
return self.downcase if self =~ /^[A-Z]+$/
|
16
|
+
self.gsub(/([A-Z]+)(?=[A-Z][a-z]?)|\B[A-Z]/, '_\&') =~ /_*(.*)/
|
17
|
+
return $+.downcase
|
18
|
+
end
|
19
|
+
|
20
|
+
##
|
21
|
+
# Convert to camel case.
|
22
|
+
#
|
23
|
+
# "foo_bar".camel_case #=> "FooBar"
|
24
|
+
#
|
25
|
+
# @return [String] Receiver converted to camel case.
|
26
|
+
#
|
27
|
+
# @api public
|
28
|
+
def camel_case
|
29
|
+
return self if self !~ /_/ && self =~ /[A-Z]+.*/
|
30
|
+
split('_').map{|e| e.capitalize}.join
|
31
|
+
end
|
32
|
+
end # class String
|