From 1554a3f0b8b38412ca80067ba8e7abab023ea4d3 Mon Sep 17 00:00:00 2001 From: Justin Date: Mon, 28 May 2018 21:32:03 -0500 Subject: [PATCH] First project commit --- .editorconfig | 7 +++++ .gitignore | 8 +++++ .travis.yml | 1 + LICENSE | 21 +++++++++++++ README.md | 68 +++++++++++++++++++++++++++++++++++++----- shard.yml | 9 ++++++ spec/docspec_spec.cr | 7 +++++ spec/spec_helper.cr | 2 ++ src/docspec.cr | 68 ++++++++++++++++++++++++++++++++++++++++++ src/docspec/version.cr | 3 ++ 10 files changed, 186 insertions(+), 8 deletions(-) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 LICENSE create mode 100644 shard.yml create mode 100644 spec/docspec_spec.cr create mode 100644 spec/spec_helper.cr create mode 100644 src/docspec.cr create mode 100644 src/docspec/version.cr diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..8f0c87a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,7 @@ +[*.cr] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e3bc6ea --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +/docs/ +/lib/ +/bin/ +/.shards/ + +# Libraries don't need dependency lock +# Dependencies will be locked in application that uses them +/shard.lock diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..ffc7b6a --- /dev/null +++ b/.travis.yml @@ -0,0 +1 @@ +language: crystal diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..aa2bcf2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2018 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md index 21c70f9..0668faa 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,14 @@ # docspec -TODO: Write a description here +A crystal library for automatically testing documentation examples. + +Docspec is crystal's equivalent of a doctest library. + +## Use Cases + +* Docspec integrates testing into your documentation. +* Docspec encourages documentation due to integrated testing. +* Docspec reduces boilerplate code for test cases. ## Installation @@ -9,24 +17,68 @@ Add this to your application's `shard.yml`: ```yaml dependencies: docspec: - github: [your-github-name]/docspec + github: skippi/docspec ``` ## Usage +Specdoc parses source files for any commented codeblocks with code in them. For +each codeblock line with a prefix of `>>`, it executes the line and stores the +result. If the line also had an expression appended with `# =>`, then specdoc +will test that the result equals the appended expression. + +In this example, we will fully doctest `Foo.bar`, while ignoring doctesting for +`Foo.echo`: + ```crystal -require "docspec" +# src/foo.cr + +module Foo + # Returns "hello world". + # + # ``` + # >> Foo.bar # => "hello world" + # + # >> name = "say #{Foo.bar}" + # >> name # => "say hello world" + # ``` + def self.bar + "hello world" + end + + # Prints a string to stdout. + # + # ``` + # Foo.echo("some text") + # + # example_text = Foo.bar # => "hello world" + # Foo.echo(example_text) + # ``` + def self.echo(string) + print(string) + end +end ``` -TODO: Write usage instructions here +Require docspec and doctest the source file: -## Development +```crystal +# spec/foo_spec.cr + +require "../src/docspec" + +Docspec.doctest("src/foo.cr") +``` + +Lastly, run your tests in your project's root directory. -TODO: Write development instructions here +```bash +crystal spec +``` ## Contributing -1. Fork it ( https://github.com/[your-github-name]/docspec/fork ) +1. Fork it ( https://github.com/skippi/docspec/fork ) 2. Create your feature branch (git checkout -b my-new-feature) 3. Commit your changes (git commit -am 'Add some feature') 4. Push to the branch (git push origin my-new-feature) @@ -34,4 +86,4 @@ TODO: Write development instructions here ## Contributors -- [[your-github-name]](https://github.com/[your-github-name]) - creator, maintainer +* [skippi](https://github.com/skippi) - creator, maintainer diff --git a/shard.yml b/shard.yml new file mode 100644 index 0000000..9667ab4 --- /dev/null +++ b/shard.yml @@ -0,0 +1,9 @@ +name: docspec +version: 0.1.0 + +authors: + - skippi + +crystal: 0.24.2 + +license: MIT diff --git a/spec/docspec_spec.cr b/spec/docspec_spec.cr new file mode 100644 index 0000000..467cf2f --- /dev/null +++ b/spec/docspec_spec.cr @@ -0,0 +1,7 @@ +require "./spec_helper" + +describe Docspec do + it "compiles properly" do + Docspec.doctest("./spec/docspec_spec.cr") + end +end diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr new file mode 100644 index 0000000..57f7735 --- /dev/null +++ b/spec/spec_helper.cr @@ -0,0 +1,2 @@ +require "spec" +require "../src/docspec" diff --git a/src/docspec.cr b/src/docspec.cr new file mode 100644 index 0000000..920d8f9 --- /dev/null +++ b/src/docspec.cr @@ -0,0 +1,68 @@ +require "./docspec/*" + +module Docspec + DOCTEST_PREFIX = /^>>/ + DOCTEST_RESULT_PREFIX = /# =>/ + + # Parses *filename* for marked examples to create specs. + macro doctest(filename) + {% code_line? = false %}\ + {% for line, index in `cat #{filename}`.lines %}\ + {% if line.strip =~ /^# ```/ %}\ + {% code_line? = !code_line? %}\ + {% elsif line.strip =~ /^#/ %}\ + {% if code_line? %}\ + Docspec.doctest_code_line({{line.strip}}, {{filename}}, {{index + 1}}) + {% else %}\ + Docspec.doctest_comment({{line.strip}}, {{filename}}, {{index + 1}}) + {% end %}\ + {% end %}\ + {% end %}\ + end + + # :nodoc: + macro doctest_comment(line, filename, row) + {% no_comment_line = line.strip.gsub(/^#/, "") %}\ + {% if no_comment_line =~ /^ {5,}/ %}\ + {% example = no_comment_line.gsub(/^ {5,}/, "") %}\ + Docspec.doctest_example({{example}}, {{filename}}, {{row}}) + {% end %}\ + end + + # :nodoc: + macro doctest_code_line(line, filename, row) + {% no_comment_line = line.strip.gsub(/^#/, "") %}\ + Docspec.doctest_example({{no_comment_line}}, {{filename}}, {{row}}) + end + + # :nodoc: + macro doctest_example(line, filename, row) + {% if line.strip =~ Docspec::DOCTEST_PREFIX %}\ + Docspec.doctest_marked_example({{line}}, {{filename}}, {{row}}) + {% end %}\ + end + + # :nodoc: + macro doctest_marked_example(line, filename, row) + {% doc_expr = line.strip.gsub(Docspec::DOCTEST_PREFIX, "").strip %}\ + {% result_expr = doc_expr.strip.gsub(Docspec::DOCTEST_RESULT_PREFIX, "# =>").strip %}\ + {% if doc_expr.starts_with?("require") %}\ + {{doc_expr.id.strip}} + {% else %}\ + {% expr_tokens = result_expr.split("# =>") %}\ + # {{filename.id}}:{{row.id}} + {% for token, index in expr_tokens %}\ + {% if index == 0 %}\ + observed = ({{token.id.strip}}) + {% else %}\ + describe %(Docspec {{filename.id}}:{{row.id}}) do + it %(({{expr_tokens[0].id.strip}} # => {{token.id.strip}})) do + expected = {{token.id.strip}} + observed.should eq expected + end + end + {% end %}\ + {% end %}\ + {% end %}\ + end +end diff --git a/src/docspec/version.cr b/src/docspec/version.cr new file mode 100644 index 0000000..03783be --- /dev/null +++ b/src/docspec/version.cr @@ -0,0 +1,3 @@ +module Docspec + VERSION = "0.1.0" +end