raconteur 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c64b155fb75f4fa4e92ca6d4c0109c7bf71905f0
4
+ data.tar.gz: 668db905ee4fbc62b71df3a73dcbf46ce2d4b21f
5
+ SHA512:
6
+ metadata.gz: 336e2825fd8658bbf8a7dcc493b7594527f49ce1346f921f8a669ac1ee45c565e91bea6a623e01a00464e67038b92169b2eaad327f4fcd7cc7e00ea68bd2f5b5
7
+ data.tar.gz: 252382f235f204f32d1a4f5f1fd63113a7ccfeb3a6705261708c5a35c1fadbcb627253206c90929d22ccd0e22b4443724bac7ffc8e5309f36671394fef8ec50d
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.3
4
+ before_install: gem install bundler -v 1.10.6
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in raconteur.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,42 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ ## Uncomment and set this to only include directories you want to watch
5
+ # directories %w(app lib config test spec features) \
6
+ # .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
7
+
8
+ ## Note: if you are using the `directories` clause above and you are not
9
+ ## watching the project directory ('.'), then you will want to move
10
+ ## the Guardfile to a watched dir and symlink it back, e.g.
11
+ #
12
+ # $ mkdir config
13
+ # $ mv Guardfile config/
14
+ # $ ln -s config/Guardfile .
15
+ #
16
+ # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
17
+
18
+ guard :minitest do
19
+ # with Minitest::Unit
20
+ watch(%r{^test/(.*)\/?(.*)_test\.rb$})
21
+ watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| "test/#{m[1]}test_#{m[2]}.rb" }
22
+ watch(%r{^test/test_helper\.rb$}) { 'test' }
23
+
24
+ # with Minitest::Spec
25
+ # watch(%r{^spec/(.*)_spec\.rb$})
26
+ # watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
27
+ # watch(%r{^spec/spec_helper\.rb$}) { 'spec' }
28
+
29
+ # Rails 4
30
+ # watch(%r{^app/(.+)\.rb$}) { |m| "test/#{m[1]}_test.rb" }
31
+ # watch(%r{^app/controllers/application_controller\.rb$}) { 'test/controllers' }
32
+ # watch(%r{^app/controllers/(.+)_controller\.rb$}) { |m| "test/integration/#{m[1]}_test.rb" }
33
+ # watch(%r{^app/views/(.+)_mailer/.+}) { |m| "test/mailers/#{m[1]}_mailer_test.rb" }
34
+ # watch(%r{^lib/(.+)\.rb$}) { |m| "test/lib/#{m[1]}_test.rb" }
35
+ # watch(%r{^test/.+_test\.rb$})
36
+ # watch(%r{^test/test_helper\.rb$}) { 'test' }
37
+
38
+ # Rails < 4
39
+ # watch(%r{^app/controllers/(.*)\.rb$}) { |m| "test/functional/#{m[1]}_test.rb" }
40
+ # watch(%r{^app/helpers/(.*)\.rb$}) { |m| "test/helpers/#{m[1]}_test.rb" }
41
+ # watch(%r{^app/models/(.*)\.rb$}) { |m| "test/unit/#{m[1]}_test.rb" }
42
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Jamie Appleseed
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,94 @@
1
+ # Raconteur
2
+
3
+ With Raconteur, you can define custom text tags and have them parsed according to your specifications, allowing you to do some neat pre- and post-processing of your texts. You could for instance insert dynamic content fetched from your database whenever {{ customer-quote: id=43 }} appears in your text.
4
+
5
+ ## Installation
6
+
7
+ Raconteur is a Ruby gem so install like any other gem. For example, if you're using Bundler, then add this to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'raconteur'
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ Raconteur is based around the notion of processors. Each processor has a tag-name and a set of instructions for how it should perform the content replacement and what it should insert.
16
+
17
+ ```ruby
18
+ @raconteur = Raconteur.new
19
+ @raconteur.processors.register!('customer-quote', {
20
+ template: '<div class="quote" id="customer-quote-{{ id }}">\'{{ text }}\' - {{ author }}</div>',
21
+ handler: lambda do |settings|
22
+ quote = CustomerQuote.find(settings[:id])
23
+ { author: quote.author_name, text: quote.citation, id: quote.id }
24
+ end
25
+ })
26
+ @raconteur.parse("Here's what some of our customers are saying about the product:\n{{ customer-quote: id=43 }}\n{{ customer-quote: id=266 }}\n{{ customer-quote: id=7 }}")
27
+ # -- outputs --
28
+ # Here's what some of our customers are saying:
29
+ # <div class="quote" id="customer-quote-43">'The most amazing usability report I have ever read!' - James Newman</div>
30
+ # <div class="quote" id="customer-quote-266">'I cannot believe how incredible the benchmark database is!' - Jane Newton</div>
31
+ # <div class="quote" id="customer-quote-7">'Spetacular customer service.' - John Oldling</div>
32
+ ```
33
+
34
+ Here's what's happening in the above example:
35
+
36
+ - We first create a new instance of Raconteur. (Tip: You can create multiple Raconteur instances, each with its own settings and set of processors.)
37
+ - We then register a processor with the tag-name 'customer-quote'. This means that any instances of {{ customer-quote }} in the parsed text will be replaced according to the settings of this processor.
38
+ - The processor has a <code>template</code> and a <code>handler</code>. The <code>template</code> is a simple string with {{ variables }} that are replaced with dynamic content. The <code>handler</code> loads the customer quotes from our database based on the id that was passed in as a setting in the customer-quote tags (i.e. {{ customer-quote: id=43 }} invokes the handler with a settings hash of { id: "43" }).
39
+ - Finally, we call the <code>parse</code> function on our Raconteur instance, which runs over the text and replaces all instances that match its registered processors according to their specifications.
40
+
41
+ Processors must always have a tag-name name along with either a template defined, a handler defined, or both. If a handler returns a hash (like in the above example), it should have a template defined as well – Raconteur will take the returned hash and use its keys as replacement variables for the template (again, like seen in the example above). Handlers may also return a string, in which Raconteur will use this as the replacement, allowing you full control over the replaced text. If there's only a template defined, Raconteur will simply pass in any inputted tag settings as variables and then replace the tag with that template.
42
+
43
+ Processors may also be used for wrapping tags, which is a great way to encapsulate sections in your text. Let's look at an example:
44
+
45
+ ```ruby
46
+ @raconteur = Raconteur.new
47
+ @raconteur.processors.register!('gallery', {
48
+ template: '<div class="aside"><p class="box-label">Aside:</p>{{ _yield_ }}</div>'
49
+ })
50
+ @raconteur.processors.register!('image', {
51
+ handler: lambda do |settings|
52
+ image = MediaLibrary.find(settings[:id]).image
53
+ "<div class=\"graphic\"><img src=\"#{image.url}\" /><p class=\"caption\">#{image.caption}</p></div>"
54
+ end
55
+ })
56
+ @raconteur.parse("Some paragraph text.\n\n{{ image: id=43 }}\n\nAnother paragraph.\n\n{{% aside %}}\n\nAdditional tangentially-related text.\n\n{{ image: id=125 }}\n\nWe're really getting off-topic here.\n\n{{% end %}}\n\nOk, back to regular text.")
57
+ # -- outputs --
58
+ # Some paragraph text.
59
+ #
60
+ # <div class="graphic"><img src="http://some-url.com/some-path-for-image-43.jpg" /><p class="caption">A captivating caption text for the image.</p></div>
61
+ #
62
+ # Another paragraph.
63
+ #
64
+ # <div class="aside"><p class="box-label">Aside:</p>
65
+ #
66
+ # Additional tangentially-related text.
67
+ #
68
+ # <div class="graphic"><img src="http://some-url.com/some-path-for-image-125.jpg" /><p class="caption">Another fascinating caption for another incredible image, but this time wrapped within an aside!</p></div>
69
+ #
70
+ # We're really getting off-topic here.
71
+ #
72
+ # </div>
73
+ #
74
+ # Ok, back to regular text.
75
+ ```
76
+
77
+ Wrappers are registered like all other processors but are invoked a lille differently in the text by having percentage symbols added to their curly braces and needing an {{% end %}} tag to signify when they should end. Wrapper templates work the same except they have a special {{ _yield_ }} variable passed into them, which holds the contents of the wrapped content.
78
+
79
+ In the above example, you'll notice that regular tags (in this case {{ image }}) can be used within wrappers. Wrappers may also be nested within each other. Any tags (both wrappers and regular blocks) nested within a wrapper will have a special _scope_ variable passed to their <code>handler</code> method, allowing you to customize the behavior of a tag based on its surrounding context. For instance, you could register an {{ image }} tag which renders differently depending on whether it is placed within a {{% gallery %}} wrapper or not.
80
+
81
+ Let's take a look at tags:
82
+
83
+ - Tags are wrapper by two curly braces and their name should be a string without any white-space <code>{{ like-this }}</code>.
84
+ - Tags don't need any settings. Something simple like <code>{{ page-count }}</code> will work perfectly fine.
85
+ - If you do want to pass in settings to a tag, the tag-name should be followed by a colon and a set of key-value pairs. The key must not include any white-space characters and will be converted to an underscored symbol for the settings hash. The separator should be an equals '=' symbol. The value may either be a word without any white-space characters, or it can be "a text wrapped by quotes". <code>{{ definition: term=UXD + description="User Experience Design (aka UXD + UED + XD) refers to the ..." }}</code>
86
+ - To escape quotes within a quoted settings value, put a backslash in front of the quote. You may alternatively configure Raconteur to use a different symbol for wrapping the text. Both non-white-space values and quoted values may be used in the same tag (as seen in the above "User Experience Design" example where the <code>term</code> isn't quoted but the <code>description</code> is). The key-value pairs in settings don't need to be separated by anything other than a white-space character but it can greatly help readability to include some character (such as a '+' symbol, as seen in the above "User Experience Design" example).
87
+
88
+ ## Contributing
89
+
90
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/raconteur.
91
+
92
+ ## License
93
+
94
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task default: :test
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "raconteur"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
data/lib/raconteur.rb ADDED
@@ -0,0 +1,49 @@
1
+ class Raconteur
2
+ DEFAULTS = {
3
+ processors: [],
4
+ settings: {
5
+ closing_tag: 'end',
6
+ setting_quotes: '"'
7
+ }
8
+ }
9
+ ORIGINAL_DEFAULTS = Marshal.load(Marshal.dump(DEFAULTS)).freeze
10
+ ATTRS = DEFAULTS.keys.freeze
11
+
12
+ # Bootstrap attributes
13
+ def initialize(customizations={})
14
+ @data = Marshal.load(Marshal.dump(DEFAULTS))
15
+ @raconteur = self
16
+ end
17
+
18
+ # Parse the inputted text with the registered processors
19
+ def parse(text="", scope=nil)
20
+ Raconteur::Parse.scoped self, text, scope
21
+ end
22
+
23
+ # Prettier print
24
+ def inspect
25
+ "#<Raconteur:0x#{object_id} #{ATTRS.map { |att| "@#{att}=#{send(att).inspect}" }.join(', ')}>"
26
+ end
27
+
28
+ # Accessing settings and processors
29
+ def settings
30
+ Raconteur::Setting.scoped self
31
+ end
32
+ def processors
33
+ Raconteur::Processor.scoped self
34
+ end
35
+
36
+
37
+ private
38
+
39
+ def data
40
+ @data
41
+ end
42
+
43
+ end
44
+
45
+ require "raconteur/version"
46
+ require "raconteur/config"
47
+ require "raconteur/processor"
48
+ require "raconteur/setting"
49
+ require "raconteur/parse"
@@ -0,0 +1,27 @@
1
+ class Raconteur::Config
2
+
3
+ # return current default settings for Raconteur
4
+ def self.default_settings
5
+ Raconteur::DEFAULTS[:settings]
6
+ end
7
+ # revert default settings for Raconteur to original defaults
8
+ def self.revert_to_original_defaults!
9
+ Raconteur::DEFAULTS[:settings] = original_default_settings
10
+ end
11
+ # return a copy of the original default settings
12
+ def self.original_default_settings
13
+ Marshal.load(Marshal.dump(Raconteur::ORIGINAL_DEFAULTS[:settings]))
14
+ end
15
+ # Let user override defaults for Raconteur
16
+ class << self
17
+ Raconteur::Config.default_settings.keys.each do |att|
18
+ define_method(att) do
19
+ self.default_settings[att]
20
+ end
21
+ define_method("#{att}=") do |val|
22
+ self.default_settings[att] = val
23
+ end
24
+ end
25
+ end
26
+
27
+ end
@@ -0,0 +1,111 @@
1
+ class Raconteur::Parse
2
+
3
+ def self.scoped(raconteur, document, scope)
4
+ @@raconteur = raconteur
5
+ parse_blocks(parse_wrappers(document, scope), scope)
6
+ end
7
+
8
+ def self.parse_wrappers(document, scope)
9
+ output = document
10
+ regex = /{{%(.*?)%}}/m
11
+ # scan for all instances of wrappers, i.e. {{% tag-name %}}
12
+ wrappers = output.scan(regex).flatten
13
+ unless wrappers.empty?
14
+ # if wrappers, loop over all opening tags in reverse order (this way the inner-most wrapper is always processed before its parent wrappers)
15
+ wrappers.map(&:strip).reject { |str| str == @@raconteur.settings.closing_tag }.reverse.each do |open_tag|
16
+ # loop over each registered processor
17
+ @@raconteur.processors.each do |processor|
18
+ # check if the processor matches the tag
19
+ match = open_tag.match(processor.regex)
20
+ if match
21
+ # if there is a match, parse the tag's settings, if it has any
22
+ match_settings = parse_settings(match[:settings])
23
+ # merge in the nested wrapper scope, if present
24
+ match_settings.merge!(_scope_: scope) if scope
25
+ # identify the tag and its contents
26
+ regex = /.*(?<tag>{{%\s*#{Regexp.quote(open_tag)}\s*%}}(?<content>.*?){{%\s*#{Regexp.quote(@@raconteur.settings.closing_tag)}\s*%}})/m
27
+ wrapper_match = output.match(regex)
28
+ if wrapper_match
29
+ # if there is a match, run its contents through raconteur with the wrapper set as scope, to perform all necessary replacements (this allows nesting and wrapper customizations)
30
+ content = @@raconteur.parse(wrapper_match[:content], {
31
+ tag: open_tag,
32
+ processor: processor,
33
+ settings: match_settings
34
+ })
35
+ # set _yield_ variable with the (parsed) inner contents of wrapper tag
36
+ match_settings.merge!(_yield_: content)
37
+ # execute processor and replace output with result
38
+ content = processor.execute(content, match_settings)
39
+ # replace wrapper and its contents with the parsed content
40
+ output = output.sub(wrapper_match[:tag], content)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ output
47
+ end
48
+
49
+ def self.parse_blocks(output, scope)
50
+ # replace all instances of {{ some-tag }}
51
+ output.gsub(/{{(.*?)}}/m) do |raw_str|
52
+ # the matched string if no processing occurs
53
+ output = raw_str
54
+ # clean the matched string to only get the tag and its settings
55
+ str = output.gsub(/^\{\{\%?\s*|\s*\%?\}\}$/, '')
56
+ # loop over each registered processor
57
+ @@raconteur.processors.each do |processor|
58
+ # check if the processor matches the tag
59
+ match = str.match(processor.regex)
60
+ if match
61
+ # parse the tag's settings, if any
62
+ match_settings = self.parse_settings(match[:settings])
63
+ # merge in the nested wrapper scope, if present
64
+ match_settings.merge!(_scope_: scope) if scope
65
+ # execute processor and replace output with result
66
+ output = processor.execute(output, match_settings)
67
+ end
68
+ end
69
+ output
70
+ end
71
+ end
72
+
73
+ # input: (string) 'id=353 + report-title="E-Commerce Checkout Usability Report"'
74
+ # output: (hash) { id: '353', report_title: 'E-Commerce Checkout Usability Report' }
75
+ def self.parse_settings(str)
76
+ # regex escape 'quote' character
77
+ quote = Regexp.quote(@@raconteur.settings.setting_quotes)
78
+ # Parsing logic:
79
+ # First, one or more non-white-space characters,
80
+ # .. followed by an equal sign '=' character,
81
+ # .. followed by either:
82
+ # a) 1+ non-white-space characters, or
83
+ # b) a string wrapped by the 'quote' character at both ends
84
+ # (instances of the 'quote' character within the string can be escaped by a backward-slash '\')
85
+ regex = /([^\s]+)\=(#{quote}.*?[^\\]#{quote}|[^\s]+)/mi
86
+ # prepare a fresh hash for the parsed settings
87
+ parsed_settings = {}
88
+ # loop over all key-value setting pairs in the string
89
+ str.scan(regex).each do |setting_str|
90
+ # grap keys and turn them into underscored symbols
91
+ key = setting_str[0].strip.gsub('-','_').to_sym
92
+ # strip values from whitespace and surrounding quote characters
93
+ value = setting_str[1].strip.gsub(/^#{quote}|#{quote}$/mi, '').gsub(/\\#{quote}/mi, quote)
94
+ # add to settings hash
95
+ parsed_settings[key] = value
96
+ end
97
+ # return the parsed settings (transformed from string to hash)
98
+ parsed_settings
99
+ end
100
+
101
+ def self.render_template(template, data)
102
+ output = template
103
+ # loop over data hash (for value substitution)
104
+ data.each do |tag, value|
105
+ # replace dynamic values in the template
106
+ output = output.gsub(/{{\s*#{tag}\s*}}/, value.to_s)
107
+ end
108
+ output
109
+ end
110
+
111
+ end
@@ -0,0 +1,118 @@
1
+ class Raconteur::Processor
2
+ DEFAULTS = {
3
+ tag: nil,
4
+ template: nil,
5
+ handler: nil,
6
+ settings: {}
7
+ }.freeze
8
+ ATTRS = DEFAULTS.keys.freeze
9
+
10
+ # register new processor by providing a tag name + any settings (optional)
11
+ def self.register!(tag, customizations={})
12
+ if find(tag)
13
+ raise 'Processor already exists!'
14
+ else
15
+ all << Raconteur::Processor.new(tag, customizations)
16
+ end
17
+ self
18
+ end
19
+
20
+ # delete existing processor by tag name
21
+ def self.deregister!(tag)
22
+ all.delete(find(tag))
23
+ self
24
+ end
25
+
26
+ # update existing processor by providing its tag name and passing in any customizations (optional)
27
+ def self.update!(tag, customizations={})
28
+ deregister!(tag)
29
+ register!(tag, customizations)
30
+ self
31
+ end
32
+
33
+ # scoped
34
+ def self.scoped(raconteur)
35
+ @@raconteur = raconteur
36
+ self
37
+ end
38
+
39
+ # return array of all processors
40
+ def self.all
41
+ @@raconteur.send(:data)[:processors]
42
+ end
43
+
44
+ # find processor by tag name
45
+ def self.find(tag)
46
+ all.detect { |processor| processor.tag == tag }
47
+ end
48
+
49
+ # print array
50
+ def self.inspect
51
+ "#{all} (Raconteur::Processor array)"
52
+ end
53
+
54
+ # treat class as array
55
+ def self.method_missing(method_sym, *arguments, &block)
56
+ if !arguments.empty? && block_given?
57
+ all.send(method_sym, *arguments, &block)
58
+ elsif !arguments.empty?
59
+ all.send(method_sym, *arguments)
60
+ elsif block_given?
61
+ all.send(method_sym, &block)
62
+ else
63
+ all.send(method_sym)
64
+ end
65
+ end
66
+
67
+
68
+ # -- Instances --
69
+
70
+ # new processor
71
+ def initialize(tag, customizations={})
72
+ @data = Marshal.load(Marshal.dump(DEFAULTS))
73
+ @processor = self
74
+ @processor.tag = tag
75
+ @processor.template = customizations[:template] if customizations[:template].is_a?(String)
76
+ @processor.handler = customizations[:handler] if customizations[:handler].is_a?(Proc)
77
+ @processor
78
+ end
79
+
80
+ # prettier print
81
+ def inspect
82
+ "#<Raconteur::Processor:0x#{object_id} #{ATTRS.map { |att| "@#{att}=#{@processor.send(att)}" }.join(', ')}>"
83
+ end
84
+
85
+ # piecemeal access and manipulation of processor attributes
86
+ ATTRS.each do |att|
87
+ define_method(att) do
88
+ @data[att]
89
+ end
90
+ define_method("#{att}=") do |val|
91
+ @data[att] = val
92
+ end
93
+ end
94
+
95
+ # regex for matching tag and its settings
96
+ def regex
97
+ /^\s*#{Regexp.quote(@processor.tag)}:?(?<settings>.*?)?\s*$/im
98
+ end
99
+
100
+ # execute the processor
101
+ def execute(content="", settings={})
102
+ output = content
103
+ if self.handler
104
+ # if the processor has a custom handler, then pass everything to it for processing
105
+ output = self.handler.call(settings)
106
+ if output.is_a?(Hash) && self.template
107
+ # if the handler returns a hash and has a template, then pass the hash as options to the template
108
+ # (this allows for custom variable setting and overriding before the template is rendered)
109
+ output = Raconteur::Parse.render_template(self.template, output)
110
+ end
111
+ elsif self.template
112
+ # if there's no handler but there is a processor, simply render the template with the tag's settings
113
+ output = Raconteur::Parse.render_template(self.template, settings)
114
+ end
115
+ output
116
+ end
117
+
118
+ end
@@ -0,0 +1,54 @@
1
+ class Raconteur::Setting
2
+
3
+ # scoped
4
+ def self.scoped(raconteur)
5
+ @@raconteur = raconteur
6
+ self
7
+ end
8
+
9
+ # return hash of all settings
10
+ def self.all
11
+ @@raconteur.send(:data)[:settings]
12
+ end
13
+
14
+ # revert the settings of this Raconteur instance to the current default settings for Raconteur
15
+ def self.revert_to_defaults!
16
+ self.all.delete_if { true }.merge!(Marshal.load(Marshal.dump(Raconteur::Config.default_settings)))
17
+ self
18
+ end
19
+
20
+ # revert the settings of this Raconteur instance to the original default settings of Raconteur
21
+ def self.revert_to_original_defaults!
22
+ self.all.delete_if { true }.merge!(Marshal.load(Marshal.dump(Raconteur::Config.original_default_settings)))
23
+ self
24
+ end
25
+
26
+ # piecemeal access and manipulation of settings
27
+ Raconteur::Config.default_settings.keys.each do |att|
28
+ define_singleton_method(att) do
29
+ all[att]
30
+ end
31
+ define_singleton_method("#{att}=") do |val|
32
+ all[att] = val
33
+ end
34
+ end
35
+
36
+ # print hash
37
+ def self.inspect
38
+ "#{all} (Raconteur::Setting hash)"
39
+ end
40
+
41
+ # treat class as hash
42
+ def self.method_missing(method_sym, *arguments, &block)
43
+ if !arguments.empty? && block_given?
44
+ all.send(method_sym, *arguments, &block)
45
+ elsif !arguments.empty?
46
+ all.send(method_sym, *arguments)
47
+ elsif block_given?
48
+ all.send(method_sym, &block)
49
+ else
50
+ all.send(method_sym)
51
+ end
52
+ end
53
+
54
+ end
@@ -0,0 +1,3 @@
1
+ class Raconteur
2
+ VERSION = "0.1.0"
3
+ end
data/raconteur.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'raconteur/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "raconteur"
8
+ spec.version = Raconteur::VERSION
9
+ spec.authors = ["Jamie Appleseed"]
10
+ spec.email = ["jamieappleseed@gmail.com"]
11
+
12
+ spec.summary = %q{Custom text tag parsing}
13
+ spec.description = %q{Define custom text tags and have Raconteur parse them as per your specifications}
14
+ spec.homepage = "https://github.com/JamieAppleseed/raconteur"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.10"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency "byebug"
25
+ spec.add_development_dependency "minitest"
26
+ spec.add_development_dependency "guard"
27
+ spec.add_development_dependency "guard-minitest"
28
+ spec.add_development_dependency "kramdown"
29
+
30
+ end
metadata ADDED
@@ -0,0 +1,158 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: raconteur
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jamie Appleseed
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-11-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.10'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.10'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: byebug
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: guard
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: guard-minitest
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: kramdown
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: Define custom text tags and have Raconteur parse them as per your specifications
112
+ email:
113
+ - jamieappleseed@gmail.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".gitignore"
119
+ - ".travis.yml"
120
+ - Gemfile
121
+ - Guardfile
122
+ - LICENSE.txt
123
+ - README.md
124
+ - Rakefile
125
+ - bin/console
126
+ - bin/setup
127
+ - lib/raconteur.rb
128
+ - lib/raconteur/config.rb
129
+ - lib/raconteur/parse.rb
130
+ - lib/raconteur/processor.rb
131
+ - lib/raconteur/setting.rb
132
+ - lib/raconteur/version.rb
133
+ - raconteur.gemspec
134
+ homepage: https://github.com/JamieAppleseed/raconteur
135
+ licenses:
136
+ - MIT
137
+ metadata: {}
138
+ post_install_message:
139
+ rdoc_options: []
140
+ require_paths:
141
+ - lib
142
+ required_ruby_version: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ required_rubygems_version: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ requirements: []
153
+ rubyforge_project:
154
+ rubygems_version: 2.4.5.1
155
+ signing_key:
156
+ specification_version: 4
157
+ summary: Custom text tag parsing
158
+ test_files: []