wikicloth 0.2.0 → 0.5.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.
data/Rakefile CHANGED
@@ -2,6 +2,7 @@ require 'rake'
2
2
  require 'rake/testtask'
3
3
  require 'rake/rdoctask'
4
4
  require 'rake/gempackagetask'
5
+ require 'init'
5
6
 
6
7
  desc 'Default: run unit tests.'
7
8
  task :default => :test
@@ -25,7 +26,7 @@ end
25
26
 
26
27
  spec = Gem::Specification.new do |s|
27
28
  s.name = "wikicloth"
28
- s.version = %q{0.2.0}
29
+ s.version = WikiCloth::VERSION
29
30
  s.author = "David Ricciardi"
30
31
  s.email = "nricciar@gmail.com"
31
32
  s.homepage = "http://github.com/nricciar/wikicloth"
@@ -41,6 +42,7 @@ spec = Gem::Specification.new do |s|
41
42
  s.extra_rdoc_files = ["README","MIT-LICENSE"]
42
43
  s.description = %q{mediawiki parser}
43
44
  s.add_dependency 'builder'
45
+ s.add_dependency 'expression_parser'
44
46
  end
45
47
  Rake::GemPackageTask.new(spec) do |pkg|
46
48
  pkg.need_tar = true
@@ -0,0 +1,133 @@
1
+ # A grammar for mathematical formulas that apply basic mathematical operations
2
+ # to all numbers, respecting operator precedence and grouping of expressions
3
+ # while ignoring whitespace.
4
+ #
5
+ # An identical grammar that is written using pure Ruby can be found in calc.rb.
6
+ grammar Calc
7
+
8
+ ## Hierarchy
9
+
10
+ rule term
11
+ additive | factor
12
+ end
13
+
14
+ rule additive
15
+ (factor additive_operator term) {
16
+ def value
17
+ additive_operator.apply(factor.value, term.value)
18
+ end
19
+ }
20
+ end
21
+
22
+ rule factor
23
+ multiplicative | prefix
24
+ end
25
+
26
+ rule multiplicative
27
+ (prefix multiplicative_operator factor) {
28
+ def value
29
+ multiplicative_operator.apply(prefix.value, factor.value)
30
+ end
31
+ }
32
+ end
33
+
34
+ rule prefix
35
+ prefixed | exponent
36
+ end
37
+
38
+ rule prefixed
39
+ (unary_operator prefix) {
40
+ def value
41
+ unary_operator.apply(prefix.value)
42
+ end
43
+ }
44
+ end
45
+
46
+ rule exponent
47
+ exponential | primary
48
+ end
49
+
50
+ rule exponential
51
+ (primary exponential_operator exponent) {
52
+ def value
53
+ exponential_operator.apply(primary.value, exponent.value)
54
+ end
55
+ }
56
+ end
57
+
58
+ rule primary
59
+ group | number
60
+ end
61
+
62
+ rule group
63
+ (lparen term rparen) {
64
+ def value
65
+ term.value
66
+ end
67
+ }
68
+ end
69
+
70
+ ## Syntax
71
+
72
+ rule number
73
+ float | integer
74
+ end
75
+
76
+ rule float
77
+ (digits '.' digits space) {
78
+ def value
79
+ text.strip.to_f
80
+ end
81
+ }
82
+ end
83
+
84
+ rule integer
85
+ (digits space) {
86
+ def value
87
+ text.strip.to_i
88
+ end
89
+ }
90
+ end
91
+
92
+ rule digits
93
+ [0-9]+ ('_' [0-9]+)*
94
+ end
95
+
96
+ rule additive_operator
97
+ (('+' | '-') space) {
98
+ def apply(n1, n2)
99
+ n1.send(text.strip, n2)
100
+ end
101
+ }
102
+ end
103
+
104
+ rule multiplicative_operator
105
+ (('*' | '/' | '%') space) {
106
+ def apply(n1, n2)
107
+ n1.send(text.strip, n2)
108
+ end
109
+ }
110
+ end
111
+
112
+ rule exponential_operator
113
+ ('**' space) {
114
+ def apply(n1, n2)
115
+ n1 ** n2
116
+ end
117
+ }
118
+ end
119
+
120
+ rule unary_operator
121
+ (('~' | '+' | '-') space) {
122
+ def apply(n)
123
+ op = text.strip
124
+ # Unary + and - require an @.
125
+ n.send(op == '~' ? op : '%s@' % op)
126
+ end
127
+ }
128
+ end
129
+
130
+ rule lparen '(' space end
131
+ rule rparen ')' space end
132
+ rule space [ \t\n\r]* end
133
+ end
@@ -19,6 +19,14 @@ class Object
19
19
  end
