bbcode-rails 0.7.0 → 0.8.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 85c25fceeb4fbc1b4c93491384ef5af2d867587d
4
- data.tar.gz: 23873eb57b191664b7b6afd60e6590841e888d2d
3
+ metadata.gz: 6e3a03c9829b29ae970341a6869350c6d81a63e6
4
+ data.tar.gz: 1075e5565218f39b55b224764911b19b419f30da
5
5
  SHA512:
6
- metadata.gz: 12894618783176fcb4053053aac63ebc69e3a6d81de7208d060cd6aa14253b5817378de929cc025f96f9255595ab6b6fae2188d846e99356507a898d4f4d2412
7
- data.tar.gz: ee74932ca1193c89a6cc4898696f390134975376b475241c459cc9b3c76b0d3f61793588a8493ab5cc20e20261bdcb92b65538699493c73977c8f107e2cce326
6
+ metadata.gz: 0a8313ca29126829b91a2338647b00adf318223ef4de44f7649dba819198070b5045ec211cf977094aa8024da3bf2b1bbfe66c24ae4dbcf1e28ad8d9a4cdfcde
7
+ data.tar.gz: f5cc8b2ae3c5dcc509c8275599b8d4c1a366c7b6a476773f300492fd1137a7860801b43e0d43ebb172ac5cf2d4138d9b48335ec660bece20045cef174f6ccdc7
data/README.md CHANGED
@@ -55,7 +55,7 @@ This will create `app/bbcode/user_tag.rb`.
55
55
  #app/bbcode/user.rb
56
56
 
57
57
  class UserTag < BBCode::Tag
58
- name :user
58
+ block_name :user
59
59
  on_layout do |args|
60
60
  "TODO: Implement user tag"
61
61
  end
@@ -68,7 +68,7 @@ You could now add something like:
68
68
  #app/bbcode/user.rb
69
69
 
70
70
  class UserTag < BBCode::Tag
71
- name :user, :argument, :no_closing_tag
71
+ block_name :user, :argument, :no_closing_tag
72
72
  on_layout do |args|
73
73
  user = User.find_by_id(args[1])
74
74
  render partial: 'shared/userquote', locals: { user: user }
@@ -78,6 +78,16 @@ end
78
78
 
79
79
  Of course, the limitations are your knowledge in ruby and rails :)
80
80
 
81
+ ### Transforming a BBCode string into HTML
82
+
83
+ Just call `bbcode_to_html` on any string.
84
+
85
+ ```ruby
86
+ #> User.all.first.bio
87
+ "[i]Hello [b]Everyone[/b][/i]"
88
+ #> User.all.first.bio.bbcode_to_html
89
+ => "<em>Hello <strong>Everyone</strong></em>"
90
+ ```
81
91
 
82
92
  ## Contributing
83
93
 
data/lib/bbcode-rails.rb CHANGED
@@ -1,12 +1,25 @@
1
1
  require "bbcode-rails/version"
2
2
 
3
3
  module BBCode
4
- @@tags = []
4
+ class ParseError < StandardError
5
+ end
6
+
7
+ @tags = {}
5
8
  def self.tags
6
- @@tags
9
+ @tags
10
+ end
11
+
12
+ def self.get_tag_by_name name
13
+ if defined?(Rails) && Rails.env.development?
14
+ begin
15
+ "#{p}_tag".camelize.constantize
16
+ rescue NameError
17
+ end
18
+ end
19
+ @tags["#{name.to_s.downcase}tag"]
7
20
  end
8
21
 
9
- def self.parse str
22
+ def self.parse str, raise_error=false
10
23
  str = str.dup
11
24
 
12
25
  str.gsub!( '&', '&amp;' )
@@ -15,20 +28,140 @@ module BBCode
15
28
  str.gsub!( '"', '&quot;' )
16
29
  str.gsub!( "'", '&apos;' )
17
30
 
