gemmyrb 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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: []
|