20
20
  end
21
21
 
22
+ module Math
23
+ def self.eval(expression)
24
+ allowed_characters = Regexp.escape('+-*/.() ')
25
+ safe_expression = expression.match(/[\d#{allowed_characters}]*/).to_s
26
+ Kernel.eval(safe_expression)
27
+ end
28
+ end
29
+
22
30
  module ExtendedString
23
31
 
24
32
  def blank?
@@ -37,6 +45,10 @@ module ExtendedString
37
45
  to_s
38
46
  end
39
47
 
48
+ def last(n)
49
+ self[-n,n]
50
+ end
51
+
40
52
  def dump()
41
53
  ret = to_s
42
54
  delete!(to_s)
@@ -0,0 +1,155 @@
1
+ # The following code taken from http://lukaszwrobel.pl/blog/math-parser-part-3-implementation
2
+ module WikiCloth
3
+
4
+ class Token
5
+ Plus = 0
6
+ Minus = 1
7
+ Multiply = 2
8
+ Divide = 3
9
+
10
+ Number = 4
11
+
12
+ LParen = 5
13
+ RParen = 6
14
+
15
+ End = 7
16
+
17
+ attr_accessor :kind
18
+ attr_accessor :value
19
+
20
+ def initialize
21
+ @kind = nil
22
+ @value = nil
23
+ end
24
+
25
+ def unknown?
26
+ @kind.nil?
27
+ end
28
+ end
29
+
30
+ class Lexer
31
+ def initialize(input)
32
+ @input = input
33
+ @return_previous_token = false
34
+ end
35
+
36
+ def get_next_token
37
+ if @return_previous_token
38
+ @return_previous_token = false
39
+ return @previous_token
40
+ end
41
+
42
+ token = Token.new
43
+
44
+ @input.lstrip!
45
+
46
+ case @input
47
+ when /\A\+/ then
48
+ token.kind = Token::Plus
49
+ when /\A-/ then
50
+ token.kind = Token::Minus
51
+ when /\A\*/ then
52
+ token.kind = Token::Multiply
53
+ when /\A\// then
54
+ token.kind = Token::Divide
55
+ when /\A\d+(\.\d+)?/
56
+ token.kind = Token::Number
57
+ token.value = $&.to_f
58
+ when /\A\(/
59
+ token.kind = Token::LParen
60
+ when /\A\)/
61
+ token.kind = Token::RParen
62
+ when ''
63
+ token.kind = Token::End
64
+ end
65
+
66
+ raise 'Unknown token' if token.unknown?
67
+ @input = $'
68
+
69
+ @previous_token = token
70
+ token
71
+ end
72
+
73
+ def revert
74
+ @return_previous_token = true
75
+ end
76
+ end
77
+
78
+ class MathParser
79
+ def parse(input)
80
+ @lexer = Lexer.new(input)
81
+
82
+ expression_value = expression
83
+
84
+ token = @lexer.get_next_token
85
+ if token.kind == Token::End
86
+ expression_value
87
+ else
88
+ raise 'End expected'
89
+ end
90
+ end
91
+
92
+ protected
93
+ def expression
94
+ component1 = factor
95
+
96
+ additive_operators = [Token::Plus, Token::Minus]
97
+
98
+ token = @lexer.get_next_token
99
+ while additive_operators.include?(token.kind)
100
+ component2 = factor
101
+
102
+ if token.kind == Token::Plus
103
+ component1 += component2
104
+ else
105
+ component1 -= component2
106
+ end
107
+
108
+ token = @lexer.get_next_token
109
+ end
110
+ @lexer.revert
111
+
112
+ component1
113
+ end
114
+
115
+ def factor
116
+ factor1 = number
117
+
118
+ multiplicative_operators = [Token::Multiply, Token::Divide]
119
+
120
+ token = @lexer.get_next_token
121
+ while multiplicative_operators.include?(token.kind)
122
+ factor2 = number
123
+
124
+ if token.kind == Token::Multiply
125
+ factor1 *= factor2
126
+ else
127
+ factor1 /= factor2
128
+ end
129
+
130
+ token = @lexer.get_next_token
131
+ end
132
+ @lexer.revert
133
+
134
+ factor1
135
+ end
136
+
137
+ def number
138
+ token = @lexer.get_next_token
139
+
140
+ if token.kind == Token::LParen
141
+ value = expression
142
+
143
+ expected_rparen = @lexer.get_next_token
144
+ raise 'Unbalanced parenthesis' unless expected_rparen.kind == Token::RParen
145
+ elsif token.kind == Token::Number
146
+ value = token.value
147
+ else
148
+ raise 'Not a number'
149
+ end
150
+
151
+ value
152
+ end
153
+ end
154
+
155
+ end
@@ -46,6 +46,12 @@ module WikiCloth
46
46
  end
47
47
  end
48
48
 
49
+ def template(&block)
50
+ self.send :define_method, 'template' do |template|
51
+ self.instance_exec(template,&block)
52
+ end
53
+ end
54
+
49
55
  def link_for_resource(&block)
50
56
  self.send :define_method, 'link_for_resource' do |prefix,resource,options|
51
57
  options ||= []
@@ -59,12 +65,6 @@ module WikiCloth
59
65
  end
60
66
  end
61
67
 
62
- def template(&block)
63
- self.send :define_method, 'template' do |template|
64
- self.instance_exec(template,&block)
65
- end
66
- end
67
-
68
68
  def link_for(&block)
69
69
  self.send :define_method, 'link_for' do |page,text|
70
70
  self.instance_exec(page,text,&block)
@@ -80,7 +80,7 @@ module WikiCloth
80
80
 
81
81
  # Replace a section, along with any sub-section in the document
82
82
  def put_section(id,data)
83
- data = @wikicloth.sections.first.wikitext({ :replace => { id => data } })
83
+ data = @wikicloth.sections.first.wikitext({ :replace => { id => data.last(1) == "\n" ? data : "#{data}\n" } })
84
84
  @wikicloth = WikiCloth.new(:data => data, :link_handler => self, :params => @params)
85
85
  end
86
86
 
@@ -20,6 +20,10 @@ module WikiCloth
20
20
  @auto_toc = val
21
21
  end
22
22
 
23
+ def template=(val)
24
+ @template = val
25
+ end
26
+
23
27
  def title=(val)
24
28
  if val =~ /^([=]{1,6})\s*(.*?)\s*(\1)/
25
29
  @depth = $1.length
@@ -71,7 +75,7 @@ module WikiCloth
71
75
  "\" title=\"Edit section: #{self.title}\">edit</a>&#93;</span>") +
72
76
  " <span id=\"#{self.id}\" class=\"mw-headline\">#{self.title}</span></h#{self.depth}>"
73
77
  end
74
- ret += self
78
+ ret += @template ? self.gsub(/\{\{\{\s*([A-Za-z0-9]+)\s*\}\}\}/,'\1') : self
75
79
  ret += "__TOC__" if @auto_toc
76
80
  ret += @children.collect { |c| c.render(options) } .join
77
81
  ret
@@ -0,0 +1,32 @@
1
+ module WikiCloth
2
+
3
+ class WikiBuffer::Template < WikiBuffer
4
+
5
+ def initialize(data="",options={})
6
+ super(data,options)
7
+ @in_quotes = false
8
+ end
9
+
10
+ def to_s
11
+ "WICKED COOL"
12
+ end
13
+
14
+ protected
15
+ def new_char()
16
+ case
17
+ # End of a template, variable, or function
18
+ when current_char == '}' && previous_char == '}'
19
+ puts "TEMP: #{self.data} ----"
20
+ self.data = ""
21
+ return false
22
+
23
+ else
24
+ self.data += current_char
25
+ end
26
+
27
+ return true
28
+ end
29
+
30
+ end
31
+
32
+ end
@@ -1,3 +1,5 @@
1
+ require 'expression_parser'
2
+
1
3
  module WikiCloth
2
4
 
3
5
  class WikiBuffer::Var < WikiBuffer
@@ -18,17 +20,83 @@ class WikiBuffer::Var < WikiBuffer
18
20
 
19
21
  def to_s
20
22
  if self.is_function?
21
- # ret = "#{buffer_type}"
22
- # ret += " function #{function_name}"
23
- # ret += "(#{params.inspect})"
24
- # ret += " [#{data}]"
25
- ret = @options[:link_handler].function(function_name, params.collect { |p| p.strip })
23
+ ret = default_functions(function_name,params.collect { |p| p.strip })
24
+ ret ||= @options[:link_handler].function(function_name, params.collect { |p| p.strip })
25
+ ret.to_s
26
26
  else
27
27
  ret = @options[:link_handler].include_resource("#{params[0]}".strip,params[1..-1])
28
+ # template params
29
+ ret = ret.to_s.gsub(/\{\{\{\s*([A-Za-z0-9]+)+(|\|+([^}]+))\s*\}\}\}/) { |match| get_param($1.strip,$3.to_s.strip) }
30
+ # put template at beginning of buffer
31
+ self.data = ret
32
+ ""
33
+ end
34
+ end
35
+
36
+ def get_param(name,default=nil)
37
+ ret = nil
38
+ # numbered params
39
+ if name =~ /^[0-9]+$/
40
+ ret = self.params[name.to_i].instance_of?(Hash) ? self.params[name.to_i][:value] : self.params[name.to_i]
41
+ end
42
+ # named params
43
+ self.params.each do |param|
44
+ ret = param[:value] if param[:name] == name
45
+ end
46
+ ret.nil? ? default : ret
47
+ end
48
+
49
+ def default_functions(name,params)
50
+ case name
51
+ when "#if"
52
+ params.first.blank? ? params[2] : params[1]
53
+ when "#switch"
54
+ params.length.times do |i|
55
+ temp = params[i].split("=")
56
+ return temp[1].strip if temp[0].strip == params[0] && i != 0
57
+ end
58
+ return ""
59
+ when "#expr"
60
+ begin
61
+ ExpressionParser::Parser.new.parse(params.first)
62
+ rescue RuntimeError
63
+ 'Expression error: ' + $!
64
+ end
65
+ when "#ifeq"
66
+ if params[0] =~ /^[0-9A-Fa-f]+$/ && params[1] =~ /^[0-9A-Fa-f]+$/
67
+ params[0].to_i == params[1].to_i ? params[2] : params[3]
68
+ else
69
+ params[0] == params[1] ? params[2] : params[3]
70
+ end
71
+ when "#len"
72
+ params.first.length
73
+ when "#sub"
74
+ params.first[params[1].to_i,params[2].to_i]
75
+ when "#pad"
76
+ case params[3]
77
+ when "right"
78
+ params[0].ljust(params[1].to_i,params[2])
79
+ when "center"
80
+ params[0].center(params[1].to_i,params[2])
81
+ else
82
+ params[0].rjust(params[1].to_i,params[2])
83
+ end
84
+ when "#iferror"
85
+ params.first =~ /error/ ? params[1] : params[2]
86
+ when "#capture"
87
+ @options[:params][params.first] = params[1]
88
+ ""
89
+ when "lc"
90
+ params.first.downcase
91
+ when "uc"
92
+ params.first.upcase
93
+ when "ucfirst"
94
+ params.first.capitalize
95
+ when "lcfirst"
96
+ params.first[0,1].downcase + params.first[1,-1]
97
+ when "plural"
98
+ params.first.to_i > 1 ? params[1] : params[2]
28
99
  end
