sgfa 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.
- checksums.yaml +7 -0
- data/LICENSE.txt +674 -0
- data/README.txt +14 -0
- data/bin/sgfa +15 -0
- data/data/sgfa_web.css +240 -0
- data/lib/sgfa/binder.rb +627 -0
- data/lib/sgfa/binder_fs.rb +203 -0
- data/lib/sgfa/cli/binder.rb +160 -0
- data/lib/sgfa/cli/jacket.rb +299 -0
- data/lib/sgfa/cli.rb +36 -0
- data/lib/sgfa/demo/web_binders.rb +111 -0
- data/lib/sgfa/demo/web_css.rb +60 -0
- data/lib/sgfa/entry.rb +697 -0
- data/lib/sgfa/error.rb +95 -0
- data/lib/sgfa/history.rb +445 -0
- data/lib/sgfa/jacket.rb +556 -0
- data/lib/sgfa/jacket_fs.rb +136 -0
- data/lib/sgfa/lock_fs.rb +141 -0
- data/lib/sgfa/state_fs.rb +342 -0
- data/lib/sgfa/store_fs.rb +214 -0
- data/lib/sgfa/web/base.rb +225 -0
- data/lib/sgfa/web/binder.rb +1190 -0
- data/lib/sgfa.rb +37 -0
- metadata +68 -0
@@ -0,0 +1,225 @@
|
|
1
|
+
#
|
2
|
+
# Simple Group of Filing Applications
|
3
|
+
# Web interface common utilities
|
4
|
+
#
|
5
|
+
# Copyright (C) 2015 by Graham A. Field.
|
6
|
+
#
|
7
|
+
# See LICENSE.txt for licensing information.
|
8
|
+
#
|
9
|
+
# This program is distributed WITHOUT ANY WARRANTY; without even the
|
10
|
+
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
11
|
+
|
12
|
+
require 'rack'
|
13
|
+
require 'time'
|
14
|
+
|
15
|
+
require_relative '../error'
|
16
|
+
|
17
|
+
module Sgfa
|
18
|
+
|
19
|
+
|
20
|
+
#####################################################################
|
21
|
+
# Web based interface to Sgfa
|
22
|
+
module Web
|
23
|
+
|
24
|
+
|
25
|
+
#####################################################################
|
26
|
+
# Shared utilities for Web classes to inherit
|
27
|
+
class Base
|
28
|
+
|
29
|
+
HtmlPage =
|
30
|
+
"<!DOCTYPE html>\n" +
|
31
|
+
"<html>\n" +
|
32
|
+
"<head>\n" +
|
33
|
+
"<title>%s</title>\n" +
|
34
|
+
"<link rel='stylesheet' type='text/css' href='%s'>\n" +
|
35
|
+
"</head>\n" +
|
36
|
+
"<body>\n" +
|
37
|
+
"<div id='sgfa_web'>\n" +
|
38
|
+
"<div class='title_bar'>" +
|
39
|
+
"<div class='title'>%s</div><div class='user'>%s</div></div>\n" +
|
40
|
+
"<div class='nav_bar'>\n%s</div>\n" +
|
41
|
+
"%s%s</div>\n" +
|
42
|
+
"</body>\n" +
|
43
|
+
"</html>\n"
|
44
|
+
|
45
|
+
HtmlMessage =
|
46
|
+
"<div class='message'>%s</div>\n"
|
47
|
+
|
48
|
+
HtmlBody =
|
49
|
+
"<div class='mainbody'>\n%s</div>\n"
|
50
|
+
|
51
|
+
#####################################
|
52
|
+
# Generate a response HTML page
|
53
|
+
def response(env)
|
54
|
+
|
55
|
+
# response code
|
56
|
+
code = case env['sgfa.status']
|
57
|
+
when :badreq; 400
|
58
|
+
when :ok; 200
|
59
|
+
when :notfound; 404
|
60
|
+
when :deny; 402
|
61
|
+
when :conflict; 409
|
62
|
+
when :servererror; 500
|
63
|
+
else; raise 'Response not set'
|
64
|
+
end
|
65
|
+
|
66
|
+
# returning a file
|
67
|
+
if env['sgfa.file']
|
68
|
+
return [code, env['sgfa.headers'], env['sgfa.file']]
|
69
|
+
end
|
70
|
+
|
71
|
+
# build the page and headers
|
72
|
+
body = HtmlBody % env['sgfa.html']
|
73
|
+
msg = env['sgfa.message'] ? (HtmlMessage % env['sgfa.message']) : ''
|
74
|
+
html = HtmlPage % [
|
75
|
+
env['sgfa.title'],
|
76
|
+
env['sgfa.css'],
|
77
|
+
env['sgfa.title'],
|
78
|
+
_escape_html(env['sgfa.user']),
|
79
|
+
env['sgfa.navbar'],
|
80
|
+
msg, body
|
81
|
+
]
|
82
|
+
head = {
|
83
|
+
'Content-Type' => 'text/html; charset=utf-8',
|
84
|
+
'Content-Length' => html.bytesize.to_s,
|
85
|
+
}
|
86
|
+
|
87
|
+
return [code, head, [html]]
|
88
|
+
end # def response()
|
89
|
+
|
90
|
+
|
91
|
+
PageNav =
|
92
|
+
"<div class='pagenav'>%s</div>\n"
|
93
|
+
|
94
|
+
PageNavNums = [-1000, -500, -100, -50, -10, -5, -4, -3, -2, -1, 0,
|
95
|
+
1, 2, 3, 4, 5, 10, 50, 100, 500, 1000]
|
96
|
+
|
97
|
+
##########################################
|
98
|
+
# Generate page navigation
|
99
|
+
def _link_pages(cur, per, max, link, query)
|
100
|
+
pages = (per-1 + max) / per
|
101
|
+
txt = ''
|
102
|
+
|
103
|
+
if query
|
104
|
+
qs = '?'
|
105
|
+
query.each{ |nam, val| qs << '%s=%s' %[_escape(nam), _escape(val)] }
|
106
|
+
else
|
107
|
+
qs = ''
|
108
|
+
end
|
109
|
+
|
110
|
+
# previous and first
|
111
|
+
if cur > 1
|
112
|
+
txt << "<a href='%s/%d%s'>Prev</a> — " % [link, cur-1, qs]
|
113
|
+
txt << "<a href='%s/1%s'>First</a> — " % [link, qs]
|
114
|
+
else
|
115
|
+
txt << "Prev — First — "
|
116
|
+
end
|
117
|
+
|
118
|
+
# numbers
|
119
|
+
PageNavNums.each do |num|
|
120
|
+
val = cur + num
|
121
|
+
next if val < 1 || val > pages
|
122
|
+
if num == 0
|
123
|
+
txt << "%d " % cur
|
124
|
+
else
|
125
|
+
txt << "<a href='%s/%d%s'>%d</a> " % [link, val, qs, val]
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# last & next
|
130
|
+
if cur < pages
|
131
|
+
txt << "— <a href='%s/%d%s'>Last</a> " % [link, pages, qs]
|
132
|
+
txt << "— <a href='%s/%s%s'>Next</a>" % [link, cur+1, qs]
|
133
|
+
else
|
134
|
+
txt << "— Last — Next"
|
135
|
+
end
|
136
|
+
|
137
|
+
return PageNav % txt
|
138
|
+
end # def _link_pages()
|
139
|
+
|
140
|
+
|
141
|
+
#####################################
|
142
|
+
# Generate a navbar
|
143
|
+
def _navbar(env, act, tabs, base)
|
144
|
+
txt = ''
|
145
|
+
tabs.each do |name, url|
|
146
|
+
cl = (name == act) ? 'active' : 'link'
|
147
|
+
if url
|
148
|
+
txt << "<div class='%s'><a href='%s/%s'>%s</a></div>\n" %
|
149
|
+
[cl, base, url, name]
|
150
|
+
else
|
151
|
+
txt << "<div class='other'>%s</div>\n" % name
|
152
|
+
end
|
153
|
+
end
|
154
|
+
return txt
|
155
|
+
end # def _navbar()
|
156
|
+
|
157
|
+
|
158
|
+
##########################################
|
159
|
+
# Escape HTML
|
160
|
+
def _escape_html(txt)
|
161
|
+
Rack::Utils.escape_html(txt)
|
162
|
+
end # def _escape_html()
|
163
|
+
|
164
|
+
|
165
|
+
##########################################
|
166
|
+
# Escape URL
|
167
|
+
def _escape(txt)
|
168
|
+
Rack::Utils.escape(txt)
|
169
|
+
end # def _escape()
|
170
|
+
|
171
|
+
|
172
|
+
##########################################
|
173
|
+
# Unescape URL
|
174
|
+
def _escape_un(txt)
|
175
|
+
Rack::Utils.unescape(txt)
|
176
|
+
end # def _escape_un()
|
177
|
+
|
178
|
+
|
179
|
+
#####################################
|
180
|
+
# Create a binder transaction
|
181
|
+
def _trans(env)
|
182
|
+
tr = { :user => env['sgfa.user'], :groups => env['sgfa.groups'] }
|
183
|
+
tr[:jacket] = env['sgfa.jacket.name'] if env['sgfa.jacket.name']
|
184
|
+
return tr
|
185
|
+
end # def _trans()
|
186
|
+
|
187
|
+
|
188
|
+
end # class Base
|
189
|
+
|
190
|
+
|
191
|
+
#####################################################################
|
192
|
+
# Provide the contents of a file in chunks. Designed to work with Rack
|
193
|
+
# response.
|
194
|
+
class FileBody
|
195
|
+
|
196
|
+
# Size of the chunks provided by each
|
197
|
+
ReadChunk = 256 * 1024
|
198
|
+
|
199
|
+
# initialize new file response
|
200
|
+
def initialize(file)
|
201
|
+
@file = file
|
202
|
+
end # def initialize()
|
203
|
+
|
204
|
+
# provide the body of the file
|
205
|
+
def each
|
206
|
+
str = ''
|
207
|
+
while @file.read(ReadChunk, str)
|
208
|
+
yield str
|
209
|
+
end
|
210
|
+
end # def each
|
211
|
+
|
212
|
+
# close
|
213
|
+
def close
|
214
|
+
if @file.respond_to?(:close!)
|
215
|
+
@file.close!
|
216
|
+
else
|
217
|
+
@file.close
|
218
|
+
end
|
219
|
+
end # def close
|
220
|
+
|
221
|
+
end # class FileBody
|
222
|
+
|
223
|
+
end # module Web
|
224
|
+
|
225
|
+
end # module Sgfa
|