m2m 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +39 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +23 -0
- data/LICENSE +21 -0
- data/README.md +89 -0
- data/bin/console +14 -0
- data/exe/m2m +51 -0
- data/exe/mailer.rb +21 -0
- data/exe/server.rb +21 -0
- data/exe/setup.rb +27 -0
- data/exe/site.rb +37 -0
- data/lib/article.rb +63 -0
- data/lib/compiler.rb +77 -0
- data/lib/generator.rb +176 -0
- data/lib/mailer.rb +235 -0
- data/lib/meta.rb +48 -0
- data/lib/product.rb +6 -0
- data/lib/scan.rb +48 -0
- data/lib/server.rb +18 -0
- data/lib/setup.rb +278 -0
- data/lib/store.rb +113 -0
- data/lib/themes/hyde/static/hyde.css +355 -0
- data/lib/themes/hyde/template/article.mustache +19 -0
- data/lib/themes/hyde/template/home.mustache +15 -0
- data/lib/themes/hyde/template/index.mustache +15 -0
- data/lib/themes/hyde/template/mail.mustache +5 -0
- data/lib/themes/hyde/template/page.mustache +17 -0
- data/lib/themes/hyde/template/partials/footer.mustache +1 -0
- data/lib/themes/hyde/template/partials/head.mustache +6 -0
- data/lib/themes/hyde/template/partials/header.mustache +22 -0
- data/lib/themes/hyde/template/partials/list.mustache +20 -0
- data/lib/toc.rb +55 -0
- data/lib/util.rb +119 -0
- data/m2m.gemspec +40 -0
- metadata +204 -0
data/lib/product.rb
ADDED
data/lib/scan.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
|
2
|
+
require 'pathname'
|
3
|
+
require_relative './util'
|
4
|
+
require_relative './setup'
|
5
|
+
|
6
|
+
class Scan
|
7
|
+
#初始化
|
8
|
+
def initialize()
|
9
|
+
@util = Util.instance
|
10
|
+
@setup = Setup.instance
|
11
|
+
@files = Array.new
|
12
|
+
end
|
13
|
+
|
14
|
+
#返回已经获取的文件列表
|
15
|
+
def files
|
16
|
+
return @files
|
17
|
+
end
|
18
|
+
|
19
|
+
#获取文件
|
20
|
+
def fetch(dir)
|
21
|
+
Dir::entries(dir).each do |filename|
|
22
|
+
#忽略的文件
|
23
|
+
next if @util.is_shadow_file?(filename)
|
24
|
+
|
25
|
+
#检查是否配置文件中所忽略的文件
|
26
|
+
#这里需要用相对路径
|
27
|
+
next if @setup.is_user_ignore_file?(filename)
|
28
|
+
|
29
|
+
file = File::join(dir, filename)
|
30
|
+
#如果是文件夹类型, 则继承查找
|
31
|
+
if(File.ftype(file) == 'directory')
|
32
|
+
self.fetch file
|
33
|
+
next
|
34
|
+
end
|
35
|
+
|
36
|
+
#如果文件扩展名是md, 则加入到files中
|
37
|
+
if @util.is_markdown_file?(filename)
|
38
|
+
current_dir = Pathname.new file
|
39
|
+
@files.push current_dir.relative_path_from(@setup.content_dir)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
#执行
|
45
|
+
def execute()
|
46
|
+
fetch @setup.content_dir
|
47
|
+
end
|
48
|
+
end
|
data/lib/server.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'webrick'
|
2
|
+
require_relative './util'
|
3
|
+
require_relative './setup'
|
4
|
+
|
5
|
+
# class Server < ::Rack::Server
|
6
|
+
# def app
|
7
|
+
# Rack::Directory::new Setup.instance.target_dir
|
8
|
+
# end
|
9
|
+
# end
|
10
|
+
|
11
|
+
class Server
|
12
|
+
def start(port)
|
13
|
+
port = port || 8000
|
14
|
+
root = Setup.instance.target_dir
|
15
|
+
server = WEBrick::HTTPServer.new :Port => port, :DocumentRoot => root
|
16
|
+
server.start
|
17
|
+
end
|
18
|
+
end
|
data/lib/setup.rb
ADDED
@@ -0,0 +1,278 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'singleton'
|
3
|
+
require_relative './util'
|
4
|
+
require_relative './product'
|
5
|
+
|
6
|
+
class Setup
|
7
|
+
include Singleton
|
8
|
+
attr :target_dir, true
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@util = Util.instance
|
12
|
+
#合并后的配置信息
|
13
|
+
@merge_config = nil
|
14
|
+
#内容目录
|
15
|
+
@content_dir = nil
|
16
|
+
#构建的目标目录
|
17
|
+
@target_dir = nil
|
18
|
+
#邮件的配置
|
19
|
+
@mail_config = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
#读取邮件的配置
|
23
|
+
def mail_config
|
24
|
+
@mail_config = self.get_merged_config['mail'] if not @mail_config
|
25
|
+
@mail_config
|
26
|
+
end
|
27
|
+
|
28
|
+
#最终的构建目录
|
29
|
+
def target_dir
|
30
|
+
dir = @target_dir
|
31
|
+
#已经处理过了
|
32
|
+
return dir if dir.class == Pathname
|
33
|
+
|
34
|
+
#如果没有指定构建目录,则使用配置文件的目录
|
35
|
+
config = self.get_merged_config
|
36
|
+
dir = config['target'] if not dir
|
37
|
+
|
38
|
+
#依然没有获取准确的目录,则使用使用临时目录
|
39
|
+
# dir = File::join(@util.get_temp_dir, @util.project_name) if not dir
|
40
|
+
#如果没有获取构建目录,则在当前目录下,创建一个
|
41
|
+
dir = './m2m-site' if not dir
|
42
|
+
#如果是字符类型,则获取相对于workbench的目录
|
43
|
+
@target_dir = @util.get_merge_path dir if dir.class == String
|
44
|
+
|
45
|
+
@target_dir
|
46
|
+
end
|
47
|
+
|
48
|
+
#获取内容的目录
|
49
|
+
def content_dir
|
50
|
+
if not @content_dir
|
51
|
+
content_dir = self.get_merged_config['content'] || './'
|
52
|
+
@content_dir = @util.get_merge_path(content_dir, @util.workbench)
|
53
|
+
end
|
54
|
+
|
55
|
+
@content_dir
|
56
|
+
end
|
57
|
+
|
58
|
+
#获取配置文件的地址
|
59
|
+
def get_config_file(is_global = true)
|
60
|
+
root = is_global ? @util.get_temp_dir : @util.workbench
|
61
|
+
#全局的配置文件
|
62
|
+
File::join root, @util.config_file
|
63
|
+
end
|
64
|
+
|
65
|
+
#读取配置文件
|
66
|
+
def read(is_global)
|
67
|
+
file = self.get_config_file is_global
|
68
|
+
return {} if not File::exists? file
|
69
|
+
|
70
|
+
#读取配置文件
|
71
|
+
YAML.load IO.read(file)
|
72
|
+
end
|
73
|
+
|
74
|
+
#写入配置文件
|
75
|
+
def write(config, is_global)
|
76
|
+
file = self.get_config_file is_global
|
77
|
+
@util.write_file file, config.to_yaml
|
78
|
+
end
|
79
|
+
|
80
|
+
#读取本地与全局的配置文件,然后合并
|
81
|
+
def get_merged_config
|
82
|
+
return @merge_config if @merge_config
|
83
|
+
|
84
|
+
global_config = self.read true
|
85
|
+
local_config = self.read false
|
86
|
+
@merge_config = global_config.merge local_config
|
87
|
+
@merge_config
|
88
|
+
end
|
89
|
+
|
90
|
+
#是否为用户在配置文件中的忽略的文件
|
91
|
+
def is_user_ignore_file?(file)
|
92
|
+
config = self.get_merged_config
|
93
|
+
ignores = config['ignore']
|
94
|
+
return false if not ignores
|
95
|
+
|
96
|
+
ignores.each { |current|
|
97
|
+
#TODO 这里还需要再增加
|
98
|
+
}
|
99
|
+
|
100
|
+
return false
|
101
|
+
end
|
102
|
+
|
103
|
+
#根据问题的配置文件列表,揭示用户输入
|
104
|
+
def ask_items(items, data)
|
105
|
+
items.each { |item|
|
106
|
+
type = item['type']
|
107
|
+
value = ask(item['ask'], type){|q|
|
108
|
+
q.default = item['default'] if item['default']
|
109
|
+
q.echo = item['echo'] if item['echo']
|
110
|
+
q.validate = item['validate'] if item['validate']
|
111
|
+
q.responses[:not_valid] = item['error'] if item['error']
|
112
|
+
}
|
113
|
+
|
114
|
+
if type == Integer
|
115
|
+
value = value.to_i
|
116
|
+
else
|
117
|
+
value = value.to_s
|
118
|
+
end
|
119
|
+
|
120
|
+
data[item['key']] = value
|
121
|
+
}
|
122
|
+
data
|
123
|
+
end
|
124
|
+
|
125
|
+
#检查邮件的配置
|
126
|
+
def check_mail_setup
|
127
|
+
mail_config = self.get_merged_config['mail']
|
128
|
+
|
129
|
+
return @util.error '请执行[m2m mail --setup]启动配置' if not mail_config
|
130
|
+
|
131
|
+
items = {
|
132
|
+
'smtp_server' => 'STMP服务器',
|
133
|
+
'port' => '端口',
|
134
|
+
'username' => '用户名',
|
135
|
+
'password' => '密码',
|
136
|
+
'from' => '发件人'
|
137
|
+
}
|
138
|
+
|
139
|
+
items.each {|key, desc|
|
140
|
+
@util.error "#{desc}没有配置,请执行[m2m mail --setup]启动配置" if mail_config[key] == ''
|
141
|
+
}
|
142
|
+
end
|
143
|
+
|
144
|
+
#询问用户的信息
|
145
|
+
def ask_mail
|
146
|
+
#获取全局的配置
|
147
|
+
data = self.read true
|
148
|
+
mail_data = data['mail'] || {}
|
149
|
+
|
150
|
+
items = [
|
151
|
+
{
|
152
|
+
'key' => 'smtp_server',
|
153
|
+
'ask' => '请输入您的【SMTP服务器地址】,如smpt.163.com',
|
154
|
+
'default' => mail_data['smtp_server'],
|
155
|
+
'type' => String,
|
156
|
+
'validate' => /(\.[a-zA-Z0-9\-]+){2,}/,
|
157
|
+
'error' => '您的SMTP地址输入不正确,请输入域名或者IP地址'
|
158
|
+
},{
|
159
|
+
'key' => 'port',
|
160
|
+
'ask' => '请输入您的【SMTP端口】,默认为465',
|
161
|
+
'default' => mail_data['port'] || 465,
|
162
|
+
'type' => Integer,
|
163
|
+
'validate' => /^\d+$/,
|
164
|
+
'error' => '您的端口输入不正确,只能输入整数'
|
165
|
+
},{
|
166
|
+
'key' => 'username',
|
167
|
+
'ask' => '请输入您的【邮件帐号】,如mail@example.com',
|
168
|
+
'default' => mail_data['username'],
|
169
|
+
'type' => String,
|
170
|
+
'validate' => /^[a-z0-9]+([._\\-]*[a-z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$/,
|
171
|
+
'error' => '您的邮箱帐号输入不正确'
|
172
|
+
},{
|
173
|
+
'key' => 'from',
|
174
|
+
'ask' => '请输入您的【发件人邮件地址】,如<张三> mail@example.com,如果没有设置,则与邮件帐号一致',
|
175
|
+
'default' => mail_data['from'],
|
176
|
+
'type' => String
|
177
|
+
},{
|
178
|
+
'key' => 'subject',
|
179
|
+
'ask' => '请输入您的【默认邮件主题】,非必填,按回车可以跳过',
|
180
|
+
'default' => mail_data['subject'],
|
181
|
+
'type' => String
|
182
|
+
},{
|
183
|
+
'key' => 'to',
|
184
|
+
'ask' => '请输入您的【默认收件人】,多个以逗号为分隔,非必填,按回车可以跳过',
|
185
|
+
'default' => mail_data['to'],
|
186
|
+
'type' => String
|
187
|
+
},{
|
188
|
+
'key' => 'ssl',
|
189
|
+
'ask' => '请确认【是否启用SSL】,一般465或者587端口都会启用SSL,[y/n]',
|
190
|
+
'default' => mail_data['ssl'] || 'y',
|
191
|
+
'validate' => /^[yn]$/,
|
192
|
+
'error' => '请输入y或者n表示是否启用SSL',
|
193
|
+
'type' => String
|
194
|
+
}
|
195
|
+
]
|
196
|
+
|
197
|
+
mail_data = self.ask_items items, mail_data
|
198
|
+
#设置默认的format
|
199
|
+
mail_data['format'] = '%Y/%m/%d' if mail_data['format'] == ''
|
200
|
+
#没有设置from,则使用username
|
201
|
+
mail_data['from'] = mail_data['username'] if mail_data['from'] == ''
|
202
|
+
|
203
|
+
#填到mail
|
204
|
+
data['mail'] = mail_data
|
205
|
+
|
206
|
+
#写入文件
|
207
|
+
self.write data, true
|
208
|
+
|
209
|
+
#询问密码
|
210
|
+
self.ask_mail_password false
|
211
|
+
|
212
|
+
puts "您的邮件基本信息配置成功,更多配置方式请参考:#{M2M::HOMEPAGE}config.html"
|
213
|
+
end
|
214
|
+
|
215
|
+
#用户输入密码以及加密内容
|
216
|
+
def ask_mail_password(show_success_message = false)
|
217
|
+
data = self.read true
|
218
|
+
mail_data = data['mail'] || {}
|
219
|
+
|
220
|
+
items = [
|
221
|
+
{
|
222
|
+
'key' => 'password',
|
223
|
+
'ask' => '请输入您的【邮箱密码】,邮件密码以加密的方式保存在您本地电脑上',
|
224
|
+
'default' => nil,
|
225
|
+
'echo' => '*',
|
226
|
+
'type' => String,
|
227
|
+
'validate' => /.{1,}/,
|
228
|
+
'error' => '请输入您的邮件密码'
|
229
|
+
},{
|
230
|
+
'key' => 'encrypt_key',
|
231
|
+
'ask' => '请输入您的【加密钥匙】,此钥匙用于解密您的密码,请务必牢记,按回车可以跳过',
|
232
|
+
'default' => nil,
|
233
|
+
'echo' => '*',
|
234
|
+
'type' => String
|
235
|
+
}
|
236
|
+
]
|
237
|
+
|
238
|
+
new_data = {}
|
239
|
+
new_data = self.ask_items items, new_data
|
240
|
+
|
241
|
+
#对密码进行加密
|
242
|
+
password = new_data['password']
|
243
|
+
encrypt_key = new_data['encrypt_key']
|
244
|
+
|
245
|
+
password = @util.encrypt password, encrypt_key
|
246
|
+
mail_data['password'] = password
|
247
|
+
mail_data['safer'] = encrypt_key != ''
|
248
|
+
|
249
|
+
data['mail'] = mail_data
|
250
|
+
self.write data, true
|
251
|
+
|
252
|
+
puts '您的邮件密码配置成功' if show_success_message
|
253
|
+
end
|
254
|
+
|
255
|
+
#配置网站相关的
|
256
|
+
def ask_site
|
257
|
+
data = self.read false
|
258
|
+
site_data = data['site'] || {}
|
259
|
+
|
260
|
+
items = [
|
261
|
+
{
|
262
|
+
'key' => 'title',
|
263
|
+
'ask' => '您的网站标题,如:M2M官方网站,按回车跳过',
|
264
|
+
'default' => site_data['title'],
|
265
|
+
'type' => String
|
266
|
+
},{
|
267
|
+
'key' => 'host',
|
268
|
+
'ask' => '主机地址,如:http://m2m.wvv8oo.com/,按回车跳过',
|
269
|
+
'default' => site_data['host'],
|
270
|
+
'type' => String
|
271
|
+
}
|
272
|
+
]
|
273
|
+
|
274
|
+
data['site'] = self.ask_items items, site_data
|
275
|
+
self.write data, false
|
276
|
+
puts "您的网站配置成功,更多配置方式请参考:#{M2M::HOMEPAGE}config.html"
|
277
|
+
end
|
278
|
+
end
|
data/lib/store.rb
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
#缓存数据, 并建立索引
|
2
|
+
|
3
|
+
require_relative './article'
|
4
|
+
require_relative './util'
|
5
|
+
|
6
|
+
class Store
|
7
|
+
def initialize(files)
|
8
|
+
#直接子级的key
|
9
|
+
@children_key = '__children__'
|
10
|
+
#所有后代的key
|
11
|
+
@posterity_key = '__posterity__'
|
12
|
+
#所有文章
|
13
|
+
@article = Article.new
|
14
|
+
|
15
|
+
@data = {
|
16
|
+
#所有文章列表
|
17
|
+
'articles' => self.get_articles(files),
|
18
|
+
#目录树
|
19
|
+
'tree' => {
|
20
|
+
@children_key => Array.new
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
self.make_tree_index
|
25
|
+
self.sort @data['tree']
|
26
|
+
end
|
27
|
+
|
28
|
+
def tree
|
29
|
+
@data['tree']
|
30
|
+
end
|
31
|
+
|
32
|
+
def articles
|
33
|
+
@data['articles']
|
34
|
+
end
|
35
|
+
|
36
|
+
#从节点数据中, 获取items
|
37
|
+
def get_children(node = @data['tree'])
|
38
|
+
node[@children_key]
|
39
|
+
end
|
40
|
+
|
41
|
+
def is_children_key(key)
|
42
|
+
key == @children_key
|
43
|
+
end
|
44
|
+
|
45
|
+
#获取所有的文章列表
|
46
|
+
#以hash的方式保存, key即是文件路径的md5值
|
47
|
+
def get_articles(files)
|
48
|
+
result = Hash.new
|
49
|
+
|
50
|
+
files.each { | file |
|
51
|
+
article = @article.convert(file)
|
52
|
+
key = article['relative_path_md5']
|
53
|
+
result[key] = article;
|
54
|
+
}
|
55
|
+
|
56
|
+
result
|
57
|
+
end
|
58
|
+
|
59
|
+
#创建树状结构的索引
|
60
|
+
def make_tree_index
|
61
|
+
this = self
|
62
|
+
@data['articles'].each{ |key, article|
|
63
|
+
dir = File::dirname(article['relative_path'])
|
64
|
+
relative_path_md5 = article['relative_path_md5']
|
65
|
+
this.mount_node_to_tree dir, relative_path_md5
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
#挂到节点上, 如果不在则创建
|
70
|
+
def mount_node_to_tree(path, relative_path_md5)
|
71
|
+
node = @data['tree']
|
72
|
+
|
73
|
+
if path == '.'
|
74
|
+
node[@children_key].push relative_path_md5
|
75
|
+
return
|
76
|
+
end
|
77
|
+
|
78
|
+
path.split('/').each{ |segment|
|
79
|
+
current_node = node[segment]
|
80
|
+
if not current_node
|
81
|
+
current_node = Hash.new()
|
82
|
+
current_node[@children_key] = Array.new
|
83
|
+
node[segment] = current_node
|
84
|
+
end
|
85
|
+
|
86
|
+
node = current_node
|
87
|
+
node[@children_key].push relative_path_md5
|
88
|
+
|
89
|
+
#所有的子级,都要向root插入数据
|
90
|
+
@data['tree'][@children_key].push relative_path_md5
|
91
|
+
}
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
#给所有的文件夹排序
|
96
|
+
def sort(node)
|
97
|
+
this = self
|
98
|
+
node.each { | key, current |
|
99
|
+
#items, 需要进行排序
|
100
|
+
if key == @children_key
|
101
|
+
#根据文章的最后修改日期进行排序
|
102
|
+
current.sort! {|left, right|
|
103
|
+
left_article = @data['articles'][left]
|
104
|
+
right_article = @data['articles'][right]
|
105
|
+
right_article['mtime'] <=> left_article['mtime']
|
106
|
+
}
|
107
|
+
else
|
108
|
+
#递归调用进行排序
|
109
|
+
this.sort(current)
|
110
|
+
end
|
111
|
+
}
|
112
|
+
end
|
113
|
+
end
|