harbor 0.16.1

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.
Files changed (84) hide show
  1. data/Rakefile +76 -0
  2. data/bin/harbor +7 -0
  3. data/lib/harbor.rb +17 -0
  4. data/lib/harbor/accessor_injector.rb +30 -0
  5. data/lib/harbor/application.rb +172 -0
  6. data/lib/harbor/auth/basic.rb +51 -0
  7. data/lib/harbor/block_io.rb +63 -0
  8. data/lib/harbor/cache.rb +90 -0
  9. data/lib/harbor/cache/disk.rb +99 -0
  10. data/lib/harbor/cache/item.rb +48 -0
  11. data/lib/harbor/cache/memory.rb +35 -0
  12. data/lib/harbor/cascade.rb +75 -0
  13. data/lib/harbor/console.rb +34 -0
  14. data/lib/harbor/container.rb +134 -0
  15. data/lib/harbor/contrib/debug.rb +236 -0
  16. data/lib/harbor/contrib/session/data_mapper.rb +74 -0
  17. data/lib/harbor/daemon.rb +105 -0
  18. data/lib/harbor/errors.rb +49 -0
  19. data/lib/harbor/events.rb +45 -0
  20. data/lib/harbor/exception_notifier.rb +59 -0
  21. data/lib/harbor/file.rb +66 -0
  22. data/lib/harbor/file_store.rb +69 -0
  23. data/lib/harbor/file_store/file.rb +100 -0
  24. data/lib/harbor/file_store/local.rb +71 -0
  25. data/lib/harbor/file_store/mosso.rb +154 -0
  26. data/lib/harbor/file_store/mosso/private.rb +8 -0
  27. data/lib/harbor/generator.rb +56 -0
  28. data/lib/harbor/generator/help.rb +34 -0
  29. data/lib/harbor/generator/setup.rb +82 -0
  30. data/lib/harbor/generator/skeletons/basic/config.ru.skel +21 -0
  31. data/lib/harbor/generator/skeletons/basic/lib/appname.rb.skel +49 -0
  32. data/lib/harbor/generator/skeletons/basic/lib/appname/controllers/home.rb +9 -0
  33. data/lib/harbor/generator/skeletons/basic/lib/appname/views/home/index.html.erb.skel +23 -0
  34. data/lib/harbor/generator/skeletons/basic/lib/appname/views/layouts/application.html.erb.skel +48 -0
  35. data/lib/harbor/generator/skeletons/basic/lib/appname/views/layouts/exception.html.erb.skel +13 -0
  36. data/lib/harbor/generator/skeletons/basic/lib/appname/views/layouts/login.html.erb.skel +11 -0
  37. data/lib/harbor/generator/skeletons/basic/log/development.log +0 -0
  38. data/lib/harbor/hooks.rb +105 -0
  39. data/lib/harbor/json_cookies.rb +37 -0
  40. data/lib/harbor/layouts.rb +61 -0
  41. data/lib/harbor/locale.rb +50 -0
  42. data/lib/harbor/locales.txt +22 -0
  43. data/lib/harbor/logging.rb +39 -0
  44. data/lib/harbor/logging/appenders/email.rb +84 -0
  45. data/lib/harbor/logging/request_logger.rb +34 -0
  46. data/lib/harbor/mail_servers/abstract.rb +12 -0
  47. data/lib/harbor/mail_servers/sendmail.rb +19 -0
  48. data/lib/harbor/mail_servers/smtp.rb +25 -0
  49. data/lib/harbor/mail_servers/test.rb +17 -0
  50. data/lib/harbor/mailer.rb +96 -0
  51. data/lib/harbor/messages.rb +17 -0
  52. data/lib/harbor/mime.rb +206 -0
  53. data/lib/harbor/plugin.rb +52 -0
  54. data/lib/harbor/plugin_list.rb +38 -0
  55. data/lib/harbor/request.rb +138 -0
  56. data/lib/harbor/response.rb +281 -0
  57. data/lib/harbor/router.rb +112 -0
  58. data/lib/harbor/script.rb +155 -0
  59. data/lib/harbor/session.rb +75 -0
  60. data/lib/harbor/session/abstract.rb +27 -0
  61. data/lib/harbor/session/cookie.rb +17 -0
  62. data/lib/harbor/shellwords.rb +26 -0
  63. data/lib/harbor/test/mailer.rb +10 -0
  64. data/lib/harbor/test/request.rb +28 -0
  65. data/lib/harbor/test/response.rb +17 -0
  66. data/lib/harbor/test/session.rb +11 -0
  67. data/lib/harbor/test/test.rb +22 -0
  68. data/lib/harbor/version.rb +3 -0
  69. data/lib/harbor/view.rb +89 -0
  70. data/lib/harbor/view_context.rb +134 -0
  71. data/lib/harbor/view_context/helpers.rb +7 -0
  72. data/lib/harbor/view_context/helpers/cache.rb +77 -0
  73. data/lib/harbor/view_context/helpers/form.rb +34 -0
  74. data/lib/harbor/view_context/helpers/html.rb +26 -0
  75. data/lib/harbor/view_context/helpers/text.rb +120 -0
  76. data/lib/harbor/view_context/helpers/url.rb +11 -0
  77. data/lib/harbor/xml_view.rb +57 -0
  78. data/lib/harbor/zipped_io.rb +203 -0
  79. data/lib/vendor/cloudfiles-1.3.0/cloudfiles.rb +77 -0
  80. data/lib/vendor/cloudfiles-1.3.0/cloudfiles/authentication.rb +46 -0
  81. data/lib/vendor/cloudfiles-1.3.0/cloudfiles/connection.rb +280 -0
  82. data/lib/vendor/cloudfiles-1.3.0/cloudfiles/container.rb +260 -0
  83. data/lib/vendor/cloudfiles-1.3.0/cloudfiles/storage_object.rb +253 -0
  84. metadata +155 -0