29
- # ret ||= "<!-- TEMPLATE[#{params[0]}] NOT FOUND -->"
30
- ret ||= ""
31
- ret
32
100
  end
33
101
 
34
102
  def is_function?
@@ -54,7 +122,7 @@ class WikiBuffer::Var < WikiBuffer
54
122
 
55
123
  # Dealing with variable names within functions
56
124
  # and variables
57
- when current_char == '=' && @in_quotes == false
125
+ when current_char == '=' && @in_quotes == false && !is_function?
58
126
  self.current_param = self.data
59
127
  self.data = ""
60
128
  self.name_current_param()
@@ -18,10 +18,7 @@ class WikiLinkHandler
18
18
  end
19
19
 
20
20
  def function(name, params)
21
- case name
22
- when "#if"
23
- params.first.blank? ? params[2] : params[1]
24
- end
21
+ nil
25
22
  end
26
23
 
27
24
  def toc_children(children)
@@ -44,10 +41,6 @@ class WikiLinkHandler
44
41
  "#{ret}</ul></td></tr></table>"
45
42
  end
46
43
 
47
- def template(template, args)
48
- nil
49
- end
50
-
51
44
  def external_links
52
45
  @external_links ||= []
53
46
  end
@@ -94,7 +87,30 @@ class WikiLinkHandler
94
87
  end
