ppz 0.0.0 → 0.0.5
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 +4 -4
- data/asset/style/ppz.css +117 -0
- data/asset/style/ppz.styl +106 -0
- data/bin/common.rb +42 -0
- data/bin/doc.rb +17 -0
- data/bin/folder.rb +10 -0
- data/bin/ppz +12 -10
- data/lib/func/util.rb +29 -0
- data/lib/model/abstract/model.rb +42 -0
- data/lib/model/abstract/wrapper-model.rb +26 -0
- data/lib/model/comment/container.rb +7 -0
- data/lib/model/comment/item.rb +12 -0
- data/lib/model/common/escape.rb +34 -0
- data/lib/model/common/tag.rb +15 -0
- data/lib/model/list/item/abstract.rb +13 -0
- data/lib/model/list/item/unordered.rb +7 -0
- data/lib/model/list/wrapper/abstract.rb +12 -0
- data/lib/model/list/wrapper/unordered.rb +3 -0
- data/lib/model/p/index.rb +17 -0
- data/lib/model/section/abstract.rb +15 -0
- data/lib/model/section/leaf.rb +29 -0
- data/lib/model/section/root.rb +16 -0
- data/lib/model/special-block/container.rb +18 -0
- data/lib/model/special-block/item.rb +8 -0
- data/lib/parser/common/context/abstract.rb +30 -0
- data/lib/parser/common/context/doc.rb +15 -0
- data/lib/parser/doc/abstract.rb +68 -0
- data/lib/parser/doc/file.rb +22 -0
- data/lib/parser/doc/string.rb +10 -0
- data/lib/parser/folder/index.rb +15 -0
- data/lib/parser/folder/model/abstract.rb +27 -0
- data/lib/parser/folder/model/file/abstract.rb +24 -0
- data/lib/parser/folder/model/file/other.rb +7 -0
- data/lib/parser/folder/model/file/ppz.rb +21 -0
- data/lib/parser/folder/model/folder.rb +63 -0
- data/lib/ppz.rb +26 -1
- metadata +36 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 52c9495b0dd9d71eb9f4cd1a6dcaec7805fb10c450dc1d96d664ae7c30d16e0f
|
|
4
|
+
data.tar.gz: 1a2959299c61dcdf554f6923f5c28c713d8667eba7669b6c683b5196081c27b6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4fc7fbcf2fc28272ab0f453477f8f23eff28bc51040b032896fd58ddb2fd6a28be3afba1ada77b956d577db0ab23e60f7fdb8c9ddd030891687f2893595f6323
|
|
7
|
+
data.tar.gz: 7f6013e0106ee614b07b0ba255e4b812cd4f3fb2773dd98fbb71c2098d51a723b3ef7cd47086a96a33db3370448be9397ae32977abfe889113b66056bacdbd57
|
data/asset/style/ppz.css
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
html {
|
|
2
|
+
line-height: 1.5;
|
|
3
|
+
}
|
|
4
|
+
body,
|
|
5
|
+
div,
|
|
6
|
+
p,
|
|
7
|
+
aside {
|
|
8
|
+
box-sizing: border-box;
|
|
9
|
+
}
|
|
10
|
+
body {
|
|
11
|
+
margin: 0;
|
|
12
|
+
padding: 0;
|
|
13
|
+
position: relative;
|
|
14
|
+
}
|
|
15
|
+
aside {
|
|
16
|
+
overflow-y: auto;
|
|
17
|
+
padding: 2rem 1rem;
|
|
18
|
+
position: fixed;
|
|
19
|
+
top: 0;
|
|
20
|
+
left: 0;
|
|
21
|
+
width: 188px;
|
|
22
|
+
height: 100vh;
|
|
23
|
+
}
|
|
24
|
+
aside ul {
|
|
25
|
+
margin: 0 0 0 1rem;
|
|
26
|
+
padding: 0;
|
|
27
|
+
list-style: none;
|
|
28
|
+
}
|
|
29
|
+
aside a {
|
|
30
|
+
width: 100%;
|
|
31
|
+
overflow: hidden;
|
|
32
|
+
text-overflow: ellipsis;
|
|
33
|
+
white-space: nowrap;
|
|
34
|
+
}
|
|
35
|
+
article {
|
|
36
|
+
margin-left: 188px;
|
|
37
|
+
padding: 1px 1rem;
|
|
38
|
+
}
|
|
39
|
+
h1 {
|
|
40
|
+
font-size: 2rem;
|
|
41
|
+
}
|
|
42
|
+
h2 {
|
|
43
|
+
font-size: 1.3rem;
|
|
44
|
+
}
|
|
45
|
+
h3 {
|
|
46
|
+
font-size: 1.1rem;
|
|
47
|
+
opacity: 0.9;
|
|
48
|
+
}
|
|
49
|
+
h1::before,
|
|
50
|
+
h2::before,
|
|
51
|
+
h3::before {
|
|
52
|
+
content: '# ';
|
|
53
|
+
opacity: 0.3;
|
|
54
|
+
font-size: 1rem;
|
|
55
|
+
}
|
|
56
|
+
.comment-container {
|
|
57
|
+
margin: 1rem 0;
|
|
58
|
+
padding: 1rem 1.6rem;
|
|
59
|
+
background: rgba(0,0,0,0.2);
|
|
60
|
+
font-size: 0.9rem;
|
|
61
|
+
border-radius: 4px;
|
|
62
|
+
}
|
|
63
|
+
.comment-item {
|
|
64
|
+
opacity: 0.8;
|
|
65
|
+
}
|
|
66
|
+
.special-block-container {
|
|
67
|
+
background: #141c22;
|
|
68
|
+
overflow: auto;
|
|
69
|
+
border-radius: 4px;
|
|
70
|
+
color: #eee;
|
|
71
|
+
font-family: monospace;
|
|
72
|
+
white-space: pre;
|
|
73
|
+
margin: 1rem 0;
|
|
74
|
+
padding: 1rem;
|
|
75
|
+
counter-reset: line-index;
|
|
76
|
+
}
|
|
77
|
+
.special-block-item::before {
|
|
78
|
+
counter-increment: line-index;
|
|
79
|
+
content: counter(line-index) " | ";
|
|
80
|
+
opacity: 0.3;
|
|
81
|
+
}
|
|
82
|
+
.special-block-item:hover::before {
|
|
83
|
+
opacity: 0.8;
|
|
84
|
+
}
|
|
85
|
+
.special-txt {
|
|
86
|
+
background: rgba(27,31,35,0.05);
|
|
87
|
+
border-radius: 4px;
|
|
88
|
+
font-size: 0.9em;
|
|
89
|
+
padding: 2px 4px;
|
|
90
|
+
}
|
|
91
|
+
.interpage-nav {
|
|
92
|
+
margin-left: 188px;
|
|
93
|
+
list-style: none;
|
|
94
|
+
padding: 0 1rem;
|
|
95
|
+
}
|
|
96
|
+
.interpage-nav .prev {
|
|
97
|
+
float: left;
|
|
98
|
+
}
|
|
99
|
+
.interpage-nav .prev::before {
|
|
100
|
+
content: '上一篇:';
|
|
101
|
+
opacity: 0.6;
|
|
102
|
+
}
|
|
103
|
+
.interpage-nav .next {
|
|
104
|
+
float: right;
|
|
105
|
+
}
|
|
106
|
+
.interpage-nav .next::before {
|
|
107
|
+
content: '下一篇:';
|
|
108
|
+
opacity: 0.6;
|
|
109
|
+
}
|
|
110
|
+
.interpage-nav::after {
|
|
111
|
+
content: '';
|
|
112
|
+
display: block;
|
|
113
|
+
clear: both;
|
|
114
|
+
}
|
|
115
|
+
.folder-nav {
|
|
116
|
+
margin: 2rem 3rem;
|
|
117
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
nav-width = 188px
|
|
2
|
+
|
|
3
|
+
html
|
|
4
|
+
line-height 1.5
|
|
5
|
+
body, div, p, aside
|
|
6
|
+
box-sizing border-box
|
|
7
|
+
|
|
8
|
+
body
|
|
9
|
+
margin 0
|
|
10
|
+
padding 0
|
|
11
|
+
position relative
|
|
12
|
+
|
|
13
|
+
aside
|
|
14
|
+
overflow-y auto
|
|
15
|
+
padding: 2rem 1rem;
|
|
16
|
+
|
|
17
|
+
position fixed
|
|
18
|
+
top 0
|
|
19
|
+
left 0
|
|
20
|
+
width nav-width
|
|
21
|
+
height 100vh
|
|
22
|
+
|
|
23
|
+
ul
|
|
24
|
+
margin 0 0 0 1rem
|
|
25
|
+
padding 0
|
|
26
|
+
list-style none
|
|
27
|
+
a
|
|
28
|
+
width 100%
|
|
29
|
+
overflow hidden
|
|
30
|
+
text-overflow ellipsis
|
|
31
|
+
white-space nowrap
|
|
32
|
+
|
|
33
|
+
article
|
|
34
|
+
margin-left: nav-width
|
|
35
|
+
padding: 1px 1rem
|
|
36
|
+
h1
|
|
37
|
+
font-size 2rem
|
|
38
|
+
h2
|
|
39
|
+
font-size 1.3rem
|
|
40
|
+
h3
|
|
41
|
+
font-size 1.1rem
|
|
42
|
+
opacity 0.9
|
|
43
|
+
|
|
44
|
+
h1, h2, h3
|
|
45
|
+
&::before
|
|
46
|
+
content '# '
|
|
47
|
+
opacity 0.3
|
|
48
|
+
font-size 1rem
|
|
49
|
+
|
|
50
|
+
.comment-container
|
|
51
|
+
margin 1rem 0
|
|
52
|
+
padding 1rem 1.6rem
|
|
53
|
+
background rgba(0, 0, 0, 0.2)
|
|
54
|
+
font-size 0.9rem
|
|
55
|
+
border-radius 4px
|
|
56
|
+
.comment-item
|
|
57
|
+
opacity 0.8
|
|
58
|
+
|
|
59
|
+
.special-block-container
|
|
60
|
+
background #141c22
|
|
61
|
+
overflow auto
|
|
62
|
+
border-radius 4px
|
|
63
|
+
color #eeeeee
|
|
64
|
+
|
|
65
|
+
font-family monospace
|
|
66
|
+
white-space pre
|
|
67
|
+
|
|
68
|
+
margin 1rem 0
|
|
69
|
+
padding 1rem
|
|
70
|
+
|
|
71
|
+
counter-reset line-index
|
|
72
|
+
.special-block-item
|
|
73
|
+
&::before
|
|
74
|
+
counter-increment line-index
|
|
75
|
+
content counter(line-index) " | "
|
|
76
|
+
opacity 0.3
|
|
77
|
+
&:hover::before
|
|
78
|
+
opacity 0.8
|
|
79
|
+
|
|
80
|
+
.special-txt
|
|
81
|
+
background rgba(27, 31, 35, 0.05)
|
|
82
|
+
border-radius 4px
|
|
83
|
+
font-size 0.9em
|
|
84
|
+
padding 2px 4px
|
|
85
|
+
|
|
86
|
+
.interpage-nav
|
|
87
|
+
margin-left nav-width
|
|
88
|
+
list-style none
|
|
89
|
+
padding 0 1rem
|
|
90
|
+
.prev
|
|
91
|
+
float left
|
|
92
|
+
&::before
|
|
93
|
+
content '上一篇:'
|
|
94
|
+
opacity 0.6
|
|
95
|
+
.next
|
|
96
|
+
float right
|
|
97
|
+
&::before
|
|
98
|
+
content '下一篇:'
|
|
99
|
+
opacity 0.6
|
|
100
|
+
&::after
|
|
101
|
+
content ''
|
|
102
|
+
display block
|
|
103
|
+
clear both
|
|
104
|
+
|
|
105
|
+
.folder-nav
|
|
106
|
+
margin 2rem 3rem
|
data/bin/common.rb
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
require 'pathname'
|
|
2
|
+
|
|
3
|
+
module PPZMain
|
|
4
|
+
CURRENT_PATH = File.dirname __FILE__
|
|
5
|
+
CSS_FILE_PATH = (Pathname CURRENT_PATH) + '../asset/style/ppz.css'
|
|
6
|
+
|
|
7
|
+
class Util
|
|
8
|
+
class << self
|
|
9
|
+
def get_in_and_out
|
|
10
|
+
target_in, target_out = ARGV
|
|
11
|
+
|
|
12
|
+
# 输入文件
|
|
13
|
+
abort '要编译哪那个文件?请告诉我' unless target_in # 检查参数存在
|
|
14
|
+
target_in = PPZ::Func.format_path target_in
|
|
15
|
+
unless File.exist? target_in # 不存在的话,看看加上 .ppz 后是否存在
|
|
16
|
+
target_in += '.ppz'
|
|
17
|
+
abort target_in[0..-5] + ' 不存在' unless File.exist? target_in # 还不存在的话,就说明是写错了
|
|
18
|
+
end
|
|
19
|
+
is_folder = File.directory? target_in
|
|
20
|
+
|
|
21
|
+
# 输出文件
|
|
22
|
+
unless target_out
|
|
23
|
+
# 从输入文件获取文件名
|
|
24
|
+
target_out = ((/(.*).ppz$/.match target_in)?$1:target_in) + '.pp'
|
|
25
|
+
end
|
|
26
|
+
# + 检查上级文件夹是否存在
|
|
27
|
+
upper_dir = ((Pathname target_out) + '..').to_s
|
|
28
|
+
abort upper_dir + ' 目录不存在' unless Dir.exist? upper_dir
|
|
29
|
+
# + 检查文件夹:有则检查里面有没有文件;无则创建文件夹
|
|
30
|
+
target_out = target_out.to_s
|
|
31
|
+
if Dir.exist? target_out
|
|
32
|
+
abort target_out + ' 不是一个空文件夹' unless (Dir.children target_out).size == 0
|
|
33
|
+
else
|
|
34
|
+
Dir.mkdir target_out
|
|
35
|
+
end
|
|
36
|
+
target_out = PPZ::Func.format_path target_out
|
|
37
|
+
|
|
38
|
+
[target_in, target_out, is_folder]
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
data/bin/doc.rb
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
target_out = Pathname TARGET_OUT
|
|
2
|
+
|
|
3
|
+
# css
|
|
4
|
+
# 输出文件路径
|
|
5
|
+
output_css_path = target_out + 'index.css'
|
|
6
|
+
# 复制
|
|
7
|
+
FileUtils.cp PPZMain::CSS_FILE_PATH, output_css_path
|
|
8
|
+
|
|
9
|
+
# html
|
|
10
|
+
# 输出文件路径
|
|
11
|
+
output_html_path = target_out + 'index.html'
|
|
12
|
+
# 解析文档
|
|
13
|
+
parser = PPZ::FileDocParser.new TARGET_IN
|
|
14
|
+
# 拼接上 css 文件链接
|
|
15
|
+
output_html = '<link rel="stylesheet" href="./index.css"/>' + parser.get_model.to_html
|
|
16
|
+
# 输出
|
|
17
|
+
PPZ::Func::write_to_file output_html_path.to_s, output_html
|
data/bin/folder.rb
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
require_relative '../lib/parser/folder/index'
|
|
2
|
+
|
|
3
|
+
puts '输入文件夹: ' + TARGET_IN
|
|
4
|
+
puts '输出文件夹: ' + TARGET_OUT
|
|
5
|
+
puts
|
|
6
|
+
|
|
7
|
+
FileUtils.cp PPZMain::CSS_FILE_PATH, ((Pathname TARGET_OUT) + 'style.css').to_s
|
|
8
|
+
|
|
9
|
+
model = PPZ::Folder::FolderModel.new TARGET_IN, 0
|
|
10
|
+
model.compile TARGET_OUT
|
data/bin/ppz
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
|
|
3
|
-
require_relative '
|
|
4
|
-
require_relative '
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
3
|
+
require_relative '../lib/ppz'
|
|
4
|
+
require_relative './common'
|
|
5
|
+
require 'fileutils'
|
|
6
|
+
require 'pathname'
|
|
7
|
+
|
|
8
|
+
TARGET_IN, TARGET_OUT, IS_FOLDER = PPZMain::Util.get_in_and_out
|
|
9
|
+
|
|
10
|
+
if IS_FOLDER
|
|
11
|
+
require_relative './folder.rb'
|
|
12
|
+
else
|
|
13
|
+
require_relative './doc.rb'
|
|
14
|
+
end
|
data/lib/func/util.rb
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
class PPZ::Func
|
|
2
|
+
class << self
|
|
3
|
+
def write_to_file filepath, data
|
|
4
|
+
if File.exist? filepath
|
|
5
|
+
throw '文件已存在'
|
|
6
|
+
end
|
|
7
|
+
file = File.new filepath, mode: 'w:UTF-8'
|
|
8
|
+
file.print data
|
|
9
|
+
file.close
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# 检查某类有没有某常量
|
|
13
|
+
def has_const? klass, const_name
|
|
14
|
+
klass.constants.include? const_name
|
|
15
|
+
end
|
|
16
|
+
# 检查某实例的类有没有某常量
|
|
17
|
+
def class_has_const? instance, const_name
|
|
18
|
+
has_const? instance.class, const_name
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def format_path path
|
|
22
|
+
if ['/', '\\'].include? path[-1]
|
|
23
|
+
path[0...-1]
|
|
24
|
+
else
|
|
25
|
+
path
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
class PPZ::AbstractModel
|
|
2
|
+
attr_accessor :left_model, :right_model, :index # 左右兄弟 model
|
|
3
|
+
attr_accessor :father_model
|
|
4
|
+
|
|
5
|
+
def self.from_line line # 静态方法,从“输入行”里实例化一个 model
|
|
6
|
+
return nil unless self::REG_EXP.match(line)
|
|
7
|
+
self.new $1
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# 加粗、斜体、链接等
|
|
11
|
+
def transform_inline_element str
|
|
12
|
+
str = str + ''
|
|
13
|
+
# 因为加粗、斜体等,会生成 html 代码,为了不使“用户原本输入的 html”和“生成的 html”冲突,因此先把“用户输入的 html 转义
|
|
14
|
+
PPZ::Escape.html_char! str
|
|
15
|
+
|
|
16
|
+
# 变粗等,使用特殊字符比如 *,来标识
|
|
17
|
+
# 但这会使用户想输入 * 时,形成歧义
|
|
18
|
+
# 因此,用户向输入 *(而不是变斜时),可以输入 \*
|
|
19
|
+
|
|
20
|
+
# 所以此流程
|
|
21
|
+
# 先把 * 等特殊字符转义(\* -> 某种形式)
|
|
22
|
+
# 再识别哪些变斜,哪些变粗
|
|
23
|
+
# 再把用户原来想输入的 * 放到字符串里(某种形式 -> *)
|
|
24
|
+
|
|
25
|
+
PPZ::Escape.ppz_char! str # 把用户输入的 \* 转义 => 剩下的 *** 就肯定是 斜体加粗 了
|
|
26
|
+
|
|
27
|
+
# + 斜体和加粗
|
|
28
|
+
str.gsub! /\*\*\*(.+)\*\*\*/, '<b><i>\1</i></b>'
|
|
29
|
+
# + 加粗
|
|
30
|
+
str.gsub! /\*\*(.+)\*\*/, '<b>\1</b>'
|
|
31
|
+
# + 斜体
|
|
32
|
+
str.gsub! /\*(.+)\*/, '<i>\1</i>'
|
|
33
|
+
# + 行内块
|
|
34
|
+
str.gsub! /```([^(```)]+)```/, '<span class="special-txt">\1</span>'
|
|
35
|
+
# + 图片 先图片后链接
|
|
36
|
+
str.gsub! /!\[([^\]]*)\]\(([^\)]+)\)/, '<img title="\1" src="\2" />'
|
|
37
|
+
# + 链接 先图片后链接
|
|
38
|
+
str.gsub! /\[([^\]]+)\]\(([^\)]+)\)/, '<a href="\2" title="\2">\1</a>'
|
|
39
|
+
|
|
40
|
+
PPZ::Escape.transform_to_real! str
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
class PPZ::AbstractWrapperModel < PPZ::AbstractModel
|
|
2
|
+
def initialize
|
|
3
|
+
@children = []
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
# 把 el 加入到 children
|
|
7
|
+
def append el
|
|
8
|
+
el.father_model = self
|
|
9
|
+
|
|
10
|
+
left_model = @children[-1]
|
|
11
|
+
if left_model
|
|
12
|
+
left_model.right_model = el
|
|
13
|
+
el.left_model = el
|
|
14
|
+
el.index = left_model.index + 1
|
|
15
|
+
else
|
|
16
|
+
el.index = 1
|
|
17
|
+
end
|
|
18
|
+
@children.push el
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def to_html
|
|
22
|
+
@children
|
|
23
|
+
.map { |child| child.to_html }
|
|
24
|
+
.join
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
class PPZ::CommentItemModel < PPZ::AbstractModel
|
|
2
|
+
ContainerClass = PPZ::CommentContainerModel
|
|
3
|
+
REG_EXP = /^\> (.+)/
|
|
4
|
+
|
|
5
|
+
def initialize content
|
|
6
|
+
@content = transform_inline_element content
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def to_html
|
|
10
|
+
"<div class=\"comment-item\">#{@content}</div>"
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
class PPZ::Escape
|
|
2
|
+
PpzRule = [
|
|
3
|
+
['\\\\', '\\backslash', '\\'],
|
|
4
|
+
['\\*', '\\star;', '*'],
|
|
5
|
+
['\\`', '\\backquote;', '`']
|
|
6
|
+
]
|
|
7
|
+
class << self
|
|
8
|
+
def ppz_char! str
|
|
9
|
+
PpzRule.each do |source, escaped|
|
|
10
|
+
str.gsub! source, escaped
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def transform_to_real! str
|
|
15
|
+
PpzRule.each do |source, escaped, target|
|
|
16
|
+
str.gsub! escaped, target
|
|
17
|
+
end
|
|
18
|
+
str
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
HtmlRule = [
|
|
23
|
+
['&', '&'],
|
|
24
|
+
['<', '<'],
|
|
25
|
+
['>', '>'],
|
|
26
|
+
[' ', ' '],
|
|
27
|
+
['"', '"']
|
|
28
|
+
]
|
|
29
|
+
def self.html_char! str
|
|
30
|
+
HtmlRule.each do |source, escaped|
|
|
31
|
+
str.gsub! source, escaped
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class Tag
|
|
2
|
+
def initialize name, content, attr
|
|
3
|
+
@tagName = tagName
|
|
4
|
+
@content = content
|
|
5
|
+
@attr = attr
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def to_s
|
|
9
|
+
attrStr = ''
|
|
10
|
+
@attr.each do |key, value|
|
|
11
|
+
attrStr << " #{key}='#{value}'"
|
|
12
|
+
end
|
|
13
|
+
"<#{@tagName}#{attrStr}>#{@content}</#{@tagName}>"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
class PPZ::PModel < PPZ::AbstractModel
|
|
2
|
+
UpperClass = PPZ::AbstractSectionModel
|
|
3
|
+
|
|
4
|
+
def initialize text
|
|
5
|
+
# 转义行首的加号
|
|
6
|
+
pre = text[0..2]
|
|
7
|
+
text = text[1..-1] if (pre == '\\+ ' or pre == '\\> ')
|
|
8
|
+
# 转义 html
|
|
9
|
+
text = transform_inline_element text
|
|
10
|
+
|
|
11
|
+
@text = text
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def to_html
|
|
15
|
+
"<p>#{@text}</p>"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class PPZ::AbstractSectionModel < PPZ::AbstractWrapperModel
|
|
2
|
+
def append section
|
|
3
|
+
super
|
|
4
|
+
if section.is_a? PPZ::AbstractSectionModel
|
|
5
|
+
section.section_dom_id = "#{section_dom_id}-#{section.level.to_s}.#{section.index.to_s}"
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def get_nav_html
|
|
10
|
+
@children
|
|
11
|
+
.select { |child| child.is_a? PPZ::AbstractSectionModel }
|
|
12
|
+
.collect { |child| child.get_nav_html }
|
|
13
|
+
.join
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
class PPZ::LeafSectionModel < PPZ::AbstractSectionModel
|
|
2
|
+
attr_accessor :title, :section_dom_id, :level
|
|
3
|
+
|
|
4
|
+
def initialize title, level
|
|
5
|
+
raise TypeError unless title.is_a?(String) && level.is_a?(Integer)
|
|
6
|
+
super() # 不可以省略括号
|
|
7
|
+
@title = transform_inline_element title
|
|
8
|
+
@level = level
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
REG_EXP = /^(\#{1,5}) (.+)/
|
|
12
|
+
def self.from_line line
|
|
13
|
+
return nil unless REG_EXP.match(line)
|
|
14
|
+
|
|
15
|
+
level = {
|
|
16
|
+
1 => 1, # 一个井号是 一级
|
|
17
|
+
5 => 3 # 五个井号是 三级
|
|
18
|
+
}[$1.size] || 2 # 其余都是 两级
|
|
19
|
+
PPZ::LeafSectionModel.new $2, level
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def get_nav_html
|
|
23
|
+
return "<li><a href=\"##{section_dom_id}\">#{@title}</a><ul>#{super}</ul></li>"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def to_html
|
|
27
|
+
"<section id=#{@section_dom_id}><h#{@level}>#{@title}</h#{@level}>#{super}</section>"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
class PPZ::RootSectionModel < PPZ::AbstractSectionModel
|
|
2
|
+
def level
|
|
3
|
+
0
|
|
4
|
+
end
|
|
5
|
+
def section_dom_id
|
|
6
|
+
'section'
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def get_nav_html
|
|
10
|
+
return "<aside><ul>#{super}</ul></aside>"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def to_html
|
|
14
|
+
"#{get_nav_html}<article>#{super}</article>"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
class PPZ::SpecialContainerModel < PPZ::AbstractWrapperModel
|
|
2
|
+
UpperClass = PPZ::AbstractSectionModel
|
|
3
|
+
|
|
4
|
+
REG_EXP = /^```( (.*))?/
|
|
5
|
+
def self.from_line line
|
|
6
|
+
return nil unless REG_EXP.match(line)
|
|
7
|
+
PPZ::SpecialContainerModel.new $2
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def initialize name
|
|
11
|
+
@name = name
|
|
12
|
+
super()
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def to_html
|
|
16
|
+
"<div class=\"special-block-container #{@name}\">#{super}</div>"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# 当前行,所处的上下文
|
|
2
|
+
# 比如 一级 section 下面的 ul 下的 第 n 个 ul
|
|
3
|
+
|
|
4
|
+
class PPZ::AbstractContext
|
|
5
|
+
def initialize root
|
|
6
|
+
@stack = [root]
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# def append # 交给子类实现
|
|
10
|
+
|
|
11
|
+
def pop
|
|
12
|
+
@stack.pop
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def head
|
|
16
|
+
@stack[-1]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def root
|
|
20
|
+
@stack[0]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
def pop_to klass
|
|
25
|
+
loop do
|
|
26
|
+
break if head.is_a? klass
|
|
27
|
+
pop
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class PPZ::DocContext < PPZ::AbstractContext
|
|
2
|
+
def append target
|
|
3
|
+
# ContainerClass: 容器类,如果上级不是,就造一个
|
|
4
|
+
if(PPZ::Func::class_has_const? target, :ContainerClass) and (head.class != target.class::ContainerClass)
|
|
5
|
+
append target.class::ContainerClass.new
|
|
6
|
+
end
|
|
7
|
+
# UpperClass: 上级类,如果上级不是,就出栈
|
|
8
|
+
if(PPZ::Func::class_has_const? target, :UpperClass) and (head.class != target.class::UpperClass)
|
|
9
|
+
pop_to target.class::UpperClass
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
head.append target # 加入 model
|
|
13
|
+
@stack.push target if target.is_a? PPZ::AbstractWrapperModel # 加入 stack
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# 解析一个 .ppz 文档(可以是一个文件、字符串)
|
|
2
|
+
|
|
3
|
+
class PPZ::AbstractDocParser
|
|
4
|
+
def initialize
|
|
5
|
+
@context = PPZ::DocContext.new PPZ::RootSectionModel.new
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def get_model
|
|
9
|
+
loop do
|
|
10
|
+
line = readline
|
|
11
|
+
break unless line != nil
|
|
12
|
+
handle_line line
|
|
13
|
+
end
|
|
14
|
+
@context.root
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
def handle_line line
|
|
19
|
+
head = @context.head
|
|
20
|
+
if head.is_a? PPZ::SpecialContainerModel # 只要进入 special-block,下面的 line 都算是 special-block 的content
|
|
21
|
+
# special-block
|
|
22
|
+
if /^``` *$/.match line # 除非遇到 ``` (special-block 的结束符)
|
|
23
|
+
# special-block-end
|
|
24
|
+
@context.pop # 遇到,就跳出去
|
|
25
|
+
return # 立刻结束
|
|
26
|
+
elsif /\\``` *$/.match line # 但是有的 ``` (是 special-block 的内容,于是需要转义)
|
|
27
|
+
line = line[1..-1]
|
|
28
|
+
end
|
|
29
|
+
# special-block-item
|
|
30
|
+
target = PPZ::SpecialItemModel.new line
|
|
31
|
+
elsif target = PPZ::LeafSectionModel.from_line(line)
|
|
32
|
+
# section
|
|
33
|
+
loop do
|
|
34
|
+
break if (head.is_a? PPZ::AbstractSectionModel) and (head.level < target.level)
|
|
35
|
+
@context.pop
|
|
36
|
+
head = @context.head
|
|
37
|
+
end
|
|
38
|
+
elsif target = PPZ::UnorderedListItemModel.from_line(line)
|
|
39
|
+
# 列表
|
|
40
|
+
# + 对上级 container 的操作
|
|
41
|
+
# ++ 没有 container -> new
|
|
42
|
+
# ++ 有,等级低 -> new
|
|
43
|
+
# ++ 有,等级高 -> pop 到同等级,如果没有同等级,则 new
|
|
44
|
+
# ++ 有,等级相等 -> 啥也不做
|
|
45
|
+
if !(head.is_a? PPZ::UnorderedListWrapperModel) || head.level < target.level
|
|
46
|
+
@context.append PPZ::UnorderedListWrapperModel.new target.level
|
|
47
|
+
elsif head.level > target.level
|
|
48
|
+
loop do
|
|
49
|
+
@context.pop
|
|
50
|
+
head = @context.head
|
|
51
|
+
break if head.level <= target.level # pop 到同等级
|
|
52
|
+
end
|
|
53
|
+
if head.level < target.level # 如果没有同等级,则 new
|
|
54
|
+
@context.append PPZ::UnorderedListWrapperModel.new target.level
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
elsif target = PPZ::CommentItemModel.from_line(line)
|
|
58
|
+
# 注释
|
|
59
|
+
elsif target = PPZ::SpecialContainerModel.from_line(line)
|
|
60
|
+
# 特殊快
|
|
61
|
+
else
|
|
62
|
+
# p
|
|
63
|
+
target = PPZ::PModel.new line
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
@context.append target
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
class PPZ::FileDocParser < PPZ::AbstractDocParser
|
|
2
|
+
def initialize path
|
|
3
|
+
super()
|
|
4
|
+
unless File.exist? path
|
|
5
|
+
throw '文件不存在,可能是路径错了(需要绝对路径):' + path
|
|
6
|
+
end
|
|
7
|
+
@file = File.new path
|
|
8
|
+
@end = false
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private def readline
|
|
12
|
+
return nil if @end
|
|
13
|
+
|
|
14
|
+
begin
|
|
15
|
+
line = @file.readline
|
|
16
|
+
return line[-1] == '\n'? line[0...-1] : line
|
|
17
|
+
rescue EOFError => err
|
|
18
|
+
@end = true
|
|
19
|
+
return nil
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# 解析一个文件夹里的 .ppz 文件
|
|
2
|
+
|
|
3
|
+
module PPZ::Folder
|
|
4
|
+
class FolderParser
|
|
5
|
+
def initialize
|
|
6
|
+
@context = Context.new
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
require_relative './model/abstract'
|
|
11
|
+
require_relative './model/folder'
|
|
12
|
+
require_relative './model/file/abstract'
|
|
13
|
+
require_relative './model/file/other'
|
|
14
|
+
require_relative './model/file/ppz'
|
|
15
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module PPZ::Folder
|
|
2
|
+
class AbstractModel
|
|
3
|
+
attr_reader :index, :name
|
|
4
|
+
|
|
5
|
+
def initialize path, level
|
|
6
|
+
throw '文件(夹)的名字得是字符串啊' unless path.is_a? String
|
|
7
|
+
@path = path
|
|
8
|
+
@basename = File.basename path
|
|
9
|
+
@level = level
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.from_path path, level
|
|
13
|
+
level += 1
|
|
14
|
+
if Dir.exist? path
|
|
15
|
+
FolderModel.new path, level
|
|
16
|
+
elsif File.exist? path
|
|
17
|
+
AbstractFileModel.from_path path, level
|
|
18
|
+
else
|
|
19
|
+
throw path + '不存在?'
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def get_css_path
|
|
24
|
+
('../' * @level) + 'style.css'
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module PPZ::Folder
|
|
2
|
+
class AbstractFileModel < AbstractModel
|
|
3
|
+
attr_reader :name
|
|
4
|
+
def self.from_path path, level
|
|
5
|
+
if (File.extname path) == '.ppz'
|
|
6
|
+
PPZFileModel.new path, level
|
|
7
|
+
else
|
|
8
|
+
OtherFileModel.new path, level
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
attr_reader :file_ext
|
|
13
|
+
|
|
14
|
+
def initialize path, level
|
|
15
|
+
super
|
|
16
|
+
unless /^((\d+)_)?([^\.]+)(\.[^\.]+)?$/.match @basename
|
|
17
|
+
throw '文件的命名方式不太理解哦:' + path
|
|
18
|
+
end
|
|
19
|
+
@index = $2?($2.to_i):(Float::INFINITY)
|
|
20
|
+
@name = $3
|
|
21
|
+
@file_ext = $4 || ''
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module PPZ::Folder
|
|
2
|
+
class PPZFileModel < AbstractFileModel
|
|
3
|
+
attr_accessor :left, :right
|
|
4
|
+
|
|
5
|
+
def _compile dir_out
|
|
6
|
+
parser = PPZ::FileDocParser.new @path
|
|
7
|
+
html_str = %!<link rel="stylesheet" href="#{get_css_path}"/>
|
|
8
|
+
<title>#{@name}</title>#{
|
|
9
|
+
parser.get_model.to_html
|
|
10
|
+
}<ul class="interpage-nav">#{
|
|
11
|
+
(@left ?
|
|
12
|
+
"<li class=\"prev\"><a href=\"#{@left.name}.html\">#{@left.name}</a></li>"
|
|
13
|
+
: "") +
|
|
14
|
+
(@right ?
|
|
15
|
+
"<li class=\"next\"><a href=\"#{@right.name}.html\">#{@right.name}</a></li>"
|
|
16
|
+
: "")
|
|
17
|
+
}</ul>!
|
|
18
|
+
PPZ::Func::write_to_file (dir_out + '/' + @name + '.html'), html_str
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
module PPZ::Folder
|
|
2
|
+
class FolderModel < AbstractModel
|
|
3
|
+
def initialize path, level
|
|
4
|
+
super
|
|
5
|
+
/^((\d+)_)?(.+)/.match @basename
|
|
6
|
+
@index = $2?($2.to_i):(Float::INFINITY)
|
|
7
|
+
@name = $3
|
|
8
|
+
|
|
9
|
+
@children = []
|
|
10
|
+
(Dir.children path, encoding: 'utf-8').each do |child_name|
|
|
11
|
+
@children.push AbstractModel.from_path (path + '/' + child_name), level
|
|
12
|
+
end
|
|
13
|
+
@children.sort! do |a, b|
|
|
14
|
+
a.index <=> b.index
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
left = right = nil
|
|
18
|
+
@children.each do |child|
|
|
19
|
+
next unless child.is_a? PPZFileModel
|
|
20
|
+
if left
|
|
21
|
+
left.right = child
|
|
22
|
+
child.left = left
|
|
23
|
+
end
|
|
24
|
+
left = child
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def title
|
|
29
|
+
@name
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def _compile out_dir # compile 是 _compile 的安全版本
|
|
33
|
+
PPZ::Func.write_to_file (out_dir + '/' + @name + '.html'), %!<title>#{title}</title>
|
|
34
|
+
<link rel="stylesheet" href="#{get_css_path}"/><div class="folder-nav"><ul>#{
|
|
35
|
+
@children
|
|
36
|
+
.map do |child|
|
|
37
|
+
child.file_ext == '.ppz' ?
|
|
38
|
+
"<li><a href=\"./#{@name}/#{child.name}.html\">#{child.name}</a></li>"
|
|
39
|
+
: ''
|
|
40
|
+
end
|
|
41
|
+
.join
|
|
42
|
+
}</ul></div>!
|
|
43
|
+
|
|
44
|
+
children_dir = out_dir + '/' + @name
|
|
45
|
+
Dir.mkdir children_dir
|
|
46
|
+
@children.each { |child| child._compile children_dir }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def compile out_dir
|
|
50
|
+
unless out_dir.is_a? String
|
|
51
|
+
throw 'out_dir 只能是字符串'
|
|
52
|
+
end
|
|
53
|
+
unless Dir.exist? out_dir
|
|
54
|
+
throw "out_dir #{out_dir} 不存在"
|
|
55
|
+
end
|
|
56
|
+
if ['/', '\\'].include? out_dir[-1]
|
|
57
|
+
_compile out_dir[0...-1]
|
|
58
|
+
else
|
|
59
|
+
_compile out_dir
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
data/lib/ppz.rb
CHANGED
|
@@ -1 +1,26 @@
|
|
|
1
|
-
|
|
1
|
+
module PPZ
|
|
2
|
+
require_relative './func/util'
|
|
3
|
+
|
|
4
|
+
require_relative './model/abstract/model'
|
|
5
|
+
require_relative './model/abstract/wrapper-model'
|
|
6
|
+
require_relative './model/section/abstract'
|
|
7
|
+
require_relative './model/section/leaf'
|
|
8
|
+
require_relative './model/section/root'
|
|
9
|
+
require_relative './model/comment/container'
|
|
10
|
+
require_relative './model/comment/item'
|
|
11
|
+
require_relative './model/common/escape'
|
|
12
|
+
require_relative './model/list/wrapper/abstract'
|
|
13
|
+
require_relative './model/list/wrapper/unordered'
|
|
14
|
+
require_relative './model/list/item/abstract'
|
|
15
|
+
require_relative './model/list/item/unordered'
|
|
16
|
+
require_relative './model/p/index'
|
|
17
|
+
require_relative './model/special-block/container'
|
|
18
|
+
require_relative './model/special-block/item'
|
|
19
|
+
|
|
20
|
+
require_relative './parser/common/context/abstract'
|
|
21
|
+
require_relative './parser/common/context/doc'
|
|
22
|
+
require_relative './parser/doc/abstract'
|
|
23
|
+
require_relative './parser/doc/file'
|
|
24
|
+
require_relative './parser/doc/string'
|
|
25
|
+
|
|
26
|
+
end
|
metadata
CHANGED
|
@@ -1,23 +1,56 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ppz
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- wuse
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2021-02-
|
|
11
|
+
date: 2021-02-13 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
|
-
description:
|
|
13
|
+
description:
|
|
14
14
|
email: 372301467@qq.com
|
|
15
15
|
executables:
|
|
16
16
|
- ppz
|
|
17
17
|
extensions: []
|
|
18
18
|
extra_rdoc_files: []
|
|
19
19
|
files:
|
|
20
|
+
- asset/style/ppz.css
|
|
21
|
+
- asset/style/ppz.styl
|
|
22
|
+
- bin/common.rb
|
|
23
|
+
- bin/doc.rb
|
|
24
|
+
- bin/folder.rb
|
|
20
25
|
- bin/ppz
|
|
26
|
+
- lib/func/util.rb
|
|
27
|
+
- lib/model/abstract/model.rb
|
|
28
|
+
- lib/model/abstract/wrapper-model.rb
|
|
29
|
+
- lib/model/comment/container.rb
|
|
30
|
+
- lib/model/comment/item.rb
|
|
31
|
+
- lib/model/common/escape.rb
|
|
32
|
+
- lib/model/common/tag.rb
|
|
33
|
+
- lib/model/list/item/abstract.rb
|
|
34
|
+
- lib/model/list/item/unordered.rb
|
|
35
|
+
- lib/model/list/wrapper/abstract.rb
|
|
36
|
+
- lib/model/list/wrapper/unordered.rb
|
|
37
|
+
- lib/model/p/index.rb
|
|
38
|
+
- lib/model/section/abstract.rb
|
|
39
|
+
- lib/model/section/leaf.rb
|
|
40
|
+
- lib/model/section/root.rb
|
|
41
|
+
- lib/model/special-block/container.rb
|
|
42
|
+
- lib/model/special-block/item.rb
|
|
43
|
+
- lib/parser/common/context/abstract.rb
|
|
44
|
+
- lib/parser/common/context/doc.rb
|
|
45
|
+
- lib/parser/doc/abstract.rb
|
|
46
|
+
- lib/parser/doc/file.rb
|
|
47
|
+
- lib/parser/doc/string.rb
|
|
48
|
+
- lib/parser/folder/index.rb
|
|
49
|
+
- lib/parser/folder/model/abstract.rb
|
|
50
|
+
- lib/parser/folder/model/file/abstract.rb
|
|
51
|
+
- lib/parser/folder/model/file/other.rb
|
|
52
|
+
- lib/parser/folder/model/file/ppz.rb
|
|
53
|
+
- lib/parser/folder/model/folder.rb
|
|
21
54
|
- lib/ppz.rb
|
|
22
55
|
homepage: https://github.com/daGaiGuanYu/ppz
|
|
23
56
|
licenses:
|