From fd62ca7afb727ea503dfcf387d0c2de2b04fe616 Mon Sep 17 00:00:00 2001 From: Gabriel Omar Cotelli Date: Sat, 4 Feb 2017 12:06:01 -0300 Subject: [PATCH 1/2] Add documentation, contribution guidelines, adapted the Readme. [ci skip] --- CONTRIBUTING.md | 33 ++++++++++ README.md | 30 ++++++++- docs/Assertions.md | 154 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 215 insertions(+), 2 deletions(-) create mode 100644 CONTRIBUTING.md create mode 100644 docs/Assertions.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..f505aa0 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,33 @@ +# How to Contribute + +There's several ways to contribute to the project: reporting bugs, sending feedback, proposing ideas for new features, fixing or adding documentation, promoting the project, or even contributing code changes. + +## Reporting issues + +Use the issue tracker in this GitHub repository. + +## Contributing Code + +- This project is MIT licensed, so any code contribution must be under the same license. +- This project uses [semantic versioning](http://semver.org/), so keep it in mind when you make backwards-incompatible changes. If some backwards incompatible change is made the major version MUST be increased. +- The source code is hosted in this GitHub repository using the filetree format in the `source` folder. The master branch contains the latest changes, feel free to send pull requests or fork the project. +- Code contributions without test cases have a lower probability of being merged into the main branch. + + +- Clone this repository or a fork of it +- Load the corresponding development version evaluating in a Playground: +```smalltalk +Metacello new + baseline: 'Buoy'; + repository: 'filetree://REPO_LOCATION/source'; + load: 'Development'. +``` +where `REPO_LOCATION` is the location of the cloned repo in the local file system. +- Do the changes and save it from Pharo (don't forget to add some test cases) +- Create a branch, commit using the usual Git tooling and open a Pull Request + +## Contributing documentation + +The project documentation is mantained in this repository in the `docs` folder. To contribute some documentation or improve the existing, feel free to create a branch or fork this repository, make your changes and send a pull request. + +Remember the docs are licensed under a CC Attribution-ShareAlike license. diff --git a/README.md b/README.md index aaae32d..cb58ab2 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,37 @@ # Buoy ![logo](https://maxcdn.icons8.com/Color/PNG/48/Transport/buoy-48.png) - + [![Build Status](https://travis-ci.org/ba-st/Buoy.svg?branch=master)](https://travis-ci.org/ba-st/Buoy) [![Coverage Status](https://coveralls.io/repos/github/ba-st/Buoy/badge.svg?branch=master)](https://coveralls.io/github/ba-st/Buoy?branch=master) -This project aims to complement Pharo (www.pharo.org) adding useful extensions. +This project aims to complement [Pharo](www.pharo.org) adding useful extensions. + +## License +The project source code is [MIT](LICENSE) licensed. Any contribution submitted to the code repository is considered to be under the same license. + +The documentation is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/). + +## Assertions + +This library is aimed at providing a simpler way to enforce and check assertions. The main focus point is to use it in the business model. + +### Get started! + +- Download a [Pharo Image and VM](http://get.pharo.org) +- Open a Playground and evaluate: + +```smalltalk +Metacello new + baseline: 'Buoy'; + repository: 'github://ba-st/Buoy:master/source'; + load +``` +- Read the [online tutorial](docs/Assertions.md) + +## Contributing + +If you want to help check the [contribution guidelines.](CONTRIBUTING.md) --- [Icon pack by Icons8](https://icons8.com) diff --git a/docs/Assertions.md b/docs/Assertions.md new file mode 100644 index 0000000..a9588e8 --- /dev/null +++ b/docs/Assertions.md @@ -0,0 +1,154 @@ +# Assertions Tutorial + +For this tutorial we will use a simple model: ISO 3166-1 Alpha-2 codes. This codes are two-letter country codes defined in the corresponding [ISO standard](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2). + +## Single Conditions + +So let start with the most basic condition this kind of code must respect: a valid code consists of exactly two letters. + +Open a playground and `Do it` this: + +```smalltalk +| code | + +code := 'AR'. + +AssertionChecker + enforce: [ code size = 2 ] + because: 'ISO 3166-1 Alpha-2 codes must have exactly two letters' +``` + +Now change `code` to something failing the condition like `'ARG'` and `Do it` again, you should get a Debugger with an `AssertionFailed` exception raised. + +So, to enforce a single condition we can send the message `enforce:because:` to `AssertionChecker` and if the condition is not met an `AssertionFailed` exception is raised including the provided explanation. + +## Multiple Conditions + +Now in the previous example we missed some of the requisites of the standard: a valid code must consists only of letters. So let's refrain our example: + +```smalltalk +| code | + +code := 'AR'. + +AssertionCheckerBuilder new + checking: [ :asserter | + asserter + enforce: [ code size = 2 ] + because: 'ISO 3166-1 Alpha-2 codes must have exactly two letters'; + enforce: [ code allSatisfy: #isLetter ] + because: 'ISO 3166-1 Alpha-2 codes must only contain letters' + ]; + buildAndCheck +``` +Note that in this case we're creating an `AssertionCheckerBuilder` and configuring all the conditions to enforce. Let's try now replacing `code` with `'AR3'` and `Do it` again. By default all the conditions to enforce are tried so you should get an error message combining both explanations, and if you handle the raised exception you can get all the failures sending to it the message `failures`. + +If you want the more usual behavior of stopping after the first failure you can configure the builder to fail fast: + +```smalltalk +| code | + +code := 'AR3'. + +AssertionCheckerBuilder new + failFast; + checking: [ :asserter | + asserter + enforce: [ code size = 2 ] + because: 'ISO 3166-1 Alpha-2 codes must have exactly two letters'; + enforce: [ code allSatisfy: #isLetter ] + because: 'ISO 3166-1 Alpha-2 codes must only contain letters' + ]; + buildAndCheck +``` + +If you `Do it` you will get only the first failure and the next conditions aren't even tried. + +## Conditional Checking + +Sometimes you want to check a condition but only after other conditions are met. So let's make our example more complex: not every two letters combination is a valid code, even for defined codes are several categories. Now we will consider only the officially assigned codes as valid: + +```smalltalk +| code officiallyAssignedCodes | + +code := 'AR'. +officiallyAssignedCodes := #('AR' 'BR' 'US'). + +AssertionCheckerBuilder new + checking: [ :asserter | + asserter + enforce: [ code size = 2 and: [ code allSatisfy: #isLetter ]] + because: 'ISO 3166-1 Alpha-2 codes must have exactly two letters'; + onSuccess: [ :sucessAsserter | + sucessAsserter + enforce: [ officiallyAssignedCodes includes: code ] + because: [ '<1s> is not an officially assigned code' expandMacrosWith: code ] + ]; + ]; + buildAndCheck +``` + +Now here we are introducing two new features: +- First `enforce:because:onSuccess:`, the main idea is that the conditions enforced in the success block will be evaluated only if the outer condition is satisfied. So we can make assumptions about what `code` looks like at this point. +- Second, using a block as the `because:` argument. This avoids creating unnecessary objects because the explanation will only be evaluated if the conditions is not met. In the case the argument is a literal String it makes no difference. + +## Refusing + +Sometimes it's easier to explain a condition using negative logic, so `enforce:because:` has an opposite partner: `refuse:because:`. + +```smalltalk +| code unassignedCodes | + +code := 'AR'. +unassignedCodes := #('LO' 'LP' 'OU'). + +AssertionCheckerBuilder new + checking: [ :asserter | + asserter + enforce: [ code size = 2 and: [ code allSatisfy: #isLetter ]] + because: 'ISO 3166-1 Alpha-2 codes must have exactly two letters'; + onSuccess: [ :sucessAsserter | + sucessAsserter + refuse: [ unassignedCodes includes: code ] + because: [ '<1s> is an unassigned code' expandMacrosWith: code ] + ]; + ]; + buildAndCheck +``` + +## Configuring the error to raise + +If not specified the library will raise `AssertionFailed` when some check fails. If you want to raise a different kind of error there's two ways of configuring it: +- For single condition checks you can use `enforce:because:raising:` or `refuse:because:raising:`. + +```smalltalk +| code | + +code := 'AR'. + +AssertionChecker + enforce: [ code size = 2 ] + because: 'ISO 3166-1 Alpha-2 codes must have exactly two letters' + raising: Error +``` + +- When using the builder you can configure it: + +```smalltalk +| code | + +code := 'AR'. + +AssertionCheckerBuilder new + raising: InstanceCreationFailed; + checking: [ :asserter | + asserter + enforce: [ code size = 2 ] + because: 'ISO 3166-1 Alpha-2 codes must have exactly two letters'; + enforce: [ code allSatisfy: #isLetter ] + because: 'ISO 3166-1 Alpha-2 codes must only contain letters' + ]; + buildAndCheck +``` + +but keep in mind when using the builder that the error to raise must understand `signalAll:` to work. From 37c667fdde67d1840bc5badf0f7d87f1f48472fb Mon Sep 17 00:00:00 2001 From: Gabriel Omar Cotelli Date: Sat, 4 Feb 2017 20:23:01 -0300 Subject: [PATCH 2/2] Fixed some code snippets --- docs/Assertions.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/Assertions.md b/docs/Assertions.md index a9588e8..c50d28e 100644 --- a/docs/Assertions.md +++ b/docs/Assertions.md @@ -71,19 +71,19 @@ Sometimes you want to check a condition but only after other conditions are met. ```smalltalk | code officiallyAssignedCodes | -code := 'AR'. +code := 'AA'. officiallyAssignedCodes := #('AR' 'BR' 'US'). AssertionCheckerBuilder new checking: [ :asserter | asserter enforce: [ code size = 2 and: [ code allSatisfy: #isLetter ]] - because: 'ISO 3166-1 Alpha-2 codes must have exactly two letters'; + because: 'ISO 3166-1 Alpha-2 codes must have exactly two letters' onSuccess: [ :sucessAsserter | sucessAsserter enforce: [ officiallyAssignedCodes includes: code ] because: [ '<1s> is not an officially assigned code' expandMacrosWith: code ] - ]; + ] ]; buildAndCheck ``` @@ -106,12 +106,12 @@ AssertionCheckerBuilder new checking: [ :asserter | asserter enforce: [ code size = 2 and: [ code allSatisfy: #isLetter ]] - because: 'ISO 3166-1 Alpha-2 codes must have exactly two letters'; + because: 'ISO 3166-1 Alpha-2 codes must have exactly two letters' onSuccess: [ :sucessAsserter | sucessAsserter refuse: [ unassignedCodes includes: code ] because: [ '<1s> is an unassigned code' expandMacrosWith: code ] - ]; + ] ]; buildAndCheck ```