Skip to content

Commit eeade9f

Browse files
committed
Going public. v0.0.1 🚀
- It is pre-release for test and experimentation.
0 parents  commit eeade9f

11 files changed

+323
-0
lines changed

.editorconfig

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
root = true
2+
3+
[*.cr]
4+
charset = utf-8
5+
end_of_line = lf
6+
insert_final_newline = true
7+
indent_style = space
8+
indent_size = 2
9+
trim_trailing_whitespace = true

.gitignore

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/docs/
2+
/lib/
3+
/bin/
4+
/.shards/
5+
*.dwarf
6+
7+
# Libraries don't need dependency lock
8+
# Dependencies will be locked in application that uses them
9+
/shard.lock

.travis.yml

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
language: crystal
2+
3+
script:
4+
- crystal spec
5+
- crystal tool format --check

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
### [0.0.1] - 2019-02-21
2+
3+
* Going public! 🚀
4+
5+
[0.0.1]: https://github.com/rodrigopinto/can_use/releases/tag/v0.0.1

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2019 Rodrigo Pinto
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in
13+
all copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
THE SOFTWARE.

README.md

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# can_use
2+
3+
🤔 Can I use this feature? Yes, sure!
4+
5+
> CanUse is a minimalist feature toggle/flag for crystal, based on yaml file.
6+
7+
[![GitHub release](https://img.shields.io/github/release/rodrigopinto/can_use.svg)](https://github.com/rodrigopinto/can_use/releases)
8+
9+
[![Build Status](https://travis-ci.org/rodrigopinto/can_use.svg?branch=master)](https://travis-ci.org/rodrigopinto/can_use)
10+
11+
## Installation
12+
13+
1. Add the dependency to your `shard.yml`:
14+
15+
```yaml
16+
dependencies:
17+
can_use:
18+
github: rodrigopinto/can_use
19+
```
20+
2. Run `shards install`
21+
22+
## Usage
23+
24+
25+
1. Require the library on your code base.
26+
27+
```crystal
28+
require "can_use"
29+
```
30+
31+
2. Create a file with the features toggle definitions.
32+
We suggest to name it as `featutes.yaml`, but it is up to you.
33+
34+
**Note:** The `defaults` block is mandatory, as it will be used as fallback when values are not defined on the environment set on `configuration`. Example:
35+
36+
```yaml
37+
defaults:
38+
new_payment_flow: false
39+
rating_service: false
40+
41+
development:
42+
new_payment_flow: true
43+
rating_service: false
44+
45+
your_environment:
46+
new_payment_flow: true
47+
```
48+
49+
3. Configure the environment and the path to the yaml.
50+
51+
```crystal
52+
CanUse.configure do |config|
53+
config.file = "path/to/features.yaml"
54+
config.environment = "your_environment"
55+
end
56+
```
57+
58+
4. Verify if a feature is toggled on/off
59+
60+
```crystal
61+
if CanUse.feature?("new_payment_flow")
62+
# do_something
63+
end
64+
```
65+
66+
67+
## Development
68+
69+
1. Install the dependencies.
70+
71+
```bash
72+
$ shards install
73+
```
74+
75+
2. Implement and test your changes.
76+
77+
```bash
78+
$ crystal spec
79+
```
80+
81+
82+
3. Run fomart tool to verify code style.
83+
84+
```bash
85+
$ crystal tool format
86+
```
87+
88+
## TODO
89+
90+
- [ ] Add ability to toggle on/off a feature programatically, ex: `CanUse.enable("feature")`.
91+
- [ ] Allows ENVIRONMENT variables to set/override a value for a key.
92+
93+
## Contributing
94+
95+
1. Fork it (<https://github.com/rodrigopinto/can_use/fork>)
96+
2. Create your feature branch (`git checkout -b my-new-feature`)
97+
3. Commit your changes (`git commit -am 'Add some feature'`)
98+
4. Push to the branch (`git push origin my-new-feature`)
99+
5. Create a new Pull Request
100+
101+
## Credits
102+
103+
This shard was initially inspired by [can_do][1] (Ruby).
104+
105+
## Contributors
106+
107+
- [Rodrigo Pinto](https://github.com/rodrigopinto) - creator and maintainer
108+
109+
[1]: https://github.com/blacklane/can_do

shard.yml

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
name: can_use
2+
version: 0.0.1
3+
4+
authors:
5+
- Rodrigo Pinto <contato@rodrigopinto.me>
6+
7+
crystal: 0.27.2
8+
9+
license: MIT

spec/can_use_spec.cr

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
require "./spec_helper"
2+
3+
describe CanUse do
4+
describe ".configure" do
5+
context "when file is not set on the configuration" do
6+
it "raises an error" do
7+
expect_raises CanUse::Error, "feature_one not found" do
8+
CanUse.feature?("feature_one")
9+
end
10+
end
11+
end
12+
end
13+
14+
describe ".feature?" do
15+
CanUse.configure do |config|
16+
config.file = "spec/fixture/features.yml"
17+
end
18+
19+
it "raises an error when a feature key does not exist" do
20+
expect_raises CanUse::Error, "does_not_exist not found" do
21+
CanUse.feature?("does_not_exist")
22+
end
23+
end
24+
25+
context "accepts a block" do
26+
it "evaluates it when feature? returns true" do
27+
evaluated = false
28+
29+
CanUse.feature?("feature_one") do
30+
evaluated = true
31+
end
32+
33+
evaluated.should be_true
34+
end
35+
36+
it "does not evaluate it when feature? returns false" do
37+
evaluated = false
38+
39+
CanUse.feature?("feature_two") do
40+
evaluated = true
41+
end
42+
43+
evaluated.should be_false
44+
end
45+
end
46+
47+
context "with no environment defined" do
48+
it "uses the defaults value" do
49+
allowed = CanUse.feature?("feature_one")
50+
allowed.should be_true
51+
52+
allowed = CanUse.feature?("feature_two")
53+
allowed.should be_false
54+
end
55+
end
56+
57+
context "when development environment defined" do
58+
Spec.before_each do
59+
CanUse.configure do |config|
60+
config.environment = "development"
61+
end
62+
end
63+
64+
it "uses the defaults value" do
65+
allowed = CanUse.feature?("feature_one")
66+
allowed.should be_true
67+
end
68+
69+
it "uses development environment if the key exists" do
70+
allowed = CanUse.feature?("feature_two")
71+
allowed.should be_true
72+
end
73+
end
74+
end
75+
end

spec/fixture/features.yml

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
defaults:
2+
feature_one: true
3+
feature_two: false
4+
development:
5+
feature_two: true
6+
production:
7+
feature_one: false

spec/spec_helper.cr

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
require "spec"
2+
require "../src/can_use"

src/can_use.cr

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
require "yaml"
2+
3+
# CanUse is a minimalist feature toggle for Crystal.
4+
module CanUse
5+
VERSION = "0.1.0"
6+
7+
@@features = Hash(YAML::Any, YAML::Any).new
8+
@@config = Config.new
9+
10+
# Sets the configuration for `CanUse`
11+
#
12+
# ### Example
13+
#
14+
# ```
15+
# CanUse.configure do |config|
16+
# config.environment = "development"
17+
# config.file = "config/features.yml"
18+
# end
19+
# ```
20+
def self.configure
21+
yield @@config
22+
@@features = YAML.parse(File.read(@@config.file)).as_h
23+
end
24+
25+
# Returns `true` or `false` based on the feature configuration.
26+
# Otherwise, reads from the default value.
27+
#
28+
# ### Example
29+
#
30+
# ```
31+
# if CanUse.feature?("feature_one")
32+
# # do_something
33+
# end
34+
# ```
35+
def self.feature?(name : String)
36+
features.fetch(name, defaults[name]).raw
37+
rescue KeyError
38+
raise Error.new("#{name} not found")
39+
end
40+
41+
# Evaluates the block if the feature returns `true`..
42+
#
43+
# ### Example
44+
#
45+
# ```
46+
# CanUse.feature?("feature_one") do
47+
# puts "Yeah, I cam use!"
48+
# end
49+
# ```
50+
def self.feature?(name : String, &block)
51+
return unless feature?(name)
52+
53+
yield
54+
end
55+
56+
private def self.features : Hash(YAML::Any, YAML::Any)
57+
@@features[@@config.environment].as_h
58+
end
59+
60+
private def self.defaults : Hash(YAML::Any, YAML::Any)
61+
@@features[Config::DEFAULTS].as_h
62+
end
63+
64+
class Error < Exception; end
65+
66+
private class Config
67+
DEFAULTS = "defaults"
68+
69+
property file : String = ""
70+
property environment = DEFAULTS
71+
end
72+
end

0 commit comments

Comments
 (0)