wikilink-converter 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.travis.yml ADDED
@@ -0,0 +1 @@
1
+ rvm: 1.9.2
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ -m markdown
data/Gemfile ADDED
@@ -0,0 +1,23 @@
1
+ source 'http://rubygems.org'
2
+ require 'rbconfig'
3
+ HOST_OS = RbConfig::CONFIG['host_os']
4
+
5
+ # Add dependencies to develop your gem here.
6
+ # Include everything needed to run rake, tests, features, etc.
7
+ group :development do
8
+ gem 'rspec'
9
+ gem 'yard'
10
+ gem 'bundler'
11
+ gem 'jeweler'
12
+ gem 'simplecov'
13
+ gem 'guard'
14
+ gem 'guard-rspec'
15
+ gem 'guard-bundler'
16
+ gem 'guard-yard'
17
+ gem 'redcarpet'
18
+
19
+ if HOST_OS =~ /linux/i
20
+ gem 'rb-inotify', require: false
21
+ gem 'libnotify', require: false
22
+ end
23
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,57 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.1.3)
5
+ ffi (1.0.11)
6
+ git (1.2.5)
7
+ guard (0.8.8)
8
+ thor (~> 0.14.6)
9
+ guard-bundler (0.1.3)
10
+ bundler (>= 1.0.0)
11
+ guard (>= 0.2.2)
12
+ guard-rspec (0.5.8)
13
+ guard (>= 0.8.4)
14
+ guard-yard (1.0.2)
15
+ guard (>= 0.2.2)
16
+ yard (>= 0.7.0)
17
+ jeweler (1.6.4)
18
+ bundler (~> 1.0)
19
+ git (>= 1.2.5)
20
+ rake
21
+ libnotify (0.5.9)
22
+ multi_json (1.0.4)
23
+ rake (0.9.2.2)
24
+ rb-inotify (0.8.8)
25
+ ffi (>= 0.5.0)
26
+ redcarpet (1.17.2)
27
+ rspec (2.7.0)
28
+ rspec-core (~> 2.7.0)
29
+ rspec-expectations (~> 2.7.0)
30
+ rspec-mocks (~> 2.7.0)
31
+ rspec-core (2.7.1)
32
+ rspec-expectations (2.7.0)
33
+ diff-lcs (~> 1.1.2)
34
+ rspec-mocks (2.7.0)
35
+ simplecov (0.5.4)
36
+ multi_json (~> 1.0.3)
37
+ simplecov-html (~> 0.5.3)
38
+ simplecov-html (0.5.3)
39
+ thor (0.14.6)
40
+ yard (0.7.4)
41
+
42
+ PLATFORMS
43
+ ruby
44
+
45
+ DEPENDENCIES
46
+ bundler
47
+ guard
48
+ guard-bundler
49
+ guard-rspec
50
+ guard-yard
51
+ jeweler
52
+ libnotify
53
+ rb-inotify
54
+ redcarpet
55
+ rspec
56
+ simplecov
57
+ yard
data/Guardfile ADDED
@@ -0,0 +1,21 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'rspec', :version => 2, :cli => '--color --format doc' do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+ watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
9
+ end
10
+
11
+
12
+ guard 'bundler' do
13
+ watch('Gemfile')
14
+ # Uncomment next line if Gemfile contain `gemspec' command
15
+ # watch(/^.+\.gemspec/)
16
+ end
17
+
18
+ guard 'yard' do
19
+ watch(%r{lib/.+\.rb})
20
+ watch('README.markdown')
21
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Ian Yang
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,14 @@
1
+ wikilink-converter
2
+ ==================
3
+
4
+ Description goes here.
5
+
6
+ [![Build Status](https://secure.travis-ci.org/doitian/wikilink-converter.png)](http://travis-ci.org/doitian/wikilink-converter)
7
+
8
+
9
+ Copyright
10
+ ---------
11
+
12
+ Copyright (c) 2011 Ian Yang. See LICENSE.txt for
13
+ further details.
14
+
data/Rakefile ADDED
@@ -0,0 +1,41 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "wikilink-converter"
18
+ gem.homepage = "http://github.com/doitian/wikilink-converter"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{convert [[WikiLink]] to <a>}
21
+ gem.description = %Q{convert [[WikiLink]] to <a>}
22
+ gem.email = "me@iany.me"
23
+ gem.authors = ["Ian Yang"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rspec/core'
29
+ require 'rspec/core/rake_task'
30
+ RSpec::Core::RakeTask.new(:spec) do |spec|
31
+ spec.pattern = FileList['spec/**/*_spec.rb']
32
+ end
33
+
34
+ task :cov do
35
+ system 'bundle exec rake COVERAGE=1 spec'
36
+ end
37
+
38
+ task :default => :spec
39
+
40
+ require 'yard'
41
+ YARD::Rake::YardocTask.new
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,58 @@
1
+ require 'wikilink/converter/utils'
2
+
3
+ module Wikilink
4
+ class Converter
5
+ # Namespace converter
6
+ class Namespace
7
+ include LinkHelper
8
+
9
+ DEFAULT_NAME = ''
10
+
11
+ attr_reader :options
12
+
13
+ def initialize(options = {})
14
+ @options = options
15
+ end
16
+
17
+ def config(&block)
18
+ @block = block
19
+ self
20
+ end
21
+
22
+ def run(colon, path, name, current_page)
23
+ if @block
24
+ instance_exec(colon, path, name, current_page, &@block)
25
+ end
26
+ end
27
+
28
+ class Default < Namespace
29
+ def run(colon, path, name, current_page)
30
+ return super if @block
31
+
32
+ path, fragment = path.split('#', 2)
33
+ path, query = path.split('?', 2)
34
+
35
+ fragment = '#' + fragment if fragment
36
+ query = '?' + query if query
37
+
38
+ url = to_url(path, fragment, query)
39
+
40
+ link_to(name, url, :class => html_class)
41
+ end
42
+
43
+ def to_url(path, fragment, query)
44
+ if path.nil? || path.empty?
45
+ [query, fragment].join
46
+ else
47
+ [options[:prefix], path, options[:suffix], query, fragment].join
48
+ end
49
+ end
50
+ end
51
+
52
+ protected
53
+ def html_class
54
+ [options[:class], ('external' if options[:external])]
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,99 @@
1
+ require 'wikilink/converter/namespace'
2
+ require 'wikilink/converter/utils'
3
+
4
+ module Wikilink
5
+ class Converter
6
+ # Site converter
7
+ class Site
8
+ include ArgumentExtractor
9
+ CURRENT_SITE_NAME = ''
10
+ DEFAULT_NAMESPACE = ::Wikilink::Converter::Namespace::DEFAULT_NAME
11
+
12
+ attr_reader :options
13
+
14
+ def initialize(options = {})
15
+ @options = options
16
+ is_current_site = @options[:name].to_s == CURRENT_SITE_NAME
17
+ @options[:external] = !is_current_site unless @options.key?(:external)
18
+ @options[:prefix] ||= '/' if is_current_site
19
+ @namespace_converters = {}
20
+
21
+ on_namespace(DEFAULT_NAMESPACE)
22
+
23
+ yield self if block_given?
24
+ end
25
+
26
+ def on_namespace(*args, &block)
27
+ namespace, converter, options = extract_arguments(*args)
28
+ namespace = DEFAULT_NAMESPACE if namespace.to_s.empty?
29
+
30
+ converter ||= namespace_converter(namespace)
31
+ if converter.nil?
32
+ # do not add new handler if there's a instance method can handle it,
33
+ # except that user passes a block to override it
34
+ if block || instance_method_converter(namespace).nil?
35
+ converter = ::Wikilink::Converter::Namespace
36
+ converter = ::Wikilink::Converter::Namespace::Default if !block && namespace == DEFAULT_NAMESPACE
37
+ end
38
+ end
39
+
40
+ if converter.is_a?(Class)
41
+ options = default_options_for_namespace.merge(options)
42
+ options[:name] ||= namespace
43
+ converter = converter.new(options)
44
+ end
45
+
46
+ if converter.respond_to?(:config) && block
47
+ converter.config(&block)
48
+ end
49
+
50
+ set_namespace_converter namespace, converter if converter
51
+ self
52
+ end
53
+ alias_method :on, :on_namespace
54
+ alias_method :namespace, :on_namespace
55
+
56
+ def on_default_namespace(*args, &block)
57
+ on_namespace(DEFAULT_NAMESPACE, *args, &block)
58
+ end
59
+ alias_method :default_namespace, :on_default_namespace
60
+
61
+ def run(colon, namespace, path, name, current_page)
62
+ if converter = namespace_converter(namespace)
63
+ converter.run(colon, path, name, current_page)
64
+ elsif converter = instance_method_converter(namespace)
65
+ converter.call(colon, path, name, current_page)
66
+ end
67
+ end
68
+
69
+ protected
70
+ def html_class
71
+ [options[:class], ('external' if options[:external])]
72
+ end
73
+
74
+ private
75
+ def namespace_converter(namespace)
76
+ namespace = namespace.to_s.downcase
77
+ @namespace_converters[namespace]
78
+ end
79
+
80
+ def instance_method_converter(namespace)
81
+ namespace = namespace.to_s.downcase
82
+ try_message = "on_namespace_#{namespace}".to_sym
83
+ method(try_message) if respond_to?(try_message)
84
+ end
85
+
86
+ def set_namespace_converter(namespace, converter)
87
+ @namespace_converters[namespace] = converter
88
+ end
89
+
90
+ def default_options_for_namespace
91
+ opts = @options.dup
92
+ opts.delete :name
93
+ opts[:site_name] = @options[:name]
94
+
95
+ opts
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,43 @@
1
+ require 'wikilink/converter/site'
2
+ require 'wikilink/converter/utils'
3
+
4
+ module Wikilink
5
+ class Converter
6
+ module Sites
7
+ class RubyChina < Wikilink::Converter::Site
8
+ include Wikilink::Converter::LinkHelper
9
+
10
+ def initialize(options = {})
11
+ if options[:name] == CURRENT_SITE
12
+ options[:domain] ||= '/'
13
+ else
14
+ options[:domain] ||= 'http://ruby-china.org/'
15
+ end
16
+ options[:prefix] = "#{options[:domain]}wiki/"
17
+
18
+ super(options)
19
+ end
20
+
21
+ def on_namespace_topic(colon, path, name, current_page)
22
+ path = "#{options[:domain]}topics/#{path}"
23
+ link_to name, path, :class => html_class
24
+ end
25
+
26
+ def on_namespace_node(colon, path, name, current_page)
27
+ path = "#{options[:domain]}topics/node#{path}"
28
+ link_to name, path, :class => html_class
29
+ end
30
+ end
31
+
32
+ class RubyTaiwan < RubyChina
33
+ def initialize(options = {})
34
+ if options[:name] != CURRENT_SITE
35
+ options[:domain] ||= 'http://ruby-taiwan.org/'
36
+ end
37
+
38
+ super(options)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,20 @@
1
+ require 'wikilink/converter/site'
2
+
3
+ module Wikilink
4
+ class Converter
5
+ module Sites
6
+ class Wikipedia < Wikilink::Converter::Site
7
+ def initialize(options = {})
8
+ options[:lang] ||= 'en'
9
+ if options[:name] == CURRENT_SITE
10
+ options[:prefix] ||= '/wiki/'
11
+ else
12
+ options[:prefix] ||= "http://#{options[:lang]}.wikipedia.org/wiki/"
13
+ end
14
+
15
+ super(options)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,37 @@
1
+ require 'cgi'
2
+
3
+ module Wikilink
4
+ class Converter
5
+ module ArgumentExtractor
6
+ def extract_arguments(*args)
7
+ options = {}
8
+ options = args.pop if args.last.is_a?(Hash)
9
+ name = args.shift
10
+
11
+ if name.nil? || name.is_a?(String) || name.is_a?(Symbol)
12
+ name = name.to_s
13
+ converter = args.shift
14
+ else
15
+ converter = name
16
+ name = nil
17
+ end
18
+
19
+ throw ArgumentError, "too many arguments" unless args.empty?
20
+
21
+ [name, converter, options]
22
+ end
23
+ end
24
+
25
+ module LinkHelper
26
+ def link_to(name, url, attributes = {})
27
+ attributes[:class] = Array(attributes[:class]).flatten.join(' ').split.uniq.join(' ')
28
+ attributes.delete(:class) if attributes[:class].empty?
29
+ attributes = attributes.inject('') do |memo, (key, value)|
30
+ memo + key.to_s + '="' + CGI.escape_html(value) + '" '
31
+ end
32
+
33
+ "<a #{attributes}href=\"#{CGI.escape_html url}\">#{CGI.escape_html name}</a>"
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,173 @@
1
+ require 'forwardable'
2
+ require 'wikilink/converter/site'
3
+ require 'wikilink/converter/utils'
4
+
5
+ module Wikilink
6
+ # Convert `[[Wikilink]]` to HTML
7
+ #
8
+ # The real work is handed over to registered handlers through {#on}.
9
+ #
10
+ # The parsing rules
11
+ # -----------------
12
+ #
13
+ # - `[[Wikilink]]` should be in one line, otherwise it is ignored.
14
+ # - `\[[Wikilink]]` is escaped and converted to `[[Wikilink]]`.
15
+ # - `[[action:arg]]` triggers handler registered on **action**. **arg** is passed as
16
+ # first argument to the handler. **arg** can contains colons.
17
+ # - `[[:action:arg]]` is the same with `[[action:arg]]`, except that `true` is
18
+ # passed as the second argument to handler to indicate a prefix colon
19
+ # exists. It is useful for resources like image, where no colon version
20
+ # inserts the image and colon version inserts the link.
21
+ # - `[[Wikilink]]` is identical with `[[page:Wikilink]]`, i.e., the default
22
+ # action is **page**.
23
+ class Converter
24
+ extend Forwardable
25
+ include ArgumentExtractor
26
+ CURRENT_SITE = ::Wikilink::Converter::Site::CURRENT_SITE_NAME
27
+
28
+ # Setup a converter. Handlers can be registered in block directly. If no
29
+ # handler is registered on **page**, a default handler
30
+ # Wikilink::Converter::Page is created with the given `options`.
31
+ #
32
+ # @param [Hash] options options for Wikilink::Converter::Page
33
+ def initialize(options = {})
34
+ @site_converts = {}
35
+ @action_handlers = {}
36
+ @options = options
37
+
38
+ on_site(CURRENT_SITE, @options)
39
+ yield self if block_given?
40
+ end
41
+
42
+ def run(text, current_page = nil)
43
+ text.gsub(/(^|.)\[\[(.*?[^:])\]\]/) do |match|
44
+ prefix, inner = $1, $2.strip
45
+ if prefix == '\\'
46
+ match[1..-1]
47
+ else
48
+ if inner.start_with?(':')
49
+ colon = ':'
50
+ inner = inner[1..-1]
51
+ end
52
+ link, name = inner.split('|', 2)
53
+ path, namespace, site = link.split(':', 3).reverse
54
+
55
+ if site.to_s.empty? && !namespace.to_s.empty?
56
+ # if namespace is a valid site name, use it as site
57
+ if site_converter(namespace)
58
+ site = namespace
59
+ namespace = nil
60
+ end
61
+ end
62
+
63
+ if name.to_s.empty?
64
+ name = resolve_name(inner, current_page)
65
+ end
66
+
67
+ # ignore malformed wikilink
68
+ if valid?(site, namespace, path)
69
+ result = convert_link(colon, site, namespace, path, name, current_page)
70
+ result ? ($1 + result) : match
71
+ else
72
+ match
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ def execute(text)
79
+ text.gsub(/(^|.)\{\{(.*?[^:])\}\}/) do |match|
80
+ prefix, inner = $1, $2.strip
81
+ if prefix == '\\'
82
+ match[1..-1]
83
+ else
84
+ action, arg = inner.split(':', 2)
85
+ result = convert_action(action, arg)
86
+ result ? ($1 + result) : match
87
+ end
88
+ end
89
+ end
90
+
91
+ def_delegator :@current_site_converter, :on_namespace
92
+ alias_method :on, :on_namespace
93
+ alias_method :namespace, :on_namespace
94
+
95
+ def_delegator :@current_site_converter, :on_default_namespace
96
+ alias_method :default_namespace, :on_default_namespace
97
+
98
+ def on_site(*args)
99
+ site, converter, options = extract_arguments(*args)
100
+ options = @options.merge(options)
101
+ site = CURRENT_SITE if site.to_s.empty?
102
+
103
+ converter ||= site_converter(site) || Wikilink::Converter::Site
104
+ if converter.is_a?(Class)
105
+ options[:name] ||= site
106
+ converter = converter.new(options)
107
+ end
108
+
109
+ yield converter if block_given?
110
+
111
+ set_site_converter site, converter
112
+ self
113
+ end
114
+ alias_method :site, :on_site
115
+
116
+ def on_current_site(*args, &block)
117
+ on_site(CURRENT_SITE, *args, &block)
118
+ end
119
+ alias_method :current_site, :on_current_site
120
+
121
+ def on_action(action, &block)
122
+ @action_handlers[action.to_s.downcase] = block
123
+ self
124
+ end
125
+ alias_method :action, :on_action
126
+
127
+
128
+ private
129
+ def site_converter(site)
130
+ site = site.to_s.downcase
131
+ site == CURRENT_SITE ? @current_site_converter : @site_converts[site]
132
+ end
133
+
134
+ def set_site_converter(site, converter)
135
+ site = site.to_s.downcase
136
+ if site == CURRENT_SITE
137
+ @current_site_converter = converter
138
+ else
139
+ @site_converts[site] = converter
140
+ end
141
+ end
142
+
143
+ def convert_action(action, argument)
144
+ handler = @action_handlers[action.to_s.downcase]
145
+ handler.call(argument) if handler
146
+ end
147
+
148
+ def convert_link(colon, site, namespace, path, name, current_page)
149
+ converter = site_converter(site)
150
+ converter.run(colon, namespace, path, name, current_page) if converter
151
+ end
152
+
153
+ # TODO: relative
154
+ # TODO: ruby (computer) -> ruby
155
+ # TODO: Shanghai, China -> Shanghai
156
+ def resolve_name(inner_text, current_path)
157
+ if inner_text.end_with?('|')
158
+ inner_text.chop.chomp('/').split(%r{[:/]}, 2).last
159
+ else
160
+ inner_text
161
+ end
162
+ end
163
+
164
+ INVALID_NAME_REGEXP = /[^[[:alnum:]][[:blank]]_-]/
165
+ INVALID_PATH_REGEXP = /^[\[\]]/
166
+ def valid?(site, namespace, path)
167
+ return false if site =~ INVALID_NAME_REGEXP
168
+ return false if namespace =~ INVALID_NAME_REGEXP
169
+ return false if path =~ INVALID_PATH_REGEXP
170
+ return true
171
+ end
172
+ end
173
+ end
@@ -0,0 +1 @@
1
+ require 'wikilink/converter'
@@ -0,0 +1,19 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ if ENV['COVERAGE']
4
+ require 'simplecov'
5
+ SimpleCov.start do
6
+ add_filter "/spec/"
7
+ end
8
+ end
9
+
10
+ require 'rspec'
11
+ require 'wikilink-converter'
12
+
13
+ # Requires supporting files with custom matchers and macros, etc,
14
+ # in ./support/ and its subdirectories.
15
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
16
+
17
+ RSpec.configure do |config|
18
+
19
+ end