95
88
 
96
89
  def include_resource(resource, options=[])
97
- return self.params[resource] unless self.params[resource].nil?
90
+ if self.params.has_key?(resource)
91
+ self.params[resource]
92
+ else
93
+ # FIXME: hack to keep template loops from raising havoc
94
+ @include_count ||= 0
95
+ @include_count += 1
96
+ raise "Page reached maximum number of includes [1000] (possible template loop?)" if @include_count > 100
97
+
98
+ ret = template(resource)
99
+ unless ret.nil?
100
+ @included_templates ||= {}
101
+ @included_templates[resource] ||= 0
102
+ @included_templates[resource] += 1
103
+ end
104
+ ret
105
+ end
106
+ end
107
+
108
+ def included_templates
109
+ @included_templates ||= {}
110
+ end
111
+
112
+ def template(template)
113
+ nil
98
114
  end
99
115
 
100
116
  def link_for_resource(prefix, resource, options=[])
data/lib/wikicloth.rb CHANGED
@@ -8,6 +8,8 @@ String.send(:include, ExtendedString)
8
8
 
9
9
  module WikiCloth
10
10
 
11
+ VERSION = "0.5.0"
12
+
11
13
  class WikiCloth
12
14
 
13
15
  def initialize(opt={})
@@ -19,6 +21,8 @@ module WikiCloth
19
21
  depth = 1
