snp 0.1

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: 49d5d8be74b2f7914fd8a8eabe8e3b886c70d843
4
+ data.tar.gz: 7fc0270b98da93a2ff118206b53d69828fa33c02
5
+ SHA512:
6
+ metadata.gz: 6b13774f35b7f26b96be00454897bdc9954e0896a775fdae12b6f9f53b81d5f2ec38cdb1f2ed318de677fb0f5e92b76ddea5d1fc5ecd11eb20d56ed8f3b08302
7
+ data.tar.gz: 35dafdd03b76034b47215c682fb5a4e540752c379318026d3fe58b58c0779af0cb82e8937cf6f18f4ff29e8d2263273b61ce8aebfa20aad52dfbaed5c95014d9
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2014 Renato Mascarenhas http://renatomascarenhas.name/
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.md ADDED
@@ -0,0 +1,104 @@
1
+ # snp - easy snippets for everyone
2
+
3
+ `snp` is a tool to allow easy snippet creation, in an automated and reusable manner.
4
+ How many times did you create that small HTML document to test out if something worked
5
+ quite the way you thought it did? `snp`'s goal is to free the user from the creation
6
+ of all boilerplate involved in the creation of such snippets, allowing the programmer
7
+ to focus immediately on the feature that she wants to test.
8
+
9
+ ### Motivation
10
+
11
+ It is often in many situations that we wonder how something works in a language or
12
+ library. One of the best ways to find out the answer in such scenarios is to create
13
+ a minimum piece of code that can test the specific case we have in mind. However,
14
+ creating such files may involve some overhead and the creation of boilerplate code
15
+ to allow the testing. This can cause people to give up creating the snippet and instead
16
+ just look up on the Internet, which can take more time and you likely will not learn
17
+ as much. The goal of this project is, therefore, reduce the cost of testing and
18
+ learning on your own.
19
+
20
+ ### Example
21
+
22
+ `snp` is especially useful for quick tests that you want to perform in environments
23
+ in which a significant amount of boilerplate is involved. Suppose you frequently
24
+ test the expected effect of `jQuery` functions. You could create a snippet such as
25
+
26
+ ~~~erb
27
+ <%# file: ~/.snp/jquery_test.js.erb %>
28
+ <html>
29
+ <head><title><%= title %></title></head>
30
+
31
+ <body>
32
+
33
+ <script src="http://code.jquery.com/jquery-<%= jquery_version %>.min.js"></script>
34
+
35
+ <script>
36
+ (function() {
37
+ // code goes here
38
+ })();
39
+ </script>
40
+ </body>
41
+ </html>
42
+ ~~~
43
+
44
+ With that file set up once, whenever you want to perform a test of something related to
45
+ `jQuery` all you have to do is type:
46
+
47
+ ~~~console
48
+ $ snp --title callbacks --jquery-version 2.1.1 jquery_test.js
49
+ ~~~
50
+
51
+ Variable substitutions happen as you expect them to, and your favorite editor is fired up
52
+ with the snippet content, waiting for you to actually test what you wanted in the first place.
53
+
54
+ ### Rules of the game
55
+
56
+ * Snippet templates are by default placed under the `~/.snp` directory. You can
57
+ override that by defining the `SNP_PATH` environment variable, which accepts a
58
+ list of directories in pretty much the same way that the shell's `PATH` does.
59
+
60
+ * All snippet templates are ERB files and must have a name with the according
61
+ `.erb` extension.
62
+
63
+ * You do not have fill in every piece of dynamic content in the command line when
64
+ creating a new snippet from a template. They can have defaults and you can define
65
+ them by placing a yaml file with the same name as the template with the default
66
+ contents of each dynamic piece of content in the template. Note that arguments
67
+ passed to the command line override those definitions.
68
+
69
+ Example: suppose you have the following snippet named `introduction.txt.erb`:
70
+
71
+ ~~~erb
72
+ <%= greeting %>, my name is <%= name %>, and I'm from <%= country %>.
73
+ ~~~
74
+
75
+ You can then create a `introduction.txt.yml` file to define the default values for
76
+ the dynamic variables in the snippet above:
77
+
78
+ ~~~yaml
79
+ greeting: "Hello"
80
+ name: "Renato"
81
+ country: "Brazil"
82
+ ~~~
83
+
84
+ Now you can create snippets from that template using the default values using just:
85
+
86
+ ~~~console
87
+ $ snp introduction.txt
88
+ # => "Hello, my name is Renato, and I'm from Brazil."
89
+ ~~~
90
+
91
+ You can override specific values when creating a new snippet as well:
92
+
93
+ ~~~console
94
+ $ snp --name David --country UK
95
+ # => "Hello, my anme is David, and I'm from UK."
96
+ ~~~
97
+
98
+ ### Contributions/Bugs
99
+
100
+ Email me, or create an issue/pull request on the GitHub repository.
101
+
102
+ ### License
103
+
104
+ MIT. See `MIT-LICENSE` file for details.
data/lib/snp.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'snp/version'
2
+ require 'snp/path'
3
+ require 'snp/template'
4
+ require 'snp/template_context'
5
+ require 'snp/data'
6
+ require 'snp/compiler'
7
+
8
+ module Snp
9
+ end
data/lib/snp/cli.rb ADDED
@@ -0,0 +1,226 @@
1
+ require 'tempfile'
2
+ require 'slop'
3
+
4
+ module Snp
5
+ # Snp::Printer
6
+ #
7
+ # This class is responsible for outputing string in normal output and error streams.
8
+ # It defaults to `STDOUT` and `STDERR`, respectively but can be used to generate output
9
+ # for other streams.
10
+ #
11
+ # Example
12
+ #
13
+ # stream = Snp::Printer.new
14
+ # stream.out('Hello') # => 'Hello' is written to the standard output
15
+ # stream.err('ERROR!') # => 'ERROR!' is written to the standard error
16
+ class Printer
17
+ def initialize(out = STDOUT, err = STDERR)
18
+ @out = out
19
+ @err = err
20
+ end
21
+
22
+ def out(message)
23
+ @out.puts message
24
+ end
25
+
26
+ def err(message)
27
+ @err.puts message
28
+ end
29
+ end
30
+
31
+ # Snp::InvalidOptions
32
+ #
33
+ # This exception is raised when there is an error parsing command line options
34
+ # passed to `snp`.
35
+ class InvalidOptions < StandardError
36
+ def initialize(invalid_option)
37
+ super("Invalid option: #{invalid_option}")
38
+ end
39
+ end
40
+
41
+ # Snp::CLI
42
+ #
43
+ # This class is responsible for parsing command line options passed to `snp` and
44
+ # retrieve the template name to be compiled and possibly options to override data
45
+ # for the template.
46
+ #
47
+ # Example
48
+ #
49
+ # CLI.parse_options # => ['/Users/john/.snp/jquery.html.erb', { version: '1.9' }]
50
+ class CLI
51
+ # Public: extract template name and other data that should be used to compile the
52
+ # template.
53
+ #
54
+ # arguments - an array of arguments that should be parsed. Defaults to `ARGV`.
55
+ #
56
+ # This method generates the snippet and fires your text editor in case it is set up,
57
+ # or prints the snippet to the standard output.
58
+ def self.run(arguments = ARGV.dup)
59
+ new(arguments).start
60
+ end
61
+
62
+ attr_reader :printer, :template_name
63
+
64
+ # Internal: creates a new `Snp::CLI` instance.
65
+ #
66
+ # params - array of arguments.
67
+ # printer - the printer object through which feedback messages are sent to.
68
+ # The passed object must respond to `out` and `err` for normal
69
+ # and error situations, respectively.
70
+ def initialize(params, printer = Printer.new)
71
+ @params = params
72
+ @options = {}
73
+ @printer = printer
74
+ end
75
+
76
+ # Internal: actually does the parsing job and compiles the snippet.
77
+ def start
78
+ @template_name, template_data = parse
79
+
80
+ snippet = Compiler.build(template_name, template_data)
81
+
82
+ edit(snippet) || printer.out(snippet)
83
+ rescue => exception
84
+ printer.err exception.message
85
+ help_and_exit
86
+ end
87
+
88
+ # Internal: parses command line options.
89
+ #
90
+ # Returns the template name and extra options to be used when compiling
91
+ # the snippet, extracted from command line arguments.
92
+ def parse
93
+ help_and_exit if no_options_passed?
94
+
95
+ template_name = parse_static_options
96
+ template_data = parse_dynamic_options
97
+
98
+ [template_name, template_data]
99
+ end
100
+
101
+ private
102
+
103
+ # Internal: parses the command line options to check for static options
104
+ # (version number and help).
105
+ def parse_static_options
106
+ option_parser.parse!(@params)
107
+ @params.pop
108
+ end
109
+
110
+ # Internal: parses dynamic options, creating options according to the arguments
111
+ # passed on the command line.
112
+ #
113
+ # Example
114
+ #
115
+ # # command is '--project snp --language ruby template_name'
116
+ # parse_dynamic_options # => { 'project' => 'snp', 'language' => 'ruby' }
117
+ def parse_dynamic_options
118
+ if no_options_passed?
119
+ {}
120
+ else
121
+ dynamic_parser.parse!(@params)
122
+
123
+ data = dynamic_parser.to_hash
124
+ invalid_key = data.find { |key, value| value.nil? }
125
+
126
+ if invalid_key
127
+ raise InvalidOptions.new(invalid_key.first)
128
+ end
129
+
130
+ data
131
+ end
132
+ end
133
+
134
+ # Internal: builds the static option parser. Recognizes `-V` for version and `-h`
135
+ # for help.
136
+ def option_parser
137
+ @_option_parser ||= Slop.new do |command|
138
+ command.banner "Usage: #{program_name} [options] [template_name]"
139
+
140
+ command.on('-V', 'Shows version and exits') do
141
+ print_and_exit Snp::VERSION
142
+ end
143
+
144
+ command.on('-h', 'Shows this message') do
145
+ print_and_exit command.to_s
146
+ end
147
+ end
148
+ end
149
+
150
+ # Internal: builds the dynamic option parser, that creates options on the fly according
151
+ # to the passed command line options.
152
+ def dynamic_parser
153
+ @_dynamic_parser ||= Slop.new(autocreate: true)
154
+ end
155
+
156
+ # Internal: prints a message and exits.
157
+ #
158
+ # message - the message to be printed before exiting.
159
+ #
160
+ # This method finishes the current process with a success exit status.
161
+ def print_and_exit(message)
162
+ printer.out message
163
+ exit
164
+ end
165
+
166
+ # Internal: returns the editor that should be used when editing a generated
167
+ # snippet. Looks for editor names in the `SNP_EDITOR` and `EDITOR` environment
168
+ # variables, respectively.
169
+ def editor
170
+ @_editor ||= ENV['SNP_EDITOR'] || ENV['EDITOR']
171
+ end
172
+
173
+ # Internal: Opens the preferred text editor with the content of a snippet.
174
+ #
175
+ # snippet - a string with the snippet content that should be edited.
176
+ def edit(snippet)
177
+ if editor && !editor.empty?
178
+ snippet_file = file_for(snippet)
179
+ Process.exec "#{editor} '#{snippet_file}'"
180
+ end
181
+ end
182
+
183
+ # Internal: creates a file in the working directory to wihch the snippet
184
+ # contents will be written to, allowing it to be edited.
185
+ #
186
+ # content - the content of the final snippet.
187
+ def file_for(content)
188
+ "snp_#{template_name}".tap do |file_name|
189
+ File.open(file_name, "w+") { |f| f.write(content) }
190
+ end
191
+ end
192
+
193
+ # Internal: the program name to be used when generating output to the user.
194
+ def program_name
195
+ File.basename($0, '.*')
196
+ end
197
+
198
+ # Internal: prints help message and exits with a failure exit status.
199
+ def help_and_exit
200
+ printer.err help_message
201
+ exit 1
202
+ end
203
+
204
+ # Internal: returns the help message for the `snp` command.
205
+ def help_message
206
+ option_parser.to_s
207
+ end
208
+
209
+ # Internal: checks whether or not any arguments were passed on the command line.
210
+ def no_options_passed?
211
+ @params.empty?
212
+ end
213
+
214
+ # Internal: returns the extension of the template name, to be used in the
215
+ # generated snippet.
216
+ def template_extension
217
+ @_extension ||= begin
218
+ match_data = template_name.match(/.+(\..+)/)
219
+
220
+ # set it to the empty string in case there is no extension to allow for
221
+ # proper memoization of its value
222
+ match_data ? match_data[1] : ""
223
+ end
224
+ end
225
+ end
226
+ end
@@ -0,0 +1,58 @@
1
+ require 'yaml'
2
+
3
+ module Snp
4
+ # Snp::Compiler
5
+ #
6
+ # This class takes a template file name and builds it, using the template
7
+ # definition, default data to be used and extra options that override the
8
+ # default ones.
9
+ #
10
+ # Example
11
+ #
12
+ # Compiler.build('js.html', inline: true)
13
+ class Compiler
14
+ def self.build(template_name, extra_options)
15
+ new(template_name, extra_options).compile
16
+ end
17
+
18
+ # Public: creates a new Snp::Compiler instance.
19
+ #
20
+ # template - the template name.
21
+ # extra_options - options to override default data to build the template.
22
+ def initialize(template, extra_options)
23
+ @template = template
24
+ @options = extra_options
25
+ end
26
+
27
+ # Public: actually compiles the template.
28
+ #
29
+ # Returns a string with the compiled version of the snippet.
30
+ def compile
31
+ template.compile(compilation_context)
32
+ end
33
+
34
+ private
35
+
36
+ # Internal: builds the ERB context to be used to generate the snippet.
37
+ # Consists of the default for the template plus the extra options
38
+ # passed on initialization.
39
+ def compilation_context
40
+ TemplateContext.for(default_data.merge(@options))
41
+ end
42
+
43
+ # Internal: searches the default data file for the template and parses it.
44
+ #
45
+ # Returns a hash with the default data if available, or an empty hash otherwise.
46
+ def default_data
47
+ Data.for(@template)
48
+ end
49
+
50
+ def template
51
+ Template.new(@template, path)
52
+ end
53
+
54
+ def path
55
+ @_path ||= Path.new
56
+ end
57
+ end
58
+ end
data/lib/snp/data.rb ADDED
@@ -0,0 +1,50 @@
1
+ require 'yaml'
2
+
3
+ module Snp
4
+ # Snp::Data
5
+ #
6
+ # This class is responsible for fetching the default data to be used when
7
+ # compiling a template. This defaults to the data available on a YAML file
8
+ # located in `SNP_PATH`, if available.
9
+ #
10
+ # Example
11
+ #
12
+ # # Given there is a jquery.yml file in `SNP_PATH`, then
13
+ # data = Snp::Data.for('jquery')
14
+ # # => { 'version' => '1.9', 'cdn' => true }
15
+ class Data
16
+ # Public: fetches the default data for the given template and parses it.
17
+ #
18
+ # template - the template name whose data is to be fetched.
19
+ #
20
+ # Returns a hash with the data.
21
+ def self.for(template)
22
+ new(template).to_hash
23
+ end
24
+
25
+ # Public: creates a new `Snp::Data` instance for the template given.
26
+ def initialize(template, path = Path.new)
27
+ @template = template
28
+ @path = path
29
+ end
30
+
31
+ # Public: fetches the data file and parses it, if available.
32
+ #
33
+ # Returns a hash of the parsed data.
34
+ def to_hash
35
+ if absolute_path
36
+ YAML.load_file(absolute_path)
37
+ else
38
+ {}
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ # Internal: returns the absolute path to the template file, searching for the
45
+ # directories in `SNP_PATH`.
46
+ def absolute_path
47
+ @path.which(@template, 'yml')
48
+ end
49
+ end
50
+ end
data/lib/snp/path.rb ADDED
@@ -0,0 +1,83 @@
1
+ module Snp
2
+ # Snp::Path
3
+ #
4
+ # This class is intended to wrap the logic of finding the paths in which template
5
+ # and data files should be placed.
6
+ #
7
+ # Example
8
+ #
9
+ # Snp::Path.new.which('jquery') # => '/etc/snp/jquery.erb'
10
+ class Path
11
+ # Public: returns the list of absolute paths to the directories in which
12
+ # the templates should be looked.
13
+ def absolute_paths
14
+ dir_list.map { |d| File.expand_path(d) }
15
+ end
16
+
17
+ # Public: resolves a template file by looking in the template path.
18
+ #
19
+ # template - the template name.
20
+ # extension - the extension of the desired template.
21
+ #
22
+ # Returns a string with the full path of the template file, or nil if it is not
23
+ # found.
24
+ def which(template, extension)
25
+ template_with_extension = with_extension(template, extension)
26
+
27
+ path = absolute_paths.find do |path|
28
+ File.exists?(File.join(path, template_with_extension))
29
+ end
30
+
31
+ if path
32
+ File.join(path, template_with_extension)
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ # Internal: retrieves a list of paths that should be searched by looking the `SNP_PATH`
39
+ # environment variable or falling back to the default path.
40
+ def dir_list
41
+ path_from_env || default_path
42
+ end
43
+
44
+ # Internal: parses the SNP_PATH environment variable, if it is set. The format
45
+ # of this variable follows the same convention of the shell's PATH variable: a series
46
+ # of directories separated by a collon.
47
+ def path_from_env
48
+ ENV['SNP_PATH'] && ENV['SNP_PATH'].split(':')
49
+ end
50
+
51
+ # Internal: The default path to be used when the SNP_PATH environment variable
52
+ # is not set.
53
+ def default_path
54
+ ['~/.snp']
55
+ end
56
+
57
+ # Internal: checks if the given name ends with the passed `extension`.
58
+ #
59
+ # template - the template file name.
60
+ def has_extension?(template, extension)
61
+ comparison_length = extension.size + 1 # account for the separator `.`
62
+ template[-comparison_length, comparison_length] == ".#{extension}"
63
+ end
64
+
65
+ # Internal: appends a given extension to the template file name, unless it is
66
+ # already present.
67
+ #
68
+ # template - the template name.
69
+ # extension - the extension to be appended.
70
+ #
71
+ # Examples
72
+ #
73
+ # with_extension('template', 'erb') # => 'template.erb'
74
+ # with_extension('template.erb', 'erb') # => 'template.erb'
75
+ def with_extension(template, extension)
76
+ if has_extension?(template, extension)
77
+ template
78
+ else
79
+ [template, extension].join(".")
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,62 @@
1
+ require 'erb'
2
+
3
+ module Snp
4
+ # Snp::TemplateNotFound
5
+ #
6
+ # This exception is raised in case the template file passed is not found in any
7
+ # directory in snp path.
8
+ class TemplateNotFound < StandardError
9
+ def initialize(template_name, path)
10
+ super("Template #{template_name} was not found in #{path.inspect}")
11
+ end
12
+ end
13
+
14
+ # Snp::Template
15
+ #
16
+ # The Template class represents a snippet definition through an ERB template.
17
+ # Template files are looked in a series of directories that can be defined via
18
+ # the SNP_PATH environment variable. By default, these snippet definitions are
19
+ # searched in the `.snp` directory in your home directory.
20
+ #
21
+ # Examples
22
+ #
23
+ # t = Snp::Template.new('jquery.erb')
24
+ # t.compile(binding) # => '<html><head>...'
25
+ class Template
26
+ # Public: creates a new template instance.
27
+ #
28
+ # template_file - the basename of the template file.
29
+ def initialize(template_file, path = Path.new)
30
+ @file = template_file
31
+ @path = path
32
+ end
33
+
34
+ # Public: compiles the template content to an effective snippet, ready to use.
35
+ #
36
+ # context - a `Binding` object to be used as context in the template compilation.
37
+ #
38
+ # Returns a string with the compiled template.
39
+ def compile(context)
40
+ if template_content
41
+ ERB.new(template_content, 0, '-').result(context)
42
+ else
43
+ raise TemplateNotFound.new(@file, @path.absolute_paths)
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ # Internal: returns a string with the content of the template file.
50
+ def template_content
51
+ if absolute_path
52
+ File.read(absolute_path)
53
+ end
54
+ end
55
+
56
+ # Internal: returns the absolute path to the template, or `nil`, in case it is
57
+ # not found.
58
+ def absolute_path
59
+ @path.which(@file, 'erb')
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,85 @@
1
+ module Snp
2
+ # Snp::TemplateContext
3
+ #
4
+ # This class aims to represent the context in which a snippet template is
5
+ # compiled. It receives a hash of keys and values that act as properties to be
6
+ # used in the template. For example, if you have a template with the content:
7
+ #
8
+ # <html>
9
+ # <head><title><%= title %></title></head>
10
+ # </html>
11
+ #
12
+ # Then a proper context for this snippet compilation would be:
13
+ #
14
+ # TemplateContext.for(title: 'My beautiful page')
15
+ class TemplateContext
16
+ class InsufficientContext < StandardError
17
+ attr_reader :missing_property
18
+
19
+ def initialize(property)
20
+ @missing_property = property.to_s
21
+ super %(Insufficient context: no defined value for property "#{missing_property}")
22
+ end
23
+ end
24
+
25
+ # Public: returns the binding for the passed `attributes`.
26
+ def self.for(attributes)
27
+ new(attributes).erb_binding
28
+ end
29
+
30
+ # Public: creates a new Snp::TemplateContext object.
31
+ #
32
+ # context - a hash of properties and values to be used in as context of the template.
33
+ #
34
+ # The hash is used so that the resulting object responds to each key in `context`,
35
+ # returning the accoring value.
36
+ def initialize(context)
37
+ @context = context
38
+
39
+ context.each do |property, value|
40
+ method_name = normalize(property)
41
+ define_property(method_name, value)
42
+ end
43
+ end
44
+
45
+ def erb_binding
46
+ binding
47
+ end
48
+
49
+ def respond_to_missing?(method, *)
50
+ @context.has_key?(normalize(method))
51
+ end
52
+
53
+ # In case an unknown method is called on the template context, we raise a proper
54
+ # exception that must be rescued and properly handled.
55
+ #
56
+ # Reaching this point means we need variables in the snippet that were not provided.
57
+ def method_missing(method, *)
58
+ raise InsufficientContext.new(method)
59
+ end
60
+
61
+ private
62
+
63
+ # Internal: returns a propperty name with underscores where dashes were present.
64
+ #
65
+ # name - the property name.
66
+ #
67
+ # Examples
68
+ #
69
+ # prepare('name') # => 'name'
70
+ # prepare('update-ref') # => 'update_ref'
71
+ def normalize(name)
72
+ name.to_s.gsub('-', '_')
73
+ end
74
+
75
+ # Internal: defines a method with `property` name, and returning `value`.
76
+ # If `value` is a boolean, this method will also define a predicate method.
77
+ def define_property(property, value)
78
+ define_singleton_method(property) { value }
79
+
80
+ if value == true || value == false
81
+ define_singleton_method("#{property}?") { value }
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,3 @@
1
+ module Snp
2
+ VERSION = '0.1'
3
+ end
@@ -0,0 +1,99 @@
1
+ require 'test_helper'
2
+ require 'snp/cli'
3
+
4
+ describe Snp::CLI do
5
+ class TestPrinter
6
+ attr_reader :output, :error
7
+
8
+ def initialize
9
+ @output = ''
10
+ @error = ''
11
+ end
12
+
13
+ def out(message)
14
+ @output << message
15
+ end
16
+
17
+ def err(message)
18
+ @error << message
19
+ end
20
+ end
21
+
22
+ describe '.parse_options' do
23
+ it 'delegates to #run' do
24
+ double = stub(start: 'double')
25
+ Snp::CLI.stubs(:new).returns(double)
26
+
27
+ Snp::CLI.run.must_equal 'double'
28
+ end
29
+ end
30
+
31
+ describe '#parse' do
32
+ def no_exit(&block)
33
+ block.call
34
+ rescue SystemExit
35
+ end
36
+
37
+ it 'prints help message when no arguments are passed' do
38
+ printer = TestPrinter.new
39
+ cli = Snp::CLI.new([], printer)
40
+
41
+ no_exit { cli.parse }
42
+
43
+ printer.output.wont_be_nil
44
+ end
45
+
46
+ it 'can print version' do
47
+ printer = TestPrinter.new
48
+ cli = Snp::CLI.new(['-V'], printer)
49
+
50
+ no_exit { cli.parse }
51
+
52
+ printer.output.must_equal Snp::VERSION
53
+ end
54
+
55
+ it 'can print help message' do
56
+ printer = TestPrinter.new
57
+ cli = Snp::CLI.new(['-h'], printer)
58
+
59
+ no_exit { cli.parse }
60
+
61
+ printer.output.wont_be_nil
62
+ end
63
+
64
+ it 'retrieves the template name' do
65
+ cli = Snp::CLI.new(['snp'])
66
+ options = cli.parse
67
+
68
+ options.must_equal ['snp', {}]
69
+ end
70
+
71
+ it 'fetches dynamic options' do
72
+ cli = Snp::CLI.new(['--type', 'gem', '--count', '3', 'snp'])
73
+ options = cli.parse
74
+
75
+ options.must_equal ['snp', { type: 'gem', count: '3' }]
76
+ end
77
+
78
+ it 'throws an error if more than one template name is given' do
79
+ printer = TestPrinter.new
80
+ cli = Snp::CLI.new(['--count', '3', 'some_name', 'snp'], printer)
81
+
82
+ lambda {
83
+ no_exit { cli.parse }
84
+ }.must_raise(Snp::InvalidOptions)
85
+ end
86
+ end
87
+
88
+ describe '#start' do
89
+ it 'writes the compiled version to its output stream' do
90
+ printer = TestPrinter.new
91
+ cli = Snp::CLI.new(['snp'], printer)
92
+ Snp::Compiler.stubs(:build).returns('compiled snippet')
93
+
94
+ cli.start
95
+
96
+ printer.output.must_equal 'compiled snippet'
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,42 @@
1
+ require 'test_helper'
2
+
3
+ describe Snp::Compiler do
4
+ describe '.build' do
5
+ it 'delegates to #compile' do
6
+ compiler = stub(compile: 'compiled snippet')
7
+ Snp::Compiler.stubs(:new).returns(compiler)
8
+
9
+ Snp::Compiler.build('snp', {}).must_equal 'compiled snippet'
10
+ end
11
+ end
12
+
13
+ describe '#compile' do
14
+ def template_content
15
+ '<% if say_hello? %>' +
16
+ 'Hello, <%= name %>' +
17
+ '<% else %>' +
18
+ 'Farewell, <%= name %>' +
19
+ '<% end %>'
20
+ end
21
+
22
+ def default_options
23
+ { say_hello: true, name: 'John' }
24
+ end
25
+
26
+ def stub_file_operations
27
+ File.stubs(:exists?).returns(true)
28
+ File.stubs(:read).returns(template_content)
29
+ YAML.stubs(:load_file).returns(default_options)
30
+ end
31
+
32
+ it 'generates compiled snippet when all options are available' do
33
+ stub_file_operations
34
+ Snp::Compiler.new('template_name', {}).compile.must_equal 'Hello, John'
35
+ end
36
+
37
+ it 'overrides default data with the ones passed' do
38
+ stub_file_operations
39
+ Snp::Compiler.new('template_name', name: 'Arthur').compile.must_equal 'Hello, Arthur'
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,30 @@
1
+ require 'test_helper'
2
+
3
+ describe Snp::Data do
4
+ describe '.for' do
5
+ it 'delegates to #to_hash' do
6
+ data = stub(to_hash: 'snp')
7
+ Snp::Data.stubs(:new).returns(data)
8
+
9
+ Snp::Data.for('anything').must_equal 'snp'
10
+ end
11
+ end
12
+
13
+ describe '#to_hash' do
14
+ it 'is empty when no data file is found' do
15
+ File.stubs(:exists?).returns(false)
16
+
17
+ Snp::Data.new('snp').to_hash.must_equal({})
18
+ end
19
+
20
+ it 'parses the content of the data file' do
21
+ data = { name: 'snp', language: 'ruby' }
22
+ path = File.expand_path('~/.snp/snp.yml')
23
+
24
+ File.stubs(:exists?).returns(true)
25
+ YAML.stubs(:load_file).with(path).returns(data)
26
+
27
+ Snp::Data.new('snp').to_hash.must_equal data
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,47 @@
1
+ require 'test_helper'
2
+
3
+ describe Snp::Path do
4
+ describe '#absolute_paths' do
5
+ it 'defaults to the user home directory' do
6
+ Snp::Path.new.absolute_paths.must_equal Array(File.expand_path('~/.snp'))
7
+ end
8
+
9
+ it 'uses the value in the `SNP_PATH` variable when available' do
10
+ ENV['SNP_PATH'] = '~/.snp:/etc/snp'
11
+
12
+ Snp::Path.new.absolute_paths.must_equal [File.expand_path('~/.snp'), '/etc/snp']
13
+
14
+ ENV['SNP_PATH'] = nil
15
+ end
16
+ end
17
+
18
+ describe '#which' do
19
+ def subject
20
+ Snp::Path.new
21
+ end
22
+
23
+ it 'is nil in case there is no file in the path' do
24
+ File.stubs(:exists?).returns(false)
25
+
26
+ subject.which('template', 'erb').must_be_nil
27
+ end
28
+
29
+ it 'returns the absolute path to the template appending the extension' do
30
+ File.stubs(:exists?).returns(true)
31
+
32
+ subject.which('snp', 'erb').must_equal File.expand_path('~/.snp/snp.erb')
33
+ end
34
+
35
+ it 'finds the snippet in case it has more than one extension' do
36
+ File.stubs(:exists?).returns(true)
37
+
38
+ subject.which('snp.erb.js', 'js').must_equal File.expand_path('~/.snp/snp.erb.js')
39
+ end
40
+
41
+ it 'does not append extension if it template already has it' do
42
+ File.stubs(:exists?).returns(true)
43
+
44
+ subject.which('snp.erb', 'erb').must_equal File.expand_path('~/.snp/snp.erb')
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,52 @@
1
+ require 'test_helper'
2
+
3
+ describe Snp::TemplateContext do
4
+ describe '.for' do
5
+ it 'delegates to #erb_binding' do
6
+ context = stub(erb_binding: 'binding')
7
+ Snp::TemplateContext.stubs(:new).returns(context)
8
+
9
+ Snp::TemplateContext.for('template_name').must_equal 'binding'
10
+ end
11
+ end
12
+
13
+ describe '#erb_binding' do
14
+ it 'returns an instance of `Binding`' do
15
+ Snp::TemplateContext.new(key: 'value').erb_binding.must_be_instance_of Binding
16
+ end
17
+ end
18
+
19
+ it 'responds to methods passed as hash' do
20
+ context = Snp::TemplateContext.new(greeting: 'Hello', name: 'snp')
21
+ context.greeting.must_equal 'Hello'
22
+ context.name.must_equal 'snp'
23
+ end
24
+
25
+ it 'generates predicate methods for boolean attributes' do
26
+ context = Snp::TemplateContext.new(awesome: true, sad: false)
27
+ context.awesome?.must_equal true
28
+ context.sad?.must_equal false
29
+ end
30
+
31
+ it 'changes dash for underscore in generated methods' do
32
+ context = Snp::TemplateContext.new('is-awesome' => true)
33
+ context.is_awesome?.must_equal true
34
+ end
35
+
36
+ it 'politely responds to methods named after context keys' do
37
+ context = Snp::TemplateContext.new(snp: 'snp')
38
+ context.must_respond_to(:snp)
39
+ end
40
+
41
+ it 'responds to method names in its normalized forms' do
42
+ context = Snp::TemplateContext.new(:"gem-name" => 'snp')
43
+ context.must_respond_to(:gem_name)
44
+ end
45
+
46
+ it 'raises proper error when called with non-existing property' do
47
+ context = Snp::TemplateContext.new(snp: 'snp')
48
+ lambda {
49
+ context.invalid_property
50
+ }.must_raise(Snp::TemplateContext::InsufficientContext)
51
+ end
52
+ end
@@ -0,0 +1,37 @@
1
+ require 'test_helper'
2
+
3
+ describe Snp::Template do
4
+ describe '#compile' do
5
+ class ERBContext
6
+ def greeting
7
+ 'Hello'
8
+ end
9
+
10
+ def name
11
+ 'snp'
12
+ end
13
+
14
+ def context
15
+ binding
16
+ end
17
+ end
18
+
19
+ it 'generates a string with the processed template' do
20
+ template_content = '<%= greeting %> from <%= name %>'
21
+ File.stubs(:exists?).returns(true)
22
+ File.stubs(:read).returns(template_content)
23
+
24
+ template = Snp::Template.new('template.erb')
25
+ template.compile(ERBContext.new.context).must_equal 'Hello from snp'
26
+ end
27
+
28
+ it 'raises an error in case the template is not found' do
29
+ File.stubs(:exists?).returns(false)
30
+ template = Snp::Template.new('template.erb')
31
+
32
+ lambda {
33
+ template.compile({})
34
+ }.must_raise Snp::TemplateNotFound
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,15 @@
1
+ require 'minitest/spec'
2
+ require 'minitest/autorun'
3
+ require 'minitest/mock'
4
+ require 'minitest/pride'
5
+
6
+ require 'mocha/setup'
7
+
8
+ require 'snp'
9
+
10
+ # unset any possibly set `SNP_PATH` variable
11
+ ENV.delete('SNP_PATH')
12
+
13
+ # unset editors by default
14
+ ENV.delete('SNP_EDITOR')
15
+ ENV.delete('EDITOR')
metadata ADDED
@@ -0,0 +1,95 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: snp
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - Renato Mascarenhas
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: slop
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.6'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: mocha
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.1'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.1'
41
+ description: snp allows you to create snippets in an automated and reusable manner.
42
+ email: mascarenhas.renato@gmail.com
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - MIT-LICENSE
48
+ - README.md
49
+ - lib/snp.rb
50
+ - lib/snp/cli.rb
51
+ - lib/snp/compiler.rb
52
+ - lib/snp/data.rb
53
+ - lib/snp/path.rb
54
+ - lib/snp/template.rb
55
+ - lib/snp/template_context.rb
56
+ - lib/snp/version.rb
57
+ - test/snp/cli_test.rb
58
+ - test/snp/compiler_test.rb
59
+ - test/snp/data_test.rb
60
+ - test/snp/path_test.rb
61
+ - test/snp/template_context_test.rb
62
+ - test/snp/template_test.rb
63
+ - test/test_helper.rb
64
+ homepage: https://github.com/rmascarenhas/snp
65
+ licenses:
66
+ - MIT
67
+ metadata: {}
68
+ post_install_message:
69
+ rdoc_options: []
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ requirements: []
83
+ rubyforge_project: snp
84
+ rubygems_version: 2.2.2
85
+ signing_key:
86
+ specification_version: 4
87
+ summary: Quickly and easily create code snippets.
88
+ test_files:
89
+ - test/snp/compiler_test.rb
90
+ - test/snp/data_test.rb
91
+ - test/snp/template_test.rb
92
+ - test/snp/cli_test.rb
93
+ - test/snp/template_context_test.rb
94
+ - test/snp/path_test.rb
95
+ - test/test_helper.rb