memorack 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,28 @@
1
+ /VERSION
2
+ *.gem
3
+ *.rbc
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
19
+
20
+ # editor noise
21
+ *~
22
+
23
+ # old skool
24
+ .svn
25
+
26
+ # osx noise
27
+ .DS_Store
28
+ Icon
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in memorack.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 gnue
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # MemoRack
2
+
3
+ Rack Application for markdown memo
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'memorack'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install memorack
18
+
19
+ ## Usage
20
+
21
+ $ memorack create PATH # Generate template folder
22
+ $ memorack theme # Show theme list
23
+ $ memorack theme THEME # Show theme info
24
+ $ memorack theme -c THEME # Copy theme
25
+ $ memorack server PATH # Instant Server
26
+
27
+ Standard startup
28
+
29
+ $ memorack create memo
30
+ $ cd memo
31
+ (Customizing...)
32
+ $ rackup
33
+
34
+ Instant server
35
+
36
+ $ mkdir content
37
+ $ echo '# Hello World' > content/hello.md
38
+ (Customizing...)
39
+ $ memorack server content
40
+
41
+ OS X (Pow + powder)
42
+
43
+ $ memorack create memo
44
+ $ cd memo
45
+ (Customizing...)
46
+ $ powder link
47
+ $ open http://memo.dev/
48
+
49
+ * [Pow: Zero-configuration Rack server for Mac OS X](http://pow.cx)
50
+ * `gem install powder`
51
+
52
+ ## Directory
53
+
54
+ Template
55
+
56
+ .
57
+ ├── .gitignore -- for git
58
+ ├── .powenv -- for pow + rbenv
59
+ ├── Gemfile -- `bundle install`
60
+ ├── config.ru -- for rack application
61
+ ├── content -- Content directory for memo
62
+ │   └── README.md -- Sample file(remove it)
63
+ └── themes
64
+ └── custom -- Default theme
65
+ ├── config.json -- Configuration
66
+ └── index.md -- Description(Show by top page)
67
+
68
+ ## Customizing
69
+
70
+ ### Layout
71
+
72
+ `index.html` is mustache template
73
+
74
+ $ cd themes/custom
75
+ $ memorack theme -c basic/index.html
76
+ Created 'index.html'
77
+ (Edit 'index.html'...)
78
+
79
+ #### mustache variables
80
+
81
+ Basic variables -- `{{VAR}}`
82
+
83
+ * `title`
84
+ * `page.title`
85
+ * `app.name`
86
+ * `app.version`
87
+ * `app.url`
88
+ * other variable in config.json
89
+
90
+ Special variables -- `{{{VAR}}}`
91
+
92
+ * `__menu__`
93
+ * `__content__`
94
+
95
+ ## TODO
96
+
97
+ * Template comments translate english
98
+ * Add customizing tips
99
+ * Server test program
100
+ * Abstraction of URI
101
+
102
+ ## Contributing
103
+
104
+ 1. Fork it
105
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
106
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
107
+ 4. Push to the branch (`git push origin my-new-feature`)
108
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ require File.expand_path('../etc/tools/update_version', __FILE__)
2
+ require "bundler/gem_tasks"
3
+
4
+ # Spec
5
+ require 'rake/testtask'
6
+ Rake::TestTask.new(:spec) do |spec|
7
+ spec.libs << "spec"
8
+ spec.test_files = Dir['spec/**/*_spec.rb']
9
+ spec.verbose = true
10
+ end
11
+
12
+
13
+ task :default => :spec
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
data/bin/memorack ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.expand_path('../../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
6
+ require 'memorack'
7
+ require 'memorack/cli'
8
+
9
+ MemoRack::CLI.run
@@ -0,0 +1,14 @@
1
+ def update_version(path)
2
+ version = `git describe --tags --dirty`.chomp
3
+ version.gsub!(/-([a-z0-9]+(-dirty)?)$/) { |m| "(#{$1})" }
4
+
5
+ begin
6
+ return if version == open(path).read.chomp
7
+ return if version.empty?
8
+ rescue
9
+ end
10
+
11
+ open(path, 'w') { |f| f.puts version }
12
+ end
13
+
14
+ update_version 'VERSION'
@@ -0,0 +1,263 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'optparse'
4
+ require 'fileutils'
5
+ require 'rubygems'
6
+ require 'i18n'
7
+
8
+
9
+ module MemoRack
10
+ class CLI
11
+
12
+ def self.run(argv = ARGV, options = {})
13
+ CLI.new.run(argv, options)
14
+ end
15
+
16
+ def initialize
17
+ i18n_init
18
+ end
19
+
20
+ def run(argv = ARGV, options = {})
21
+ subcmd = nil
22
+
23
+ parser = OptionParser.new do |opts|
24
+ begin
25
+ opts.version = LONG_VERSION || VERSION
26
+
27
+ opts.banner = <<-BANNER.gsub(/^\t+/,'')
28
+ Usage: #{opts.program_name} create [options] PATH
29
+ #{opts.program_name} theme [options] [THEME]
30
+ #{opts.program_name} server [options] PATH
31
+ BANNER
32
+
33
+ opts.separator ""
34
+ opts.on("-h", "--help", t(:help)) { abort opts.help }
35
+
36
+ opts.order!(argv)
37
+
38
+ subcmd = argv.shift
39
+ abort opts.help unless subcmd
40
+ abort opts.help unless has_action?(subcmd)
41
+ rescue => e
42
+ abort e.to_s
43
+ end
44
+ end
45
+
46
+ action(subcmd, argv, options)
47
+ end
48
+
49
+ # I18n を初期化する
50
+ def i18n_init
51
+ I18n.load_path = Dir[File.expand_path('../locales/*.yml', __FILE__)]
52
+ I18n.backend.load_translations
53
+
54
+ locale = ENV['LANG'][0, 2].to_sym if ENV['LANG']
55
+ I18n.locale = locale if I18n.available_locales.include?(locale)
56
+ end
57
+
58
+ # I18n で翻訳する
59
+ def t(code, locals = {}, options = {})
60
+ options[:scope] ||= [:usage]
61
+ sprintf(I18n.t(code, options), locals)
62
+ end
63
+
64
+ # ディレクトリを繰返す
65
+ def dir_earch(dir, match = '**/*', flag = File::FNM_DOTMATCH)
66
+ Dir.chdir(dir) { |d|
67
+ Dir.glob(match, flag).each { |file|
68
+ next if File.basename(file) =~ /^[.]{1,2}$/
69
+ file = File.join(file, '') if File.directory?(file)
70
+ yield(file)
71
+ }
72
+ }
73
+ end
74
+
75
+ # テーマ一覧を表示する
76
+ def show_themes(domain, themes)
77
+ return unless File.directory?(themes)
78
+
79
+ puts "#{domain}:"
80
+
81
+ Dir.foreach(themes) { |file|
82
+ next if /^\./ =~ file
83
+ puts " #{file}"
84
+ }
85
+ end
86
+
87
+ # サブコマンドが定義されているか?
88
+ def has_action?(command)
89
+ respond_to? "memorack_#{command}"
90
+ end
91
+
92
+ # サブコマンドの実行
93
+ def action(command, argv, options = {})
94
+ command = command.gsub(/-/, '_')
95
+
96
+ # オプション解析
97
+ options_method = "options_#{command}"
98
+ options.merge!(send(options_method, argv)) if respond_to?(options_method)
99
+
100
+ send("memorack_#{command}", options, *argv)
101
+ end
102
+
103
+ # サブコマンド・オプションのバナー作成
104
+ def banner(opts, method, *args)
105
+ subcmd = method.to_s.gsub(/^.+_/, '')
106
+ ["Usage: #{opts.program_name} #{subcmd}", *args].join(' ')
107
+ end
108
+
109
+ # オプション解析を定義する
110
+ def self.define_options(command, *banner, &block)
111
+ define_method "options_#{command}" do |argv|
112
+ options = {}
113
+
114
+ OptionParser.new { |opts|
115
+ begin
116
+ opts.banner = banner(opts, command, *banner)
117
+ instance_exec(opts, argv, options, &block)
118
+ rescue => e
119
+ abort e.to_s
120
+ end
121
+ }
122
+
123
+ options
124
+ end
125
+ end
126
+
127
+ # オプション解析
128
+
129
+ # テンプレートの作成
130
+ define_options(:create, '[options] PATH') { |opts, argv, options|
131
+ opts.on("-h", "--help", t(:help)) { abort opts.help }
132
+
133
+ opts.parse!(argv)
134
+ abort opts.help if argv.empty?
135
+ }
136
+
137
+ # テーマ関連の操作
138
+ define_options(:theme, '[options] [THEME]') { |opts, argv, options|
139
+ default_options = {dir: 'themes'}
140
+
141
+ options.merge!(default_options)
142
+
143
+ opts.separator ""
144
+ opts.on("-c", "--copy", t(:copy)) { options[:copy] = true }
145
+ opts.on("-d", "--dir DIR", t(:dir, options)) { |arg| options[:dir] = arg }
146
+ opts.on("-h", "--help", t(:help)) { abort opts.help }
147
+
148
+ opts.parse!(argv)
149
+ }
150
+
151
+ # サーバーの実行
152
+ define_options(:server, '[options] PATH') { |opts, argv, options|
153
+ default_options = {
154
+ theme: 'oreilly',
155
+
156
+ server: {
157
+ environment: ENV['RACK_ENV'] || 'development',
158
+ Port: 9292,
159
+ Host: '0.0.0.0',
160
+ AccessLog: [],
161
+ }
162
+ }
163
+
164
+ options.merge!(default_options)
165
+
166
+ opts.separator ""
167
+ opts.on("-p", "--port PORT", String,
168
+ t(:port, options[:server])) { |arg| options[:server][:Port] = arg }
169
+ opts.on("-t", "--theme THEME", String,
170
+ t(:theme, options)) { |arg| options[:theme] = arg }
171
+ opts.on("-h", "--help", t(:help)) { abort opts.help }
172
+
173
+ opts.parse!(argv)
174
+ abort opts.help if argv.empty?
175
+ }
176
+
177
+
178
+ # サブコマンド
179
+
180
+ # テンプレートの作成
181
+ def memorack_create(options, *argv)
182
+ path = argv.shift
183
+ abort "File exists '#{path}'" if File.exists?(path)
184
+
185
+ FileUtils.copy_entry(File.expand_path('../template', __FILE__), path)
186
+ puts "Created '#{path}'"
187
+ end
188
+
189
+ # テーマ関連の操作
190
+ def memorack_theme(options, *argv)
191
+ theme_or_file = argv.shift
192
+
193
+ themes = File.expand_path("../themes", __FILE__)
194
+ dir = options[:dir]
195
+
196
+ if theme_or_file
197
+ theme = theme_or_file.gsub(%r(/.*), '')
198
+
199
+ if options[:copy]
200
+ # テーマをコピー
201
+ theme_dir = File.join(themes, theme)
202
+ abort "Theme not exists '#{theme}'" unless File.directory?(theme_dir)
203
+
204
+ from = File.join(themes, theme_or_file)
205
+ abort "File not exists '#{theme_or_file}'" unless File.exists?(from)
206
+
207
+ path = name = File.basename(from)
208
+ path = File.join(dir, name) if File.directory?(dir) && File.directory?(from)
209
+
210
+ FileUtils.copy_entry(from, path)
211
+ puts "Created '#{path}'"
212
+ else
213
+ # テーマの情報を表示
214
+ app = MemoRack::MemoApp.new(nil, theme: theme, root: dir)
215
+ theme_dir = app.themes.first
216
+
217
+ abort "Theme not exists '#{theme}'" unless theme_dir
218
+
219
+ # 継承関係の表示
220
+ theme_chain = app.themes.collect { |path|
221
+ name = File.basename(path)
222
+ File.dirname(path) == themes ? "[#{name}]" : name
223
+ }
224
+
225
+ puts theme_chain.join(' --> ')
226
+
227
+ # ファイル一覧の表示
228
+ dir_earch(theme_dir) { |file|
229
+ puts " #{file}"
230
+ }
231
+ end
232
+ else
233
+ # テーマ一覧を表示
234
+ show_themes('MemoRack', themes)
235
+ show_themes('User', dir)
236
+ end
237
+ end
238
+
239
+ # サーバーの実行
240
+ def memorack_server(options, *argv)
241
+ path = argv.shift
242
+ abort "Directory not exists '#{path}'" unless File.exists?(path)
243
+ abort "Not directory '#{path}'" unless File.directory?(path)
244
+
245
+ server_options = options[:server]
246
+
247
+ # サーバーの起動
248
+ require 'rack/builder'
249
+ require 'rack/handler/webrick'
250
+ app = Rack::Builder.new {
251
+ require 'memorack'
252
+ require 'tmpdir'
253
+
254
+ Dir.mktmpdir do |tmpdir|
255
+ run MemoRack::MemoApp.new(nil, theme: options[:theme], root: path, tmpdir: tmpdir)
256
+ end
257
+ }
258
+
259
+ server_options[:app] = app
260
+ Rack::Server.new(server_options).start
261
+ end
262
+ end
263
+ end
@@ -0,0 +1,7 @@
1
+ en:
2
+ usage:
3
+ help: Show this message
4
+ copy: Copy theme
5
+ dir: "Theme directory (default: %{dir})"
6
+ port: "use PORT (default: %{Port})"
7
+ theme: "use THEME (default: %{theme})"
@@ -0,0 +1,7 @@
1
+ ja:
2
+ usage:
3
+ help: このメッセージを表示
4
+ copy: テーマをコピーする
5
+ dir: "テーマのディレクトリー(省略値: %{dir})"
6
+ port: "ポートを使う (省略値: %{Port})"
7
+ theme: "テーマを使う (省略値: %{theme})"
@@ -0,0 +1,185 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- encoding: utf-8 -*-
3
+
4
+ require 'optparse'
5
+ require 'uri'
6
+
7
+
8
+ module MemoRack
9
+
10
+ class MdMenu
11
+ URI_UNSAFE = /[^\-_.!~*'a-zA-Z\d;\/?:@&=+$,\[\]]/
12
+
13
+ DEFAULT_FORMATS = {
14
+ 'markdown' => ['txt', 'md', 'markdown'],
15
+ 'textile' => ['tt'],
16
+ 'wiki' => ['wiki'],
17
+ 'json' => ['json'],
18
+ 'html' => ['html', 'htm']
19
+ }
20
+
21
+ def initialize(config)
22
+ @config = config
23
+ @file = config[:file]
24
+ @links = analyze(@file)
25
+ @files = []
26
+ @extentions = {}
27
+
28
+ regFormats(config[:formats] || DEFAULT_FORMATS)
29
+ end
30
+
31
+ # フォーマット・ハッシュからファイル拡張子を登録する
32
+ def regFormats(formats)
33
+ formats.each { |name, extentions|
34
+ regFormat(name, extentions)
35
+ }
36
+ end
37
+
38
+ # フォーマットとファイル拡張子を登録する
39
+ def regFormat(name, extentions)
40
+ extentions.each { |value|
41
+ @extentions[value] = name
42
+ }
43
+ end
44
+
45
+ # Markdownファイルを解析してリンクを取出す
46
+ def analyze(path)
47
+ links = []
48
+
49
+ if path && File.exist?(path) then
50
+ open(path) { |f|
51
+ while line = f.gets()
52
+ case line
53
+ when /\[.+\]\s*\(([^()]+)\)/
54
+ # インライン・リンク
55
+ links << $1
56
+ when /\[.+\]:\s*([^\s]+)/
57
+ # 参照リンク
58
+ links << $1
59
+ end
60
+ end
61
+ }
62
+ end
63
+
64
+ return links
65
+ end
66
+
67
+ # 追加されたファイルを集める
68
+ def collection(dir)
69
+ # 拡張子
70
+ exts = @extentions.keys.join(',')
71
+ # 検索パターン
72
+ pattern = File.join(dir, "**/*.{#{exts}}");
73
+ pattern.gsub!(/^\.\//, '')
74
+
75
+ Dir.glob(pattern) { |path|
76
+ link = @config[:prefix].to_s + (@config[:uri_escape] ? URI.escape(path, URI_UNSAFE) : path)
77
+ @files << {:link => link, :path => path} if ! @links.member?(link)
78
+ }
79
+ end
80
+
81
+ # 標準出力を切換える
82
+ def stdout(path, mode = 'r', perm = 0666)
83
+ curr = $stdout
84
+ f = nil
85
+
86
+ begin
87
+ if path.kind_of?(String)
88
+ $stdout = f = File.open(path, mode, perm)
89
+ elsif path
90
+ $stdout = path
91
+ end
92
+
93
+ yield
94
+ ensure
95
+ f.close if f
96
+ $stdout = curr
97
+ end
98
+ end
99
+
100
+ # ディレクトリが違うときにサブディレクトリを引数に実行する
101
+ def each_subdir(d, dir = nil, dirs = [])
102
+ return [dir, dirs] if d == dir
103
+
104
+ prefix = @config[:prefix]
105
+
106
+ d.gsub!(/^#{prefix}/, '') if prefix
107
+ ds = d.scan(/[^\/]+/)
108
+ ds.delete('.')
109
+
110
+ ds.each_with_index { |name, i|
111
+ next if name == dirs[i]
112
+
113
+ name = URI.unescape(name) if @config[:uri_escape]
114
+ yield(name, i)
115
+ }
116
+
117
+ [d, ds]
118
+ end
119
+
120
+ # 新規リンクを追加する
121
+ def generate(outfile = @file)
122
+ len = @files.length
123
+
124
+ if 0 < len
125
+ outfile = nil if outfile && outfile.kind_of?(String) && File.exist?(outfile) && ! @config[:update]
126
+
127
+ stdout(outfile, 'a') {
128
+ prefix = @config[:prefix]
129
+ dir = nil
130
+ dirs = []
131
+
132
+ @files.each { |item|
133
+ title = File.basename(item[:path], '.*')
134
+ link = item[:link]
135
+
136
+ dir, dirs = each_subdir(File.dirname(link), dir, dirs) { |name, i|
137
+ print ' ' * i + "- #{name}\n"
138
+ }
139
+
140
+ print ' ' * dirs.size + "- [#{title}](#{link})\n"
141
+ }
142
+ }
143
+
144
+ if outfile then
145
+ # update
146
+ message = "append #{len} links"
147
+ else
148
+ # dry-run
149
+ message = "found #{len} links (can update with -u option)"
150
+ end
151
+ else
152
+ message = "not found"
153
+ end
154
+
155
+ $stderr.print message, "\n" if message && @config[:verbose]
156
+
157
+ outfile
158
+ end
159
+ end
160
+
161
+ end
162
+
163
+
164
+ if __FILE__ == $0
165
+ # コマンド引数の解析
166
+ config = {:verbose => true}
167
+
168
+ OptionParser.new { |opts|
169
+ opts.banner = "Usage: #{opts.program_name} [-f FILE] [-u] [-e] [--prefix PREFIX] DIRECTORY… "
170
+
171
+ opts.on('-f FILE', 'menu file') { |v| config[:file] = v }
172
+ opts.on('-u', 'file update(default is dry-run)') { config[:update] = true }
173
+ opts.on('-e', 'URI escape') { config[:uri_escape] = true }
174
+ opts.on('-p PREFIX', '--prefix PREFIX', 'link prefix') { |v| config[:prefix] = v }
175
+ opts.on('-h', '--help') { abort opts.help }
176
+ opts.parse!(ARGV)
177
+ }
178
+
179
+ ARGV.push '.' if ARGV.length == 0
180
+
181
+ mdmenu = MemoRack::MdMenu.new(config)
182
+
183
+ ARGV.each { |dir| mdmenu.collection(dir) }
184
+ mdmenu.generate
185
+ end