m2m 0.2.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.
- 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/generator.rb
ADDED
@@ -0,0 +1,176 @@
|
|
1
|
+
#生成全站
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
require_relative './scan'
|
5
|
+
require_relative './compiler'
|
6
|
+
require_relative './util'
|
7
|
+
require_relative './store'
|
8
|
+
require_relative './setup'
|
9
|
+
|
10
|
+
class Generator
|
11
|
+
def initialize
|
12
|
+
@util = Util.instance
|
13
|
+
@setup = Setup.instance
|
14
|
+
|
15
|
+
#删除目标目录
|
16
|
+
FileUtils.rmtree @setup.target_dir
|
17
|
+
|
18
|
+
#扫描文件
|
19
|
+
scan = Scan.new
|
20
|
+
scan.execute
|
21
|
+
|
22
|
+
#存储文件
|
23
|
+
@store = Store.new scan.files
|
24
|
+
|
25
|
+
@compiler = Compiler.new
|
26
|
+
@page_size = @setup.get_merged_config['page_size'] || 10
|
27
|
+
@page_size = 10 if @page_size.class != Integer
|
28
|
+
|
29
|
+
self.generate_articles
|
30
|
+
self.copy_theme_resource
|
31
|
+
#复制文件有问题
|
32
|
+
self.copy_workbench_resource
|
33
|
+
self.generate_home
|
34
|
+
self.generate_all_index @store.tree, '', true
|
35
|
+
puts 'Done...'
|
36
|
+
end
|
37
|
+
|
38
|
+
#创建首页
|
39
|
+
def generate_home
|
40
|
+
children = @store.get_children(@store.tree)
|
41
|
+
self.generate_index children, 1, 'home', ''
|
42
|
+
end
|
43
|
+
|
44
|
+
#创建所有的索引页, 如果有文件夹, 则根据配置创建文件夹中的索引页
|
45
|
+
#不会创建首页的index
|
46
|
+
def generate_all_index(node, dir, ignore_first_index)
|
47
|
+
this = self
|
48
|
+
|
49
|
+
#创建children的所有索引
|
50
|
+
children = @store.get_children node
|
51
|
+
page_count = (children.length.to_f / @page_size).ceil
|
52
|
+
|
53
|
+
#生成此文件夹下的所有索引页
|
54
|
+
(1..page_count).each { |page_index|
|
55
|
+
#忽略第一页的索引, 一般是首页的情况下
|
56
|
+
next if page_index == 1 and ignore_first_index
|
57
|
+
|
58
|
+
# puts dir, page_index
|
59
|
+
this.generate_index children, page_index, 'index', dir
|
60
|
+
}
|
61
|
+
|
62
|
+
#遍历所有的文件夹节点, 递归调用
|
63
|
+
node.each { |key, sub_node|
|
64
|
+
if not @store.is_children_key(key)
|
65
|
+
parent_dir = dir + key + '/'
|
66
|
+
this.generate_all_index sub_node, parent_dir, false
|
67
|
+
end
|
68
|
+
}
|
69
|
+
end
|
70
|
+
|
71
|
+
#创建所有文章页
|
72
|
+
def generate_articles
|
73
|
+
@store.articles.each { |key, article|
|
74
|
+
relative_url = article['relative_url']
|
75
|
+
|
76
|
+
data = {
|
77
|
+
'article' => article
|
78
|
+
}
|
79
|
+
self.compiler(relative_url, 'article', data)
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
#创建索引页, 包括首页以及子目录的索引页
|
84
|
+
def generate_index(children, page_index, template_name, dir)
|
85
|
+
articles = []
|
86
|
+
start_index = page_index * @page_size - @page_size
|
87
|
+
end_index = start_index + @page_size
|
88
|
+
#总页数
|
89
|
+
page_count = (children.length.to_f / @page_size).ceil
|
90
|
+
|
91
|
+
path = dir + (page_index == 1 ? 'index.html' : "page-#{page_index}.html")
|
92
|
+
|
93
|
+
children[start_index..end_index].each { |current|
|
94
|
+
articles.push @store.articles[current]
|
95
|
+
}
|
96
|
+
|
97
|
+
|
98
|
+
|
99
|
+
data = {
|
100
|
+
'articles' => articles,
|
101
|
+
'nav' => self.get_nav(page_index, page_count)
|
102
|
+
}
|
103
|
+
|
104
|
+
self.compiler path, template_name, data
|
105
|
+
end
|
106
|
+
|
107
|
+
#处理上一页下一页
|
108
|
+
def get_nav(page_index, page_count)
|
109
|
+
nav = {}
|
110
|
+
#上一页
|
111
|
+
if(page_index == 2)
|
112
|
+
nav['previous'] = 'index.html'
|
113
|
+
elsif(page_index > 2)
|
114
|
+
nav['previous'] = "page-#{page_index - 1}.html"
|
115
|
+
end
|
116
|
+
|
117
|
+
#下一页
|
118
|
+
if(page_index < page_count)
|
119
|
+
nav['next'] = "page-#{page_index + 1}.html"
|
120
|
+
end
|
121
|
+
|
122
|
+
#以后要处理总的分页信息
|
123
|
+
|
124
|
+
nav
|
125
|
+
end
|
126
|
+
|
127
|
+
#编译模板
|
128
|
+
def compiler(filename, template_name, data)
|
129
|
+
data['product'] = @util.get_product
|
130
|
+
data['root/relative_path'] = @util.get_relative_dot(filename)
|
131
|
+
|
132
|
+
@compiler.execute template_name, data, true, filename
|
133
|
+
end
|
134
|
+
|
135
|
+
#复制theme中的资源到目录
|
136
|
+
def copy_theme_resource
|
137
|
+
this = self
|
138
|
+
theme_dir = @compiler.theme_dir
|
139
|
+
|
140
|
+
Dir::entries(theme_dir).each{ |filename|
|
141
|
+
#忽略掉以.开头的
|
142
|
+
next if (/^\.|(template)/i =~ filename) != nil
|
143
|
+
|
144
|
+
this.copy File::join(theme_dir, filename), filename
|
145
|
+
}
|
146
|
+
end
|
147
|
+
|
148
|
+
#复制文件到目标
|
149
|
+
def copy(source, filename)
|
150
|
+
target = File::join @setup.target_dir, filename
|
151
|
+
|
152
|
+
FileUtils.cp_r source, target
|
153
|
+
end
|
154
|
+
|
155
|
+
#复制工作目录的所有资源, 除忽略/.md/配置文件以外的
|
156
|
+
def copy_workbench_resource
|
157
|
+
this = self
|
158
|
+
Dir::entries(@util.workbench).each{ |filename|
|
159
|
+
#忽略掉以.开头的, 以及markdown文件, 还有用户忽略的文件
|
160
|
+
next if @util.is_shadow_file?(filename) or
|
161
|
+
@util.is_markdown_file?(filename) or
|
162
|
+
@util.local_theme_dir == filename or
|
163
|
+
@setup.is_user_ignore_file?(filename)
|
164
|
+
|
165
|
+
#当前的路径
|
166
|
+
current_path = @util.get_merge_path filename, @util.workbench
|
167
|
+
|
168
|
+
#build和内容退出
|
169
|
+
next if @setup.target_dir === current_path or
|
170
|
+
@setup.content_dir === current_path or
|
171
|
+
@util.is_config_file? current_path
|
172
|
+
|
173
|
+
this.copy File::join(@util.workbench, filename), filename
|
174
|
+
}
|
175
|
+
end
|
176
|
+
end
|
data/lib/mailer.rb
ADDED
@@ -0,0 +1,235 @@
|
|
1
|
+
#将markdown发送为邮件
|
2
|
+
require 'mail'
|
3
|
+
require_relative './scan'
|
4
|
+
require_relative './compiler'
|
5
|
+
require_relative './util'
|
6
|
+
require_relative './store'
|
7
|
+
require_relative './setup'
|
8
|
+
|
9
|
+
class Mailer
|
10
|
+
def initialize
|
11
|
+
@util = Util.instance
|
12
|
+
@mail_config = Setup.instance.get_merged_config['mail']
|
13
|
+
#检查邮件的配置信息
|
14
|
+
Setup.instance.check_mail_setup
|
15
|
+
|
16
|
+
#扫描所有文件
|
17
|
+
scan = Scan.new
|
18
|
+
scan.execute
|
19
|
+
|
20
|
+
@store = Store.new scan.files
|
21
|
+
@compiler = Compiler.new
|
22
|
+
end
|
23
|
+
|
24
|
+
#添加附件, 并返回替换后的body
|
25
|
+
def add_pictures(mail, article, body)
|
26
|
+
images = body.scan(/<img.+src=["'](.+?)['"]/i)
|
27
|
+
return body if images.length == 0
|
28
|
+
|
29
|
+
root = Pathname.new File::dirname(article['file'])
|
30
|
+
#去重,并去掉非相对路径的
|
31
|
+
list = Hash.new
|
32
|
+
images.each { | line |
|
33
|
+
image = line[0]
|
34
|
+
next if not /^[\.\/]/ =~ image
|
35
|
+
|
36
|
+
#将全路径作为key,达到去重的效果
|
37
|
+
file = (root + Pathname.new(image)).to_s
|
38
|
+
list[file] = image
|
39
|
+
}
|
40
|
+
|
41
|
+
#遍历所有图片,添加到附件
|
42
|
+
list.each do |file, src|
|
43
|
+
mail.add_file file
|
44
|
+
cid = mail.attachments.last.cid
|
45
|
+
body = body.gsub(src, 'CID:' + cid)
|
46
|
+
end
|
47
|
+
|
48
|
+
body
|
49
|
+
end
|
50
|
+
|
51
|
+
#添加邮件的主体内容,包括body/处理附件等
|
52
|
+
def add_content(mail, article)
|
53
|
+
#获取body的内容
|
54
|
+
data = {
|
55
|
+
"article" => article
|
56
|
+
}
|
57
|
+
body = @compiler.execute 'mail', data, false
|
58
|
+
body = body + self.get_ad
|
59
|
+
|
60
|
+
#分析出图片列表
|
61
|
+
body = self.add_pictures mail, article, body
|
62
|
+
|
63
|
+
#添加邮件的HTML内容
|
64
|
+
mail.html_part = Mail::Part.new do
|
65
|
+
content_type 'text/html; charset=UTF-8'
|
66
|
+
body body
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
#获取用户的密码,如果用户使用了密码进行加密,则提示用户输入密钥
|
71
|
+
def get_password
|
72
|
+
safer = @mail_config['safer']
|
73
|
+
password = @mail_config['password']
|
74
|
+
|
75
|
+
encrypt_key = nil
|
76
|
+
if safer
|
77
|
+
message = "请输入加密您密码的钥匙"
|
78
|
+
encript_key = ask(message, String){|q|
|
79
|
+
q.echo = '*'
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
@util.decrypt password, encript_key
|
84
|
+
end
|
85
|
+
|
86
|
+
#设置邮件的默认配置
|
87
|
+
def set_mail_defaults
|
88
|
+
smtp_server = @mail_config['smtp_server']
|
89
|
+
port = @mail_config['port']
|
90
|
+
username = @mail_config['username']
|
91
|
+
ssl = @mail_config['ssl'] == 'y'
|
92
|
+
password = self.get_password
|
93
|
+
|
94
|
+
#配置邮件参数
|
95
|
+
Mail.defaults do
|
96
|
+
delivery_method :smtp, {
|
97
|
+
:address => smtp_server,
|
98
|
+
:port => port,
|
99
|
+
:user_name => username,
|
100
|
+
:password => password,
|
101
|
+
:ssl => ssl,
|
102
|
+
:enable_starttls_auto => true
|
103
|
+
}
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
#添加广告
|
108
|
+
def get_ad()
|
109
|
+
<<EOF
|
110
|
+
<div class="product" style="background-color: rgba(204, 204, 204, 0.26);padding: 4px 10px; text-align: right; font-size: 12px;">
|
111
|
+
本邮件由
|
112
|
+
<a href="https://github.com/wvv8oo/m2m" target="_blank">m2m</a>
|
113
|
+
根据Markdown自动转换并发送
|
114
|
+
</div>
|
115
|
+
EOF
|
116
|
+
end
|
117
|
+
|
118
|
+
#获取邮件接收人
|
119
|
+
def get_to(to, article)
|
120
|
+
meta = article['meta']
|
121
|
+
|
122
|
+
#优先取meta中的to
|
123
|
+
to = meta['to'] if not to and meta['to']
|
124
|
+
#如果meta没有to,且用户也没有指定to,则使用
|
125
|
+
to = @mail_config['to'] if not to
|
126
|
+
#依然没有找到收件人
|
127
|
+
return @util.error '邮件接收人无效,可使用-a参数指定收件人' if not to
|
128
|
+
|
129
|
+
to = [to] if to.class == String
|
130
|
+
to
|
131
|
+
end
|
132
|
+
|
133
|
+
#获取将要发送的markdown文件
|
134
|
+
def get_article(md_file)
|
135
|
+
items = @store.get_children()
|
136
|
+
return @util.error '没有找到任何的Markdown文件' if items.length == 0
|
137
|
+
|
138
|
+
#如果用户没有指定, 则取最新的
|
139
|
+
return @store.articles[items[0]] if not md_file
|
140
|
+
|
141
|
+
special_article = nil
|
142
|
+
index = 0
|
143
|
+
begin
|
144
|
+
key = items[index]
|
145
|
+
article = @store.articles[key]
|
146
|
+
file = article['file']
|
147
|
+
relative_path = @util.get_relative_path file, @util.workbench
|
148
|
+
|
149
|
+
special_article = article if relative_path == md_file
|
150
|
+
|
151
|
+
|
152
|
+
index += 1
|
153
|
+
end while special_article == nil and index < items.length
|
154
|
+
|
155
|
+
@util.error "当前目录下未找到Markdown文件 => #{md_file}" if not special_article
|
156
|
+
return special_article
|
157
|
+
end
|
158
|
+
|
159
|
+
#优先读取用户指定的,然后读取文章中指定的subject,再读取配置文件中的
|
160
|
+
def get_subject(subject, article)
|
161
|
+
meta = article['meta']
|
162
|
+
#读取文章中mate的,如果在命令行没有指定主题
|
163
|
+
subject = meta['subject'] if not subject and meta['subject']
|
164
|
+
|
165
|
+
#文章中没有,则使用配置文件中的
|
166
|
+
subject = @mail_config['subject'] if not subject
|
167
|
+
|
168
|
+
#配置文件也没有,则使用title,article无论如何都会有title的
|
169
|
+
subject = meta['title'] if not subject
|
170
|
+
|
171
|
+
self.covert_date_macro subject
|
172
|
+
end
|
173
|
+
|
174
|
+
def get_from
|
175
|
+
from = @mail_config['from']
|
176
|
+
from = @mail_config['account'] if not from
|
177
|
+
from
|
178
|
+
end
|
179
|
+
|
180
|
+
#将标题中的日期宏,转换为对应的日期
|
181
|
+
def covert_date_macro(subject)
|
182
|
+
format = @mail_config['format'] || '%Y-%m-%d'
|
183
|
+
subject = subject.gsub('$now', Date.today.strftime(format))
|
184
|
+
subject = subject.gsub('$last_week', (Date.today - 7).strftime(format))
|
185
|
+
subject
|
186
|
+
end
|
187
|
+
|
188
|
+
#警示用户,由用户确定是否发送
|
189
|
+
def alarm(relative_path, subject, to)
|
190
|
+
puts "您确定要发送这封邮件吗?"
|
191
|
+
puts "邮件标题:#{subject}"
|
192
|
+
puts "Markdown:#{relative_path}"
|
193
|
+
puts "收件人:#{to}"
|
194
|
+
puts ""
|
195
|
+
|
196
|
+
#提示用户是否需要发送
|
197
|
+
result = ask("确认发送请按y或者回车,取消请按其它键", lambda { |yn| yn.downcase[0] == ?y or yn == ''})
|
198
|
+
|
199
|
+
@util.error '您中止了邮件的发送' if not result
|
200
|
+
end
|
201
|
+
|
202
|
+
#发送邮件
|
203
|
+
def send(to, md_file, subject, silent = false)
|
204
|
+
article = self.get_article md_file
|
205
|
+
|
206
|
+
from = self.get_from
|
207
|
+
to = self.get_to to, article
|
208
|
+
subject = self.get_subject subject, article
|
209
|
+
|
210
|
+
relative_path = @util.get_relative_path article['file'], @util.workbench
|
211
|
+
|
212
|
+
#配置邮件信息
|
213
|
+
self.set_mail_defaults
|
214
|
+
|
215
|
+
#警示用户是否需要发送
|
216
|
+
self.alarm relative_path, subject, to if not silent
|
217
|
+
|
218
|
+
#创建一个mail的实例,以后再添加附件和html内容
|
219
|
+
mail = Mail.new do
|
220
|
+
from from
|
221
|
+
to to
|
222
|
+
subject subject
|
223
|
+
end
|
224
|
+
|
225
|
+
self.add_content mail, article
|
226
|
+
|
227
|
+
# @util.write_file './send.log', mail.parts.last.decoded
|
228
|
+
mail.deliver
|
229
|
+
|
230
|
+
puts "恭喜,您的邮件发送成功"
|
231
|
+
puts "邮件标题:#{subject}"
|
232
|
+
puts "Markdown:#{relative_path}"
|
233
|
+
puts "收件人:#{to}"
|
234
|
+
end
|
235
|
+
end
|
data/lib/meta.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
#分析meta值
|
2
|
+
# http://pythonhosted.org/Markdown/extensions/meta_data.html
|
3
|
+
|
4
|
+
class Meta
|
5
|
+
def initialize
|
6
|
+
|
7
|
+
end
|
8
|
+
|
9
|
+
#分析meta的部分
|
10
|
+
def analysis_meta(original)
|
11
|
+
return nil if not original
|
12
|
+
|
13
|
+
result = Hash.new
|
14
|
+
list = original.split(/[\r\n?]/)
|
15
|
+
|
16
|
+
# 提取meta值
|
17
|
+
list.each{ |line|
|
18
|
+
next if line == ''
|
19
|
+
next if (/^(\w+):(.+)/i =~ line) == nil
|
20
|
+
|
21
|
+
key = $1.lstrip.rstrip
|
22
|
+
value = $2.lstrip.rstrip
|
23
|
+
|
24
|
+
result[key] = value
|
25
|
+
}
|
26
|
+
|
27
|
+
result
|
28
|
+
end
|
29
|
+
|
30
|
+
#分析内容
|
31
|
+
def analysis(original)
|
32
|
+
result = Hash.new
|
33
|
+
|
34
|
+
pattern = /(\s+)?<!\-\-(.+?)\-\->(.+)?/m
|
35
|
+
matches = pattern.match(original)
|
36
|
+
|
37
|
+
#如果没有匹配到, 则body就是完整的original
|
38
|
+
if matches == nil
|
39
|
+
result['body'] = original
|
40
|
+
return result
|
41
|
+
end
|
42
|
+
|
43
|
+
#获取body内容
|
44
|
+
result['body'] = $3
|
45
|
+
result['meta'] = self.analysis_meta $2
|
46
|
+
result
|
47
|
+
end
|
48
|
+
end
|