bbsexp 0.2.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/LICENSE +22 -0
- data/README.md +28 -0
- data/lib/bbsexp.rb +4 -0
- data/lib/bbsexp/compiler.rb +88 -0
- data/lib/bbsexp/dialect.rb +57 -0
- data/lib/bbsexp/expression.rb +23 -0
- metadata +50 -0
data/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2012, David Kahn
|
|
2
|
+
All rights reserved.
|
|
3
|
+
|
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
|
6
|
+
|
|
7
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
8
|
+
list of conditions and the following disclaimer.
|
|
9
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
10
|
+
this list of conditions and the following disclaimer in the documentation
|
|
11
|
+
and/or other materials provided with the distribution.
|
|
12
|
+
|
|
13
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
14
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
15
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
16
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
|
17
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
18
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
19
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
20
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
21
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
22
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
Library for making bbcode like markup for message boards.
|
|
2
|
+
|
|
3
|
+
Look at test.rb and below for example usage.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
You can define your own elements and chain them together, but they can only be 1 character:
|
|
7
|
+
|
|
8
|
+
"[biu]bold italic underline[.]" => "<strong><em><u>bold italic underline</u></em></strong>
|
|
9
|
+
|
|
10
|
+
you can also close multiple tags at once:
|
|
11
|
+
|
|
12
|
+
"[b][i][u]bold italic underline[...]" => "<strong><em><u>bold italic underline</u></em></strong>"
|
|
13
|
+
|
|
14
|
+
unclosed tags are automatically closed at the end of the text:
|
|
15
|
+
|
|
16
|
+
"[b][i][u]bold italic underline" => "<strong><em><u>bold italic underline</u></em></strong>"
|
|
17
|
+
|
|
18
|
+
Redundant elements are automatically ignored:
|
|
19
|
+
|
|
20
|
+
"[bbiiuu][b][i][u][b][i][u]text" => "<strong><em><u>text</u></em></strong>"
|
|
21
|
+
|
|
22
|
+
The compiler follows rules such as not allowing block elements to be inside of inline elements:
|
|
23
|
+
|
|
24
|
+
"[i][q]q is blockquote" => "<em>q is blockquote</em>"
|
|
25
|
+
|
|
26
|
+
you can also define custom functions to operate on the tags:
|
|
27
|
+
|
|
28
|
+
"[r]reversed[.]" => "desrever"
|
data/lib/bbsexp.rb
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
module BBSexp
|
|
2
|
+
class Compiler
|
|
3
|
+
def initialize(parser, text)
|
|
4
|
+
@parser = parser
|
|
5
|
+
@text = text
|
|
6
|
+
@result = ''
|
|
7
|
+
@stack = []
|
|
8
|
+
@func_stack = []
|
|
9
|
+
@state = [3]
|
|
10
|
+
@locks = Hash[ @parser.exps.keys.map{|k| [k, false] } ]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def build
|
|
14
|
+
@result << tokens.map do |type, value|
|
|
15
|
+
case type
|
|
16
|
+
when :string then eval_string value
|
|
17
|
+
when :exp then eval_exp value
|
|
18
|
+
end
|
|
19
|
+
end.join
|
|
20
|
+
|
|
21
|
+
# close unclosed expressions
|
|
22
|
+
@result << @stack.reverse.map {|token|
|
|
23
|
+
token.reverse.map {|exp|
|
|
24
|
+
@parser.exps[exp].tags.last }.join }.join
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def tokens
|
|
28
|
+
return to_enum(:tokens) unless block_given?
|
|
29
|
+
scanner = StringScanner.new(@text)
|
|
30
|
+
|
|
31
|
+
while string = scanner.scan_until(@parser.regexp)
|
|
32
|
+
exp = scanner.matched
|
|
33
|
+
yield [:string, string[0..-(exp.size + 1)]] unless string == exp
|
|
34
|
+
yield [:exp, exp[1..-2]]
|
|
35
|
+
end
|
|
36
|
+
yield [:string, scanner.rest] unless scanner.eos?
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def eval_string(string)
|
|
40
|
+
# run callbacks on string
|
|
41
|
+
@func_stack.reduce(string) {|memo, func| func.(memo) }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def eval_exp(exp)
|
|
45
|
+
# dont parse if we are in the no parse zone
|
|
46
|
+
return eval_string(@parser.brackets(exp)) if @state.last == 0 and not exp.include? @parser.no_parse
|
|
47
|
+
# process expressions
|
|
48
|
+
unless exp[0] == @parser.end_exp
|
|
49
|
+
register(exp) #returns start tags
|
|
50
|
+
else
|
|
51
|
+
terminate(exp) #returns end tags
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def register(token)
|
|
56
|
+
@stack << active_exps = # append valid expressions to the stack
|
|
57
|
+
token.chars.to_a.uniq.select do |exp| # ignore redundant and illegal expressions
|
|
58
|
+
@locks[exp] == false and @parser.exps[exp] <= @state.last
|
|
59
|
+
end.sort do |a,b|
|
|
60
|
+
@parser.exps[b] <=> @parser.exps[a] # sort nocode > block > inline
|
|
61
|
+
end.map do |exp|
|
|
62
|
+
@locks[exp] = true # lock this expression to prevent its use deeper in the stack
|
|
63
|
+
@state << @parser.exps[exp].state # set parser state to current element's level
|
|
64
|
+
exp # return to token.active_exps as a valid expression
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
@func_stack += active_exps.map {|exp| @parser.exps[exp].block }.compact
|
|
68
|
+
|
|
69
|
+
# return the start tags for the token
|
|
70
|
+
active_exps.map {|exp| @parser.exps[exp].tags.first }.join
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def terminate(token)
|
|
74
|
+
return @parser.brackets(token) if @stack.empty? # don't do anything if the stack is empty
|
|
75
|
+
|
|
76
|
+
token.chars.map do |close|
|
|
77
|
+
if exps = @stack.pop
|
|
78
|
+
exps.map do |exp|
|
|
79
|
+
@locks[exp] = false # unlock this expression so it can be used again
|
|
80
|
+
@state.pop # return parser state to parent scope
|
|
81
|
+
@func_stack.pop if @parser.exps[exp].block # disable function
|
|
82
|
+
@parser.exps[exp].tags.last # return end tag
|
|
83
|
+
end.reverse.join
|
|
84
|
+
end
|
|
85
|
+
end.join
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module BBSexp
|
|
2
|
+
module Dialect
|
|
3
|
+
module ClassMethods
|
|
4
|
+
attr_accessor :exps, :brackets, :end_exp, :no_parse
|
|
5
|
+
attr_reader :regexp, :end_exp
|
|
6
|
+
|
|
7
|
+
def self.extended(by)
|
|
8
|
+
by.instance_exec do
|
|
9
|
+
@exps = {}
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def initialize
|
|
14
|
+
@brackets ||= '[]'
|
|
15
|
+
@no_parse ||= '`'
|
|
16
|
+
@end_exp ||= '|'
|
|
17
|
+
exps = [ @brackets[0],
|
|
18
|
+
@exps.keys.join,
|
|
19
|
+
@end_exp,
|
|
20
|
+
@no_parse,
|
|
21
|
+
@brackets[1] ].map {|v| Regexp.escape(v) }
|
|
22
|
+
|
|
23
|
+
regexp = "%s([%s]+|%s+(%s)?)%s" % exps
|
|
24
|
+
@regexp = Regexp.new(regexp)
|
|
25
|
+
@initialized = true
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def brackets(brackets=nil)
|
|
29
|
+
return @brackets[0] + brackets + @brackets[1] if @brackets
|
|
30
|
+
@brackets ||= brackets
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def end_exp(exp=nil)
|
|
34
|
+
@end_exp ||= exp
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def method_missing(level, sym, args={})
|
|
38
|
+
exp(sym, level, args)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def exp(sym, state, args={})
|
|
42
|
+
sym = sym.to_s
|
|
43
|
+
@no_parse = sym if state == :noparse
|
|
44
|
+
@exps[sym] = Expression.new(sym, state, args)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def parse(text)
|
|
48
|
+
initialize unless @initialized
|
|
49
|
+
Compiler.new(self, text).build
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def self.included(by)
|
|
54
|
+
by.extend ClassMethods
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module BBSexp
|
|
2
|
+
class Expression
|
|
3
|
+
include Comparable
|
|
4
|
+
attr_reader :sym, :state, :tags, :block
|
|
5
|
+
|
|
6
|
+
def initialize(sym, state, args={})
|
|
7
|
+
@sym = sym
|
|
8
|
+
@state = case state
|
|
9
|
+
when :block then 3
|
|
10
|
+
when :inline then 2
|
|
11
|
+
when :func then 1
|
|
12
|
+
when :noparse then 0
|
|
13
|
+
else state
|
|
14
|
+
end
|
|
15
|
+
@tags = args[:tags] || ['', '']
|
|
16
|
+
@block = args[:func]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def <=>(b)
|
|
20
|
+
@state <=> (b.class == Expression ? b.state : b.to_i)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: bbsexp
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.1
|
|
5
|
+
prerelease:
|
|
6
|
+
platform: ruby
|
|
7
|
+
authors:
|
|
8
|
+
- tca
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 2012-02-04 00:00:00.000000000 Z
|
|
13
|
+
dependencies: []
|
|
14
|
+
description: Concise BBcode Framework
|
|
15
|
+
email:
|
|
16
|
+
executables: []
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- LICENSE
|
|
21
|
+
- README.md
|
|
22
|
+
- lib/bbsexp.rb
|
|
23
|
+
- lib/bbsexp/expression.rb
|
|
24
|
+
- lib/bbsexp/compiler.rb
|
|
25
|
+
- lib/bbsexp/dialect.rb
|
|
26
|
+
homepage: https://github.com/tca/bbsexp
|
|
27
|
+
licenses: []
|
|
28
|
+
post_install_message:
|
|
29
|
+
rdoc_options: []
|
|
30
|
+
require_paths:
|
|
31
|
+
- lib
|
|
32
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
33
|
+
none: false
|
|
34
|
+
requirements:
|
|
35
|
+
- - ! '>='
|
|
36
|
+
- !ruby/object:Gem::Version
|
|
37
|
+
version: 1.9.2
|
|
38
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
39
|
+
none: false
|
|
40
|
+
requirements:
|
|
41
|
+
- - ! '>='
|
|
42
|
+
- !ruby/object:Gem::Version
|
|
43
|
+
version: '0'
|
|
44
|
+
requirements: []
|
|
45
|
+
rubyforge_project:
|
|
46
|
+
rubygems_version: 1.8.13
|
|
47
|
+
signing_key:
|
|
48
|
+
specification_version: 3
|
|
49
|
+
summary: Concise BBcode Framework
|
|
50
|
+
test_files: []
|