liars 0.0.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.
- data/CHANGELOG +0 -0
- data/README +0 -0
- data/Rakefile +69 -0
- data/bin/liars +112 -0
- data/extras/flipbook_rdoc.rb +491 -0
- data/liars.rb +8 -0
- data/lib/controller.rb +102 -0
- data/lib/core_ext.rb +45 -0
- data/lib/db.rb +120 -0
- data/lib/example.rb +190 -0
- data/lib/model.rb +99 -0
- data/lib/server.rb +24 -0
- data/lib/view.rb +28 -0
- metadata +89 -0
data/CHANGELOG
ADDED
File without changes
|
data/README
ADDED
File without changes
|
data/Rakefile
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/clean'
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
require 'rake/rdoctask'
|
5
|
+
require 'fileutils'
|
6
|
+
include FileUtils
|
7
|
+
|
8
|
+
NAME = "liars"
|
9
|
+
VERS = "0.0.1"
|
10
|
+
CLEAN.include ['**/.*.sw?', '*.gem', '.config']
|
11
|
+
RDOC_OPTS = ['--quiet', '--title', "Camping, the Documentation",
|
12
|
+
"--opname", "index.html",
|
13
|
+
"--line-numbers",
|
14
|
+
"--main", "README",
|
15
|
+
"--inline-source"]
|
16
|
+
|
17
|
+
# platform = if ENV['HOME'] then "POSIX" elsif ENV['APPDATA'] then "MSWIN" end
|
18
|
+
|
19
|
+
desc "将liars gem打包"
|
20
|
+
task :default => [:package]
|
21
|
+
task :package => [:clean]
|
22
|
+
|
23
|
+
task :doc => [:before_doc, :rdoc, :after_doc]
|
24
|
+
|
25
|
+
Rake::RDocTask.new do |rdoc|
|
26
|
+
rdoc.rdoc_dir = 'doc/rdoc'
|
27
|
+
rdoc.options += RDOC_OPTS
|
28
|
+
rdoc.template = "extras/flipbook_rdoc.rb"
|
29
|
+
rdoc.main = "README"
|
30
|
+
rdoc.title = "Liars文档"
|
31
|
+
rdoc.rdoc_files.add ['README', 'CHANGELOG','lib/*.rb']
|
32
|
+
end
|
33
|
+
|
34
|
+
spec =
|
35
|
+
Gem::Specification.new do |s|
|
36
|
+
s.name = NAME
|
37
|
+
s.version = VERS
|
38
|
+
s.platform = Gem::Platform::RUBY
|
39
|
+
s.has_rdoc = true
|
40
|
+
s.extra_rdoc_files = ["README", "CHANGELOG"]
|
41
|
+
s.rdoc_options += RDOC_OPTS + ['--exclude', '^(examples|extras)\/']
|
42
|
+
s.summary = "最小化的MVC框架"
|
43
|
+
s.description = s.summary
|
44
|
+
s.author = "luonet"
|
45
|
+
s.email = 'luonet@safore.com'
|
46
|
+
s.homepage = 'http://www.safore.com/'
|
47
|
+
s.executables = ['liars']
|
48
|
+
|
49
|
+
s.files = %w(CHANGELOG README Rakefile liars.rb) +
|
50
|
+
Dir.glob("{bin,doc,test,lib,extras}/**/*")
|
51
|
+
|
52
|
+
s.require_path = "."
|
53
|
+
s.autorequire = 'liars'
|
54
|
+
s.bindir = "bin"
|
55
|
+
end
|
56
|
+
|
57
|
+
Rake::GemPackageTask.new(spec) do |p|
|
58
|
+
p.need_tar = ENV['APPDATA'].nil?
|
59
|
+
p.gem_spec = spec
|
60
|
+
end
|
61
|
+
|
62
|
+
task :install do
|
63
|
+
sh %{rake package}
|
64
|
+
sh %{sudo gem install pkg/#{NAME}-#{VERS}}
|
65
|
+
end
|
66
|
+
|
67
|
+
task :uninstall => [:clean] do
|
68
|
+
sh %{sudo gem uninstall #{NAME}}
|
69
|
+
end
|
data/bin/liars
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'delegate'
|
3
|
+
require 'optparse'
|
4
|
+
require 'ostruct'
|
5
|
+
|
6
|
+
require 'stringio'
|
7
|
+
require 'yaml'
|
8
|
+
require 'liars'
|
9
|
+
|
10
|
+
conf = OpenStruct.new(:host => '0.0.0.0', :port => 2008, :db => Liars::DbEngine.db_name)
|
11
|
+
|
12
|
+
if conf.rc and File.exists?( conf.rc )
|
13
|
+
YAML.load_file(conf.rc).each do |k,v|
|
14
|
+
conf.send("#{k}=", v)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
opts = OptionParser.new do |opts|
|
19
|
+
opts.banner = "用法: liars example.rb..."
|
20
|
+
opts.define_head "Liars, 超小型的Ruby MVC Web开发框架 Ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]"
|
21
|
+
opts.separator ""
|
22
|
+
opts.separator " 选项列表:"
|
23
|
+
|
24
|
+
opts.on("-h", "--host HOSTNAME", "Web服务器绑定地址(默认为#{conf.host})") { |conf.host| }
|
25
|
+
opts.on("-p", "--port NUM", "Web服务器绑定端口(默认为#{conf.port})") { |conf.port| }
|
26
|
+
opts.on("-d", "--database FILE", "数据库文件(默认为#{conf.db})") { |conf.db| }
|
27
|
+
opts.on("-c", "--console", "运行在irb模式") { conf.server = :console }
|
28
|
+
opts.on("-s", "--scaffold FILE", "快速创建一个模板") do |conf.scaffold|
|
29
|
+
conf.scaffold =~ /^(\w+)(\.rb)?$/
|
30
|
+
File.open("#{Dir.pwd}/#{$1}.rb", 'w') do |f|
|
31
|
+
class_name = $1.singularize.camelize
|
32
|
+
f.puts <<-EOF
|
33
|
+
module Liars
|
34
|
+
# Model ======================================================================
|
35
|
+
class #{class_name} < M
|
36
|
+
end
|
37
|
+
|
38
|
+
# Controller =================================================================
|
39
|
+
class #{class_name}C < C
|
40
|
+
route_of :index, '/'
|
41
|
+
def index
|
42
|
+
render
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# View =======================================================================
|
47
|
+
class #{class_name}V < V
|
48
|
+
def layout
|
49
|
+
html do
|
50
|
+
head { title 'Liars never tell lies!' }
|
51
|
+
body do
|
52
|
+
yield
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def index
|
58
|
+
h1 { a '#{class_name}', :href => "/" }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
EOF
|
63
|
+
end
|
64
|
+
puts "模板文件#{$1}.rb创建成功"
|
65
|
+
exit
|
66
|
+
end
|
67
|
+
|
68
|
+
opts.separator ""
|
69
|
+
opts.separator "其他选项:"
|
70
|
+
|
71
|
+
opts.on_tail("-?", "--help", "显示帮助信息") do
|
72
|
+
puts opts
|
73
|
+
exit
|
74
|
+
end
|
75
|
+
|
76
|
+
opts.on_tail("-v", "--version", "显示版本信息") do
|
77
|
+
class << Gem; attr_accessor :loaded_specs; end
|
78
|
+
puts Gem.loaded_specs['liars'].version
|
79
|
+
exit
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
opts.parse! ARGV
|
84
|
+
if ARGV.length < 1
|
85
|
+
puts opts
|
86
|
+
exit
|
87
|
+
else
|
88
|
+
ARGV.each {|f| require "#{Dir.pwd}/#{f}" }
|
89
|
+
end
|
90
|
+
|
91
|
+
if conf.server == :console
|
92
|
+
ARGV.clear
|
93
|
+
require 'irb'
|
94
|
+
require 'irb/completion'
|
95
|
+
ENV['IRBRC'] = ".irbrc" if File.exists? ".irbrc"
|
96
|
+
IRB.start
|
97
|
+
else
|
98
|
+
# 建立数据库
|
99
|
+
Liars::DbEngine.db_name = conf.db
|
100
|
+
Liars::DbEngine.create_db
|
101
|
+
|
102
|
+
# Mount the root
|
103
|
+
s = WEBrick::HTTPServer.new(:BindAddress => conf.host, :Port => conf.port)
|
104
|
+
s.mount "/", Liars::LiarsHandler, Liars::C
|
105
|
+
|
106
|
+
# Server up
|
107
|
+
trap(:INT) do
|
108
|
+
s.shutdown
|
109
|
+
Liars::DbEngine.write_db
|
110
|
+
end
|
111
|
+
s.start
|
112
|
+
end
|
@@ -0,0 +1,491 @@
|
|
1
|
+
CAMPING_EXTRAS_DIR = File.expand_path(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
module Generators
|
4
|
+
class HTMLGenerator
|
5
|
+
def generate_html
|
6
|
+
@files_and_classes = {
|
7
|
+
'allfiles' => gen_into_index(@files),
|
8
|
+
'allclasses' => gen_into_index(@classes),
|
9
|
+
"initial_page" => main_url,
|
10
|
+
'realtitle' => CGI.escapeHTML(@options.title),
|
11
|
+
'charset' => @options.charset
|
12
|
+
}
|
13
|
+
|
14
|
+
# the individual descriptions for files and classes
|
15
|
+
gen_into(@files)
|
16
|
+
gen_into(@classes)
|
17
|
+
gen_main_index
|
18
|
+
|
19
|
+
# this method is defined in the template file
|
20
|
+
write_extra_pages if defined? write_extra_pages
|
21
|
+
end
|
22
|
+
|
23
|
+
def gen_into(list)
|
24
|
+
hsh = @files_and_classes.dup
|
25
|
+
list.each do |item|
|
26
|
+
if item.document_self
|
27
|
+
op_file = item.path
|
28
|
+
hsh['root'] = item.path.split("/").map { ".." }[1..-1].join("/")
|
29
|
+
item.instance_variable_set("@values", hsh)
|
30
|
+
File.makedirs(File.dirname(op_file))
|
31
|
+
File.open(op_file, "w") { |file| item.write_on(file) }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def gen_into_index(list)
|
37
|
+
res = []
|
38
|
+
list.each do |item|
|
39
|
+
hsh = item.value_hash
|
40
|
+
hsh['href'] = item.path
|
41
|
+
hsh['name'] = item.index_name
|
42
|
+
res << hsh
|
43
|
+
end
|
44
|
+
res
|
45
|
+
end
|
46
|
+
|
47
|
+
def gen_main_index
|
48
|
+
template = TemplatePage.new(RDoc::Page::INDEX)
|
49
|
+
File.open("index.html", "w") do |f|
|
50
|
+
values = @files_and_classes.dup
|
51
|
+
if @options.inline_source
|
52
|
+
values['inline_source'] = true
|
53
|
+
end
|
54
|
+
template.write_html_on(f, values)
|
55
|
+
end
|
56
|
+
['Camping.gif', 'permalink.gif'].each do |img|
|
57
|
+
ipath = File.join(CAMPING_EXTRAS_DIR, img)
|
58
|
+
File.copy(ipath, img)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
module RDoc
|
66
|
+
module Page
|
67
|
+
######################################################################
|
68
|
+
#
|
69
|
+
# The following is used for the -1 option
|
70
|
+
#
|
71
|
+
|
72
|
+
FONTS = "verdana,arial,'Bitstream Vera Sans',helvetica,sans-serif"
|
73
|
+
|
74
|
+
STYLE = %{
|
75
|
+
body, th, td {
|
76
|
+
font: normal 14px verdana,arial,'Bitstream Vera Sans',helvetica,sans-serif;
|
77
|
+
line-height: 160%;
|
78
|
+
padding: 0; margin: 0;
|
79
|
+
margin-bottom: 30px;
|
80
|
+
/* background-color: #402; */
|
81
|
+
background-color: #694;
|
82
|
+
}
|
83
|
+
h1, h2, h3, h4 {
|
84
|
+
font-family: Utopia, Georgia, serif;
|
85
|
+
font-weight: bold;
|
86
|
+
letter-spacing: -0.018em;
|
87
|
+
}
|
88
|
+
h1 { font-size: 24px; margin: .15em 1em 0 0 }
|
89
|
+
h2 { font-size: 24px }
|
90
|
+
h3 { font-size: 19px }
|
91
|
+
h4 { font-size: 17px; font-weight: normal; }
|
92
|
+
h4.ruled { border-bottom: solid 1px #CC9; }
|
93
|
+
h2.ruled { padding-top: 35px; border-top: solid 1px #AA5; }
|
94
|
+
|
95
|
+
/* Link styles */
|
96
|
+
:link, :visited {
|
97
|
+
color: #00b;
|
98
|
+
}
|
99
|
+
:link:hover, :visited:hover {
|
100
|
+
background-color: #eee;
|
101
|
+
color: #B22;
|
102
|
+
}
|
103
|
+
#fullpage {
|
104
|
+
width: 720px;
|
105
|
+
margin: 0 auto;
|
106
|
+
}
|
107
|
+
.page_shade, .page {
|
108
|
+
padding: 0px 5px 5px 0px;
|
109
|
+
background-color: #fcfcf9;
|
110
|
+
border: solid 1px #983;
|
111
|
+
}
|
112
|
+
.page {
|
113
|
+
margin-left: -5px;
|
114
|
+
margin-top: -5px;
|
115
|
+
padding: 20px 35px;
|
116
|
+
}
|
117
|
+
.page .header {
|
118
|
+
float: right;
|
119
|
+
color: #777;
|
120
|
+
font-size: 10px;
|
121
|
+
}
|
122
|
+
.page h1, .page h2, .page h3 {
|
123
|
+
clear: both;
|
124
|
+
text-align: center;
|
125
|
+
}
|
126
|
+
#pager {
|
127
|
+
padding: 10px 4px;
|
128
|
+
color: white;
|
129
|
+
font-size: 11px;
|
130
|
+
}
|
131
|
+
#pager :link, #pager :visited {
|
132
|
+
color: #bfb;
|
133
|
+
padding: 0px 5px;
|
134
|
+
}
|
135
|
+
#pager :link:hover, #pager :visited:hover {
|
136
|
+
background-color: #262;
|
137
|
+
color: white;
|
138
|
+
}
|
139
|
+
#logo { float: left; }
|
140
|
+
#menu { background-color: #dfa; padding: 4px 12px; margin: 0; }
|
141
|
+
#menu h3 { padding: 0; margin: 0; }
|
142
|
+
#menu #links { float: right; }
|
143
|
+
pre { font-weight: bold; color: #730; }
|
144
|
+
tt { color: #703; font-size: 12pt; }
|
145
|
+
.dyn-source { background-color: #775915; padding: 4px 8px; margin: 0; display: none; }
|
146
|
+
.dyn-source pre { color: #DDDDDD; font-size: 8pt; }
|
147
|
+
.source-link { text-align: right; font-size: 8pt; }
|
148
|
+
.ruby-comment { color: green; font-style: italic }
|
149
|
+
.ruby-constant { color: #CCDDFF; font-weight: bold; }
|
150
|
+
.ruby-identifier { color: #CCCCCC; }
|
151
|
+
.ruby-ivar { color: #BBCCFF; }
|
152
|
+
.ruby-keyword { color: #EEEEFF; font-weight: bold }
|
153
|
+
.ruby-node { color: #FFFFFF; }
|
154
|
+
.ruby-operator { color: #CCCCCC; }
|
155
|
+
.ruby-regexp { color: #DDFFDD; }
|
156
|
+
.ruby-value { color: #FFAAAA; font-style: italic }
|
157
|
+
.kw { color: #DDDDFF; font-weight: bold }
|
158
|
+
.cmt { color: #CCFFCC; font-style: italic }
|
159
|
+
.str { color: #EECCCC; font-style: italic }
|
160
|
+
.re { color: #EECCCC; }
|
161
|
+
}
|
162
|
+
|
163
|
+
CONTENTS_XML = %{
|
164
|
+
IF:description
|
165
|
+
%description%
|
166
|
+
ENDIF:description
|
167
|
+
|
168
|
+
IF:requires
|
169
|
+
<h4>Requires:</h4>
|
170
|
+
<ul>
|
171
|
+
START:requires
|
172
|
+
IF:aref
|
173
|
+
<li><a href="%aref%">%name%</a></li>
|
174
|
+
ENDIF:aref
|
175
|
+
IFNOT:aref
|
176
|
+
<li>%name%</li>
|
177
|
+
ENDIF:aref
|
178
|
+
END:requires
|
179
|
+
</ul>
|
180
|
+
ENDIF:requires
|
181
|
+
|
182
|
+
IF:attributes
|
183
|
+
<h4>Attributes</h4>
|
184
|
+
<table>
|
185
|
+
START:attributes
|
186
|
+
<tr><td>%name%</td><td>%rw%</td><td>%a_desc%</td></tr>
|
187
|
+
END:attributes
|
188
|
+
</table>
|
189
|
+
ENDIF:attributes
|
190
|
+
|
191
|
+
IF:includes
|
192
|
+
<h4>Includes</h4>
|
193
|
+
<ul>
|
194
|
+
START:includes
|
195
|
+
IF:aref
|
196
|
+
<li><a href="%aref%">%name%</a></li>
|
197
|
+
ENDIF:aref
|
198
|
+
IFNOT:aref
|
199
|
+
<li>%name%</li>
|
200
|
+
ENDIF:aref
|
201
|
+
END:includes
|
202
|
+
</ul>
|
203
|
+
ENDIF:includes
|
204
|
+
|
205
|
+
START:sections
|
206
|
+
IF:method_list
|
207
|
+
<h2 class="ruled">Methods</h2>
|
208
|
+
START:method_list
|
209
|
+
IF:methods
|
210
|
+
START:methods
|
211
|
+
<h4 class="ruled">%type% %category% method:
|
212
|
+
IF:callseq
|
213
|
+
<strong><a name="%aref%">%callseq%</a></strong> <a href="#%aref%"><img src="%root%/permalink.gif" border="0" title="Permalink to %callseq%" /></a>
|
214
|
+
ENDIF:callseq
|
215
|
+
IFNOT:callseq
|
216
|
+
<strong><a name="%aref%">%name%%params%</a></strong> <a href="#%aref%"><img src="%root%/permalink.gif" border="0" title="Permalink to %type% %category% method: %name%" /></a></h4>
|
217
|
+
ENDIF:callseq
|
218
|
+
|
219
|
+
IF:m_desc
|
220
|
+
%m_desc%
|
221
|
+
ENDIF:m_desc
|
222
|
+
|
223
|
+
IF:sourcecode
|
224
|
+
<div class="sourcecode">
|
225
|
+
<p class="source-link">[ <a href="javascript:toggleSource('%aref%_source')" id="l_%aref%_source">show source</a> ]</p>
|
226
|
+
<div id="%aref%_source" class="dyn-source">
|
227
|
+
<pre>
|
228
|
+
%sourcecode%
|
229
|
+
</pre>
|
230
|
+
</div>
|
231
|
+
</div>
|
232
|
+
ENDIF:sourcecode
|
233
|
+
END:methods
|
234
|
+
ENDIF:methods
|
235
|
+
END:method_list
|
236
|
+
ENDIF:method_list
|
237
|
+
END:sections
|
238
|
+
}
|
239
|
+
|
240
|
+
############################################################################
|
241
|
+
|
242
|
+
|
243
|
+
BODY = %{
|
244
|
+
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
245
|
+
<html>
|
246
|
+
<head>
|
247
|
+
<title>
|
248
|
+
IF:title
|
249
|
+
%realtitle% » %title%
|
250
|
+
ENDIF:title
|
251
|
+
IFNOT:title
|
252
|
+
%realtitle%
|
253
|
+
ENDIF:title
|
254
|
+
</title>
|
255
|
+
<meta http-equiv="Content-Type" content="text/html; charset=%charset%" />
|
256
|
+
<link rel="stylesheet" href="%style_url%" type="text/css" media="screen" />
|
257
|
+
<script language="JavaScript" type="text/javascript">
|
258
|
+
// <![CDATA[
|
259
|
+
|
260
|
+
function toggleSource( id )
|
261
|
+
{
|
262
|
+
var elem
|
263
|
+
var link
|
264
|
+
|
265
|
+
if( document.getElementById )
|
266
|
+
{
|
267
|
+
elem = document.getElementById( id )
|
268
|
+
link = document.getElementById( "l_" + id )
|
269
|
+
}
|
270
|
+
else if ( document.all )
|
271
|
+
{
|
272
|
+
elem = eval( "document.all." + id )
|
273
|
+
link = eval( "document.all.l_" + id )
|
274
|
+
}
|
275
|
+
else
|
276
|
+
return false;
|
277
|
+
|
278
|
+
if( elem.style.display == "block" )
|
279
|
+
{
|
280
|
+
elem.style.display = "none"
|
281
|
+
link.innerHTML = "show source"
|
282
|
+
}
|
283
|
+
else
|
284
|
+
{
|
285
|
+
elem.style.display = "block"
|
286
|
+
link.innerHTML = "hide source"
|
287
|
+
}
|
288
|
+
}
|
289
|
+
|
290
|
+
function openCode( url )
|
291
|
+
{
|
292
|
+
window.open( url, "SOURCE_CODE", "width=400,height=400,scrollbars=yes" )
|
293
|
+
}
|
294
|
+
// ]]>
|
295
|
+
</script>
|
296
|
+
</head>
|
297
|
+
<body>
|
298
|
+
<div id="menu">
|
299
|
+
<div id="links">
|
300
|
+
<a href="http://redhanded.hobix.com/bits/campingAMicroframework.html">backstory</a> |
|
301
|
+
<a href="http://code.whytheluckystiff.net/camping/">wiki</a> |
|
302
|
+
<a href="http://code.whytheluckystiff.net/camping/newticket">bugs</a> |
|
303
|
+
<a href="http://code.whytheluckystiff.net/svn/camping/">svn</a>
|
304
|
+
</div>
|
305
|
+
<h3 class="title">%title%</h3>
|
306
|
+
</div>
|
307
|
+
<div id="fullpage">
|
308
|
+
<div id="logo"><img src="%root%/Camping.gif" /></div>
|
309
|
+
<div id="pager">
|
310
|
+
<strong>Files:</strong>
|
311
|
+
START:allfiles
|
312
|
+
<a href="%root%/%href%" value="%title%">%name%</a>
|
313
|
+
END:allfiles
|
314
|
+
IF:allclasses
|
315
|
+
|
|
316
|
+
<strong>classes:</strong>
|
317
|
+
START:allclasses
|
318
|
+
<a href="%root%/%href%" title="%title%">%name%</a>
|
319
|
+
END:allclasses
|
320
|
+
ENDIF:allclasses
|
321
|
+
</ul>
|
322
|
+
</div>
|
323
|
+
|
324
|
+
!INCLUDE!
|
325
|
+
|
326
|
+
</div>
|
327
|
+
</body>
|
328
|
+
</html>
|
329
|
+
}
|
330
|
+
|
331
|
+
###############################################################################
|
332
|
+
|
333
|
+
FILE_PAGE = <<_FILE_PAGE_
|
334
|
+
<div id="%full_path%" class="page_shade">
|
335
|
+
<div class="page">
|
336
|
+
<div class="header">
|
337
|
+
<div class="path">%full_path% / %dtm_modified%</div>
|
338
|
+
</div>
|
339
|
+
#{CONTENTS_XML}
|
340
|
+
</div>
|
341
|
+
</div>
|
342
|
+
_FILE_PAGE_
|
343
|
+
|
344
|
+
###################################################################
|
345
|
+
|
346
|
+
CLASS_PAGE = %{
|
347
|
+
<div id="%full_name%" class="page_shade">
|
348
|
+
<div class="page">
|
349
|
+
IF:parent
|
350
|
+
<h3>%classmod% %full_name% < HREF:par_url:parent:</h3>
|
351
|
+
ENDIF:parent
|
352
|
+
IFNOT:parent
|
353
|
+
<h3>%classmod% %full_name%</h3>
|
354
|
+
ENDIF:parent
|
355
|
+
|
356
|
+
IF:infiles
|
357
|
+
(in files
|
358
|
+
START:infiles
|
359
|
+
HREF:full_path_url:full_path:
|
360
|
+
END:infiles
|
361
|
+
)
|
362
|
+
ENDIF:infiles
|
363
|
+
} + CONTENTS_XML + %{
|
364
|
+
</div>
|
365
|
+
</div>
|
366
|
+
}
|
367
|
+
|
368
|
+
###################################################################
|
369
|
+
|
370
|
+
METHOD_LIST = %{
|
371
|
+
IF:includes
|
372
|
+
<div class="tablesubsubtitle">Included modules</div><br>
|
373
|
+
<div class="name-list">
|
374
|
+
START:includes
|
375
|
+
<span class="method-name">HREF:aref:name:</span>
|
376
|
+
END:includes
|
377
|
+
</div>
|
378
|
+
ENDIF:includes
|
379
|
+
|
380
|
+
IF:method_list
|
381
|
+
START:method_list
|
382
|
+
IF:methods
|
383
|
+
<table cellpadding=5 width="100%">
|
384
|
+
<tr><td class="tablesubtitle">%type% %category% methods</td></tr>
|
385
|
+
</table>
|
386
|
+
START:methods
|
387
|
+
<table width="100%" cellspacing = 0 cellpadding=5 border=0>
|
388
|
+
<tr><td class="methodtitle">
|
389
|
+
<a name="%aref%">
|
390
|
+
IF:callseq
|
391
|
+
<b>%callseq%</b>
|
392
|
+
ENDIF:callseq
|
393
|
+
IFNOT:callseq
|
394
|
+
<b>%name%</b>%params%
|
395
|
+
ENDIF:callseq
|
396
|
+
IF:codeurl
|
397
|
+
<a href="%codeurl%" target="source" class="srclink">src</a>
|
398
|
+
ENDIF:codeurl
|
399
|
+
</a></td></tr>
|
400
|
+
</table>
|
401
|
+
IF:m_desc
|
402
|
+
<div class="description">
|
403
|
+
%m_desc%
|
404
|
+
</div>
|
405
|
+
ENDIF:m_desc
|
406
|
+
IF:aka
|
407
|
+
<div class="aka">
|
408
|
+
This method is also aliased as
|
409
|
+
START:aka
|
410
|
+
<a href="%aref%">%name%</a>
|
411
|
+
END:aka
|
412
|
+
</div>
|
413
|
+
ENDIF:aka
|
414
|
+
IF:sourcecode
|
415
|
+
<div class="sourcecode">
|
416
|
+
<p class="source-link">[ <a href="javascript:toggleSource('%aref%_source')" id="l_%aref%_source">show source</a> ]</p>
|
417
|
+
<div id="%aref%_source" class="dyn-source">
|
418
|
+
<pre>
|
419
|
+
%sourcecode%
|
420
|
+
</pre>
|
421
|
+
</div>
|
422
|
+
</div>
|
423
|
+
ENDIF:sourcecode
|
424
|
+
END:methods
|
425
|
+
ENDIF:methods
|
426
|
+
END:method_list
|
427
|
+
ENDIF:method_list
|
428
|
+
}
|
429
|
+
|
430
|
+
|
431
|
+
########################## Index ################################
|
432
|
+
|
433
|
+
FR_INDEX_BODY = %{
|
434
|
+
!INCLUDE!
|
435
|
+
}
|
436
|
+
|
437
|
+
FILE_INDEX = %{
|
438
|
+
<html>
|
439
|
+
<head>
|
440
|
+
<meta http-equiv="Content-Type" content="text/html; charset=%charset%">
|
441
|
+
<style>
|
442
|
+
<!--
|
443
|
+
body {
|
444
|
+
background-color: #ddddff;
|
445
|
+
font-family: #{FONTS};
|
446
|
+
font-size: 11px;
|
447
|
+
font-style: normal;
|
448
|
+
line-height: 14px;
|
449
|
+
color: #000040;
|
450
|
+
}
|
451
|
+
div.banner {
|
452
|
+
background: #0000aa;
|
453
|
+
color: white;
|
454
|
+
padding: 1;
|
455
|
+
margin: 0;
|
456
|
+
font-size: 90%;
|
457
|
+
font-weight: bold;
|
458
|
+
line-height: 1.1;
|
459
|
+
text-align: center;
|
460
|
+
width: 100%;
|
461
|
+
}
|
462
|
+
|
463
|
+
-->
|
464
|
+
</style>
|
465
|
+
<base target="docwin">
|
466
|
+
</head>
|
467
|
+
<body>
|
468
|
+
<div class="banner">%list_title%</div>
|
469
|
+
START:entries
|
470
|
+
<a href="%href%">%name%</a><br>
|
471
|
+
END:entries
|
472
|
+
</body></html>
|
473
|
+
}
|
474
|
+
|
475
|
+
CLASS_INDEX = FILE_INDEX
|
476
|
+
METHOD_INDEX = FILE_INDEX
|
477
|
+
|
478
|
+
INDEX = %{
|
479
|
+
<HTML>
|
480
|
+
<HEAD>
|
481
|
+
<META HTTP-EQUIV="refresh" content="0;URL=%initial_page%">
|
482
|
+
<TITLE>%realtitle%</TITLE>
|
483
|
+
</HEAD>
|
484
|
+
<BODY>
|
485
|
+
Click <a href="%initial_page%">here</a> to open the Camping docs.
|
486
|
+
</BODY>
|
487
|
+
</HTML>
|
488
|
+
}
|
489
|
+
|
490
|
+
end
|
491
|
+
end
|
data/liars.rb
ADDED
data/lib/controller.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
module Liars
|
2
|
+
class C
|
3
|
+
attr_accessor :body, :headers, :status, :params, :cookies
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def inherited(child); (@@subclasses ||= []) << child; end
|
7
|
+
def subclasses; @@subclasses; end
|
8
|
+
|
9
|
+
def run(r=$stdin, e=ENV)
|
10
|
+
route = e['PATH_INFO']
|
11
|
+
@@subclasses.each do |klass|
|
12
|
+
action, args = klass.get_action_and_args(route)
|
13
|
+
if action
|
14
|
+
controller = klass.new(r, e, e['REQUEST_METHOD']||"GET")
|
15
|
+
return controller.send(action, *args) && controller
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def route_of(action, *routes)
|
21
|
+
@@routes ||= {}
|
22
|
+
@@routes[action] = routes
|
23
|
+
end
|
24
|
+
|
25
|
+
# 实际上是返回一个action的名称和这个action所有可能用到的参数列表
|
26
|
+
# 如edit上映射的route是'/edit/(\d+)',而实际访问的路径是'/edit/1'时
|
27
|
+
# 1将会被作为一个参数传给edit方法
|
28
|
+
def get_action_and_args(route)
|
29
|
+
@@routes.each do |action, action_routes|
|
30
|
+
action_routes.find do |r|
|
31
|
+
match = Regexp.new("^#{r}$").match(route)
|
32
|
+
return action, match[1..-1] if match
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize(r, e, m)
|
39
|
+
@status, @method, @env, @headers, @root = 200, m.downcase, e,
|
40
|
+
{'Content-Type'=>'text/html'}, e["SCRIPT_NAME"].sub(/\/$/,'')
|
41
|
+
@params = qsp(e["QUERY_STRING"])
|
42
|
+
@cookies = kp(e["HTTP_COOKIE"])
|
43
|
+
@params.merge!(qsp(r.read)) if @method == "post"
|
44
|
+
end
|
45
|
+
|
46
|
+
def redirect_to(url)
|
47
|
+
r(302,'','Location'=>url)
|
48
|
+
end
|
49
|
+
|
50
|
+
# A quick means of setting this controller's status, body and headers.
|
51
|
+
# Used internally by Camping, but... by all means...
|
52
|
+
#
|
53
|
+
# r(302, '', 'Location' => self / "/view/12")
|
54
|
+
#
|
55
|
+
# Is equivalent to:
|
56
|
+
#
|
57
|
+
# redirect "/view/12"
|
58
|
+
#
|
59
|
+
def r(s, b, h = {}); @status = s; @headers.merge!(h); @body = b; end
|
60
|
+
# URL escapes a string.
|
61
|
+
#
|
62
|
+
# Camping.escape("I'd go to the museum straightway!")
|
63
|
+
# #=> "I%27d+go+to+the+museum+straightway%21"
|
64
|
+
#
|
65
|
+
def escape(s); s.to_s.gsub(/[^ \w.-]+/n){'%'+($&.unpack('H2'*$&.size)*'%').upcase}.tr(' ', '+') end
|
66
|
+
|
67
|
+
# Unescapes a URL-encoded string.
|
68
|
+
#
|
69
|
+
# Camping.un("I%27d+go+to+the+museum+straightway%21")
|
70
|
+
# #=> "I'd go to the museum straightway!"
|
71
|
+
#
|
72
|
+
def un(s); s.tr('+', ' ').gsub(/%([\da-f]{2})/in){[$1].pack('H*')} end
|
73
|
+
|
74
|
+
def qsp(qs, d='&;')
|
75
|
+
(qs || '').split(/[#{d}] */n).inject({}) do |h, p|
|
76
|
+
k, v = un(p).split('=', 2)
|
77
|
+
if (k = k.split(/[\]\[]+/)).length == 1
|
78
|
+
h[k[0].to_sym] = v
|
79
|
+
else
|
80
|
+
(h[k[0].to_sym] ||= {})[k[1].to_sym] = v
|
81
|
+
end
|
82
|
+
h
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def kp(s); qsp(s, ';,'); end
|
87
|
+
|
88
|
+
def render(action=(caller[0]=~/`(\w+)'/;$1))
|
89
|
+
# 查找当前controller对应的view,然后根据action来找到渲染方法
|
90
|
+
name = (self.class.to_s =~ /(\w+)(C|Controller)$/; $1)
|
91
|
+
view_class = eval("#{name}View") rescue eval("#{name}V")
|
92
|
+
view_instance = view_class.new
|
93
|
+
instance_variables.each do |iv|
|
94
|
+
view_instance.instance_variable_set(iv, instance_variable_get(iv))
|
95
|
+
end
|
96
|
+
render_result = view_instance.respond_to?(:layout) ?
|
97
|
+
view_instance.send(:layout, &view_instance.method(action)) :
|
98
|
+
view_instance.send(action)
|
99
|
+
@body = view_instance.stream || render_result
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
data/lib/core_ext.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
class Array
|
2
|
+
def extract_options!
|
3
|
+
last.is_a?(Hash) ? pop : {}
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
class String
|
8
|
+
def singularize
|
9
|
+
case self
|
10
|
+
when /ies$/ then $` + "y"
|
11
|
+
when /s$/ then $`
|
12
|
+
else self.dup
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def pluralize
|
17
|
+
case self
|
18
|
+
when /y$/ then $` + "ies"
|
19
|
+
when /s$/ then self + "es"
|
20
|
+
else self + "s"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def camelize
|
25
|
+
self.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
|
26
|
+
end
|
27
|
+
|
28
|
+
def underscore
|
29
|
+
self.gsub(/::/, '/').
|
30
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
31
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
32
|
+
tr("-", "_").
|
33
|
+
downcase
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class Symbol
|
38
|
+
%w[singularize pluralize camelize underscore].each do |change_type|
|
39
|
+
class_eval <<-EOF
|
40
|
+
def #{change_type}
|
41
|
+
self.to_s.#{change_type}.to_sym
|
42
|
+
end
|
43
|
+
EOF
|
44
|
+
end
|
45
|
+
end
|
data/lib/db.rb
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Liars
|
4
|
+
# 暂定为这样的一个数据结构
|
5
|
+
# {
|
6
|
+
# :User => {
|
7
|
+
# :max_id => 2,
|
8
|
+
# :records => [
|
9
|
+
# { :id => 1, :name => 'luoxin', :roles_ids => [1,2] },
|
10
|
+
# { :id => 2, :name => 'luonet', :roles_ids => [1] }
|
11
|
+
# ]
|
12
|
+
# },
|
13
|
+
# :Role => {
|
14
|
+
# :max_id => 2,
|
15
|
+
# :records => [
|
16
|
+
# { :id => 1, :name => 'admin' },
|
17
|
+
# { :id => 2, :name => 'guest' }
|
18
|
+
# ]
|
19
|
+
# }
|
20
|
+
# }
|
21
|
+
#
|
22
|
+
class DbEngine
|
23
|
+
class << self
|
24
|
+
def all_models
|
25
|
+
@all_models ||= []
|
26
|
+
end
|
27
|
+
|
28
|
+
def db_name
|
29
|
+
@db_name || "db.yaml"
|
30
|
+
end
|
31
|
+
|
32
|
+
def db_name=(name)
|
33
|
+
@db_name = name
|
34
|
+
end
|
35
|
+
|
36
|
+
def create_db
|
37
|
+
return if File.exists?(db_name)
|
38
|
+
data = all_models.inject({}) do |data_hash, model|
|
39
|
+
data_hash[model.to_s.to_sym] = { :max_id=>0, :records=>[] }
|
40
|
+
data_hash
|
41
|
+
end
|
42
|
+
write_db(data)
|
43
|
+
end
|
44
|
+
|
45
|
+
def write_db(data=nil)
|
46
|
+
File.open(db_name, "w") {|f| YAML.dump((data || @db_data), f)}
|
47
|
+
end
|
48
|
+
|
49
|
+
def db_data
|
50
|
+
@db_data ||= YAML.load_file(db_name)
|
51
|
+
end
|
52
|
+
|
53
|
+
def records_of(klass)
|
54
|
+
db_data[klass.to_s.to_sym][:records]
|
55
|
+
end
|
56
|
+
|
57
|
+
def record_of(obj)
|
58
|
+
records_of(obj.class).find {|x| x[:id] == obj.id} if obj.id
|
59
|
+
end
|
60
|
+
|
61
|
+
def increase_max_id_of(klass)
|
62
|
+
db_data[klass.to_s.to_sym][:max_id] += 1
|
63
|
+
end
|
64
|
+
|
65
|
+
def find(klass, ids)
|
66
|
+
case ids
|
67
|
+
when [] then []
|
68
|
+
when :all then records_of(klass).collect {|r| klass.new(r)}
|
69
|
+
when Array then find_records(klass, ids).collect! {|r| klass.new(r)}
|
70
|
+
else find_records(klass, [ids]).collect! {|r| klass.new(r)}.first
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def find_records(klass, ids)
|
75
|
+
records_of(klass).select {|x| ids.include? x[:id]}
|
76
|
+
end
|
77
|
+
|
78
|
+
def insert(obj)
|
79
|
+
records_of(obj.class) << obj.data
|
80
|
+
obj
|
81
|
+
end
|
82
|
+
|
83
|
+
def delete(obj)
|
84
|
+
records_of(obj.class).delete_if {|x| x[:id] == obj.id }
|
85
|
+
end
|
86
|
+
|
87
|
+
def update(obj)
|
88
|
+
record_of(obj).merge!(obj.data)
|
89
|
+
obj
|
90
|
+
end
|
91
|
+
|
92
|
+
def save(obj)
|
93
|
+
if obj.new?
|
94
|
+
obj.instance_variable_set("@id", increase_max_id_of(obj.class))
|
95
|
+
new_record = true
|
96
|
+
end
|
97
|
+
save_associations(obj)
|
98
|
+
new_record ? insert(obj) : update(obj)
|
99
|
+
end
|
100
|
+
|
101
|
+
def save_associations(obj)
|
102
|
+
obj.class.associations.each do |assoc|
|
103
|
+
assoc_type, assoc_name = assoc
|
104
|
+
save_has_many_associations(obj, assoc_name) if assoc_type == :has_many
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def save_has_many_associations(obj, assoc_name)
|
109
|
+
assoc_objs = obj.instance_variable_get("@#{assoc_name}_obj") || []
|
110
|
+
assoc_ids = []
|
111
|
+
assoc_objs.each do |assoc_obj|
|
112
|
+
assoc_obj.instance_variable_set("@#{obj.class.to_s.underscore.sub(/.*\//, '')}", obj.id)
|
113
|
+
assoc_obj.save
|
114
|
+
assoc_ids << assoc_obj.id
|
115
|
+
end
|
116
|
+
obj.instance_variable_set("@#{assoc_name}", assoc_ids)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
data/lib/example.rb
ADDED
@@ -0,0 +1,190 @@
|
|
1
|
+
module Liars
|
2
|
+
# Model ======================================================================
|
3
|
+
class Article < M
|
4
|
+
has_field :title
|
5
|
+
has_field :content
|
6
|
+
has_many :comments
|
7
|
+
end
|
8
|
+
|
9
|
+
class Comment < M
|
10
|
+
has_field :title
|
11
|
+
has_field :content
|
12
|
+
belongs_to :article
|
13
|
+
end
|
14
|
+
|
15
|
+
class ArticleC < C
|
16
|
+
route_of :index, '/', '/index', '/default'
|
17
|
+
def index
|
18
|
+
@articles = Article.find(:all)
|
19
|
+
render
|
20
|
+
end
|
21
|
+
|
22
|
+
route_of :show, '/show/(\d+)'
|
23
|
+
def show(id)
|
24
|
+
@article = Article.find(id.to_i)
|
25
|
+
render
|
26
|
+
end
|
27
|
+
|
28
|
+
route_of :create, '/create'
|
29
|
+
def create
|
30
|
+
Article.create(params[:article])
|
31
|
+
redirect_to '/'
|
32
|
+
end
|
33
|
+
|
34
|
+
route_of :add_comment, '/add_comment/(\d+)'
|
35
|
+
def add_comment(id)
|
36
|
+
@article = Article.find(id.to_i)
|
37
|
+
@article.comments << Comment.create(params[:comment])
|
38
|
+
@article.save
|
39
|
+
redirect_to "/show/#{id}"
|
40
|
+
end
|
41
|
+
|
42
|
+
route_of :style, '/styles.css'
|
43
|
+
def style
|
44
|
+
@headers["Content-Type"] = "text/css; charset=utf-8"
|
45
|
+
@body = %[
|
46
|
+
body {
|
47
|
+
font-family: Utopia, Georga, serif;
|
48
|
+
}
|
49
|
+
h1 {
|
50
|
+
background-color: #fef;
|
51
|
+
margin: 0; padding: 10px;
|
52
|
+
}
|
53
|
+
div {
|
54
|
+
padding: 10px;
|
55
|
+
}
|
56
|
+
]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class ArticleV < V
|
61
|
+
def layout
|
62
|
+
html do
|
63
|
+
head do
|
64
|
+
title 'Liars never tell lies!'
|
65
|
+
link :rel => 'stylesheet', :type => 'text/css',
|
66
|
+
:href => '/styles.css', :media => 'screen'
|
67
|
+
end
|
68
|
+
body do
|
69
|
+
h1 { a 'Articles', :href => "http://www.google.cn" }
|
70
|
+
div do
|
71
|
+
yield
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def index
|
78
|
+
h1 'Articles'
|
79
|
+
hr
|
80
|
+
@articles.each do |ar|
|
81
|
+
b ar.title
|
82
|
+
p ar.content
|
83
|
+
a 'show comments', :href=> "/show/#{ar.id}"
|
84
|
+
hr
|
85
|
+
end
|
86
|
+
form :action=>'/create', :method=>'Post' do
|
87
|
+
input :type=>'text', :name=>'article[title]'; br
|
88
|
+
textarea '', :name=>'article[content]'; br
|
89
|
+
input :type=>'submit'
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def show
|
94
|
+
h1 'Articles'
|
95
|
+
a 'index', :href=> "/"
|
96
|
+
hr
|
97
|
+
b @article.title
|
98
|
+
p @article.content
|
99
|
+
h1 'Comments'
|
100
|
+
hr
|
101
|
+
@article.comments.each do |c|
|
102
|
+
b c.title
|
103
|
+
p c.content
|
104
|
+
end
|
105
|
+
hr
|
106
|
+
form :action=> "/add_comment/#{@article.id}", :method=>'Post' do
|
107
|
+
input :type=>'text', :name=>'comment[title]'; br
|
108
|
+
textarea '', :name=>'comment[content]'; br
|
109
|
+
input :type=>'submit'
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# # Controller =================================================================
|
115
|
+
# class BlogC < C
|
116
|
+
# route_of :index, '/', '/index', '/default', '/home'
|
117
|
+
# def index
|
118
|
+
# @time = Time.now
|
119
|
+
# render
|
120
|
+
# end
|
121
|
+
#
|
122
|
+
# route_of :edit, '/edit/(\d+)'
|
123
|
+
# def edit(id)
|
124
|
+
# @user = params[:user]
|
125
|
+
# @people = [1,2,3,4]
|
126
|
+
# render
|
127
|
+
# end
|
128
|
+
#
|
129
|
+
# route_of :style, '/styles.css'
|
130
|
+
# def style
|
131
|
+
# @headers["Content-Type"] = "text/css; charset=utf-8"
|
132
|
+
# @body = %[
|
133
|
+
# body {
|
134
|
+
# font-family: Utopia, Georga, serif;
|
135
|
+
# }
|
136
|
+
# h1 {
|
137
|
+
# background-color: #fef;
|
138
|
+
# margin: 0; padding: 10px;
|
139
|
+
# }
|
140
|
+
# div {
|
141
|
+
# padding: 10px;
|
142
|
+
# }
|
143
|
+
# ]
|
144
|
+
# end
|
145
|
+
# end
|
146
|
+
#
|
147
|
+
# # View =======================================================================
|
148
|
+
# class BlogV < V
|
149
|
+
# def layout
|
150
|
+
# html do
|
151
|
+
# head do
|
152
|
+
# title 'Liars never tell lies!'
|
153
|
+
# link :rel => 'stylesheet', :type => 'text/css',
|
154
|
+
# :href => '/styles.css', :media => 'screen'
|
155
|
+
# end
|
156
|
+
# body do
|
157
|
+
# h1 { a 'blog', :href => "http://www.google.cn" }
|
158
|
+
# div do
|
159
|
+
# yield
|
160
|
+
# end
|
161
|
+
# end
|
162
|
+
# end
|
163
|
+
# end
|
164
|
+
#
|
165
|
+
# def index
|
166
|
+
# form :action=>'/edit/3', :method=>'Post' do
|
167
|
+
# label "name"
|
168
|
+
# input :type=>:text, :name=>'user[name]'
|
169
|
+
# label "age"
|
170
|
+
# input :type=>:text, :name=>'user[age]'
|
171
|
+
# input :type=>:submit
|
172
|
+
# end
|
173
|
+
# end
|
174
|
+
#
|
175
|
+
# def edit
|
176
|
+
# if @user
|
177
|
+
# h1 "The user name is #{@user[:name]}"
|
178
|
+
# h1 "The user age is #{@user[:age]}"
|
179
|
+
# end
|
180
|
+
# @people.each do |pl|
|
181
|
+
# p "I am #{pl}"
|
182
|
+
# end
|
183
|
+
# input :type=>:text
|
184
|
+
# div :class=>"luoxin" do
|
185
|
+
# input :type=>:text
|
186
|
+
# input :type=>:button, :value=>"luoxin"
|
187
|
+
# end
|
188
|
+
# end
|
189
|
+
# end
|
190
|
+
end
|
data/lib/model.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
module Liars
|
2
|
+
# 目前实现了has_many和belongs_to关系
|
3
|
+
# 如假设user和role是一对多的关系
|
4
|
+
# u = Liars::User.new(:name=>'luoxin')
|
5
|
+
# u.roles << Liars::Role.new(:name=>'admin')
|
6
|
+
# u.save
|
7
|
+
# 则对象已自动建立了关联,以后可以通过u.roles和u.roles[0].user来访问关联对象
|
8
|
+
# 目前的实现存在着许多bug
|
9
|
+
# 首先,如果是通过给从属对象赋主对象值,保存从属对象时主对象不会有从对象的引用
|
10
|
+
# 比如role.user = u
|
11
|
+
# 调用role.save时user并不会保存,因此role上保存有user的引用但user上并没有role的引用
|
12
|
+
# 这里的一个解决方案是判断user是不是新对象,如果是新对象则不保存,如果不是新对象则仅保存关联关系
|
13
|
+
# 另外,通过user.roles << role加入role时,再调用role.user时并不会马上产生效果,这是因为<<还是常规的数组方法,
|
14
|
+
# 要实现立即的效果应该再定义一个Association类集成至数组但改变了其中很多方法的定义
|
15
|
+
class M
|
16
|
+
attr_reader :id
|
17
|
+
|
18
|
+
def initialize(fields={})
|
19
|
+
fields.each {|k, v| instance_variable_set("@#{k}", v)}
|
20
|
+
end
|
21
|
+
|
22
|
+
class << self
|
23
|
+
def inherited(child)
|
24
|
+
DbEngine.all_models << child
|
25
|
+
end
|
26
|
+
|
27
|
+
def fields
|
28
|
+
@fields ||= [:id]
|
29
|
+
end
|
30
|
+
|
31
|
+
def associations
|
32
|
+
(@associations ||= [])
|
33
|
+
end
|
34
|
+
|
35
|
+
def fields_and_associations
|
36
|
+
fields + associations.collect {|x| x[1]}
|
37
|
+
end
|
38
|
+
|
39
|
+
def create(fields={})
|
40
|
+
self.new(fields).save
|
41
|
+
end
|
42
|
+
|
43
|
+
def find(ids)
|
44
|
+
DbEngine.find(self, ids)
|
45
|
+
end
|
46
|
+
|
47
|
+
def has_field(name)
|
48
|
+
attr_accessor name
|
49
|
+
fields << name
|
50
|
+
end
|
51
|
+
|
52
|
+
def has_many(name)
|
53
|
+
class_name = name.singularize.camelize
|
54
|
+
associations << [:has_many, name]
|
55
|
+
class_eval <<-EOF
|
56
|
+
def #{name}
|
57
|
+
@#{name} ||= []
|
58
|
+
@#{name}_obj ||= DbEngine.find(#{class_name}, @#{name})
|
59
|
+
end
|
60
|
+
|
61
|
+
def #{name}=(value)
|
62
|
+
@#{name}_obj = value
|
63
|
+
end
|
64
|
+
EOF
|
65
|
+
end
|
66
|
+
|
67
|
+
def belongs_to(name)
|
68
|
+
class_name = name.camelize
|
69
|
+
associations << [:belongs_to, name]
|
70
|
+
class_eval <<-EOF
|
71
|
+
def #{name}
|
72
|
+
@#{name}_obj ||= DbEngine.find(#{class_name}, @#{name})
|
73
|
+
end
|
74
|
+
|
75
|
+
def #{name}=(value)
|
76
|
+
@#{name} = value.id
|
77
|
+
@#{name}_obj = value
|
78
|
+
end
|
79
|
+
EOF
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def save
|
84
|
+
DbEngine.save(self)
|
85
|
+
end
|
86
|
+
|
87
|
+
def new?
|
88
|
+
@id == nil
|
89
|
+
end
|
90
|
+
|
91
|
+
def data
|
92
|
+
self.class.fields_and_associations.inject({}) {|h, f| h[f] = instance_variable_get("@#{f}"); h}
|
93
|
+
end
|
94
|
+
|
95
|
+
def destroy
|
96
|
+
DbEngine.delete(self)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
data/lib/server.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'webrick'
|
2
|
+
require 'webrick/httpservlet/abstract.rb'
|
3
|
+
|
4
|
+
module Liars
|
5
|
+
class LiarsHandler < WEBrick::HTTPServlet::DefaultFileHandler
|
6
|
+
def initialize(server, klass)
|
7
|
+
super(server, klass)
|
8
|
+
@klass = klass
|
9
|
+
end
|
10
|
+
# Handler for WEBrick requests (also aliased as do_POST).
|
11
|
+
def service(req, resp)
|
12
|
+
controller = @klass.run((req.body && StringIO.new(req.body)), req.meta_vars)
|
13
|
+
resp.body = controller.body.to_s
|
14
|
+
resp.status = controller.status
|
15
|
+
controller.headers.each do |k, v|
|
16
|
+
unless k =~ /^X-SENDFILE$/i
|
17
|
+
[*v].each do |vi|
|
18
|
+
resp[k] = vi
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/view.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
module Liars
|
2
|
+
class V
|
3
|
+
attr_reader :stream
|
4
|
+
def tag(*args, &b)
|
5
|
+
@stream ||= ""
|
6
|
+
tag_name = args.shift
|
7
|
+
attrs = args.extract_options!
|
8
|
+
attrs = attrs.collect {|k,v| "#{k}=\"#{v}\""}.join(" ")
|
9
|
+
text = args[0]
|
10
|
+
if block_given?
|
11
|
+
@stream += "<#{tag_name} #{attrs}>#{text}"
|
12
|
+
b.call
|
13
|
+
@stream += "</#{tag_name}>"
|
14
|
+
else
|
15
|
+
@stream += (text ? "<#{tag_name} #{attrs}>#{text}</#{tag_name}>" : "<#{tag_name} #{attrs}/>")
|
16
|
+
end
|
17
|
+
@stream
|
18
|
+
end
|
19
|
+
|
20
|
+
%w[html body head title h1 h2 h3 form div p input table tr td tbody link a b span label hr br textarea].each do |tag_name|
|
21
|
+
class_eval <<-EOF
|
22
|
+
def #{tag_name}(*args, &b)
|
23
|
+
tag(*(args.unshift('#{tag_name}')), &b)
|
24
|
+
end
|
25
|
+
EOF
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: liars
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- luonet
|
13
|
+
autorequire: liars
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-03-16 00:00:00 +08:00
|
18
|
+
default_executable:
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: !binary |
|
22
|
+
5pyA5bCP5YyW55qETVZD5qGG5p62
|
23
|
+
|
24
|
+
email: luonet@safore.com
|
25
|
+
executables:
|
26
|
+
- liars
|
27
|
+
extensions: []
|
28
|
+
|
29
|
+
extra_rdoc_files:
|
30
|
+
- README
|
31
|
+
- CHANGELOG
|
32
|
+
files:
|
33
|
+
- CHANGELOG
|
34
|
+
- README
|
35
|
+
- Rakefile
|
36
|
+
- liars.rb
|
37
|
+
- bin/liars
|
38
|
+
- lib/server.rb
|
39
|
+
- lib/controller.rb
|
40
|
+
- lib/core_ext.rb
|
41
|
+
- lib/view.rb
|
42
|
+
- lib/example.rb
|
43
|
+
- lib/db.rb
|
44
|
+
- lib/model.rb
|
45
|
+
- extras/flipbook_rdoc.rb
|
46
|
+
has_rdoc: true
|
47
|
+
homepage: http://www.safore.com/
|
48
|
+
licenses: []
|
49
|
+
|
50
|
+
post_install_message:
|
51
|
+
rdoc_options:
|
52
|
+
- --quiet
|
53
|
+
- --title
|
54
|
+
- Camping, the Documentation
|
55
|
+
- --opname
|
56
|
+
- index.html
|
57
|
+
- --line-numbers
|
58
|
+
- --main
|
59
|
+
- README
|
60
|
+
- --inline-source
|
61
|
+
- --exclude
|
62
|
+
- ^(examples|extras)\/
|
63
|
+
require_paths:
|
64
|
+
- .
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
segments:
|
70
|
+
- 0
|
71
|
+
version: "0"
|
72
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
segments:
|
77
|
+
- 0
|
78
|
+
version: "0"
|
79
|
+
requirements: []
|
80
|
+
|
81
|
+
rubyforge_project:
|
82
|
+
rubygems_version: 1.3.6
|
83
|
+
signing_key:
|
84
|
+
specification_version: 3
|
85
|
+
summary: !binary |
|
86
|
+
5pyA5bCP5YyW55qETVZD5qGG5p62
|
87
|
+
|
88
|
+
test_files: []
|
89
|
+
|