20
22
  count = 0
21
23
  root = [self.sections]
24
+
25
+ # parse wiki document into sections
22
26
  data.each_line do |line|
23
27
  if line =~ /^([=]{1,6})\s*(.*?)\s*(\1)/
24
28
  root << root.last[-1].children if $1.length > depth
@@ -30,7 +34,15 @@ module WikiCloth
30
34
  root.last[-1] << line
31
35
  end
32
36
  end
37
+
38
+ # if we find template variables assume document is
39
+ # a template
40
+ self.sections.first.template = true if data =~ /\{\{\{\s*([A-Za-z0-9]+)\s*\}\}\}/
41
+
42
+ # If there are more than four sections enable automatic
43
+ # table of contents
33
44
  self.sections.first.auto_toc = true unless count < 4 || data =~ /__(NO|)TOC__/
45
+
34
46
  self.params = p
35
47
  end
36
48
 
@@ -40,11 +52,11 @@ module WikiCloth
40
52
 
41
53
  def render(opt={})
42
54
  noedit = false
55
+ self.params.merge!({ 'WIKI_VERSION' => ::WikiCloth::VERSION })
43
56
  self.options = { :output => :html, :link_handler => self.link_handler, :params => self.params, :sections => self.sections }.merge(opt)
44
57
  self.options[:link_handler].params = options[:params]
45
58
  data = self.sections.first.render(self.options)
46
59
  data.gsub!(/<!--(.|\s)*?-->/,"")
47
- data.gsub!(/\{\{(.*?)(\|(.*?))?\}\}/){ |match| expand_templates($1,$3,["."]) }
48
60
  buffer = WikiBuffer.new("",options)
49
61
  data.each_char { |c| buffer.add_char(c) }
50
62
  buffer.to_s
@@ -63,24 +75,6 @@ module WikiCloth
63
75
  end
64
76
 
65
77
  protected
66
- def expand_templates(template, args, stack)
67
- template.strip!
68
- article = link_handler.template(template, args)
69
-
70
- if article.nil?
71
- data = "{{#{template}}}"
72
- else
73
- unless stack.include?(template)
74
- data = article
75
- else
76
- data = "WARNING: TEMPLATE LOOP"
77
- end
78
- data = data.gsub(/\{\{(.*?)(?:\|(.*?))?\}\}?/){ |match| expand_templates($1, $2, stack + [template])}
79
- end
80
-
81
- data
82
- end
83
-
84
78
  def sections=(val)
85
79
  @sections = val
86
80
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wikicloth
3
3
  version: !ruby/object:Gem::Version
4
- hash: 23
4
+ hash: 11
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 2
8
+ - 5
9
9
  - 0
10
- version: 0.2.0
10
+ version: 0.5.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - David Ricciardi
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-09-15 00:00:00 +00:00
18
+ date: 2010-09-20 00:00:00 +00:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -32,6 +32,20 @@ dependencies:
32
32
  version: "0"
33
33
  type: :runtime
34
34
  version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: expression_parser
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ type: :runtime
48
+ version_requirements: *id002
35
49
  description: mediawiki parser
36
50
  email: nricciar@gmail.com
37
51
  executables: []
@@ -43,10 +57,13 @@ extra_rdoc_files:
43
57
  - MIT-LICENSE
44
58
  files:
45
59
  - lib/wikicloth.rb
60
+ - lib/wikicloth/calc.citrus
61
+ - lib/wikicloth/math.rb
46
62
  - lib/wikicloth/core_ext.rb
47
63
  - lib/wikicloth/wiki_buffer/html_element.rb
48
64
  - lib/wikicloth/wiki_buffer/var.rb
49
65
  - lib/wikicloth/wiki_buffer/table.rb
66
+ - lib/wikicloth/wiki_buffer/template.rb
50
67
  - lib/wikicloth/wiki_buffer/link.rb
51
68
  - lib/wikicloth/parser.rb
52
69
  - lib/wikicloth/section.rb