wikilink-converter 0.1.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/.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