18
- # For eager loading
19
- if defined?(Rails) && Rails.env.development?
20
- str.scan(/\[(\w+)(?:=.+)?\]/).each do |tagname|
21
- begin
22
- "#{tagname[0]}_tag".camelize.constantize
23
- rescue NameError
31
+ # Let's iterate over the pieces to build a tree
32
+ # It works like this:
33
+ # For each object you have two things:
34
+ # 1. It is a tag name a la [img]
35
+ # 2. It is a simple string, something like 'Hello'
36
+ #
37
+ # Now, we want a tree that looks like this
38
+ #
39
+ # -> |
40
+ # | ImgTag src: http://adada
41
+ # | String This cat
42
+ # | BTag is -> |
43
+ # | ITag funny!
44
+ # In the end we just recursively append
45
+
46
+ result = []
47
+
48
+ tag_open = /\[/
49
+ tag_close = /\]/
50
+ tag_close_prefix = /\//
51
+ tag_arg = /=/
52
+ tag_arg_delim = /&quot;/
53
+ tag_name = /[-_a-z0-9]/
54
+
55
+ current_state = :text
56
+ current_tag = result
57
+
58
+ begin
59
+ pos = 0
60
+ while pos < str.length
61
+ case current_state
62
+ when :text
63
+ tmp = ""
64
+ # We iterate through the string either until the end or if we find a [
65
+ while not str[pos] =~ tag_open and pos < str.length
66
+ tmp << str[pos]
67
+ pos = pos.next
68
+ end
69
+ current_tag << tmp
70
+ current_state = :tag_name if pos < str.length # Okay, we have found the beginning of a possible tag!
71
+ pos = pos.next
72
+ when :tag_name
73
+ name = ""
74
+ if str[pos-1] =~ tag_open and str[pos] =~ tag_close_prefix
75
+ # It's a closing tag!
76
+ # Let's check if it applies to our current tag..
77
+
78
+ broke_out = false
79
+ tag = current_tag
80
+ until tag.is_a? Array
81
+ len = tag.name.length
82
+ if str[pos+1,len] == tag.name and str[pos+len+1] =~ tag_close
83
+ # If it's the current one?
84
+ if current_tag == tag
85
+ current_tag = tag.parent
86
+ pos += len+1
87
+ broke_out = true
88
+ break
89
+ else
90
+ # It's not the current one! Invalid construct
91
+ raise BBCode::ParseError, "Invalid nested tags"
92
+ end
93
+ end
94
+ tag = tag.parent
95
+ end
96
+
97
+ # It's not a closing tag we recognize, so it's text really!
98
+ # Let's restore this!
99
+ if not broke_out
100
+ current_tag << "[/"
101
+ end
102
+ current_state = :text
103
+ pos = pos.next
104
+ next
105
+ end
106
+ while not (str[pos] =~ tag_close and str[pos] =~ tag_arg and pos < str.length) and str[pos] =~ tag_name
107
+ name << str[pos]
108
+ pos = pos.next
109
+ end
110
+
111
+ if str[pos] =~ tag_arg or str[pos] =~ tag_close
112
+ if self.get_tag_by_name(name)
113
+ new_tag = self.get_tag_by_name(name).new(current_tag)
114
+ current_tag << new_tag
115
+ if new_tag.has_option(:content) or new_tag.has_option(:argument)
116
+ current_tag = new_tag
117
+ end
118
+ if str[pos] =~ tag_arg
119
+ if new_tag.has_option :argument
120
+ current_state = :tag_arg
121
+ else
122
+ raise BBCode::ParseError
123
+ end
124
+ else
125
+ current_state = :text
126
+ end
127
+ pos = pos.next
128
+ else
129
+ current_tag << "[#{name}"
130
+ current_state = :text
131
+ end
132
+ next
133
+ end
134
+
135
+
136
+ # Did we hit the end? Let's get out
137
+ current_tag << name
138
+ current_state = :text
139
+ when :tag_arg
140
+ arg = ""
141
+ while not str[pos] =~ tag_close and pos < str.length
142
+ arg << str[pos]
143
+ pos = pos.next
144
+ end
145
+
146
+ current_tag.argument = arg.gsub(/^#{tag_arg_delim}(.*)#{tag_arg_delim}$/, '\1')
147
+ if not current_tag.has_option(:content)
148
+ current_tag = current_tag.parent
149
+ end
150
+ current_state = :text
151
+ pos = pos.next
152
+ else
153
+ pos = pos.next
24
154
  end
25
155
  end
26
- end
27
156
 
28
- @@tags.each do |t|
29
- str.gsub!(t.regex) { t.instance.instance_exec($~, &t.block) }
157
+ result.map(&:to_s).join('').strip
158
+ rescue BBCode::ParseError => e
159
+ if raise_error
160
+ raise e
161
+ else
162
+ str
163
+ end
30
164
  end
31
- str
32
165
  end
33
166
 
34
167
  end
@@ -17,33 +17,73 @@ class BBCode::Tag
17
17
  self.view_paths = "app/views"
18
18
  end
19
19
 
20
- def self.instance
21
- @_tag ||= new
20
+ attr_accessor :parent
21
+
22
+ def initialize parent
23
+ @parent = parent
24
+ @content = []
25
+ @argument = ""
22
26
  end
23
27
 
24
- def self.inherited subclass
25
- # In case we autoreload, remove earlier instances
26
- BBCode.tags.delete_if {|c| c.to_s == subclass.to_s }
27
- BBCode.tags << subclass
28
+ def has_option opt
29
+ self.class.options.include?(opt)
30
+ end
31
+
32
+ def get_block
33
+ self.class.block
28
34
  end
29
35
 
30
- def self.block_name n, *args
31
- if args.include? :argument
32
- arg = '=(?:&quot;)?(.+?)(?:&quot;)?'
36
+ def argument= arg
37
+ if !has_option :argument
38
+ raise BBCode::ParseError, "Tried to assign an argument to a tag which takes none, #{name}"
39
+ else
40
+ @argument = arg
33
41
  end
34
- if args.include? :no_closing_tag
35
- @regex = /\[#{n.to_s}#{arg}\]/mi
42
+ end
43
+
44
+ def << c
45
+ if !has_option :content
46
+ raise BBCode::ParseError, "Tried to assign content to a tag which takes none, #{name}"
36
47
  else
37
- @regex = /\[#{n.to_s}#{arg}\](.+?)\[\/#{n.to_s}\]/mi
48
+ @content << c
49
+ end
50
+ end
51
+
52
+ def to_s
53
+ if has_option :content
54
+ result = @content.map(&:to_s).join('')
38
55
  end
56
+ if has_option(:content) and has_option(:argument)
57
+ get_block.call(@argument, result)
58
+ elsif has_option :content
59
+ get_block.call(result)
60
+ elsif has_option :argument
61
+ get_block.call(@argument)
62
+ else
63
+ get_block.call
64
+ end
65
+ end
66
+
67
+ def name
68
+ self.class.to_s.downcase.gsub(/tag$/i,'')
69
+ end
70
+
71
+ def self.inherited subclass
72
+ # In case we autoreload, remove earlier instances
73
+ BBCode.tags.delete_if {|c| c.to_s == subclass.to_s }
74
+ BBCode.tags[subclass.to_s.downcase] = subclass
75
+ end
76
+
77
+ def self.block_options *args
78
+ @options = args
39
79
  end
40
80
 
41
81
  def self.on_layout &b
42
82
  @block = b
43
83
  end
44
84
 
45
- def self.regex
46
- @regex
85
+ def self.options
86
+ @options ||= []
47
87
  end
48
88
 
49
89
  def self.block
@@ -1,3 +1,3 @@
1
1
  module BBCode
2
- VERSION = "0.7.0"
2
+ VERSION = "0.8.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bbcode-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marcel Müller
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2015-04-16 00:00:00.000000000 Z
11
+ date: 2015-04-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler