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 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% &raquo; %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% &lt; 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
@@ -0,0 +1,8 @@
1
+ require 'lib/core_ext'
2
+ require 'lib/db'
3
+ require 'lib/model'
4
+ require 'lib/controller'
5
+ require 'lib/view'
6
+ require 'lib/model'
7
+ require 'lib/server'
8
+ require 'webrick/httpserver'
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
+