summaryrefslogtreecommitdiff
path: root/spec/mspec/lib/mspec/commands/mkspec.rb
blob: d10cc35d189b15fd012995e9db4f8b234fcec2aa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
#!/usr/bin/env ruby

require 'rbconfig'
require 'mspec/version'
require 'mspec/utils/options'
require 'mspec/utils/name_map'
require 'mspec/helpers/fs'

class MkSpec
  attr_reader :config

  def initialize
    @config = {
      :constants => [],
      :requires  => [],
      :base      => "core",
      :version   => nil
    }
    @map = NameMap.new true
  end

  def options(argv = ARGV)
    options = MSpecOptions.new "mkspec [options]", 32

    options.on("-c", "--constant", "CONSTANT",
               "Class or Module to generate spec stubs for") do |name|
      config[:constants] << name
    end
    options.on("-b", "--base", "DIR",
               "Directory to generate specs into") do |directory|
      config[:base] = File.expand_path directory
    end
    options.on("-r", "--require", "LIBRARY",
               "A library to require") do |file|
      config[:requires] << file
    end
    options.on("-V", "--version-guard", "VERSION",
               "Specify version for ruby_version_is guards") do |version|
      config[:version] = version
    end
    options.version MSpec::VERSION
    options.help

    options.doc "\n How might this work in the real world?\n"
    options.doc "   1. To create spec stubs for every class or module in Object\n"
    options.doc "     $ mkspec\n"
    options.doc "   2. To create spec stubs for Fixnum\n"
    options.doc "     $ mkspec -c Fixnum\n"
    options.doc "   3. To create spec stubs for Complex in 'superspec/complex'\n"
    options.doc "     $ mkspec -c Complex -r complex -b superspec"
    options.doc ""

    options.parse argv
  end

  def create_directory(mod)
    subdir = @map.dir_name mod, config[:base]

    if File.exist? subdir
      unless File.directory? subdir
        puts "#{subdir} already exists and is not a directory."
        return nil
      end
    else
      mkdir_p subdir
    end

    subdir
  end

  def write_requires(dir, file)
    prefix = config[:base] + '/'
    raise dir unless dir.start_with? prefix
    sub = dir[prefix.size..-1]
    parents = '../' * (sub.split('/').length + 1)

    File.open(file, 'w') do |f|
      f.puts "require_relative '#{parents}spec_helper'"
      config[:requires].each do |lib|
        f.puts "require '#{lib}'"
      end
    end
  end

  def write_version(f)
    f.puts ""
    if version = config[:version]
      f.puts "ruby_version_is #{version} do"
      yield "  "
      f.puts "end"
    else
      yield ""
    end
  end

  def write_spec(file, meth, exists)
    if exists
      out = `#{ruby} #{MSPEC_HOME}/bin/mspec-run --dry-run --unguarded -fs -e '#{meth}' #{file}`
      return if out.include?(meth)
    end

    File.open file, 'a' do |f|
      write_version(f) do |indent|
        f.puts <<-EOS
#{indent}describe "#{meth}" do
#{indent}  it "needs to be reviewed for spec completeness"
#{indent}end
EOS
      end
    end

    puts file
  end

  def create_file(dir, mod, meth, name)
    file = File.join dir, @map.file_name(meth, mod)
    exists = File.exist? file

    write_requires dir, file unless exists
    write_spec file, name, exists
  end

  def run
    config[:requires].each { |lib| require lib }
    constants = config[:constants]
    constants = Object.constants if constants.empty?

    @map.map({}, constants).each do |mod, methods|
      name = mod.chop
      next unless dir = create_directory(name)

      methods.each { |method| create_file dir, name, method, mod + method }
    end
  end

  ##
  # Determine and return the path of the ruby executable.

  def ruby
    ruby = File.join(RbConfig::CONFIG['bindir'],
                     RbConfig::CONFIG['ruby_install_name'])

    ruby.gsub! File::SEPARATOR, File::ALT_SEPARATOR if File::ALT_SEPARATOR

    return ruby
  end

  def self.main
    ENV['MSPEC_RUNNER'] = '1'

    script = new
    script.options
    script.run
  end
end