gemmyrb 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +18 -0
- data/bin/gemmy +5 -0
- data/lib/gemmy/cli.rb +41 -0
- data/lib/gemmy/components/dynamic_steps.rb +92 -0
- data/lib/gemmy/components.rb +17 -0
- data/lib/gemmy/patches/array_patch.rb +19 -0
- data/lib/gemmy/patches/hash_patch.rb +104 -0
- data/lib/gemmy/patches/method_patch.rb +22 -0
- data/lib/gemmy/patches/object_patch.rb +62 -0
- data/lib/gemmy/patches/string_patch.rb +21 -0
- data/lib/gemmy/patches/symbol_patch.rb +19 -0
- data/lib/gemmy/patches/thread_patch.rb +18 -0
- data/lib/gemmy/patches.rb +25 -0
- data/lib/gemmy/tasks/make_gem.rb +189 -0
- data/lib/gemmy/tasks.rb +4 -0
- data/lib/gemmy/tests/component_tests/dynamic_steps_tests.rb +21 -0
- data/lib/gemmy/tests/component_tests.rb +11 -0
- data/lib/gemmy/tests/patch_test.rb +127 -0
- data/lib/gemmy/tests.rb +16 -0
- data/lib/gemmy/version.rb +3 -0
- data/lib/gemmy.rb +34 -0
- metadata +141 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 18532f58f02f659faa596f0ed22dd3f85acfa1a4
|
4
|
+
data.tar.gz: 17ee68de85cf655f22bb5190e33a27378ecee904
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 32ef4734ff5168c54883208469f150927eb09053786e8dc6f5721f6d449061f509a60b0a1028eca628d973ef042339d4e55ee9be8d9c3edba1aea3ecf2e60d7c
|
7
|
+
data.tar.gz: cee902db7fa77e8e555f6473bf4bd1191353206df35697d0ade31a5bcb0a13322db9335ebbb59a330810cb4aacc3c1ad3e04c0f035b5dabf99d06b0799360c85
|
data/README.md
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
### Gemmy gem
|
2
|
+
|
3
|
+
This is a general purpose gem.
|
4
|
+
|
5
|
+
I'm continually adding small core patches and utility classes.
|
6
|
+
|
7
|
+
It is on RubyGems and can be installed with `gem install gemmyrb`
|
8
|
+
|
9
|
+
**note**
|
10
|
+
|
11
|
+
because there is an existing gem named `gemmy`, the name passed
|
12
|
+
to `gem install` is `gemmyrb`. But `require 'gemmy'` is still used.
|
13
|
+
|
14
|
+
For more information, see the following documents:
|
15
|
+
|
16
|
+
- [examples/01_using_as_refinement.rb](.examples/01_using_as_refinement.rb)
|
17
|
+
- [examples/02_using_globally.rb](.examples/02_using_globally.rb)
|
18
|
+
- [examples/03_full_api.rb](.examples/03_full_api.rb)
|
data/bin/gemmy
ADDED
data/lib/gemmy/cli.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# Command line interface
|
2
|
+
#
|
3
|
+
# Run from command line:
|
4
|
+
# gemmy <arguments>
|
5
|
+
# e.g. gemmy help
|
6
|
+
#
|
7
|
+
# Start from code with Gemmy::CLI.run
|
8
|
+
#
|
9
|
+
class Gemmy::CLI < Thor
|
10
|
+
|
11
|
+
# Start the CLI
|
12
|
+
# @param arguments [Array<String>] passed to thor, defaults to ARGV
|
13
|
+
#
|
14
|
+
def self.run(arguments: nil)
|
15
|
+
# Store a copy of the arguments.
|
16
|
+
# The originals are shifted so they don't intefere with gets
|
17
|
+
arguments = ARGV.clone
|
18
|
+
ARGV.clear
|
19
|
+
|
20
|
+
# Can't make this conditional on "__FILE__ == $0"
|
21
|
+
# Because of the way gem executables are run
|
22
|
+
start arguments
|
23
|
+
end
|
24
|
+
|
25
|
+
# Task to make a gem
|
26
|
+
# @param name [String] the name of the gem
|
27
|
+
# Other options are requested via gets
|
28
|
+
#
|
29
|
+
desc "make_gem NAME", "make a skeleton ruby gem project"
|
30
|
+
def make_gem(name)
|
31
|
+
Gemmy::Tasks::MakeGem.run(name)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Test the gem.
|
35
|
+
#
|
36
|
+
desc "test", "test the gem"
|
37
|
+
def test
|
38
|
+
Gemmy::Tests.run
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# Mimics the Cucumber API:
|
2
|
+
# {DynamicSteps#step} runs a step
|
3
|
+
# {DynamicSteps#define_step} defines a step
|
4
|
+
#
|
5
|
+
# The usage is the same as Cucumber:
|
6
|
+
#
|
7
|
+
# define_step /I print (.+) (.+) times/ do |string, n|
|
8
|
+
# n.times { print string }
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# step "I print hello 2 times"
|
12
|
+
# => 'hellohello'
|
13
|
+
#
|
14
|
+
# Like Cucumber, it will raise an error if there is > 1 matching step
|
15
|
+
# {DynamicSteps::AmbiguousMatchError} can be rescued if desired.
|
16
|
+
#
|
17
|
+
# It also raises an error if no matcher was found
|
18
|
+
#
|
19
|
+
module Gemmy::Components::DynamicSteps
|
20
|
+
|
21
|
+
Gemmy::Patches.refinements.each { |r| using r }
|
22
|
+
|
23
|
+
# A hash mapping regex to proc
|
24
|
+
#
|
25
|
+
attr_reader :steps
|
26
|
+
|
27
|
+
# Error raised when a string matches multiple step regexes.
|
28
|
+
# It's frequently accidental to come into this situation,
|
29
|
+
# and having this check prevents surprise errors.
|
30
|
+
#
|
31
|
+
class AmbiguousMatchError < StandardError; end
|
32
|
+
|
33
|
+
# Error raised when no matcher is found for a string
|
34
|
+
#
|
35
|
+
class NoMatchFoundError < StandardError; end
|
36
|
+
|
37
|
+
def steps
|
38
|
+
@steps ||= {}
|
39
|
+
end
|
40
|
+
|
41
|
+
# Defines a regex => proc mapping
|
42
|
+
# A good pattern for regex is to use (.+) as match groups, and
|
43
|
+
# mirror those as sequential named parameters in the block.
|
44
|
+
#
|
45
|
+
# Match groups are left-greedy, for example:
|
46
|
+
# "1 2 3 4 5".match(/(.+) (.+)/).tap &:shift
|
47
|
+
# # => ['1 2 3 4', '5']
|
48
|
+
#
|
49
|
+
def define_step(regex, &blk)
|
50
|
+
steps[regex] = blk
|
51
|
+
end
|
52
|
+
|
53
|
+
# run a step.
|
54
|
+
# searches @step keys for regex which matches the string
|
55
|
+
# then runs the associated proc, passing the regex match results
|
56
|
+
#
|
57
|
+
def step(string)
|
58
|
+
matching_steps = find_matching_steps(string)
|
59
|
+
if matching_steps.keys.length > 1
|
60
|
+
# Failure, multiple matching steps
|
61
|
+
raise(
|
62
|
+
AmbiguousMatchError,
|
63
|
+
"step #{string} matched: #{matching_steps.keys.join(", ")}"
|
64
|
+
)
|
65
|
+
elsif matching_steps.keys.length == 0
|
66
|
+
# Failure, no matching step
|
67
|
+
raise(
|
68
|
+
NoMatchFoundError,
|
69
|
+
"step #{string} had no match"
|
70
|
+
)
|
71
|
+
else
|
72
|
+
# Success, run the proc
|
73
|
+
matching_step = matching_steps.values[0]
|
74
|
+
matching_step[:proc].call(*(matching_step[:matches]))
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Searches the keys in @steps for a regex the matches the string.
|
79
|
+
# If one is found, it adds the regex as a key in the results hash.
|
80
|
+
# The value is a hash with two keys: :matches (an array) and :proc
|
81
|
+
#
|
82
|
+
def find_matching_steps(string)
|
83
|
+
matching_steps = steps.reduce({}) do |matching_steps, (regex, proc)|
|
84
|
+
match_results = string.match(regex).to_a.tap &:shift
|
85
|
+
if match_results.any_not? &:blank?
|
86
|
+
matching_steps[regex] = { matches: match_results, proc: proc }
|
87
|
+
end
|
88
|
+
matching_steps
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# Different collections of functionality
|
2
|
+
#
|
3
|
+
module Gemmy::Components
|
4
|
+
def self.included(base)
|
5
|
+
list.each do |klass|
|
6
|
+
base.include klass
|
7
|
+
base.extend klass
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.list
|
12
|
+
[
|
13
|
+
Gemmy::Components::DynamicSteps
|
14
|
+
]
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# Global patches. Could be applied to Object or Kernel
|
2
|
+
#
|
3
|
+
module Gemmy::Patches::ArrayPatch
|
4
|
+
# checks if any of the results of an array do not respond truthily
|
5
|
+
# to a block
|
6
|
+
#
|
7
|
+
# For example, to check if any items of an array are truthy:
|
8
|
+
# [false, nil, ''].any_not? &:blank?
|
9
|
+
# => false
|
10
|
+
#
|
11
|
+
def any_not?(&blk)
|
12
|
+
any? { |item| ! blk.call(item) }
|
13
|
+
end
|
14
|
+
|
15
|
+
refine Array do
|
16
|
+
include Gemmy::Patches::ArrayPatch
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# Hash patches
|
2
|
+
#
|
3
|
+
module Gemmy::Patches::HashPatch
|
4
|
+
|
5
|
+
# The opposite of Hash#dig
|
6
|
+
# Takes a list of keys followed by a value to set
|
7
|
+
#
|
8
|
+
# Example:
|
9
|
+
#
|
10
|
+
# a = {a: {b: {}} }
|
11
|
+
# a.bury(:a, :b, :c, 0)
|
12
|
+
# puts a[:a][:b][:c]
|
13
|
+
# => 0
|
14
|
+
#
|
15
|
+
# Source: https://github.com/dam13n/ruby-bury/blob/master/hash.rb
|
16
|
+
#
|
17
|
+
def bury *args
|
18
|
+
Gemmy::Patches::HashPatch._bury(self, *args)
|
19
|
+
end
|
20
|
+
|
21
|
+
# The bury method, taking the input hash as a parameter
|
22
|
+
# Used by the Hash#bury instance method
|
23
|
+
def self._bury(caller_hash, *args)
|
24
|
+
if args.count < 2
|
25
|
+
raise ArgumentError.new("2 or more arguments required")
|
26
|
+
elsif args.count == 2
|
27
|
+
caller_hash[args[0]] = args[1]
|
28
|
+
else
|
29
|
+
arg = args.shift
|
30
|
+
caller_hash[arg] = {} unless caller_hash[arg]
|
31
|
+
_bury(caller_hash[arg], *args) unless args.empty?
|
32
|
+
end
|
33
|
+
caller_hash
|
34
|
+
end
|
35
|
+
|
36
|
+
# Turns a hash into one that's "autovivified"
|
37
|
+
# meaning it's default values for keys is an empty hash.
|
38
|
+
# The result is that you can set nested keys without initializing
|
39
|
+
# more than one hash layer.
|
40
|
+
#
|
41
|
+
# Usage:
|
42
|
+
# hash = {}.autovivified
|
43
|
+
# hash[:a][:b] = 0
|
44
|
+
# puts hash[:a][:b]
|
45
|
+
# => 0
|
46
|
+
#
|
47
|
+
def autovivified
|
48
|
+
Gemmy::Patches::HashPatch._autovivified(self)
|
49
|
+
end
|
50
|
+
|
51
|
+
def self._autovivified(caller_hash)
|
52
|
+
result = Hash.new { |hash,key| hash[key] = Hash.new(&hash.default_proc) }
|
53
|
+
result.deep_merge caller_hash
|
54
|
+
end
|
55
|
+
|
56
|
+
# Sets up a hash to mirror all changes to a database file
|
57
|
+
# All nested gets & sets require a list of keys, passed as subsequent args.
|
58
|
+
# Instead of [] and []=, use get and set
|
59
|
+
#
|
60
|
+
# Everything in the db is contained in a hash with one predefined
|
61
|
+
# key, :data. The value is an empty, autovivified hash.
|
62
|
+
#
|
63
|
+
# This also makes the caller hash autovivified
|
64
|
+
#
|
65
|
+
# Example:
|
66
|
+
#
|
67
|
+
# hash = {}.persisted("db.yaml")
|
68
|
+
# hash.set(:a, :b, 0) # => this writes to disk and memory
|
69
|
+
# hash.get(:a, :b) # => reads from memory
|
70
|
+
# hash.get(:a, :b, disk: true) # => reads from disk
|
71
|
+
#
|
72
|
+
def persisted(path)
|
73
|
+
require 'yaml/store'
|
74
|
+
Gemmy::Patches::HashPatch._autovivified(self).tap do |hash|
|
75
|
+
hash.instance_exec do
|
76
|
+
@db = YAML::Store.new path
|
77
|
+
@db.transaction do
|
78
|
+
@db[:data] = Gemmy::Patches::HashPatch._autovivified({})
|
79
|
+
end
|
80
|
+
end
|
81
|
+
hash.extend Gemmy::Patches::HashPatch::PersistedHash
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Helper methods for the persistence patch
|
86
|
+
#
|
87
|
+
module PersistedHash
|
88
|
+
def get(*keys, disk: false)
|
89
|
+
disk ? @db.transaction { @db[:data].dig(*keys) } : dig(*keys)
|
90
|
+
end
|
91
|
+
def set(*keys, val)
|
92
|
+
Gemmy::Patches::HashPatch._bury(self, *keys, val)
|
93
|
+
@db.transaction do
|
94
|
+
Gemmy::Patches::HashPatch._bury(@db[:data], *(keys + [val]))
|
95
|
+
end
|
96
|
+
val
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
refine Hash do
|
101
|
+
include Gemmy::Patches::HashPatch
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Method patches. To the 'Method' class i.e. `method(:puts)`
|
2
|
+
#
|
3
|
+
module Gemmy::Patches::MethodPatch
|
4
|
+
|
5
|
+
# Bind an argument to a method.
|
6
|
+
# Very useful for the proc shorthand.
|
7
|
+
# For example say there's a method "def add(a,b); print a + b; end"
|
8
|
+
# You can run it for each number in a list:
|
9
|
+
# [1,2,3].each &method(:add).bind(1)
|
10
|
+
# => 234
|
11
|
+
#
|
12
|
+
def bind *args
|
13
|
+
Proc.new do |*more|
|
14
|
+
self.call *(args + more)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
refine Method do
|
19
|
+
include Gemmy::Patches::MethodPatch
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# Object patches. Can be called with implicit receiver
|
2
|
+
#
|
3
|
+
module Gemmy::Patches::ObjectPatch
|
4
|
+
|
5
|
+
# Turns on verbose mode, showing warnings
|
6
|
+
#
|
7
|
+
def verbose_mode
|
8
|
+
$VERBOSE = true
|
9
|
+
end
|
10
|
+
|
11
|
+
# Generic error. Raises RuntimeError
|
12
|
+
# @param msg [String] optional
|
13
|
+
#
|
14
|
+
def error(msg='')
|
15
|
+
raise RuntimeError, msg
|
16
|
+
end
|
17
|
+
|
18
|
+
# Prints a string then gets input
|
19
|
+
# @param txt [String]
|
20
|
+
#
|
21
|
+
def _prompt(txt)
|
22
|
+
puts txt
|
23
|
+
gets.chomp
|
24
|
+
end
|
25
|
+
|
26
|
+
# Shifts one ARGV and raises a message if it's undefined.
|
27
|
+
# @param msg [String]
|
28
|
+
#
|
29
|
+
def get_arg_or_error(msg)
|
30
|
+
([ARGV.shift, msg].tap &method(:error_if_blank)).shift
|
31
|
+
end
|
32
|
+
|
33
|
+
# Writes a string to a file
|
34
|
+
# @param file [String] path to write to
|
35
|
+
# @param text [String] text to write
|
36
|
+
#
|
37
|
+
def write(file:, text:)
|
38
|
+
File.open(file, 'w', &:write.with(text))
|
39
|
+
end
|
40
|
+
|
41
|
+
# if args[0] (object) is blank, raises args[1] (message)
|
42
|
+
# @param args [Array] - value 1 is obj, value 2 is msg
|
43
|
+
#
|
44
|
+
def error_if_blank(args)
|
45
|
+
obj, msg = args
|
46
|
+
obj.blank? && error(msg)
|
47
|
+
end
|
48
|
+
|
49
|
+
# shorter proc shorthands
|
50
|
+
#
|
51
|
+
alias m method
|
52
|
+
|
53
|
+
# method which does absolutely nothing, ignoring all arguments
|
54
|
+
#
|
55
|
+
def nothing(*args)
|
56
|
+
end
|
57
|
+
|
58
|
+
refine Object do
|
59
|
+
include Gemmy::Patches::ObjectPatch
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# String patches
|
2
|
+
#
|
3
|
+
module Gemmy::Patches::StringPatch
|
4
|
+
|
5
|
+
# reference 'strip_heredoc' (provided by active support) by 'unindent'
|
6
|
+
#
|
7
|
+
# this takes an indented heredoc and treats it as if the first line is not
|
8
|
+
# indented.
|
9
|
+
#
|
10
|
+
# Instead of using alias, a method is defined here to enable modular
|
11
|
+
# patches
|
12
|
+
#
|
13
|
+
def unindent
|
14
|
+
strip_heredoc
|
15
|
+
end
|
16
|
+
|
17
|
+
refine String do
|
18
|
+
include Gemmy::Patches::StringPatch
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# Symbol patches
|
2
|
+
#
|
3
|
+
module Gemmy::Patches::SymbolPatch
|
4
|
+
|
5
|
+
# Patch symbol so the proc shorthand can take extra arguments
|
6
|
+
# http://stackoverflow.com/a/23711606/2981429
|
7
|
+
#
|
8
|
+
# Example: [1,2,3].map &:*.with(2)
|
9
|
+
# => [2,4,6]
|
10
|
+
#
|
11
|
+
def with(*args, &block)
|
12
|
+
->(caller, *rest) { caller.send(self, *rest, *args, &block) }
|
13
|
+
end
|
14
|
+
|
15
|
+
refine Symbol do
|
16
|
+
include Gemmy::Patches::SymbolPatch
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Thread patches
|
2
|
+
# This does some core configuration to Thread as soon as the patches are
|
3
|
+
# included, so functionality will be changed even without calling any of these
|
4
|
+
# methods
|
5
|
+
#
|
6
|
+
module Gemmy::Patches::ThreadPatch
|
7
|
+
|
8
|
+
# Ensure that threads bubble up their errors
|
9
|
+
#
|
10
|
+
def self.included(base)
|
11
|
+
Thread.abort_on_exception = true
|
12
|
+
end
|
13
|
+
|
14
|
+
refine Thread do
|
15
|
+
include Gemmy::Patches::ThreadPatch
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# Gemmy provides patches for a few of the core classes.
|
2
|
+
#
|
3
|
+
# For example:
|
4
|
+
# Gemmy::Patches.load(only: [:string])
|
5
|
+
# Gemmy::Patches.load(except: [:symbol, :global])
|
6
|
+
#
|
7
|
+
module Gemmy::Patches
|
8
|
+
|
9
|
+
def self.refinements
|
10
|
+
core_patches.values
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.core_patches
|
14
|
+
@@core_patches ||= {
|
15
|
+
String: Gemmy::Patches::StringPatch,
|
16
|
+
Symbol: Gemmy::Patches::SymbolPatch,
|
17
|
+
Object: Gemmy::Patches::ObjectPatch,
|
18
|
+
Array: Gemmy::Patches::ArrayPatch,
|
19
|
+
Method: Gemmy::Patches::MethodPatch,
|
20
|
+
Hash: Gemmy::Patches::HashPatch,
|
21
|
+
Thread: Gemmy::Patches::ThreadPatch,
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,189 @@
|
|
1
|
+
# A task to create skeleton structure for a ruby gem
|
2
|
+
#
|
3
|
+
# Only one method is intended for public use, {Tasks::MakeGem#run}.
|
4
|
+
#
|
5
|
+
# It takes one argument - the name of the ruby gem.
|
6
|
+
#
|
7
|
+
# It then prompts for a few more defails (summary, email, and name).
|
8
|
+
#
|
9
|
+
# Here's the structure it creates:
|
10
|
+
#
|
11
|
+
# ├── <name>.gemspec
|
12
|
+
# ├── Gemfile
|
13
|
+
# └── lib
|
14
|
+
# ├── <name>.rb
|
15
|
+
# └── version.rb
|
16
|
+
#
|
17
|
+
# At which point you can run "gem build" then "gem install"
|
18
|
+
#
|
19
|
+
class Gemmy::Tasks::MakeGem
|
20
|
+
|
21
|
+
Gemmy::Patches.refinements.each { |r| using r }
|
22
|
+
|
23
|
+
# Builds a skeleton ruby gem.
|
24
|
+
# Prompts for some input using gets.chomp
|
25
|
+
#
|
26
|
+
def self.run(name)
|
27
|
+
new.make_gem(name)
|
28
|
+
end
|
29
|
+
|
30
|
+
# calls a sequence of commands to build out the gem directory
|
31
|
+
#
|
32
|
+
def make_gem(name)
|
33
|
+
@name = name
|
34
|
+
@class_name = name.capitalize
|
35
|
+
|
36
|
+
usage_io
|
37
|
+
create_root_dir
|
38
|
+
create_lib_dir
|
39
|
+
create_version_file
|
40
|
+
create_main_file
|
41
|
+
gemspec_info_io
|
42
|
+
create_gemspec_file
|
43
|
+
create_gemfile
|
44
|
+
create_reinstall_file
|
45
|
+
end
|
46
|
+
|
47
|
+
attr_reader :name, :root_dir, :lib, :version_file, :main_file, :summary,
|
48
|
+
:author, :email, :gemspec_file, :class_name, :rubygems_version,
|
49
|
+
:gemfile
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# A reinstall file is very helpful for development
|
54
|
+
def create_reinstall_file
|
55
|
+
file_txt = <<-TXT.unindent
|
56
|
+
#!/usr/bin/env ruby
|
57
|
+
puts `gem uninstall -x #{name}`
|
58
|
+
puts `gem build #{name}.gemspec`
|
59
|
+
Dir.glob("./*.gem").each { |path| puts `gem install #{path}` }
|
60
|
+
TXT
|
61
|
+
file_path = "#{root_dir}/reinstall"
|
62
|
+
File.open(file_path, 'w') do |file|
|
63
|
+
file.write file_txt
|
64
|
+
end
|
65
|
+
`chmod a+x #{file_path}`
|
66
|
+
end
|
67
|
+
|
68
|
+
# prints usage instructions unless the gem name was specified
|
69
|
+
#
|
70
|
+
def usage_io
|
71
|
+
usage = <<-TXT.unindent
|
72
|
+
\nUsage: make_gem <name>
|
73
|
+
TXT
|
74
|
+
raise usage if @name.blank?
|
75
|
+
self
|
76
|
+
end
|
77
|
+
|
78
|
+
# creates a root directory for the gem
|
79
|
+
#
|
80
|
+
def create_root_dir
|
81
|
+
@root_dir = name
|
82
|
+
`mkdir #{root_dir}`
|
83
|
+
puts "created root dir".green
|
84
|
+
self
|
85
|
+
end
|
86
|
+
|
87
|
+
# creates a lib directory for the gme
|
88
|
+
#
|
89
|
+
def create_lib_dir
|
90
|
+
@lib = "#{root_dir}/lib"
|
91
|
+
`mkdir #{lib}`
|
92
|
+
puts "created lib dir".green
|
93
|
+
self
|
94
|
+
end
|
95
|
+
|
96
|
+
# creates a version file for the gem
|
97
|
+
#
|
98
|
+
def create_version_file
|
99
|
+
@version_file = "#{lib}/version.rb"
|
100
|
+
version_text = <<-TXT.unindent
|
101
|
+
module #{class_name}
|
102
|
+
VERSION = '0.0.0'
|
103
|
+
end
|
104
|
+
TXT
|
105
|
+
write file: version_file, text: version_text
|
106
|
+
puts "wrote version file".green
|
107
|
+
self
|
108
|
+
end
|
109
|
+
|
110
|
+
# creates a main file for the gem.
|
111
|
+
# this file explicitly requires each of the gem's dependencies.
|
112
|
+
#
|
113
|
+
def create_main_file
|
114
|
+
@main_file = "#{lib}/#{name}.rb"
|
115
|
+
main_text = <<-TXT.unindent
|
116
|
+
require 'colored'
|
117
|
+
require 'pry'
|
118
|
+
require 'active_support/all'
|
119
|
+
require 'thor'
|
120
|
+
module #{class_name}
|
121
|
+
end
|
122
|
+
Gem.find_files("#{name}/**/*.rb").each &method(:require)
|
123
|
+
TXT
|
124
|
+
write file: main_file, text: main_text
|
125
|
+
puts "wrote main file".green
|
126
|
+
self
|
127
|
+
end
|
128
|
+
|
129
|
+
# prompts for some additional info required for the gemspec
|
130
|
+
#
|
131
|
+
def gemspec_info_io
|
132
|
+
puts "Some info is needed for the gemspec:".red
|
133
|
+
@summary = _prompt "add a one-line summary."
|
134
|
+
@author = _prompt "enter the author's name"
|
135
|
+
@email = _prompt "enter the author's email"
|
136
|
+
@rubygems_version = `gem -v`.chomp
|
137
|
+
self
|
138
|
+
end
|
139
|
+
|
140
|
+
# creates a gemspec file
|
141
|
+
# add gem dependencies here, not the Gemfile
|
142
|
+
# the Gemfile contains a reference to this file
|
143
|
+
#
|
144
|
+
def create_gemspec_file
|
145
|
+
@gemspec_file = "./#{root_dir}/#{name}.gemspec"
|
146
|
+
gemspec_text = <<-TXT.unindent
|
147
|
+
require_relative './lib/version.rb'
|
148
|
+
Gem::Specification.new do |s|
|
149
|
+
s.name = name
|
150
|
+
s.version = #{class_name}::VERSION
|
151
|
+
s.date = "#{Time.now.strftime("%Y-%m-%d")}"
|
152
|
+
s.summary = "#{summary}"
|
153
|
+
s.description = ""
|
154
|
+
s.platform = Gem::Platform::RUBY
|
155
|
+
s.authors = ["#{author}"]
|
156
|
+
s.email = '#{email}'
|
157
|
+
s.homepage = "http://github.com/maxpleaner/gemmy"
|
158
|
+
s.files = Dir["lib/**/*.rb", "bin/*", "**/*.md", "LICENSE"]
|
159
|
+
s.require_path = 'lib'
|
160
|
+
s.required_rubygems_version = ">= #{rubygems_version}"
|
161
|
+
s.executables = Dir["bin/*"].map &File.method(:basename)
|
162
|
+
s.add_dependency "colored", '~> 1.2'
|
163
|
+
s.add_dependency 'activesupport', '~> 4.2', '>= 4.2.7'
|
164
|
+
s.add_dependency 'pry', '~> 0.10.4'
|
165
|
+
s.add_dependency 'thor', '~> 0.19.4'
|
166
|
+
s.license = 'MIT'
|
167
|
+
end
|
168
|
+
TXT
|
169
|
+
write file: gemspec_file, text: gemspec_text
|
170
|
+
puts "wrote gemspec".green
|
171
|
+
self
|
172
|
+
end
|
173
|
+
|
174
|
+
# creates a gemfile which contains a reference to the gemspec
|
175
|
+
# in a ruby gem, gems are listed in the gemspec, not the gemfile.
|
176
|
+
#
|
177
|
+
def create_gemfile
|
178
|
+
@gemfile = "./#{root_dir}/Gemfile"
|
179
|
+
gemfile_text = <<-TXT.unindent
|
180
|
+
source "http://www.rubygems.org"
|
181
|
+
gemspec
|
182
|
+
TXT
|
183
|
+
write file: gemfile, text: gemfile_text
|
184
|
+
puts "wrote gemfile".green
|
185
|
+
self
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
|
data/lib/gemmy/tasks.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module Gemmy::Tests::ComponentTests::DynamicStepsTests
|
2
|
+
|
3
|
+
Gemmy::Patches.refinements.each { |r| using r }
|
4
|
+
|
5
|
+
def self.run
|
6
|
+
runner_class = Class.new
|
7
|
+
runner_class.include Gemmy::Components::DynamicSteps
|
8
|
+
runner = runner_class.new
|
9
|
+
runner.define_step(/(.+) case (.+)/) do |a,b|
|
10
|
+
error("failed") unless (a=='test') && (b=='pass')
|
11
|
+
end
|
12
|
+
puts " define_step".blue
|
13
|
+
runner.step "test case pass"
|
14
|
+
begin
|
15
|
+
runner.step "test case fail"
|
16
|
+
rescue RuntimeError => e
|
17
|
+
error("unexpected fail") unless e.message==("failed")
|
18
|
+
puts " step".blue
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module Gemmy::Tests
|
2
|
+
|
3
|
+
module PatchTests
|
4
|
+
|
5
|
+
def self.run
|
6
|
+
# call each of the patch_class methods
|
7
|
+
%i{
|
8
|
+
array_test symbol_test method_test thread_test
|
9
|
+
global_test string_test hash_test
|
10
|
+
}.each { |method_name| PatchedClass.send method_name }
|
11
|
+
end
|
12
|
+
|
13
|
+
module Error
|
14
|
+
# Allow using raise with one argument only
|
15
|
+
# raises RuntimeError
|
16
|
+
#
|
17
|
+
# this is included in the global patches,
|
18
|
+
# but the test suite doesn't depend on it.
|
19
|
+
#
|
20
|
+
# @param msg [String]
|
21
|
+
#
|
22
|
+
def error(msg='')
|
23
|
+
raise(RuntimeError, msg)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class PatchedClass
|
28
|
+
|
29
|
+
Gemmy::Patches.refinements.each { |r| using r }
|
30
|
+
|
31
|
+
extend Gemmy::Tests::PatchTests::Error
|
32
|
+
|
33
|
+
def self.thread_test
|
34
|
+
# Threads abort on exception
|
35
|
+
Thread.new { fail }
|
36
|
+
sleep 0.25
|
37
|
+
error "thread didn't bubble up error"
|
38
|
+
rescue
|
39
|
+
puts " Threads abort on exception".blue
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.hash_test
|
43
|
+
# Hash#autovivified
|
44
|
+
hash = {}.autovivified
|
45
|
+
hash[:a][:b] = 0
|
46
|
+
error unless hash[:a][:b] == 0
|
47
|
+
puts " Hash#autovivified".blue
|
48
|
+
|
49
|
+
# Hash#bury
|
50
|
+
hash = { a: { b: { } } }
|
51
|
+
hash.bury(:a, :b, :c, 0)
|
52
|
+
error unless hash[:a][:b][:c] == 0
|
53
|
+
puts " Hash#bury".blue
|
54
|
+
|
55
|
+
# Hash#persisted
|
56
|
+
db = "test_db.yaml"
|
57
|
+
hash = {}.persisted db
|
58
|
+
hash.set(:a, :b, 0)
|
59
|
+
error unless hash.get(:a, :b) == 0
|
60
|
+
error unless hash.get(:a, :b, disk: true) == 0
|
61
|
+
error unless YAML.load(File.read db)[:data][:a][:b] == 0
|
62
|
+
File.delete(db)
|
63
|
+
puts " Hash#persisted".blue
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.array_test
|
67
|
+
# Array#any_not?
|
68
|
+
false_case = [[]].any_not? &:empty?
|
69
|
+
true_case = [[1]].any_not? &:empty?
|
70
|
+
error unless true_case && !false_case
|
71
|
+
puts " Array#any_not?".blue
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.symbol_test
|
75
|
+
# Symbol#with
|
76
|
+
result = [[]].map &:concat.with([1])
|
77
|
+
error unless result == [[1]]
|
78
|
+
puts " Symbol#with".blue
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.method_test
|
82
|
+
# Method#bind
|
83
|
+
def self.add(a,b)
|
84
|
+
a + b
|
85
|
+
end
|
86
|
+
result = [0].map &method(:add).bind(1)
|
87
|
+
error unless result == [1]
|
88
|
+
puts " Method#bind".blue
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.global_test
|
92
|
+
# m is an alias for method
|
93
|
+
def self.sample_method(x)
|
94
|
+
x
|
95
|
+
end
|
96
|
+
result = [0].map &m(:sample_method)
|
97
|
+
error unless result == [0]
|
98
|
+
puts " Object#m".blue
|
99
|
+
|
100
|
+
# error_if_blank
|
101
|
+
# error an error if an object is blank
|
102
|
+
blank = nil
|
103
|
+
not_blank = 0
|
104
|
+
error_if_blank not_blank
|
105
|
+
begin
|
106
|
+
error_if_blank blank
|
107
|
+
error("this won't be raised")
|
108
|
+
rescue RuntimeError => e
|
109
|
+
error if e.message == "this won't be raised"
|
110
|
+
puts " Object#error_if_blank".blue
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.string_test
|
115
|
+
# String#unindent
|
116
|
+
result = " 0".unindent
|
117
|
+
error unless result == "0"
|
118
|
+
puts " String#unindent".blue
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
TestSets << PatchTests
|
126
|
+
|
127
|
+
end
|
data/lib/gemmy/tests.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# Test out the gem
|
2
|
+
# Can be called from the command line: gemmy test
|
3
|
+
#
|
4
|
+
module Gemmy::Tests
|
5
|
+
TestSets = []
|
6
|
+
|
7
|
+
def self.run
|
8
|
+
TestSets.each &method(:run_test_class)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.run_test_class(klass)
|
12
|
+
klass.run
|
13
|
+
puts "#{klass} passed".green
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
data/lib/gemmy.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'colored'
|
3
|
+
require 'active_support/all'
|
4
|
+
require 'pry'
|
5
|
+
require 'colored'
|
6
|
+
|
7
|
+
# Container class for all the functionality.
|
8
|
+
#
|
9
|
+
class Gemmy
|
10
|
+
|
11
|
+
# There are two main usages:
|
12
|
+
# - namespaced (using refinements and explicit include/extend calls)
|
13
|
+
# - global
|
14
|
+
#
|
15
|
+
# This is the method handling the global case
|
16
|
+
#
|
17
|
+
def self.load_globally
|
18
|
+
Patches.core_patches.each do |core_klass_name, patch_klass|
|
19
|
+
core_klass_name.to_s.constantize.include patch_klass
|
20
|
+
end
|
21
|
+
Components.list.each do |patch_klass|
|
22
|
+
Object.include patch_klass
|
23
|
+
Object.extend patch_klass
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Load files in order of their "/" count
|
29
|
+
# to preserve constant hierarchies
|
30
|
+
#
|
31
|
+
Gem.find_files("gemmy/**/*.rb").sort_by do |x|
|
32
|
+
x.split("/").length
|
33
|
+
end.each &method(:require)
|
34
|
+
|
metadata
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gemmyrb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- max pleaner
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-12-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: colored
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activesupport
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '4.2'
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: 4.2.7
|
37
|
+
type: :runtime
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - "~>"
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '4.2'
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 4.2.7
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: pry
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 0.10.4
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: 0.10.4
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: byebug
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: 9.0.6
|
68
|
+
type: :runtime
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: 9.0.6
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: thor
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: 0.19.4
|
82
|
+
type: :runtime
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: 0.19.4
|
89
|
+
description: ''
|
90
|
+
email: maxpleaner@gmail.com
|
91
|
+
executables:
|
92
|
+
- gemmy
|
93
|
+
extensions: []
|
94
|
+
extra_rdoc_files: []
|
95
|
+
files:
|
96
|
+
- README.md
|
97
|
+
- bin/gemmy
|
98
|
+
- lib/gemmy.rb
|
99
|
+
- lib/gemmy/cli.rb
|
100
|
+
- lib/gemmy/components.rb
|
101
|
+
- lib/gemmy/components/dynamic_steps.rb
|
102
|
+
- lib/gemmy/patches.rb
|
103
|
+
- lib/gemmy/patches/array_patch.rb
|
104
|
+
- lib/gemmy/patches/hash_patch.rb
|
105
|
+
- lib/gemmy/patches/method_patch.rb
|
106
|
+
- lib/gemmy/patches/object_patch.rb
|
107
|
+
- lib/gemmy/patches/string_patch.rb
|
108
|
+
- lib/gemmy/patches/symbol_patch.rb
|
109
|
+
- lib/gemmy/patches/thread_patch.rb
|
110
|
+
- lib/gemmy/tasks.rb
|
111
|
+
- lib/gemmy/tasks/make_gem.rb
|
112
|
+
- lib/gemmy/tests.rb
|
113
|
+
- lib/gemmy/tests/component_tests.rb
|
114
|
+
- lib/gemmy/tests/component_tests/dynamic_steps_tests.rb
|
115
|
+
- lib/gemmy/tests/patch_test.rb
|
116
|
+
- lib/gemmy/version.rb
|
117
|
+
homepage: http://github.com/maxpleaner/gemmy
|
118
|
+
licenses:
|
119
|
+
- MIT
|
120
|
+
metadata: {}
|
121
|
+
post_install_message:
|
122
|
+
rdoc_options: []
|
123
|
+
require_paths:
|
124
|
+
- lib
|
125
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
126
|
+
requirements:
|
127
|
+
- - ">="
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: '0'
|
130
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
131
|
+
requirements:
|
132
|
+
- - ">="
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: 1.3.6
|
135
|
+
requirements: []
|
136
|
+
rubyforge_project:
|
137
|
+
rubygems_version: 2.5.1
|
138
|
+
signing_key:
|
139
|
+
specification_version: 4
|
140
|
+
summary: general utils
|
141
|
+
test_files: []
|