jekyll-wikilinks 0.0.5 → 0.0.9

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.
@@ -0,0 +1,169 @@
1
+ require "nokogiri"
2
+ require_relative "regex"
3
+ require_relative "wikilink"
4
+
5
+ module Jekyll
6
+ module WikiLinks
7
+
8
+ # more of a "parser" than a parser
9
+ class Parser
10
+ attr_accessor :doc_manager, :markdown_converter, :wikilink_inlines, :wikilink_blocks
11
+
12
+ # Use Jekyll's native relative_url filter
13
+ include Jekyll::Filters::URLFilters
14
+
15
+ CONVERTER_CLASS = Jekyll::Converters::Markdown
16
+
17
+ def initialize(site)
18
+ @context ||= Jekyll::WikiLinks::Context.new(site)
19
+ # do not use @dm in parser -- it is only meant to be passed down into wikilink classes.
20
+ @doc_manager ||= site.doc_mngr
21
+ @markdown_converter ||= site.find_converter_instance(CONVERTER_CLASS)
22
+ @wikilink_blocks, @wikilink_inlines = [], []
23
+ end
24
+
25
+ # parsing
26
+
27
+ def parse(doc_filename, doc_content)
28
+ @wikilink_blocks, @wikilink_inlines = [], []
29
+ if !$wiki_conf.disabled_attributes?
30
+ self.parse_blocks(doc_filename, doc_content)
31
+ end
32
+ self.parse_inlines(doc_filename, doc_content)
33
+ end
34
+
35
+ def parse_blocks(doc_filename, doc_content)
36
+ block_matches = doc_content.scan(REGEX_WIKI_LINK_BLOCKS)
37
+ if !block_matches.nil? && block_matches.size != 0
38
+ block_matches.each do |wl_match|
39
+ # init block wikilink
40
+ wikilink_block = WikiLinkBlock.new(
41
+ @doc_manager,
42
+ doc_filename,
43
+ wl_match[0], # link_type
44
+ wl_match[2], # bullet_type
45
+ )
46
+ # extract + add filenames
47
+ items = wl_match[1]
48
+ filename_matches = items.scan(/#{REGEX_LINK_LEFT}#{REGEX_FILENAME}#{REGEX_LINK_RIGHT}/i)
49
+ filename_matches.each do |match|
50
+ match.each do |fname|
51
+ wikilink_block.add_item(fname)
52
+ end
53
+ end
54
+ # replace text
55
+ doc_content.gsub!(wikilink_block.md_regex, "\n")
56
+ @wikilink_blocks << wikilink_block
57
+ end
58
+ end
59
+ end
60
+
61
+ def parse_inlines(doc_filename, doc_content)
62
+ inline_matches = doc_content.scan(REGEX_WIKI_LINK_INLINES)
63
+ if !inline_matches.nil? && inline_matches.size != 0
64
+ inline_matches.each do |wl_match|
65
+ @wikilink_inlines << WikiLinkInline.new(
66
+ @doc_manager,
67
+ doc_filename,
68
+ wl_match[0],
69
+ wl_match[1],
70
+ wl_match[2],
71
+ wl_match[3],
72
+ wl_match[4],
73
+ wl_match[5],
74
+ )
75
+ end
76
+ end
77
+ # replace text
78
+ return if @wikilink_inlines.nil?
79
+ self.sort_typed_first
80
+ @wikilink_inlines.each do |wikilink|
81
+ doc_content.gsub!(
82
+ wikilink.md_regex,
83
+ self.build_html(wikilink)
84
+ )
85
+ end
86
+ end
87
+
88
+ # building/converting
89
+
90
+ def build_html_embed(title, content, url)
91
+ # multi-line for readability
92
+ return [
93
+ "<div class=\"#{$wiki_conf.css_name("embed_wrapper")}\">",
94
+ "<div class=\"#{$wiki_conf.css_name("embed_title")}\">",
95
+ "#{title}",
96
+ "</div>",
97
+ "<div class=\"#{$wiki_conf.css_name("embed_content")}\">",
98
+ "#{@markdown_converter.convert(content)}",
99
+ "</div>",
100
+ "<a class=\"#{$wiki_conf.css_name("embed_wiki_link")}\" href=\"#{url}\"></a>",
101
+ "</div>",
102
+ ].join("\n").gsub!("\n", "")
103
+ end
104
+
105
+ def build_html_img_embed(static_doc, is_svg=false)
106
+ svg_content = ""
107
+ if is_svg
108
+ File.open(static_doc.path, "r") do |svg_img|
109
+ svg_content = svg_img.read
110
+ end
111
+ return "<p><span class=\"#{$wiki_conf.css_name("embed_image_wrapper")}\">#{svg_content}</span></p>"
112
+ else
113
+ return "<p><span class=\"#{$wiki_conf.css_name("embed_image_wrapper")}\"><img class=\"#{$wiki_conf.css_name("embed_image")}\" src=\"#{relative_url(static_doc.relative_path)}\"></span></p>"
114
+ end
115
+ end
116
+
117
+ def build_html(wikilink)
118
+ if !wikilink.is_valid?
119
+ return '<span class="' + $wiki_conf.css_name("invalid_wiki") + '">' + wikilink.md_str + '</span>'
120
+ end
121
+ # image processing
122
+ if wikilink.embedded? && wikilink.is_img?
123
+ return build_html_img_embed(wikilink.linked_img, is_svg=wikilink.is_img_svg?)
124
+ end
125
+ # markdown file processing
126
+ linked_doc = wikilink.linked_doc
127
+ link_type_txt = wikilink.is_typed? ? " #{$wiki_conf.css_name("typed")} #{wikilink.link_type}" : ""
128
+
129
+ inner_txt = wikilink.label_txt if wikilink.labelled?
130
+ lnk_doc_rel_url = relative_url(linked_doc.url)
131
+
132
+ if (wikilink.level == "file")
133
+ inner_txt = "#{linked_doc['title'].downcase}" if inner_txt.nil?
134
+ return build_html_embed(
135
+ linked_doc['title'],
136
+ linked_doc.content,
137
+ lnk_doc_rel_url
138
+ ) if wikilink.embedded?
139
+ elsif (wikilink.level == "header")
140
+ # from: https://github.com/jekyll/jekyll/blob/6855200ebda6c0e33f487da69e4e02ec3d8286b7/Rakefile#L74
141
+ lnk_doc_rel_url += "\#" + Jekyll::Utils.slugify(wikilink.header_txt)
142
+ inner_txt = "#{linked_doc['title'].downcase} > #{wikilink.header_txt.downcase}" if inner_txt.nil?
143
+ elsif (wikilink.level == "block")
144
+ lnk_doc_rel_url += "\#" + wikilink.block_id
145
+ inner_txt = "#{linked_doc['title'].downcase} > ^#{wikilink.block_id}" if inner_txt.nil?
146
+ else
147
+ Jekyll.logger.error("Jekyll-Wikilinks: Invalid wikilink level")
148
+ end
149
+ return '<a class="' + $wiki_conf.css_name("wiki") + link_type_txt + '" href="' + lnk_doc_rel_url + '">' + inner_txt + '</a>'
150
+ end
151
+
152
+ # helpers
153
+
154
+ def sort_typed_first
155
+ # sorting inline wikilinks is necessary so when wikilinks are replaced,
156
+ # longer strings are replaced first so as not to accidentally overwrite
157
+ # substrings
158
+ # (this is especially likely if there is a matching wikilink that
159
+ # appears as both untyped and typed in a document)
160
+ temp = @wikilink_inlines.dup
161
+ @wikilink_inlines.clear()
162
+ typed_wikilinks = temp.select { |wl| wl.is_typed? }
163
+ untyped_wikilinks = temp.select { |wl| !wl.is_typed? }
164
+ @wikilink_inlines = typed_wikilinks.concat(untyped_wikilinks)
165
+ end
166
+ end
167
+
168
+ end
169
+ end
@@ -0,0 +1,72 @@
1
+ # regex.rb
2
+ # regex constants defining supported file types and valid names for files, variables, or text
3
+ #
4
+
5
+ module Jekyll
6
+ module WikiLinks
7
+ # <regex_variables> only work with 'match' function, not with 'scan' function. :/
8
+ # oh well...they are there for easier debugging...
9
+
10
+ # supported image formats
11
+ # from: https://docs.github.com/en/github/managing-files-in-a-repository/working-with-non-code-files/rendering-and-diffing-images
12
+ SUPPORTED_IMG_FORMATS = Set.new(['.png', '.jpg', '.gif', '.psd', '.svg'])
13
+
14
+ # wikilink constants
15
+ REGEX_LINK_LEFT = /\[\[/
16
+ REGEX_LINK_RIGHT = /\]\]/
17
+ REGEX_LINK_EMBED = /(?<embed>\!)/
18
+ REGEX_LINK_TYPE = /\s*::\s*/
19
+ REGEX_LINK_HEADER = /\#/
20
+ REGEX_LINK_BLOCK = /\#\^/
21
+ REGEX_LINK_LABEL = /\|/
22
+
23
+ # wikitext usable char requirements
24
+ REGEX_LINK_TYPE_CHARS = /[^\n\s\!\#\^\|\]]+/i
25
+ REGEX_FILENAME_CHARS = /[^\\\/:\#\^\|\[\]]+/i
26
+ REGEX_HEADER_CHARS = /[^\!\#\^\|\[\]]+/i
27
+ REGEX_BLOCK_ID_CHARS = /[^\\\/:\!\#\^\|\[\]^\n]+/i
28
+ REGEX_LABEL_CHARS = /(.+?)(?=\]{2}[^\]])/i
29
+
30
+ # capture groups
31
+ REGEX_LINK_TYPE_TXT = /(?<link-type-txt>#{REGEX_LINK_TYPE_CHARS})/i
32
+ REGEX_FILENAME = /(?<filename>#{REGEX_FILENAME_CHARS})/i
33
+ REGEX_HEADER_TXT = /(?<header-txt>#{REGEX_HEADER_CHARS})/i
34
+ REGEX_BLOCK_ID_TXT = /(?<block-id>#{REGEX_BLOCK_ID_CHARS})/i
35
+ REGEX_LABEL_TXT = /(?<label-txt>#{REGEX_LABEL_CHARS})/i
36
+
37
+ # target markdown text (headers, lists, and blocks)
38
+ ## kramdown regexes
39
+ ### atx header: https://github.com/gettalong/kramdown/blob/master/lib/kramdown/parser/kramdown/header.rb#L29
40
+ REGEX_ATX_HEADER = /^\#{1,6}[\t ]*([^ \t].*)\n/i
41
+ ### setext header: https://github.com/gettalong/kramdown/blob/master/lib/kramdown/parser/kramdown/header.rb#L17
42
+ REGEX_SETEXT_HEADER = /^\s{0,3}([^ \t].*)\n[-=][-=]*[ \t\r\f\v]*\n/i
43
+ ## list item: https://github.com/gettalong/kramdown/blob/master/lib/kramdown/parser/kramdown/list.rb#L49
44
+ REGEX_BULLET = /(?<bullet>[+*-])/i
45
+ ## markdown-style block-reference
46
+ REGEX_BLOCK = /.*\s\^#{REGEX_BLOCK_ID_TXT}/i
47
+
48
+ # wikilinks
49
+
50
+ ## inline
51
+ REGEX_WIKI_LINK_INLINES = %r{ # capture indeces
52
+ (#{REGEX_LINK_EMBED})? # 0
53
+ (#{REGEX_LINK_TYPE_TXT}#{REGEX_LINK_TYPE})? # 1
54
+ #{REGEX_LINK_LEFT}
55
+ #{REGEX_FILENAME} # 2
56
+ (#{REGEX_LINK_HEADER}#{REGEX_HEADER_TXT})? # 3
57
+ (#{REGEX_LINK_BLOCK}#{REGEX_BLOCK_ID_TXT})? # 4
58
+ (#{REGEX_LINK_LABEL}#{REGEX_LABEL_TXT})? # 5
59
+ #{REGEX_LINK_RIGHT}
60
+ }x
61
+
62
+ ## block
63
+ ### single
64
+ REGEX_SINGLE = /#{REGEX_LINK_LEFT}#{REGEX_FILENAME_CHARS}#{REGEX_LINK_RIGHT}/i
65
+ ### list (comma is responsible for catching the single case)
66
+ REGEX_LIST_COMMA = /((?:\s*#{REGEX_SINGLE}\s*)(?:,\s*#{REGEX_SINGLE}\s*)*)/i
67
+ REGEX_LIST_MKDN = /((?<=\n)\s{0,3}#{REGEX_BULLET}\s#{REGEX_SINGLE}\s*)+/i # (see REGEX_LIST_ITEM)
68
+ ### process
69
+ REGEX_BLOCK_TYPES = /((?<!\n)(?:#{REGEX_LIST_COMMA})|#{REGEX_LIST_MKDN})/i
70
+ REGEX_WIKI_LINK_BLOCKS = /^\s{0,3}#{REGEX_LINK_TYPE_TXT}#{REGEX_LINK_TYPE}(?:\s*|\G)(?<items>#{REGEX_BLOCK_TYPES})\n/i
71
+ end
72
+ end
@@ -0,0 +1,278 @@
1
+ # wiki data structures
2
+ require_relative "regex"
3
+
4
+ module Jekyll
5
+ module WikiLinks
6
+
7
+ # wikilink classes know everything about the original markdown syntax and its semantic meaning
8
+
9
+ class WikiLinkBlock
10
+ attr_accessor :link_type, :filenames
11
+
12
+ # parameters ordered by appearance in regex
13
+ def initialize(doc_mngr, context_filename, link_type, bullet_type=nil)
14
+ @doc_mngr ||= doc_mngr
15
+ @context_filename ||= context_filename
16
+ @link_type ||= link_type
17
+ @bullet_type ||= bullet_type
18
+ @filenames = []
19
+ end
20
+
21
+ def add_item(filename)
22
+ Jekyll.logger.error("Jekyll-Wikilinks: 'filename' required") if filename.nil? || filename.empty?
23
+ @filenames << filename
24
+ end
25
+
26
+ # data
27
+
28
+ def md_regex
29
+ if !is_typed? || !has_filenames?
30
+ Jekyll.logger.error("Jekyll-Wikilinks: WikiLinkBlock.md_regex error -- type: #{@link_type}, fnames: #{@filenames.inspect}, for: #{@context_filename}")
31
+ end
32
+ # comma (including singles)
33
+ if @bullet_type.nil?
34
+ link_type = /#{@link_type}#{REGEX_LINK_TYPE}/i
35
+ tmp_filenames = @filenames.dup
36
+ first_filename = /\s*#{REGEX_LINK_LEFT}#{tmp_filenames.shift()}#{REGEX_LINK_RIGHT}\s*/i
37
+ filename_strs = tmp_filenames.map { |f| /,\s*#{REGEX_LINK_LEFT}#{f}#{REGEX_LINK_RIGHT}\s*/i }
38
+ md_regex = /#{link_type}#{first_filename}#{filename_strs.join('')}\n/i
39
+ # mkdn
40
+ elsif !@bullet_type.match(REGEX_BULLET).nil?
41
+ link_type = /#{@link_type}#{REGEX_LINK_TYPE}\n/i
42
+ filename_strs = @filenames.map { |f| /\s{0,3}#{Regexp.escape(@bullet_type)}\s#{REGEX_LINK_LEFT}#{f}#{REGEX_LINK_RIGHT}\n/i }
43
+ md_regex = /#{link_type}#{filename_strs.join("")}/i
44
+ else
45
+ Jekyll.logger.error("Jekyll-Wikilinks: WikiLinkBlock.bullet_type error: #{@bullet_type}")
46
+ end
47
+ return md_regex
48
+ end
49
+
50
+ def md_str
51
+ if !is_typed? || !has_filenames?
52
+ Jekyll.logger.error("Jekyll-Wikilinks: WikiLinkBlockList.md_str error -- type: #{@link_type}, fnames: #{@filenames.inspect}, for: #{@context_filename}")
53
+ end
54
+ # comma (including singles)
55
+ if @bullet_type.nil?
56
+ link_type = "#{@link_type}::"
57
+ filename_strs = @filenames.map { |f| "\[\[#{f}\]\]," }
58
+ md_str = (link_type + filename_strs.join('')).delete_suffix(",")
59
+ # mkdn
60
+ elsif !@bullet_type.match(REGEX_BULLET).nil?
61
+ link_type = "#{@link_type}::\n"
62
+ filename_strs = @filenames.map { |f| li[0] + " \[\[#{li[1]}\]\]\n" }
63
+ md_str = link_type + filename_strs.join('')
64
+ else
65
+ Jekyll.logger.error("Jekyll-Wikilinks: 'bullet_type' invalid: #{@bullet_type}")
66
+ end
67
+ return md_str
68
+ end
69
+
70
+ def urls
71
+ # return @filenames.map { |f| @doc_mngr.get_doc_by_fname(f) }
72
+ urls = []
73
+ @filenames.each do |f|
74
+ doc = @doc_mngr.get_doc_by_fname(f)
75
+ urls << doc.url if !doc.nil?
76
+ end
77
+ return urls
78
+ end
79
+
80
+ # 'fm' -> frontmatter
81
+
82
+ def context_fm_data
83
+ return {
84
+ 'type' => @link_type,
85
+ 'urls' => [self.context_doc.url],
86
+ }
87
+ end
88
+
89
+ def linked_fm_data
90
+ return {
91
+ 'type' => @link_type,
92
+ 'urls' => self.urls,
93
+ }
94
+ end
95
+
96
+ def context_doc
97
+ return @doc_mngr.get_doc_by_fname(@context_filename)
98
+ end
99
+
100
+ def linked_docs
101
+ docs = []
102
+ @filenames.each do |f|
103
+ doc = @doc_mngr.get_doc_by_fname(f)
104
+ docs << doc if !doc.nil?
105
+ end
106
+ return docs
107
+ end
108
+
109
+ # descriptor methods
110
+
111
+ def has_filenames?
112
+ return !@filenames.nil? && !@filenames.empty?
113
+ end
114
+
115
+ def is_typed?
116
+ return !@link_type.nil? && !@link_type.empty?
117
+ end
118
+
119
+ # validation methods
120
+
121
+ def is_valid?
122
+ return false if !is_typed?
123
+ return false if !has_filenames?
124
+ @filenames.each do |f|
125
+ return false if !@doc_mngr.file_exists?(f)
126
+ end
127
+ return true
128
+ end
129
+ end
130
+
131
+ class WikiLinkInline
132
+ attr_accessor :context_filename, :embed, :link_type, :filename, :header_txt, :block_id, :label_txt
133
+
134
+ FILENAME = "filename"
135
+ HEADER_TXT = "header_txt"
136
+ BLOCK_ID = "block_id"
137
+
138
+ # parameters ordered by appearance in regex
139
+ def initialize(doc_mngr, context_filename, embed, link_type, filename, header_txt, block_id, label_txt)
140
+ @doc_mngr ||= doc_mngr
141
+ @context_filename ||= context_filename
142
+ @embed ||= embed
143
+ @link_type ||= link_type
144
+ @filename ||= filename
145
+ @header_txt ||= header_txt
146
+ @block_id ||= block_id
147
+ @label_txt ||= label_txt
148
+ end
149
+
150
+ # escape square brackets if they appear in label text
151
+ def label_txt
152
+ return @label_txt.sub("[", "\\[").sub("]", "\\]")
153
+ end
154
+
155
+ # data
156
+
157
+ def md_regex
158
+ regex_embed = embedded? ? REGEX_LINK_EMBED : %r{}
159
+ regex_link_type = is_typed? ? %r{#{@link_type}#{REGEX_LINK_TYPE}} : %r{}
160
+ filename = described?(FILENAME) ? @filename : ""
161
+ if described?(HEADER_TXT)
162
+ header = %r{#{REGEX_LINK_HEADER}#{@header_txt}}
163
+ block = %r{}
164
+ elsif described?(BLOCK_ID)
165
+ header = %r{}
166
+ block = %r{#{REGEX_LINK_BLOCK}#{@block_id}}
167
+ elsif !described?(FILENAME)
168
+ Jekyll.logger.error("Jekyll-Wikilinks: WikiLinkInline.md_regex error")
169
+ end
170
+ label_ = labelled? ? %r{#{REGEX_LINK_LABEL}#{label_txt}} : %r{}
171
+ return %r{#{regex_embed}#{regex_link_type}#{REGEX_LINK_LEFT}#{filename}#{header}#{block}#{label_}#{REGEX_LINK_RIGHT}}
172
+ end
173
+
174
+ def md_str
175
+ embed = embedded? ? "!" : ""
176
+ link_type = is_typed? ? "#{@link_type}::" : ""
177
+ filename = described?(FILENAME) ? @filename : ""
178
+ if described?(HEADER_TXT)
179
+ header = "\##{@header_txt}"
180
+ block = ""
181
+ elsif described?(BLOCK_ID)
182
+ header = ""
183
+ block = "\#\^#{@block_id}"
184
+ elsif !described?(FILENAME)
185
+ Jekyll.logger.error("Jekyll-Wikilinks: WikiLinkInline.md_str error")
186
+ end
187
+ label_ = labelled? ? "\|#{@label_txt}" : ""
188
+ return "#{embed}#{link_type}\[\[#{filename}#{header}#{block}#{label_}\]\]"
189
+ end
190
+
191
+ # 'fm' -> frontmatter
192
+
193
+ def context_fm_data
194
+ return {
195
+ 'type' => @link_type,
196
+ 'url' => self.context_doc.url,
197
+ }
198
+ end
199
+
200
+ def linked_fm_data
201
+ return {
202
+ 'type' => @link_type,
203
+ 'url' => self.linked_doc.url,
204
+ }
205
+ end
206
+
207
+ def context_doc
208
+ return @doc_mngr.get_doc_by_fname(@context_filename)
209
+ end
210
+
211
+ def linked_doc
212
+ return @doc_mngr.get_doc_by_fname(@filename)
213
+ end
214
+
215
+ def linked_img
216
+ return @doc_mngr.get_image_by_fname(@filename) if self.is_img?
217
+ return nil
218
+ end
219
+
220
+ # descriptor methods
221
+
222
+ # def describe
223
+ # return {
224
+ # 'level' => level,
225
+ # 'labelled' => labelled?,
226
+ # 'embedded' => embedded?,
227
+ # 'typed_link' => is_typed?,
228
+ # }
229
+ # end
230
+
231
+ def labelled?
232
+ return !@label_txt.nil? && !@label_txt.empty?
233
+ end
234
+
235
+ def is_typed?
236
+ return !@link_type.nil? && !@link_type.empty?
237
+ end
238
+
239
+ def embedded?
240
+ return !@embed.nil? && @embed == "!"
241
+ end
242
+
243
+ def is_img?
244
+ # github supported image formats: https://docs.github.com/en/github/managing-files-in-a-repository/working-with-non-code-files/rendering-and-diffing-images
245
+ return SUPPORTED_IMG_FORMATS.any?{ |ext| ext == File.extname(@filename).downcase }
246
+ end
247
+
248
+ def is_img_svg?
249
+ return File.extname(@filename).downcase == ".svg"
250
+ end
251
+
252
+ # this method helps to make the 'WikiLinkInline.level' code read like a clean truth table.
253
+ def described?(chunk)
254
+ return (!@filename.nil? && !@filename.empty?) if chunk == FILENAME
255
+ return (!@header_txt.nil? && !@header_txt.empty?) if chunk == HEADER_TXT
256
+ return (!@block_id.nil? && !@block_id.empty?) if chunk == BLOCK_ID
257
+ Jekyll.logger.error("Jekyll-Wikilinks: There is no link level '#{chunk}' in the WikiLink Class")
258
+ end
259
+
260
+ def level
261
+ return "file" if described?(FILENAME) && !described?(HEADER_TXT) && !described?(BLOCK_ID)
262
+ return "header" if described?(FILENAME) && described?(HEADER_TXT) && !described?(BLOCK_ID)
263
+ return "block" if described?(FILENAME) && !described?(HEADER_TXT) && described?(BLOCK_ID)
264
+ return "invalid"
265
+ end
266
+
267
+ # validation methods
268
+
269
+ def is_valid?
270
+ return false if !@doc_mngr.file_exists?(@filename)
271
+ return false if (self.level == "header") && !@doc_mngr.doc_has_header?(self.linked_doc, @header_txt)
272
+ return false if (self.level == "block") && !@doc_mngr.doc_has_block_id?(self.linked_doc, @block_id)
273
+ return true
274
+ end
275
+ end
276
+
277
+ end
278
+ end
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module JekyllWikiLinks
4
- VERSION = "0.0.5"
5
- end
3
+ module Jekyll
4
+ module WikiLinks
5
+
6
+ VERSION = "0.0.9"
7
+
8
+ end
9
+ end