mdspec 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MzU5ZDc5YTcwNTY5OWI2ODFjMWMxNzVhNDIyNDEyZTBiYTYwYzhmMQ==
5
+ data.tar.gz: !binary |-
6
+ ZWJhYTk2MDJiYThjZmRiNmVhZjEyOThkZjc5ZjAzNWZjYmU4MGRmMg==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ YTRlMDY5ZWRjNWI1NGYwYzM1NjVhZWIwYjUzNzY4ODA4ZjhjNWEzYzZhOTc1
10
+ M2E2YmZlZWNlY2Y3NTkyZjg4NmJmYmNiODY0YzI1ZTgzOTQ4ZTczNzg1MWJl
11
+ NmQ5OWJmNTM5ZDUyMzE2NTYwOGM2ZDI3YWYwMGJjOGMyNDFkNTM=
12
+ data.tar.gz: !binary |-
13
+ MzE4Yjg4MzZiMWUyODY3MzUwZDcxMmJhMzY5NWVjMmEwZTk4MDBmOWIzNDFj
14
+ ODk4Yzg2YTFlMWY2ZDExNWE3MzI0NjdhNWZiOWQwOTZlNWI2YTY4YWY1Nzk4
15
+ ZjAzNjVjMGEwZjQ2ZjcyZmVkYTJhZjYyMGQwN2ExMTNiNGVkN2E=
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
@@ -0,0 +1,16 @@
1
+ # global rules
2
+ rules:
3
+ # pre-defined rule to run each command
4
+ shell: bash-repl
5
+
6
+ # it is able to define a custom command
7
+ yml:
8
+ type: command
9
+ command: yaml-lint
10
+
11
+ # rules for specified files
12
+ README.md:
13
+ # match pattern
14
+ /Config/:
15
+ type: diff
16
+ diff: .mdspec.yml
@@ -0,0 +1,26 @@
1
+ language: ruby
2
+
3
+ rvm:
4
+ - "2.3.0"
5
+ - "2.2"
6
+ - "2.1"
7
+ - "2.0"
8
+ - "1.9"
9
+
10
+ cache:
11
+ bundler: true
12
+
13
+ script:
14
+ - bundle exec rake
15
+ - bundle exec mdspec README.md
16
+
17
+ deploy:
18
+ provider: rubygems
19
+ api_key:
20
+ master:
21
+ secure: "Q26pMTrS3l/JHFbo/Qk/rhmLhYz6oMJppENKwiaBuR1TjzOpavgUAaFJpgUUgl0P+iHy0Oja3YBoSUm4TEacYjiZfH3FFD8TYsGCuQbN4dvmH9xhn7yycS3zTICTWi6iY0GY1h/k6kjre6gxpDOTQt/O0PBrBN4aa3hjBfbZSXCRMI2aAhJm82mU5q14QIfmJYepXnO5XJ9sZleK7RDDddm/ey3a3XKkR3Tx6kQwrU8u6GJWJRXBVC3dMHYD2BVgWk8AawttSZPJezgPoUrp53p4Ha15fJA+KEWf64azPvqsY4FrofYVh3+mjuHi/BVXJC0NAaI6ijHEkZsFnpA8ERZ6M5XjQ3nsrICqc8lwJwHBAY+sHlZc1W2NrFbg3HDokca/efl+5MHkr9YHOnQdINGAH48CXdMX98bOonhaNwhe1NLADTzDklUI/nzj17t4RsgoB5qZcJ6q6blmKeJql6Q6PsW4xLpTmMGY5aDPulPFJRPqJYy9UXwT82MkBWrMc7B3QpkQ8Fb0FWaxNGa3+dCteUAU5iU7eqFZscgJyPJlpF5R/ELbSHx+bO/pWDASd5iRHQ+97ca6taW7eSdloPJEP1YKMYo2X0I5kfzMdmjxO1EW632yQq1oqVywE1yDM1FtuMwMTbg1VWenn6Ql6U0pFvx1WHCEg3mlj9mZmM4="
22
+ gem:
23
+ master: mdspec
24
+ on:
25
+ repo: mdspec/mdspec
26
+ ruby: "2.3.0"
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in mdspec.gemspec
4
+ gemspec
@@ -0,0 +1,59 @@
1
+ # MdSpec: Testable Markdown for Documentation Testing :memo:
2
+
3
+ **MdSpec** is a testing tool for example codes of your markdown files like ***README.md***.
4
+
5
+ ## Usage
6
+
7
+ ```shell
8
+ $ mdspec --version
9
+ MdSpec version ***
10
+ $ mdspec README.md
11
+
12
+ README.md
13
+ Usage
14
+ should pass `$ mdspec --version`
15
+ should pass `$ mdspec README.md`
16
+ Example of Configurations
17
+ should be .mdspec.yml
18
+ should pass yaml-lint
19
+
20
+ Finished in *** seconds
21
+ 4 examples, 0 failures
22
+
23
+ ```
24
+
25
+ ## Example of Configurations
26
+
27
+ ```yml
28
+ # global rules
29
+ rules:
30
+ # pre-defined rule to run each command
31
+ shell: bash-repl
32
+ # it is able to define a custom command
33
+ yml:
34
+ type: command
35
+ command: yaml-lint
36
+
37
+ # rules for specified files
38
+ README.md:
39
+ # match pattern
40
+ /Config/:
41
+ type: diff
42
+ diff: .mdspec.yml
43
+ ```
44
+
45
+ ## Development
46
+
47
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
48
+
49
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
50
+
51
+ ## Contributing
52
+
53
+ 1. Fork it
54
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
55
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
56
+ 4. Push to the branch (`git push origin my-new-feature`)
57
+ 5. Create new Pull Request
58
+
59
+ Bug reports and pull requests are welcome on GitHub at https://github.com/mdspec/mdspec.
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
3
+
4
+ require "rspec/core/rake_task"
5
+ RSpec::Core::RakeTask.new(:spec) do |t|
6
+ t.rspec_opts = ["--format doc", "--color"]
7
+ end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "mdspec"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "mdspec"
4
+ MdSpec::CLI.new(ARGV.clone)
@@ -0,0 +1,12 @@
1
+ require "optparse"
2
+ require "yaml"
3
+ require "kramdown"
4
+ require "colorize"
5
+ require "mdspec/version"
6
+ require "mdspec/parser"
7
+ require "mdspec/runner"
8
+ require "mdspec/runner/base_runner"
9
+ require "mdspec/runner/diff"
10
+ require "mdspec/runner/bash_repl"
11
+ require "mdspec/runner/command"
12
+ require "mdspec/cli"
@@ -0,0 +1,124 @@
1
+ module MdSpec
2
+ class CLI
3
+ def initialize(argv)
4
+ parse_options(argv)
5
+ exit 0 if within_mdspec?
6
+ @config = ::YAML.parse(::File.read(".mdspec.yml")).to_ruby
7
+ @global_rules = config.fetch("rules", [])
8
+ normalize! global_rules
9
+ @rules = global_rules
10
+
11
+ # load and run markdown
12
+ render argv.map(&load_and_run)
13
+ end
14
+
15
+ private
16
+ attr_reader :config
17
+ attr_reader :global_rules
18
+ attr_reader :rules
19
+
20
+ def parse_options(argv)
21
+ opts = ::OptionParser.new
22
+
23
+ opts.on_tail("--version", "Show version") do
24
+ puts "MdSpec version #{VERSION}"
25
+ exit
26
+ end
27
+
28
+ opts.parse!(argv)
29
+ end
30
+
31
+ def render(results, level = 0)
32
+ puts ""
33
+ @num = 0
34
+ @ok = 0
35
+ render_results results
36
+ puts ""
37
+ puts "Finished in 1 seconds"
38
+ puts "#{@num} examples, #{@num - @ok} failures"
39
+ puts ""
40
+ end
41
+
42
+ def render_results(results, level = 0)
43
+ results.each do |result|
44
+ unless result[:flat]
45
+ print " " * level
46
+ if result[:childs].nil?
47
+ @num += 1
48
+ @ok += 1 if result[:result]
49
+ if result[:result]
50
+ puts result[:name].colorize(:green)
51
+ else
52
+ puts result[:name].colorize(:red)
53
+ end
54
+ else
55
+ puts result[:name]
56
+ end
57
+ end
58
+ next_level = result[:flat] ? level : level + 1
59
+ render_results result[:childs], next_level unless result[:childs].nil?
60
+ end
61
+ end
62
+
63
+ def within_mdspec?
64
+ ENV["MDSPEC_WITHIN_MDSPEC"].to_i >= 2
65
+ end
66
+
67
+ def normalize!(rules)
68
+ rules.each do |k, v|
69
+ rules[k] = symbolize({ :pattern => pattern_for(k) }.merge!(v.is_a?(Hash) ? v : { :type => v }))
70
+ end
71
+ end
72
+
73
+ def symbolize(hash)
74
+ hash.reduce({}) do |nxt, (k, v)|
75
+ nxt[k.to_sym] = v
76
+ nxt
77
+ end
78
+ end
79
+
80
+ def pattern_for(k)
81
+ ::Regexp.compile(k.match(/^\/.*\/$/) ? k[1..-2] : "^#{k}$")
82
+ end
83
+
84
+ def load_and_run
85
+ lambda do |path|
86
+ markdown = ::File.read(path)
87
+ local_rules = config.fetch(path, {})
88
+ normalize! local_rules
89
+ @rules = global_rules.merge!(local_rules)
90
+
91
+ root = { :name => path, :result => true, :childs => [] }
92
+ Parser.new(markdown).codeblocks.select(&match?).each do |cb|
93
+ block_root = { :name => cb.name, :childs => [] }
94
+ [cb.name, cb.lang].each do |key|
95
+ block_root[:childs].push(run(key, cb)) if match_pattern?(key)
96
+ end
97
+ root[:childs].push(block_root)
98
+ end
99
+
100
+ root
101
+ end
102
+ end
103
+
104
+ def run(key, codeblock)
105
+ if opts = ruleopts(key)
106
+ Runner.for(opts[1]).run(codeblock)
107
+ end
108
+ end
109
+
110
+ def ruleopts(key)
111
+ rules.find {|_, v| v[:pattern] === key }
112
+ end
113
+
114
+ def match?
115
+ lambda do |cb|
116
+ match_pattern?(cb.lang) or match_pattern?(cb.name)
117
+ end
118
+ end
119
+
120
+ def match_pattern?(item)
121
+ rules.any? {|_, v| v[:pattern] === item }
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,48 @@
1
+ module MdSpec
2
+ class Parser
3
+ attr_reader :codeblocks
4
+
5
+ def initialize(text)
6
+ @codeblocks = []
7
+ parse text
8
+ end
9
+
10
+ private
11
+ attr_reader :last_header
12
+
13
+ def parse(text)
14
+ @last_header = nil
15
+ walk ::Kramdown::Document.new(text, :input => "GFM").root
16
+ end
17
+
18
+ def walk(node)
19
+ codeblocks.push(codeblock(node)) if node.type == :codeblock
20
+ @last_header = header(node)
21
+ node.children.each {|child| walk child }
22
+ end
23
+
24
+ def header(node)
25
+ if node.type == :header
26
+ node.children.first.value
27
+ elsif node.type == :text && node.value.match(/^#/)
28
+ node.value.match(/^#+(.*)/)[1].strip
29
+ elsif node.type == :codeblock
30
+ nil
31
+ else
32
+ last_header
33
+ end
34
+ end
35
+
36
+ def codeblock(node)
37
+ ::Struct.new(:src, :name, :lang).new(node.value, last_header, lang(node))
38
+ end
39
+
40
+ def lang(node)
41
+ if name = node.attr["class"]
42
+ name.match(/^language-(.*)/)[1]
43
+ else
44
+ "text"
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,14 @@
1
+ module MdSpec
2
+ module Runner
3
+ class << self
4
+ def for(opts)
5
+ const_get(classname(opts[:type])).new(opts)
6
+ end
7
+
8
+ def classname(type)
9
+ raise "illegal type" unless /^[a-zA-Z0-9\-]+$/ === type
10
+ type.capitalize.gsub(/-[a-z]/) {|m| m[1..-1].capitalize }
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,21 @@
1
+ module MdSpec::Runner
2
+ class BaseRunner
3
+ def initialize(opts)
4
+ @opts = opts
5
+ end
6
+
7
+ protected
8
+ attr_reader :opts
9
+
10
+ def sh(cmd, output, opts = {})
11
+ ::IO.popen(cmd, "r+") do |io|
12
+ io.puts opts[:input] unless opts[:input].nil?
13
+ io.close_write
14
+ while line = io.gets
15
+ output.puts line
16
+ end
17
+ end
18
+ $?.to_i # exit status
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,59 @@
1
+ module MdSpec::Runner
2
+ class BashRepl < BaseRunner
3
+ WITHIN_KEY = "MDSPEC_WITHIN_MDSPEC"
4
+
5
+ def initialize(_)
6
+ super
7
+ @prompt = opts.fetch(:prompt, "$ ")
8
+ end
9
+
10
+ def run(codeblock)
11
+ root = { :name => "repl", :childs => [], :flat => true }
12
+ each_with_counter(examples_for(codeblock)) do |example|
13
+ output = ::StringIO.new
14
+ status = sh(example[:cmd], output)
15
+ root[:childs].push(
16
+ :name => "should pass \`$ #{example[:cmd]}\`",
17
+ :result => status === 0 && same?(example[:expected], output.string.split("\n")),
18
+ :output => output.string,
19
+ )
20
+ end
21
+ root
22
+ end
23
+
24
+ private
25
+ attr_reader :prompt
26
+
27
+ def each_with_counter(items, &b)
28
+ ENV[WITHIN_KEY] = (ENV.fetch(WITHIN_KEY, 0).to_i + 1).to_s
29
+ items.each(&b)
30
+ ENV[WITHIN_KEY] = (ENV[WITHIN_KEY].to_i - 1).to_s
31
+ end
32
+
33
+ def examples_for(codeblock)
34
+ codeblock.src.split($/).reduce([]) do |examples, line|
35
+ if line.start_with?(prompt)
36
+ examples.push(:cmd => line[prompt.length..-1], :expected => [])
37
+ else
38
+ examples.last[:expected].push(line) unless examples.empty?
39
+ end
40
+ examples
41
+ end
42
+ end
43
+
44
+ def same?(a, b)
45
+ # return false if a.length < b.length
46
+ b.map! {|s| s.uncolorize }
47
+ while not b.empty?
48
+ la = a.shift.split
49
+ lb = b.shift.split
50
+ la.each_with_index {|_, k| return false unless same_word?(la[k], lb[k]) }
51
+ end
52
+ return true
53
+ end
54
+
55
+ def same_word?(a, b)
56
+ /^\*{3,}/ === a || a === b
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,9 @@
1
+ module MdSpec::Runner
2
+ class Command < BaseRunner
3
+ def run(codeblock)
4
+ output = ::StringIO.new
5
+ status = sh(opts[:command], output)
6
+ { :name => "should pass #{opts[:command]}", :result => status === 0 }
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,18 @@
1
+ module MdSpec::Runner
2
+ class Diff < BaseRunner
3
+ def run(codeblock)
4
+ out = ::StringIO.new
5
+ status = sh(command, out, :input => codeblock.src)
6
+ { :name => "should be #{opts[:diff]}", :result => status === 0 }
7
+ end
8
+
9
+ def command
10
+ cmd = []
11
+ cmd << "diff"
12
+ cmd << "--ignore-blank-lines"
13
+ cmd << opts[:diff]
14
+ cmd << "-"
15
+ cmd.join(" ")
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,3 @@
1
+ module MdSpec
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'mdspec/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "mdspec"
8
+ spec.version = MdSpec::VERSION
9
+ spec.authors = ["Hiroyuki Sano"]
10
+ spec.email = ["sh19910711@gmail.com"]
11
+
12
+ spec.summary = %q{Markdown as testable document.}
13
+ spec.homepage = "https://github.com/mdspec/mdspec"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
16
+ spec.bindir = "exe"
17
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_runtime_dependency "colorize", "~> 0.7"
21
+ spec.add_runtime_dependency "kramdown", "~> 1.10"
22
+
23
+ spec.add_development_dependency "bundler"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ spec.add_development_dependency "rspec", "~> 3.4"
26
+ spec.add_development_dependency "yaml-lint", "~> 0.0.7"
27
+ end
metadata ADDED
@@ -0,0 +1,147 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mdspec
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Hiroyuki Sano
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-05-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: colorize
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '0.7'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '0.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: kramdown
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.10'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.10'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: '3.4'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: '3.4'
83
+ - !ruby/object:Gem::Dependency
84
+ name: yaml-lint
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ version: 0.0.7
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ~>
95
+ - !ruby/object:Gem::Version
96
+ version: 0.0.7
97
+ description:
98
+ email:
99
+ - sh19910711@gmail.com
100
+ executables:
101
+ - mdspec
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - .gitignore
106
+ - .mdspec.yml
107
+ - .travis.yml
108
+ - Gemfile
109
+ - README.md
110
+ - Rakefile
111
+ - bin/console
112
+ - bin/setup
113
+ - exe/mdspec
114
+ - lib/mdspec.rb
115
+ - lib/mdspec/cli.rb
116
+ - lib/mdspec/parser.rb
117
+ - lib/mdspec/runner.rb
118
+ - lib/mdspec/runner/base_runner.rb
119
+ - lib/mdspec/runner/bash_repl.rb
120
+ - lib/mdspec/runner/command.rb
121
+ - lib/mdspec/runner/diff.rb
122
+ - lib/mdspec/version.rb
123
+ - mdspec.gemspec
124
+ homepage: https://github.com/mdspec/mdspec
125
+ licenses: []
126
+ metadata: {}
127
+ post_install_message:
128
+ rdoc_options: []
129
+ require_paths:
130
+ - lib
131
+ required_ruby_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ! '>='
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ required_rubygems_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ! '>='
139
+ - !ruby/object:Gem::Version
140
+ version: '0'
141
+ requirements: []
142
+ rubyforge_project:
143
+ rubygems_version: 2.4.5
144
+ signing_key:
145
+ specification_version: 4
146
+ summary: Markdown as testable document.
147
+ test_files: []