wire-framework 0.1.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.
@@ -0,0 +1,91 @@
1
+ require_relative '../render'
2
+
3
+ module Render
4
+ # Page builds the a page that is presented directly to a user
5
+ # @author Bryan T. Meyers
6
+ module Page
7
+ include Render
8
+ extend self
9
+
10
+ # Render a full template, handling the gathering of additional Sources
11
+ # @param [Array] actions the allowed actions for this URI
12
+ # @param [Hash] context the context for this request
13
+ # @param [Tilt::Template] template a pre-loaded Tilt template to render
14
+ # @param [String] content the content to render into the page
15
+ # @return [Response] a Rack Response triplet, or status code
16
+ def render_template(actions, context, template, content)
17
+ if template[:path]
18
+ hash = { actions: actions, context: context, content: content }
19
+ template[:sources].each do |k, s|
20
+ uri = "http://#{context.app[:remote_host]}/#{s[:uri]}"
21
+ case s[:key]
22
+ when :user
23
+ uri += "/#{context.user}"
24
+ when :resource
25
+ uri += "/#{context.uri[2]}"
26
+ end
27
+ begin
28
+ temp = RestClient.get uri
29
+ rescue RestClient::ResourceNotFound
30
+ temp = nil
31
+ end
32
+ hash[k] = temp
33
+ end
34
+ message = template[:path].render(self, hash)
35
+ if template[:use_layout]
36
+ message = render_template(actions, context, $apps[:global][:template], message)
37
+ end
38
+ else
39
+ message = 'Invalid Template'
40
+ end
41
+ message
42
+ end
43
+
44
+ # Render a page to its final form
45
+ # @param [Array] actions the allowed actions for this URI
46
+ # @param [Hash] context the context for this request
47
+ # @param [Symbol] specific the kind of read to perform
48
+ # @return [Response] a Rack Response triplet, or status code
49
+ def do_read(actions, context, specific)
50
+ template = context.app[:template]
51
+ resource = context.uri[2]
52
+ message = 'Resource not specified'
53
+ headers = {}
54
+ if resource
55
+ begin
56
+ result = forward(specific, context)
57
+ if template
58
+ message = render_template(actions, context, template, result)
59
+ else
60
+ headers['Content-Type'] = result.headers[:content_type]
61
+ message = [200, headers, [result.to_str]]
62
+ end
63
+ rescue RestClient::ResourceNotFound
64
+ message = 404
65
+ end
66
+ end
67
+ message
68
+ end
69
+
70
+ # Proxy method used when routing
71
+ # @param [Array] actions the allowed actions for this URI
72
+ # @param [Hash] context the context for this request
73
+ # @return [Response] a Rack Response triplet, or status code
74
+ def self.invoke(actions, context)
75
+ case context.action
76
+ when :create
77
+ forward(:create, context)
78
+ when :read
79
+ if context.uri[3]
80
+ do_read(actions, context, :read)
81
+ else
82
+ do_read(actions, context, :readAll)
83
+ end
84
+ when :update
85
+ forward(:update, context)
86
+ else
87
+ 405
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,110 @@
1
+ require_relative '../render'
2
+
3
+ module Render
4
+ # Partials are URI mapped renderers which generate only a piece of a document
5
+ # @author Bryan T.Meyers
6
+ module Partial
7
+ extend Render
8
+
9
+ # DSL method to enable forwarding to remote
10
+ # @return [void]
11
+ def self.use_forward
12
+ $current_resource[:forward] = true
13
+ end
14
+
15
+ # DSL method to pull in Source like objects
16
+ # @param [Symbol] name the key for this item
17
+ # @param [Hash] path the remote sub-URI for this item
18
+ # @return [void]
19
+ def self.extra(name, path)
20
+ unless $current_resource[:sources]
21
+ $current_resource[:sources] = {}
22
+ end
23
+ $current_resource[:sources][name] = path
24
+ end
25
+
26
+ # Read a listing and render to HTML
27
+ # @param [Array] actions the allowed actions for this URI
28
+ # @param [Hash] context the context for this request
29
+ # @return [Response] a Rack Response triplet, or status code
30
+ def self.do_read_all(actions, context)
31
+ resource = context.uri[2]
32
+ begin
33
+ mime = ''
34
+ body = ''
35
+ if context.resource[:forward]
36
+ response = forward(:readAll, context)
37
+ mime = response.headers[:content_type]
38
+ body = response.body
39
+ else
40
+ body = 401
41
+ end
42
+
43
+ template = context.resource[:multiple]
44
+ hash = { actions: actions, resource: resource, mime: mime, response: body }
45
+ if context.resource[:sources]
46
+ context.resource[:sources].each do |k, v|
47
+ hash[k] = RestClient.get("http://#{context.app[:remote_host]}/#{v}")
48
+ end
49
+ end
50
+ mime = 'text/html'
51
+ if template
52
+ [200, { 'Content-Type' => mime }, [template.render(self, hash)]]
53
+ else
54
+ [200, { 'Content-Type' => mime }, [body]]
55
+ end
56
+ rescue RestClient::ResourceNotFound
57
+ 404
58
+ end
59
+ end
60
+
61
+ # Read a Partial and render it to HTML
62
+ # @param [Array] actions the allowed actions for this URI
63
+ # @param [Hash] context the context for this request
64
+ # @return [Response] a Rack Response triplet, or status code
65
+ def self.do_read(actions, context)
66
+ app = context.app[:uri]
67
+ resource = context.uri[2]
68
+ begin
69
+ response = forward(:read, context)
70
+ mime = response.headers[:content_type]
71
+ template = context.resource[:single]
72
+ id = context.uri[3...context.uri.length].join('/')
73
+ hash = { actions: actions, app: app, id: id, resource: resource, mime: mime, response: response.body }
74
+ if context.resource[:sources]
75
+ context.resource[:sources].each do |k, v|
76
+ hash[k] = RestClient.get("http://#{context.app[:remote_host]}/#{v}")
77
+ end
78
+ end
79
+ if template
80
+ [200, { 'Content-Type' => 'text/html' }, [template.render(self, hash)]]
81
+ else
82
+ [200, { 'Content-Type' => 'text/plain' }, [response.body]]
83
+ end
84
+ rescue RestClient::ResourceNotFound
85
+ 404
86
+ end
87
+ end
88
+
89
+ # Proxy method used when routing
90
+ # @param [Array] actions the allowed actions for this URI
91
+ # @param [Hash] context the context for this request
92
+ # @return [Response] a Rack Response triplet, or status code
93
+ def self.invoke(actions, context)
94
+ case context.action
95
+ when :create
96
+ forward(:create, context)
97
+ when :read
98
+ if context.uri[3]
99
+ do_read(actions, context)
100
+ else
101
+ do_read_all(actions, context)
102
+ end
103
+ when :update
104
+ forward(:update, context)
105
+ else
106
+ 403
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,52 @@
1
+ require_relative '../render'
2
+
3
+ module Render
4
+ # Style uses Tilt to render and serve stylesheets
5
+ # @author Bryan T. Meyers
6
+ module Style
7
+ extend Render
8
+
9
+ # DSL method to create a style
10
+ # @param [String] resource the sub-URI for this style
11
+ # @param [Hash] path the file location of the stylesheet
12
+ # @return [void]
13
+ def self.style(resource, path)
14
+ unless $current_app[:styles]
15
+ $current_app[:styles] = {}
16
+ end
17
+ $current_app[:styles][resource] = path.nil? ? nil : Tilt.new(path, 1, { ugly: true }).render
18
+ end
19
+
20
+ # Render a stylesheet to CSS
21
+ # @param [Hash] context the context for this request
22
+ # @return [Response] a Rack Response triplet, or status code
23
+ def self.do_read_all(context)
24
+ begin
25
+ resource = context.uri[2]
26
+ template = context.app[:styles][resource]
27
+ headers = {}
28
+ if template
29
+ headers['Content-Type'] = 'text/css'
30
+ [200, headers, [template]]
31
+ else
32
+ 500
33
+ end
34
+ rescue RestClient::ResourceNotFound
35
+ 404
36
+ end
37
+ end
38
+
39
+ # Proxy method used when routing
40
+ # @param [Array] actions the allowed actions for this URI
41
+ # @param [Hash] context the context for this request
42
+ # @return [Response] a Rack Response triplet, or status code
43
+ def self.invoke(actions, context)
44
+ case context.action
45
+ when :read
46
+ do_read_all(context)
47
+ else
48
+ 403
49
+ end
50
+ end
51
+ end
52
+ end
data/lib/app/repo.rb ADDED
@@ -0,0 +1,148 @@
1
+ require 'awesome_print'
2
+ require 'base64'
3
+ require_relative '../wire'
4
+ require_relative 'repo/svn'
5
+
6
+ # Repo is a Wire::App for accessing versioned content
7
+ # @author Bryan T. Meyers
8
+ module Repo
9
+
10
+ # Select the location of the repositories
11
+ # @param [Symbol] path location of the repositories
12
+ # @return [void]
13
+ def repos(path)
14
+ $current_app[:repos_path] = path
15
+ end
16
+
17
+ # Select the render template for file listings
18
+ # @param [Symbol] path location of the Tilt compatible template
19
+ # @return [void]
20
+ def listing(path)
21
+ $current_app[:template] = Tilt.new(path, 1, { ugly: true })
22
+ end
23
+
24
+ # Select the sub-directory for web-serveable content
25
+ # @param [Symbol] path the sub-directory path
26
+ # @return [void]
27
+ def web_folder(path)
28
+ $current_app[:web] = path
29
+ end
30
+
31
+ # Create a new Repo
32
+ # @param [Hash] context the context for this request
33
+ # @return [Response] status code
34
+ def do_create(context)
35
+ path = context.app[:repos_path]
36
+ resource = context.uri[2]
37
+ if path
38
+ if Dir.exist?("#{path}/#{resource}")
39
+ 401
40
+ else
41
+ do_create_file(path, resource)
42
+ end
43
+ else
44
+ 400
45
+ end
46
+ end
47
+
48
+ # Get the a root directory listing
49
+ # @param [Hash] context the context for this request
50
+ # @return [Response] the listing, or status code
51
+ def do_read_all(context)
52
+ resource = context.uri[2]
53
+ referrer = context.referer
54
+ repos = context.app[:repos_path]
55
+ web = context.app[:web]
56
+ mime = 'text/html'
57
+ list = do_read_listing(web, repos, resource)
58
+ if list == 404
59
+ return 404
60
+ end
61
+ template = context.app[:template]
62
+ list = template.render(self, list: list, resource: resource, id: '', referrer: referrer)
63
+ headers = {}
64
+ headers['Content-Type'] = mime
65
+ headers['Cache-Control'] = 'public'
66
+ headers['Expires'] = "#{(Time.now + 1000).utc}"
67
+ [200, headers, [list]]
68
+ end
69
+
70
+ # Get the a single file or directory listing
71
+ # @param [Hash] context the context for this request
72
+ # @return [Response] the file, listing, or status code
73
+ def do_read(context)
74
+ path = context.uri[2]
75
+ referrer = context.referer
76
+ repos = context.app[:repos_path]
77
+ web = context.app[:web]
78
+ rev = context.query[:rev]
79
+ id = context.uri[3...context.uri.length].join('/')
80
+ info = do_read_info(rev, web, repos, path, id)
81
+ if info == 404
82
+ return 404
83
+ end
84
+ type = info[:@kind]
85
+ if type.eql? 'dir'
86
+ mime = 'text/html'
87
+ list = do_read_listing(web, repos, path, id)
88
+ template = context.app[:template]
89
+ body = template.render(self, list: list, resource: path, id: id, referrer: referrer)
90
+ else
91
+ body = do_read_file(rev, web, repos, path, id)
92
+ if body == 500
93
+ return body
94
+ end
95
+ mime = do_read_mime(rev, web, repos, path, id)
96
+ end
97
+ headers = {}
98
+ headers['Content-Type'] = mime
99
+ headers['Cache-Control'] = 'public'
100
+ headers['Expires'] = "#{(Time.now + 1000).utc}"
101
+ [200, headers, [body]]
102
+ end
103
+
104
+ # Update the a single file
105
+ # @param [Hash] context the context for this request
106
+ # @return [Response] status code
107
+ def do_update(context)
108
+ path = context.uri[2]
109
+ repos = context.app[:repos_path]
110
+ web = context.app[:web]
111
+ content = context.json
112
+ id = context.uri[3...context.uri.length].join('/')
113
+ if content[:file]
114
+ file = content[:file][:content].match(/base64,(.*)/)[1]
115
+ file = Base64.decode64(file)
116
+ if context.query[:type]
117
+ mime = context.query[:type]
118
+ else
119
+ mime = content[:file][:mime]
120
+ end
121
+ do_update_file(web, repos, path, id, file, content[:message], mime, context.user)
122
+ else
123
+ do_update_file(web, repos, path, id, URI.unescape(content[:updated]), content[:message], context.query[:type], context.user)
124
+ end
125
+ end
126
+
127
+ # Proxy method used when routing
128
+ # @param [Array] actions the allowed actions for this URI
129
+ # @param [Hash] context the context for this request
130
+ # @return [Response] a Rack Response triplet, or status code
131
+ def invoke(actions, context)
132
+ return 404 unless context.uri[2]
133
+ case context.action
134
+ when :create
135
+ do_create(context)
136
+ when :read
137
+ if context.uri[3]
138
+ do_read(context)
139
+ else
140
+ do_read_all(context)
141
+ end
142
+ when :update
143
+ do_update(context)
144
+ else
145
+ 403
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,177 @@
1
+ require_relative '../repo'
2
+ require 'nori'
3
+
4
+ # Force Nori to convert tag names to Symbols
5
+ $nori = Nori.new :convert_tags_to => lambda { |tag| tag.snakecase.to_sym }
6
+
7
+ module Repo
8
+ # Repo::SVN is a connector for svnserve
9
+ # @author Bryan T. Meyers
10
+ module SVN
11
+ extend Wire::App
12
+ extend Wire::Resource
13
+ extend Repo
14
+
15
+ # Make a new SVN repo
16
+ # @param [String] path the path to the repositories
17
+ # @param [String] repo the new repo name
18
+ # @return [Integer] status code
19
+ def self.do_create_file(path, repo)
20
+ result = 200
21
+ `svnadmin create #{path}/#{repo}`
22
+ if $?.exitstatus != 0
23
+ return 500
24
+ end
25
+
26
+ if $?.exitstatus != 0
27
+ 500
28
+ else
29
+ result
30
+ end
31
+ end
32
+
33
+ # Read a single file
34
+ # @param [String] rev the revision number to access
35
+ # @param [String] web the subdirectory for web content
36
+ # @param [String] path the path to the repositories
37
+ # @param [String] repo the new repo name
38
+ # @param [String] id the relative path to the file
39
+ # @return [String] the file
40
+ def self.do_read_file(rev, web, path, repo, id)
41
+ @options = "--username=#{$environment[:repos_user]} --password=#{$environment[:repos_password]}"
42
+ if rev.nil?
43
+ rev = 'HEAD'
44
+ end
45
+ if web.nil?
46
+ body = `svn cat #{@options} -r #{rev} 'svn://localhost/#{repo}/#{id}'`
47
+ else
48
+ body = `svn cat #{@options} -r #{rev} 'svn://localhost/#{repo}/#{web}/#{id}'`
49
+ end
50
+
51
+ if $?.success?
52
+ body
53
+ else
54
+ 500
55
+ end
56
+ end
57
+
58
+ # Read a directory listing
59
+ # @param [String] web the subdirectory for web content
60
+ # @param [String] path the path to the repositories
61
+ # @param [String] repo the new repo name
62
+ # @param [String] id the relative path to the file
63
+ # @return [Array] the directory listing
64
+ def self.do_read_listing(web, path, repo, id = nil)
65
+ @options = "--username=#{$environment[:repos_user]} --password=#{$environment[:repos_password]}"
66
+ if web.nil?
67
+ if id.nil?
68
+ list = `svn list #{@options} --xml 'svn://localhost/#{repo}'`
69
+ else
70
+ list = `svn list #{@options} --xml 'svn://localhost/#{repo}/#{id}'`
71
+ end
72
+ else
73
+ if id.nil?
74
+ list = `svn list #{@options} --xml 'svn://localhost/#{repo}/#{web}'`
75
+ else
76
+ list = `svn list #{@options} --xml 'svn://localhost/#{repo}/#{web}/#{id}'`
77
+ end
78
+ end
79
+ unless $?.exitstatus == 0
80
+ return 404
81
+ end
82
+ list = $nori.parse(list)
83
+ list[:lists][:list][:entry]
84
+ end
85
+
86
+ # Read Metadata for a single file
87
+ # @param [String] rev the revision number to access
88
+ # @param [String] web the subdirectory for web content
89
+ # @param [String] path the path to the repositories
90
+ # @param [String] repo the new repo name
91
+ # @param [String] id the relative path to the file
92
+ # @return [Hash] the metadata
93
+ def self.do_read_info(rev, web, path, repo, id)
94
+ @options = "--username=#{$environment[:repos_user]} --password=#{$environment[:repos_password]}"
95
+ if rev.nil?
96
+ rev = 'HEAD'
97
+ end
98
+ if web.nil?
99
+ info = `svn info #{@options} -r #{rev} --xml 'svn://localhost/#{repo}/#{id}'`
100
+ else
101
+ info = `svn info #{@options} -r #{rev} --xml 'svn://localhost/#{repo}/#{web}/#{id}'`
102
+ end
103
+
104
+ unless $?.exitstatus == 0
105
+ return 404
106
+ end
107
+ info = $nori.parse(info)
108
+ info[:info][:entry]
109
+ end
110
+
111
+ # Get a file's MIME type
112
+ # @param [String] rev the revision number to access
113
+ # @param [String] web the subdirectory for web content
114
+ # @param [String] path the path to the repositories
115
+ # @param [String] repo the new repo name
116
+ # @param [String] id the relative path to the file
117
+ # @return [String] the MIME type
118
+ def self.do_read_mime(rev, web, path, repo, id)
119
+ @options = "--username=#{$environment[:repos_user]} --password=#{$environment[:repos_password]}"
120
+ if rev.nil?
121
+ rev = 'HEAD'
122
+ end
123
+ if web.nil?
124
+ mime = `svn propget #{@options} -r #{rev} --xml svn:mime-type 'svn://localhost/#{repo}/#{id}'`
125
+ else
126
+ mime = `svn propget #{@options} -r #{rev} --xml svn:mime-type 'svn://localhost/#{repo}/#{web}/#{id}'`
127
+ end
128
+
129
+ unless $?.success?
130
+ return 500
131
+ end
132
+ mime = $nori.parse(mime)
133
+ if mime[:properties].nil?
134
+ 'application/octet-stream'
135
+ else
136
+ mime[:properties][:target][:property]
137
+ end
138
+ end
139
+
140
+ # Update a single file
141
+ # @param [String] web the subdirectory for web content
142
+ # @param [String] path the path to the repositories
143
+ # @param [String] repo the new repo name
144
+ # @param [String] id the relative path to the file
145
+ # @param [String] content the updated file
146
+ # @param [String] message the commit message
147
+ # @param [String] mime the mime-type to set
148
+ # @param [String] user the Author of this change
149
+ # @return [Integer] status code
150
+ def self.do_update_file(web, path, repo, id, content, message, mime, user)
151
+ @options = "--username=#{$environment[:repos_user]} --password=#{$environment[:repos_password]}"
152
+ status = 500
153
+ `svn checkout #{@options} svn://localhost/#{repo} /tmp/svn/#{repo}`
154
+ if $?.exitstatus == 0
155
+ if web.nil?
156
+ file_path = "/tmp/svn/#{repo}/#{id}"
157
+ else
158
+ file_path = "/tmp/svn/#{repo}/#{web}/#{id}"
159
+ end
160
+ file = File.open(file_path, 'w+')
161
+ file.syswrite(content)
162
+ file.close
163
+ `svn add #{file_path}`
164
+ `svn propset svn:mime-type #{mime} #{file_path}`
165
+ `svn commit #{@options} -m "#{message}" /tmp/svn/#{repo}`
166
+ if $?.exitstatus == 0
167
+ status = 200
168
+ end
169
+ info = `svn info /tmp/svn/#{repo}`
170
+ rev = info.match(/Last Changed Rev: (\d+)/)[1]
171
+ `svn propset --revprop -r #{rev} svn:author '#{user}' /tmp/svn/#{repo}`
172
+ end
173
+ `rm -R /tmp/svn/#{repo}`
174
+ status
175
+ end
176
+ end
177
+ end