wikicloth 0.2.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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