memorack 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/.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