mamemose 0.3.0 → 0.4.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.
data/README.md CHANGED
@@ -50,7 +50,7 @@ $ gem install mamemose
50
50
  パスを通す必要があるかも。
51
51
 
52
52
  ```bash
53
- $ mamemose &> /dev/null &
53
+ $ nohup mamemose &> /dev/null &
54
54
  ```
55
55
 
56
56
  するとローカルで HTTP サーバが立ち上がります。その後ブラウザから
@@ -69,6 +69,23 @@ http://localhost:PORT/
69
69
  - 文字コードは UTF-8 で書くようにしてください。
70
70
  - コマンドラインオプションは `mamemose help` で出ます。一応。
71
71
 
72
+
73
+ ### 自動更新
74
+
75
+ WebSocket を使って自動更新できます。
76
+ mamemose サーバを立てた後アクセスされたファイルを監視しておき、
77
+ 更新があればそのファイルを開いているブラウザのページを自動的にリロードします。
78
+
79
+ 現在のところ、 WebSocket 用のサーバを別に立てておくという設計になっています。
80
+ 以下のマンドを叩いて mamemose WebSocket サーバを起動しておいてください。
81
+
82
+ ```bash
83
+ $ nohup mamemose_websocket &> /dev/null &
84
+ ```
85
+
86
+ mamemose WebSocket サーバを立てなくても利用できます。
87
+ その場合は手動で更新してください。
88
+
72
89
  ### 一時ファイル閲覧
73
90
 
74
91
  一時的に `DOCUMENT_ROOT` で指定したディレクトリ以外にあるファイルを
data/bin/mamemose CHANGED
@@ -13,7 +13,7 @@ class Mamemose::CLI < Thor
13
13
  def server(filename=nil, port=nil)
14
14
  mamemose = Mamemose::Server.new(port)
15
15
  if !filename && !port
16
- mamemose.start
16
+ mamemose.server
17
17
  elsif filename && port
18
18
  mamemose.file(filename)
