raconteur 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.
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: []