@@ -0,0 +1,17 @@
1
+ module Harbor
2
+ module Test
3
+ class Response < Harbor::Response
4
+
5
+ attr_accessor :request
6
+
7
+ ##
8
+ # We redefine Harbor::Response.initialize(request) with an empty arg
9
+ # variant for use with a container.
10
+ ##
11
+ def initialize
12
+ super(nil)
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,11 @@
1
+ module Harbor
2
+ module Test
3
+ class Session < Harbor::Session
4
+
5
+ def initialize(session_data = {})
6
+ @data = session_data
7
+ end
8
+
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,22 @@
1
+ module Harbor
2
+ module Test
3
+
4
+ require Pathname(__FILE__).dirname + "request"
5
+ require Pathname(__FILE__).dirname + "response"
6
+ require Pathname(__FILE__).dirname + "session"
7
+ require Pathname(__FILE__).dirname + "mailer"
8
+
9
+ def assert_redirect(response)
10
+ assert_equal 303, response.status, "Expected Response#status 303 but was #{response.status}"
11
+ end
12
+
13
+ def assert_success(response)
14
+ assert_equal 200, response.status, "Expected Response#status 200 but was #{response.status}"
15
+ end
16
+
17
+ def assert_unauthorized(response)
18
+ assert_equal 401, response.status, "Expected Response#status 401 but was #{response.status}"
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,3 @@
1
+ module Harbor
2
+ VERSION = "0.16.1"
3
+ end
@@ -0,0 +1,89 @@
1
+ require "rubygems"
2
+
3
+ gem "erubis"
4
+ require "erubis"
5
+
6
+ require Pathname(__FILE__).dirname + "view_context"
7
+ require Pathname(__FILE__).dirname + "layouts"
8
+ require Pathname(__FILE__).dirname + "plugin_list"
9
+
10
+ module Harbor
11
+ class View
12
+
13
+ class LayoutNotFoundError < StandardError
14
+ def initialize(name)
15
+ super("Layout #{name.inspect} not found")
16
+ end
17
+ end
18
+
19
+ def self.path
20
+ @path ||= []
21
+ end
22
+
23
+ def self.layouts
24
+ @layouts ||= Harbor::Layouts.new
25
+ end
26
+
27
+ def self.plugins(key)
28
+ @plugins ||= Hash.new { |h, k| h[k] = PluginList.new }
29
+
30
+ @plugins[key.to_s.gsub(/^\/+/, '')]
31
+ end
32
+
33
+ @cache_templates = false
34
+ def self.cache_templates?
35
+ @cache_templates
36
+ end
37
+
38
+ def self.cache_templates!
39
+ @cache_templates = true
40
+ end
41
+
42
+ def self.exists?(filename)
43
+ self.path.detect { |dir| ::File.file?(dir + filename) }
44
+ end
45
+
46
+ attr_accessor :content_type, :context, :extension, :path
47
+
48
+ def initialize(view, context = {})
49
+ @content_type = "text/html"
50
+ @extension = ".html.erb"
51
+ @context = context.is_a?(ViewContext) ? context : ViewContext.new(self, context)
52
+ @filename = ::File.extname(view) == "" ? (view + @extension) : view
53
+ end
54
+
55
+ def supports_layouts?
56
+ true
57
+ end
58
+
59
+ def content
60
+ @content ||= _erubis_render(@context)
61
+ end
62
+
63
+ def to_s(layout = nil)
64
+ layout = self.class.layouts.match(@filename) if layout == :search
65
+
66
+ layout ? View.new(layout, @context.merge(:content => content)).to_s : content
67
+ end
68
+
69
+ private
70
+
71
+ def _erubis_render(context)
72
+ @path ||= self.class.exists?(@filename)
73
+ raise "Could not find '#{@filename}' in #{self.class.path.inspect}" unless @path
74
+
75
+ full_path = @path + @filename
76
+
77
+ if self.class.cache_templates?
78
+ (self.class.__templates[full_path] ||= Erubis::FastEruby.new(::File.read(full_path), :filename => full_path)).evaluate(context)
79
+ else
80
+ Erubis::FastEruby.new(::File.read(full_path), :filename => full_path).evaluate(context)
81
+ end
82
+ end
83
+
84
+ def self.__templates
85
+ @__templates ||= {}
86
+ end
87
+ end
88
+
89
+ end
@@ -0,0 +1,134 @@
1
+ module Harbor
2
+ class ViewContext
3
+ require Pathname(__FILE__).dirname + "view_context/helpers"
4
+
5
+ include Helpers::Form
6
+ include Helpers::Text
7
+ include Helpers::Html
8
+ include Helpers::Url
9
+ include Helpers::Cache
10
+
11
+ attr_accessor :view, :keys
12
+
13
+ def initialize(view, variables)
14
+ @view = view
15
+ @keys = Set.new
16
+
17
+ merge(variables)
18
+ end
19
+
20
+ def render(partial, variables = nil)
21
+ context = to_hash
22
+
23
+ result = View.new(partial, merge(variables)).to_s
24
+
25
+ replace(context)
26
+
27
+ result
28
+ end
29
+
30
+ def plugin(name, variables = {})
31
+ if (plugin_list = Harbor::View::plugins(name)).any?
32
+ plugin_list.map do |plugin|
33
+ Plugin::prepare(plugin, self, variables)
34
+ end
35
+ else
36
+ []
37
+ end
38
+ end
39
+
40
+ def locale
41
+ @locale ||= Harbor::Locale.default
42
+ end
43
+
44
+ def capture(*args, &block)
45
+ # get the buffer from the block's binding
46
+ buffer = _erb_buffer( block.binding ) rescue nil
47
+
48
+ # If there is no buffer, just call the block and get the contents
49
+ if buffer.nil?
50
+ block.call(*args)
51
+ # If there is a buffer, execute the block, then extract its contents
52
+ else
53
+ pos = buffer.length
54
+
55
+ block.call(*args)
56
+
57
+ # extract the block
58
+ data = buffer[pos..-1]
59
+
60
+ # replace it in the original with empty string
61
+ buffer[pos..-1] = ''
62
+
63
+ data
64
+ end
65
+ end
66
+
67
+ def [](key)
68
+ instance_variable_get("@#{key}")
69
+ end
70
+
71
+ def []=(key, value)
72
+ @keys << key
73
+ instance_variable_set("@#{key}", value)
74
+ end
75
+
76
+ def merge(variables)
77
+ variables.each do |key, value|
78
+ self[key] = value
79
+ end if variables
80
+
81
+ self
82
+ end
83
+
84
+ def each
85
+ keys.each { |key| yield(key, self[key]) }
86
+ end
87
+
88
+ def clear
89
+ keys.each { |key| remove_instance_variable("@#{key}") }
90
+ keys.clear
91
+
92
+ self
93
+ end
94
+
95
+ def replace(variables)
96
+ clear
97
+ merge(variables)
98
+ end
99
+
100
+ def to_hash
101
+ hash = {}
102
+ keys.each { |key| hash[key] = self[key] }
103
+ hash
104
+ end
105
+
106
+ private
107
+
108
+ def request
109
+ @request
110
+ end
111
+
112
+ def response
113
+ @response
114
+ end
115
+
116
+ def _erb_buffer( the_binding ) # :nodoc:
117
+ eval( "_buf", the_binding, __FILE__, __LINE__)
118
+ end
119
+
120
+ ##
121
+ # Useful when you need to output content to the buffer.
122
+ #
123
+ # def wrap_with_p_tag(&block)
124
+ # with_buffer(block) do |buffer|
125
+ # buffer << "<p>" << capture(&block) << "</p>"
126
+ # end
127
+ # end
128
+ ##
129
+ def with_buffer(block)
130
+ yield(_erb_buffer(block.binding))
131
+ end
132
+
133
+ end
134
+ end
@@ -0,0 +1,7 @@
1
+ module Harbor::ViewContext::Helpers
2
+ autoload :Form, (Pathname(__FILE__).dirname + "helpers/form").to_s
3
+ autoload :Text, (Pathname(__FILE__).dirname + "helpers/text").to_s
4
+ autoload :Html, (Pathname(__FILE__).dirname + "helpers/html").to_s
5
+ autoload :Url, (Pathname(__FILE__).dirname + "helpers/url").to_s
6
+ autoload :Cache, (Pathname(__FILE__).dirname + "helpers/cache").to_s
7
+ end
@@ -0,0 +1,77 @@
1
+ ##
2
+ # Set Harbor::View.cache equal to a supported Cache Store for use
3
+ # in the ViewContext#cache helper.
4
+ ##
5
+ class Harbor::View
6
+
7
+ class << self
8
+
9
+ def cache=(value)
10
+ if value && !value.is_a?(Harbor::Cache)
11
+ raise ArgumentError.new("Harbor::View.cache must be nil or an instance of Harbor::Cache")
12
+ end
13
+
14
+ @__cache__ = value
15
+ end
16
+
17
+ def cache
18
+ @__cache__
19
+ end
20
+
21
+ end
22
+
23
+ end
24
+
25
+ ##
26
+ # Cache helper that provides fragment-caching
27
+ ##
28
+ module Harbor::ViewContext::Helpers::Cache
29
+
30
+ class CacheRenderError < StandardError
31
+ def initialize(inner_error, content_item)
32
+ @inner_error = inner_error
33
+ @content_item = content_item
34
+ end
35
+
36
+ def to_s
37
+ "#{@content_item.class.name}:#{@content_item.inspect}\n\t#{@inner_error.message}"
38
+ end
39
+
40
+ def inspect
41
+ "<#CacheRenderError content_item=#{@content_item.class.name}:#{@content_item.inspect} inner_error=#{@inner_error.inspect} backtrace=#{@inner_error.backtrace.join("\n\t")}>"
42
+ end
43
+ end
44
+
45
+ ##
46
+ # Caches the result of a block using the given TTL and maximum_age values.
47
+ # If no ttl is given, a default of 30 minutes is used. If no maximum_age value is given
48
+ # the item will expire after Time.now + ttl. If a maximum_age is specified, "get" requests
49
+ # to the cache for a given key will push the expiration time up for the item by the TTL, until
50
+ # Time.now + TTL is equal to or greater than the cache-insertion-time + maximum_age.
51
+ ##
52
+ def cache(key, ttl = 30 * 60, max_age = nil, &generator)
53
+ store = @cache_store || Harbor::View.cache
54
+
55
+ if store.nil?
56
+ raise ArgumentError.new("Cache Store Not Defined. Please set Harbor::View.cache to your desired cache store.")
57
+ end
58
+
59
+ content = if item = store.get(key)
60
+ begin
61
+ item.content
62
+ rescue => e
63
+ raise CacheRenderError.new(e, item)
64
+ end
65
+ else
66
+ data = capture(&generator)
67
+ store.put(key, data, ttl, max_age)
68
+
69
+ data
70
+ end
71
+
72
+ with_buffer(generator) do |buffer|
73
+ buffer << content
74
+ end
75
+ end
76
+
77
+ end
@@ -0,0 +1,34 @@
1
+ module Harbor::ViewContext::Helpers::Form
2
+
3
+ ##
4
+ # Form helper which abstracts away setting necessary information:
5
+ # 1. If no enctype is passed, and a file input is found, uses
6
+ # multipart/form-data.
7
+ # 2. Creates a hidden input for setting _method when method option
8
+ # is put or delete.
9
+ ##
10
+ def form(action, options = {}, &block)
11
+ method = options.delete(:method) || :post
12
+
13
+ get = method == :get
14
+ post = method == :post
15
+
16
+ body = capture(&block)
17
+
18
+ enctype = options.delete(:enctype) || (body =~ /\<input[^>]+?type\=["']file["']/ ? "multipart/form-data" : nil)
19
+
20
+ with_buffer(block) do |buffer|
21
+ buffer << "<form action=\"#{action}\" method=\"#{get ? "get" : "post"}\""
22
+ buffer << " enctype=\"#{enctype}\"" if enctype
23
+ buffer << " #{options.map { |k, v| "#{k}=\"#{v}\"" }.join(" ")}" unless options.empty?
24
+ buffer << ">\n"
25
+
26
+ unless get || post
27
+ buffer << " <input type=\"hidden\" name=\"_method\" value=\"#{method}\">\n"
28
+ end
29
+
30
+ buffer << body
31
+ buffer << "</form>\n"
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,26 @@
1
+ module Harbor::ViewContext::Helpers::Html
2
+
3
+ ##
4
+ # Takes a flat array and yields the data properly separated into columns.
5
+ ##
6
+ def split_into_columns(data, columns) #:yields: column
7
+ return if data.empty?
8
+
9
+ per_column = (data.size / columns.to_f).ceil
10
+
11
+ columns.times do |i|
12
+ yield data[i*per_column, per_column]
13
+ end
14
+ end
15
+
16
+ def split_into_groups(data, groups) #:yields: group
17
+ return if data.empty?
18
+
19
+ rows = (data.size / groups.to_f).ceil
20
+
21
+ rows.times do |i|
22
+ yield data[i*groups, groups]
23
+ end
24
+ end
25
+
26
+ end
@@ -0,0 +1,120 @@
1
+ ##
2
+ # Text helper which provides common routines such as HTML escaping,
3
+ # or truncating (previewing) long pieces of text (such as photo captions).
4
+ ##
5
+ module Harbor::ViewContext::Helpers::Text
6
+
7
+ # Querystring escape +value+
8
+ def q(value)
9
+ # TODO: Remove external dependency!
10
+ Rack::Utils::escape(value)
11
+ end
12
+
13
+ # HTML escape +value+
14
+ def h(value, default = nil)
15
+ # TODO: Remove external dependency!
16
+ Rack::Utils::escape_html(value.blank? ? default : value)
17
+ end
18
+
19
+ ##
20
+ # Truncates an object to the specified character count, appending the specified
21
+ # trailing text. The character count includes the length of the trailer. HTML entities
22
+ # are counted as 1 character in trailing.
23
+ #
24
+ # truncate("Lorem ipsum dolor sit amet, consectetur") # => "Lorem ipsum dolor sit amet, c&hellip;"
25
+ # truncate("Lorem ipsum dolor sit amet, consectetur", 20) # => "Lorem ipsum dolor s&hellip;"
26
+ # truncate("Lorem ipsum dolor sit amet, consectetur", 20, "...") # => "Lorem ipsum dolor..."
27
+ #
28
+ ##
29
+ def truncate(value, character_count = 30, trailing = "&hellip;")
30
+ unless character_count.is_a?(Integer)
31
+ raise ArgumentError.new(
32
+ "Harbor::ViewContext::Helpers::Text#truncate[character_count] must be an Integer, was #{character_count.inspect}"
33
+ )
34
+ end
35
+
36
+ unless character_count > 0
37
+ raise ArgumentError.new(
38
+ "Harbor::ViewContext::Helpers::Text#truncate[character_count] must be greater than zero, was #{character_count.inspect}."
39
+ )
40
+ end
41
+
42
+ unless trailing.is_a?(String)
43
+ raise ArgumentError.new(
44
+ "Harbor::ViewContext::Helpers::Text#truncate[trailing] must be a String, was #{trailing.inspect}"
45
+ )
46
+ end
47
+
48
+ if value.nil?
49
+ ""
50
+ else
51
+ string_form = value.to_s
52
+
53
+ if string_form.nil? || string_form.empty?
54
+ ""
55
+ elsif string_form.size <= character_count
56
+ string_form
57
+ else
58
+ # The Regexp match here is to determine if the +trailing+ value is an HTML entity code,
59
+ # in which case we assume it's length is 1, or a textual value, in which case we use the
60
+ # actual size.
61
+ string_form[0, character_count - (trailing =~ /\&\w+\;/ ? 1 : trailing.size)] + trailing
62
+ end
63
+ end
64
+ end
65
+
66
+ ##
67
+ # Truncates an object on the nearest word to the specified character count, appending the specified
68
+ # trailing text.
69
+ #
70
+ # truncate_on_words("Lorem ipsum dolor sit amet, consectetur") # => "Lorem ipsum dolor sit amet&hellip;"
71
+ # truncate_on_words("Lorem ipsum dolor sit amet, consectetur", 20) # => "Lorem ipsum dolor&hellip;"
72
+ # truncate_on_words("Lorem ipsum dolor sit amet, consectetur", 20, "...") # => "Lorem ipsum dolor..."
73
+ #
74
+ # The truncation will always look backwards unless the forward word boundary is within 5% of the specified
75
+ # character count. Thus:
76
+ #
77
+ # truncate_on_words("Lorem ipsum dolor sit amet, consectetur est.", 38) # => "Lorem ipsum dolor sit amet, consectetur..."
78
+ #
79
+ ##
80
+ def truncate_on_words(value, character_count = 30, trailing = "&hellip;")
81
+ unless character_count.is_a?(Integer)
82
+ raise ArgumentError.new(
83
+ "Harbor::ViewContext::Helpers::Text#truncate_on_words[character_count] must be an Integer, was #{character_count.inspect}"
84
+ )
85
+ end
86
+
87
+ unless character_count > 0
88
+ raise ArgumentError.new(
89
+ "Harbor::ViewContext::Helpers::Text#truncate_on_words[character_count] must be greater than zero, was #{character_count.inspect}."
90
+ )
91
+ end
92
+
93
+ unless trailing.is_a?(String)
94
+ raise ArgumentError.new(
95
+ "Harbor::ViewContext::Helpers::Text#truncate_on_words[trailing] must be a String, was #{trailing.inspect}"
96
+ )
97
+ end
98
+
99
+ return "" if value.nil?
100
+
101
+ truncated_text = value.to_s.dup
102
+ text_length = truncated_text.length
103
+
104
+ return value if character_count >= text_length
105
+
106
+ leftover = truncated_text.slice!(character_count, text_length)
107
+
108
+ if (index = leftover.index(/\W|$/)) && index < (character_count * 0.05).ceil
109
+ truncated_text << leftover.slice(0, index)
110
+ else
111
+ truncated_text = truncated_text[0, truncated_text.rindex(/\W/)]
112
+ end
113
+
114
+ # Remove any trailing punctuation.
115
+ truncated_text.slice!(truncated_text.length - 1) if truncated_text[truncated_text.length - 1, 1] =~ /\W/
116
+
117
+ truncated_text + trailing
118
+ end
119
+
120
+ end