snp 0.1

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: 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