19
19
  else
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.expand_path("../../lib", __FILE__)
4
+
5
+ load 'mamemose/websocket.rb'
6
+
7
+ require 'rubygems'
8
+ require 'thor'
9
+
10
+ class Mamemose::WebSocket::CLI < Thor
11
+ desc "server", "run the mamemose websocket server"
12
+ def server
13
+ ws = Mamemose::WebSocket::Server.new
14
+ ws.start
15
+ end
16
+
17
+ desc "s", "alias of server"
18
+ alias :s :server
19
+
20
+ desc "version", "print version"
21
+ def version
22
+ puts Mamemose::WebSocket::VERSION
23
+ end
24
+ end
25
+
26
+ args = ARGV == [] ? ["s"] : ARGV
27
+ Mamemose::WebSocket::CLI.start(args)
@@ -0,0 +1,19 @@
1
+ conf = File.expand_path("~" + File::SEPARATOR + ".mamemose.rb")
2
+ load conf if File.exists?(conf)
3
+
4
+ DOCUMENT_ROOT = "~/Dropbox/memo" if !defined?(DOCUMENT_ROOT)
5
+ PORT = 20000 if !defined?(PORT)
6
+ WS_PORT = 30000 if !defined?(WS_PORT)
7
+ RECENT_NUM = 10 if !defined?(RECENT_NUM)
8
+ RECENT_PATTERN = /.*/ if !defined?(RECENT_PATTERN)
9
+ IGNORE_FILES = ['.DS_Store','.AppleDouble','.LSOverride','Icon',/^\./,/~$/,
10
+ '.Spotlight-V100','.Trashes','Thumbs.db','ehthumbs.db',
11
+ 'Desktop.ini','$RECYCLE.BIN',/^#/,'MathJax','syntaxhighlighter'] if !defined?(IGNORE_FILES)
12
+ MARKDOWN_PATTERN = /\.(md|markdown)$/ if !defined?(MARKDOWN_PATTERN)
13
+ INDEX_PATTERN = /^README/i if !defined?(INDEX_PATTERN)
14
+ CUSTOM_HEADER = '' if !defined?(CUSTOM_HEADER)
15
+ CUSTOM_BODY = '' if !defined?(CUSTOM_BODY)
16
+ CUSTOM_FOOTER = '' if !defined?(CUSTOM_FOOTER)
17
+
18
+ CONTENT_TYPE = "text/html; charset=utf-8"
19
+ DIR = File::expand_path(DOCUMENT_ROOT, '/')
@@ -0,0 +1,32 @@
1
+ module Mamemose::Path
2
+ # returns escaped characters so that the markdown parser doesn't interpret it has special meaning.
3
+ def escape(text)
4
+ return text.gsub(/[\`*_{}\[\]()#+\-.!]/, "\\\\\\0")
5
+ end
6
+
7
+ # returns /-rooted path. eg. /path/to/my_document.md
8
+ def uri(path)
9
+ s = File::expand_path(path).gsub(DIR, "").gsub(File::SEPARATOR, '/')
10
+ return s == '' ? '/' : s
11
+ end
12
+
13
+ # returns fullpath. eg. /home/daimatz/Dropbox/memo/path/to/my_document.md
14
+ def fullpath(uri)
15
+ return File.join(DIR, uri.gsub(DIR, '').gsub('/', File::SEPARATOR))
16
+ end
17
+
18
+ # returns DOCUMENT_ROOT-rooted path. eg. ~/Dropbox/memo/path/to/my_document.md
19
+ def docpath(uri)
20
+ return File.join(DOCUMENT_ROOT, uri.gsub('/', File::SEPARATOR)).gsub(/#{File::SEPARATOR}$/, "")
21
+ end
22
+
23
+ # returns DOCUMENT_ROOT-rooted path, but escaped. eg. ~/Dropbox/memo/path/to/my\_document.md
24
+ # used in user-viewable (HTML) context.
25
+ def showpath(uri)
26
+ return escape(docpath(uri))
27
+ end
28
+
29
+ def escaped_basename(filename)
30
+ return escape(File::basename(filename))
31
+ end
32
+ end
@@ -0,0 +1,5 @@
1
+ module Mamemose::Util
2
+ def debug(tag, msg)
3
+ STDERR.puts "#{tag}: #{msg}"
4
+ end
5
+ end
@@ -1,3 +1,7 @@
1
1
  module Mamemose
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
+
4
+ module WebSocket
5
+ VERSION = "0.1.0"
6
+ end
3
7
  end
@@ -0,0 +1,108 @@
1
+ require 'em-websocket'
2
+ require 'thread'
3
+
4
+ require 'mamemose/version'
5
+
6
+ require 'mamemose/path'
7
+ require 'mamemose/util'
8
+
9
+ require 'mamemose/env'
10
+
11
+ class Mamemose::WebSocket::Server
12
+ include Mamemose::Util
13
+
14
+ @@update_send_message = "updated"
15
+
16
+ def initialize
17
+ @connections = []
18
+ @mutex = Mutex::new
19
+ @tag = "WebSocket"
20
+ end
21
+
22
+ def start
23
+ Thread.new do
24
+ debug(@tag, "start watcher...")
25
+ watcher
26
+ end
27
+
28
+ EventMachine::WebSocket.start(:host => '0.0.0.0', :port => WS_PORT) do |ws|
29
+ ws.onopen {
30
+ debug(@tag, "connected.")
31
+ ws.send("connected.")
32
+ }
33
+
34
+ ws.onmessage { |fullpath|
35
+ # receive url from client
36
+ debug(@tag, "receive: #{fullpath}")
37
+ if File.exists?(fullpath)
38
+ # connections are managed as tuple of (socket, url, mtime_cache)
39
+ con = {:ws => ws, :fullpath => fullpath, :mtime_cache => get_mtime(fullpath)}
40
+ @mutex.synchronize do
41
+ @connections.push(con) unless @connections.index(con)
42
+ debug(@tag, "added path to watch: #{fullpath}. now watch #{fullpaths.to_s}")
43
+ end
44
+ end
45
+ }
46
+
47
+ ws.onclose {
48
+ debug(@tag, "closed.")
49
+ # when a connection is closed, delete it from @connections
50
+ @mutex.synchronize do
51
+ @connections.delete_if { |con| con[:ws] == ws }
52
+ debug(@tag, "closed and removed path. now watch #{fullpaths.to_s}")
53
+ end
54
+ }
55
+ end
56
+ end
57
+
58
+ def watcher
59
+ loop do
60
+ # gather paths to watch using mutex
61
+ watch_fullpaths = []
62
+ @mutex.synchronize do
63
+ watch_fullpaths = fullpaths
64
+ end
65
+
66
+ # gather mtimes of watch_fullpaths
67
+ mtimes = {}
68
+ watch_fullpaths.uniq.each do |fullpath|
69
+ if File.exists?(fullpath)
70
+ # get mtime
71
+ mtimes[fullpath] = get_mtime(fullpath)
72
+ else
73
+ # file no longer exists. remove the entry
74
+ @mutex.synchronize do
75
+ @connections.delete_if { |con| con[:fullpath] == fullpath }
76
+ debug(@tag, "detected deletion: #{fullpath} and updated the list. now watch #{fullpaths.to_s}")
77
+ end
78
+ end
79
+ end
80
+
81
+ # push notification if watching file is updated
82
+ to_notify = []
83
+ @mutex.synchronize do
84
+ @connections.each do |con|
85
+ fullpath = con[:fullpath]
86
+ if mtimes[fullpath] && con[:mtime_cache] < mtimes[fullpath]
87
+ debug(@tag, "detected update: #{fullpath}. pushing...")
88
+ con[:mtime_cache] = mtimes[fullpath]
89
+ to_notify << con
90
+ end
91
+ end
92
+ end
93
+ to_notify.each do |con|
94
+ con[:ws].send(@@update_send_message)
95
+ end
96
+
97
+ sleep 1
98
+ end
99
+ end
100
+
101
+ def fullpaths
102
+ @connections.map{ |con| con[:fullpath] }
103
+ end
104
+
105
+ def get_mtime(fullpath)
106
+ File.mtime(fullpath) if File.exists?(fullpath)
107
+ end
108
+ end
data/lib/mamemose.rb CHANGED
@@ -5,26 +5,11 @@ require 'uri'
5
5
  require 'redcarpet'
6
6
  require 'htmlentities'
7
7
 
8
- require "mamemose/version"
9
-
10
- conf = File.expand_path("~" + File::SEPARATOR + ".mamemose.rb")
11
- load conf if File.exists?(conf)
12
-
13
- DOCUMENT_ROOT = "~/Dropbox/memo" if !defined?(DOCUMENT_ROOT)
14
- PORT = 20000 if !defined?(PORT)
15
- RECENT_NUM = 10 if !defined?(RECENT_NUM)
16
- RECENT_PATTERN = /.*/ if !defined?(RECENT_PATTERN)
17
- IGNORE_FILES = ['.DS_Store','.AppleDouble','.LSOverride','Icon',/^\./,/~$/,
18
- '.Spotlight-V100','.Trashes','Thumbs.db','ehthumbs.db',
19
- 'Desktop.ini','$RECYCLE.BIN',/^#/,'MathJax','syntaxhighlighter'] if !defined?(IGNORE_FILES)
20
- MARKDOWN_PATTERN = /\.(md|markdown)$/ if !defined?(MARKDOWN_PATTERN)
21
- INDEX_PATTERN = /^README/i if !defined?(INDEX_PATTERN)
22
- CUSTOM_HEADER = '' if !defined?(CUSTOM_HEADER)
23
- CUSTOM_BODY = '' if !defined?(CUSTOM_BODY)
24
- CUSTOM_FOOTER = '' if !defined?(CUSTOM_FOOTER)
25
-
26
- CONTENT_TYPE = "text/html; charset=utf-8"
27
- DIR = File::expand_path(DOCUMENT_ROOT, '/')
8
+ require 'mamemose/version'
9
+
10
+ require 'mamemose/path'
11
+
12
+ require 'mamemose/env'
28
13
 
29
14
  class HTMLwithSyntaxHighlighter < Redcarpet::Render::XHTML
30
15
  def block_code(code, lang)
@@ -35,21 +20,28 @@ class HTMLwithSyntaxHighlighter < Redcarpet::Render::XHTML
35
20
  end
36
21
 
37
22
  class Mamemose::Server
23
+ include Mamemose::Path
24
+
38
25
  def initialize(port)
39
- @server = WEBrick::HTTPServer.new({ :Port => port ? port.to_i : PORT })
40
- trap(:INT){@server.shutdown}
41
- trap(:TERM){@server.shutdown}
26
+ @mamemose = WEBrick::HTTPServer.new({ :Port => port ? port.to_i : PORT })
27
+ trap(:INT){finalize}
28
+ trap(:TERM){finalize}
42
29
  end
43
30
 
44
31
  def start
45
- @server.mount_proc('/') do |req, res|
32
+ @mamemose.start
33
+ end
34
+
35
+ def server
36
+ @mamemose.mount_proc('/') do |req, res|
46
37
  res['Cache-Control'] = 'no-cache, no-store, must-revalidate'
47
38
  res['Pragma'] = 'no-cache'
48
39
  res['Expires'] = '0'
49
40
 
41
+ p fullpath(req.path)
50
42
  if req.path =~ /^\/search/
51
43
  res = req_search(req, res)
52
- elsif File.directory?(fullpath(req.path)) then
44
+ elsif File.directory?(fullpath(req.path))
53
45
  res = req_index(req, res)
54
46
  elsif File.exists?(fullpath(req.path))
55
47
  res = req_file(fullpath(req.path), res, false)
@@ -57,25 +49,28 @@ class Mamemose::Server
57
49
  res.status = WEBrick::HTTPStatus::RC_NOT_FOUND
58
50
  end
59
51
  end
60
-
61
- @server.start
52
+ start
62
53
  end
63
54
 
64
55
  def file(filename)
65
- @server.mount_proc('/') do |req, res|
56
+ @mamemose.mount_proc('/') do |req, res|
66
57
  res['Cache-Control'] = 'no-cache, no-store, must-revalidate'
67
58
  res['Pragma'] = 'no-cache'
68
59
  res['Expires'] = '0'
69
60
  res = req_file(File.absolute_path(filename), res, true)
70
61
  res.content_type = CONTENT_TYPE
71
62
  end
72
-
73
- @server.start
63
+ start
74
64
  end
75
65
 
76
66
  private
77
67
 
78
- def header_html(title, path)
68
+ def finalize
69
+ Thread::list.each {|t| Thread::kill(t) if t != Thread::current}
70
+ @mamemose.shutdown
71
+ end
72
+
73
+ def header_html(title, fullpath)
79
74
  html = <<HTML
80
75
  <!DOCTYPE HTML>
81
76
  <html>
@@ -221,6 +216,23 @@ function copy(text) {
221
216
  prompt("Copy filepath below:", text);
222
217
  }
223
218
  </script>
219
+ <script>
220
+ (function(){
221
+ var fullpath = "#{fullpath}";
222
+ ws = new WebSocket("ws://localhost:#{WS_PORT}");
223
+ ws.onopen = function() {
224
+ console.log("WebSocket (port=#{WS_PORT}) connected: " + fullpath);
225
+ ws.send(fullpath);
226
+ };
227
+ ws.onmessage = function(evt) {
228
+ console.log("received: " + evt.data);
229
+ if (evt.data == "updated") {
230
+ console.log("update detected. reloading...");
231
+ location.reload();
232
+ }
233
+ };
234
+ })();
235
+ </script>
224
236
  #{CUSTOM_HEADER}
225
237
  </head>
226
238
  <body>
@@ -288,7 +300,7 @@ HTML
288
300
  value.each {|v| body += link_list(v[0], v[1])}
289
301
  end
290
302
 
291
- res.body = header_html(title, uri(path))\
303
+ res.body = header_html(title, path)\
292
304
  + search_form(uri(path), q)\
293
305
  + markdown(body)\
294
306
  + footer_html
@@ -321,7 +333,7 @@ HTML
321
333
  body += File.read(index)
322
334
  end
323
335
 
324
- res.body = header_html(title, req.path)\
336
+ res.body = header_html(title, directory)\
325
337
  + search_form(uri(req.path))\
326
338
  + markdown(body)\
327
339
  + footer_html(index)
@@ -407,37 +419,6 @@ HTML
407
419
  return {:dirs=>dirs, :markdowns=>markdowns, :others=>others}
408
420
  end
409
421
 
410
- # returns escaped characters so that the markdown parser doesn't interpret it has special meaning.
411
- def escape(text)
412
- return text.gsub(/[\`*_{}\[\]()#+\-.!]/, "\\\\\\0")
413
- end
414
-
415
- # returns /-rooted path. eg. /path/to/my_document.md
416
- def uri(path)
417
- s = File::expand_path(path).gsub(DIR, "").gsub(File::SEPARATOR, '/')
418
- return s == '' ? '/' : s
419
- end
420
-
421
- # returns fullpath. eg. /home/daimatz/Dropbox/memo/path/to/my_document.md
422
- def fullpath(uri)
423
- return File.join(DIR, uri.gsub('/', File::SEPARATOR))
424
- end
425
-
426
- # returns DOCUMENT_ROOT-rooted path. eg. ~/Dropbox/memo/path/to/my_document.md
427
- def docpath(uri)
428
- return File.join(DOCUMENT_ROOT, uri.gsub('/', File::SEPARATOR)).gsub(/#{File::SEPARATOR}$/, "")
429
- end
430
-
431
- # returns DOCUMENT_ROOT-rooted path, but escaped. eg. ~/Dropbox/memo/path/to/my\_document.md
432
- # used in user-viewable (HTML) context.
433
- def showpath(uri)
434
- return escape(docpath(uri))
435
- end
436
-
437
- def escaped_basename(filename)
438
- return escape(File::basename(filename))
439
- end
440
-
441
422
  def link_list(title, link)
442
423
  file = fullpath(link)
443
424
  str = filesize(file)
data/mamemose.gemspec CHANGED
@@ -17,5 +17,6 @@ Gem::Specification.new do |gem|
17
17
 
18
18
  gem.add_dependency "redcarpet", ">= 2.2.0"
19
19
  gem.add_dependency "htmlentities", ">= 4.3.0"
20
- gem.add_dependency "thor"
20
+ gem.add_dependency "thor", ">= 0.17.0"
21
+ gem.add_dependency "em-websocket", ">= 0.5.0"
21
22
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mamemose
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-03-16 00:00:00.000000000 Z
12
+ date: 2013-03-30 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: redcarpet
@@ -50,7 +50,7 @@ dependencies:
50
50
  requirements:
51
51
  - - ! '>='
52
52
  - !ruby/object:Gem::Version
53
- version: '0'
53
+ version: 0.17.0
54
54
  type: :runtime
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
@@ -58,12 +58,29 @@ dependencies:
58
58
  requirements:
59
59
  - - ! '>='
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
61
+ version: 0.17.0
62
+ - !ruby/object:Gem::Dependency
63
+ name: em-websocket
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: 0.5.0
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: 0.5.0
62
78
  description: Markdown memo server
63
79
  email:
64
80
  - dai@daimatz.net
65
81
  executables:
66
82
  - mamemose
83
+ - mamemose_websocket
67
84
  extensions: []
68
85
  extra_rdoc_files: []
69
86
  files:
@@ -73,9 +90,14 @@ files:
73
90
  - README.md
74
91
  - Rakefile
75
92
  - bin/mamemose
93
+ - bin/mamemose_websocket
76
94
  - index.png
77
95
  - lib/mamemose.rb
96
+ - lib/mamemose/env.rb
97
+ - lib/mamemose/path.rb
98
+ - lib/mamemose/util.rb
78
99
  - lib/mamemose/version.rb
100
+ - lib/mamemose/websocket.rb
79
101
  - mamemose.gemspec
80
102
  - sample.png
81
103
  homepage: https://github.com/daimatz/mamemose