Skip to content

Commit

Permalink
Merge branch 'master' of git@github.com:bmabey/rspec-story-tmbundle
Browse files Browse the repository at this point in the history
  • Loading branch information
bmabey committed Jun 1, 2008
2 parents 60adf61 + b18f42c commit 24ae381
Show file tree
Hide file tree
Showing 6 changed files with 198 additions and 62 deletions.
23 changes: 23 additions & 0 deletions Commands/Create Unimplemented Steps.tmCommand
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>beforeRunningCommand</key>
<string>nop</string>
<key>command</key>
<string>#!/usr/bin/env ruby
require ENV['TM_BUNDLE_SUPPORT'] + "/lib/spec/mate/story/story_helper"
Spec::Mate::Story::StoryHelper.new(ENV['TM_PROJECT_DIRECTORY'], ENV['TM_FILEPATH']).create_uninplemented_steps</string>
<key>input</key>
<string>none</string>
<key>keyEquivalent</key>
<string>^~$@</string>
<key>name</key>
<string>Create Unimplemented Steps</string>
<key>output</key>
<string>showAsTooltip</string>
<key>uuid</key>
<string>A165BB7A-E047-499B-8DF2-CA1FC9049298</string>
</dict>
</plist>
5 changes: 3 additions & 2 deletions README
Expand Up @@ -41,12 +41,13 @@ Caveats:


TODO:
* 6/1/2008 4:10AM - If in story file, and are switching to any other file, if a runner file doesn't exist
- prompt to create it (right now - going to specific step from story file dies if not)
* Run All Stories command.
* Run Selected Stories command.
* (MAYBE) Make running a story more robust to work with any configuration.
* Refactor to make cleaner and add more specs.


* Warning / prompt about creating step runner file (when creating either a story or a steps file)

TODONE:
* Improve HTML output to show failing information.
Expand Down
2 changes: 1 addition & 1 deletion Support/fixtures/stories/feature1/foo.rb
@@ -1,4 +1,4 @@
require File.join(File.dirname(__FILE__), "helper")
require File.join(File.dirname(__FILE__), "../helper")

with_steps_for :foo, :global do
run_story(File.expand_path(__FILE__))
Expand Down
212 changes: 154 additions & 58 deletions Support/lib/spec/mate/story/story_helper.rb
Expand Up @@ -10,6 +10,9 @@ class StoryHelper
def initialize(project_root, full_file_path)
@project_root = project_root
@full_file_path = full_file_path

@steps = []
@step_file_contents = {}
end

def goto_alternate_file
Expand All @@ -36,30 +39,101 @@ def choose_steps_file
end
end

def create_uninplemented_steps
return unless is_story_file?
step_types_and_names_to_create = unlimplmemated_steps_for(full_file_path)
open_or_create_steps_file(alternate_file(full_file_path), step_types_and_names_to_create)
end

protected
attr_reader :full_file_path, :project_root

def default_content(file_path)
case file_path
when /(story|txt)$/ then story_content(file_path)
when /_steps\.rb$/ then steps_content(file_path)
else ''
def open_or_create_steps_file(file_path, step_types_and_names_to_create = [["Given", "condition"]])
create_return_value = ::Spec::Mate::TextMateHelper.open_or_prompt(file_path, :line_number => 2, :column_number => 1)
if create_return_value == true # File was created
::Spec::Mate::TextMateHelper.insert_text(steps_content(file_path, step_types_and_names_to_create))
elsif create_return_value != false # File already existed
#text = ::Spec::Mate::TextMateHelper.snippet_text_for("#{proper_case_step_type} Step", current_step_name)
::Spec::Mate::TextMateHelper.insert_text(steps_content(nil, step_types_and_names_to_create))
end
end

def story_content(file_path)
::Spec::Mate::TextMateHelper.snippet_text_for('Story')
# Story file should be passed in
# TODO: add more funcionality around this
def create_runner_file_for(file_path)
return if File.file?(story_runner_file_for(file_path))
::Spec::Mate::TextMateHelper.silently_create_file_with_contents(story_runner_file_for(file_path), default_content(story_runner_file_for(file_path)))
end

def steps_content(file_path, step_type = 'Given', step_name = 'condition')
step_file_name = file_path.match(/([^\/]*)_steps.rb$/).captures.first
content = <<-STEPS
steps_for(:${1:#{step_file_name}}) do
#{step_type} "${2:#{step_name}}" do
$0
end
end
STEPS

def unlimplmemated_steps_for(file_path)
return unless is_story_file?(file_path)

runner_file_path = story_runner_file_for(file_path)
create_runner_file_for(file_path) unless File.file?(runner_file_path)

# Get all currently defined steps (from relevent files)
step_hashes = all_steps_for_file(file_path)

# Get all plain text steps from story file
story_text_steps = all_text_steps_in_story(file_path)

uninplemented_text_steps = []
story_text_steps.each do |plain_text_type, plain_text_step|
unless step_hashes.detect{|s| s[:type].downcase == plain_text_type.downcase && s[:step].matches?(plain_text_step) }
uninplemented_text_steps << [plain_text_type, plain_text_step]
end
end

uninplemented_text_steps.uniq
end

def all_text_steps_in_story(file_path)
file_lines = File.read(file_path).split("\n").collect{|l| l.strip}

text_steps = []
step_type = 'unknown'
file_lines.each do |line|
step_type = $1 if line.match(/^(Given|When|Then)\s+/)
text_steps << [step_type, $2] if line.match(/^(Given|When|Then|And)\s+(.*)$/)
end

text_steps
end

# HAS TO TAKE STORY FILE - will find all steps that the runner file includes
def all_steps_for_file(file_path)
runner_file_path = story_runner_file_for(file_path)

create_runner_file_for(file_path) unless File.file?(runner_file_path)

step_group_tags = parse_step_group_tags(File.read(runner_file_path))

step_group_tags.each do |step_group_tag|
@current_step_group_tag = step_group_tag
@full_steps_file_path = full_path_for_step_name(@current_step_group_tag)
next unless @full_steps_file_path

@step_file_contents[@current_step_group_tag] = File.read(@full_steps_file_path)
eval(@step_file_contents[@current_step_group_tag])
end

@steps
end

def find_matching_step(current_step_type, current_step_name)
all_steps_for_file(full_file_path)

# Find matching step
@steps.detect{|s| s[:type] == current_step_type && s[:step].matches?(current_step_name) }
end

# While evaluating step definitions code - This called when a new step has been parse
# We need to save these to be able to match plain text
def add_step(type, pattern)
step = Spec::Story::Step.new(pattern){raise "Step doesn't exist."}
@steps << {:file => @full_steps_file_path, :step => step, :type => type,
:pattern => pattern, :tag => @current_step_group_tag,
:line_number => caller[1].match(/:(\d+)/).captures.first.to_i}
end

def goto_new_or_current_step(current_step_type, current_step_name)
Expand All @@ -68,16 +142,8 @@ def goto_new_or_current_step(current_step_type, current_step_name)
col_number = (md = next_line.match(/\s*($|[^\s])/)) ? md[0].length : 1
::Spec::Mate::TextMateHelper.open_or_prompt(matching_step[:file], :line_number => matching_step[:line_number]+1, :column_number => col_number)
else
alt_file_path = alternate_file(full_file_path)
proper_case_step_type = "#{current_step_type[0...1].upcase}#{current_step_type[1..-1].downcase}"

create_return_value = ::Spec::Mate::TextMateHelper.open_or_prompt(alt_file_path, :line_number => 2, :column_number => 3)
if create_return_value == true
::Spec::Mate::TextMateHelper.insert_text(steps_content(alt_file_path, proper_case_step_type, current_step_name))
elsif create_return_value != false
text = ::Spec::Mate::TextMateHelper.snippet_text_for("#{proper_case_step_type} Step", current_step_name)
::Spec::Mate::TextMateHelper.insert_text(text)
end
open_or_create_steps_file(alternate_file(full_file_path),
[["#{current_step_type[0...1].upcase}#{current_step_type[1..-1].downcase}", current_step_name]])
end
end

Expand All @@ -95,23 +161,58 @@ def get_current_step_info(line_number)
return step_type, source_step_name
end

def find_matching_step(current_step_type, current_step_name)
return unless File.file?(story_runner_file_for(full_file_path))
step_group_tags = parse_step_group_tags(File.read(story_runner_file_for(full_file_path)))
def default_content(file_path)
case file_path
when /(story|txt)$/ then story_content(file_path)
when /_steps\.rb$/ then steps_content(file_path)
when /stories\/([^\/\.]*).rb/ then runner_content(file_path)
else ''
end
end

def runner_content(file_path)
story_name = file_path.match(/([^\/]*).rb$/).captures.first
num_paths_up = file_path.match(/^.*?\/stories\/(.*)$/).captures.first.split('/').size - 1
content = <<-EOF
require File.join(File.dirname(__FILE__), #{("'..', " * num_paths_up) + "'helper'"})
with_steps_for :#{story_name} do
run_story(File.expand_path(__FILE__))
end
EOF
end

def story_content(file_path)
::Spec::Mate::TextMateHelper.snippet_text_for('Story')
end

def steps_content(file_path, step_types_and_names_to_create = [["Given", "condition"]])
create_wrapper_content = false

@steps = []
@step_file_contents = {}
if file_path
create_wrapper_content = true
step_file_name = file_path.match(/([^\/]*)_steps.rb$/).captures.first
end

step_group_tags.each do |step_group_tag|
@current_step_group_tag = step_group_tag
@full_steps_file_path = full_path_for_step_name(@current_step_group_tag)

@step_file_contents[@current_step_group_tag] = File.read(@full_steps_file_path)
eval(@step_file_contents[@current_step_group_tag])
# sort the steps into Given, When, Then
sorted_steps = step_types_and_names_to_create.inject({'Given' => [], 'When' => [], 'Then' => []}) do |steps_so_far, current_type_and_step_name|
steps_so_far[current_type_and_step_name.first] << current_type_and_step_name.last
steps_so_far
end

# Find matching step
@steps.detect{|s| s[:type] == current_step_type && s[:step].matches?(current_step_name) }
content = ""
content = %Q{steps_for(:${1:#{step_file_name}}) do\n} if create_wrapper_content

already_included_snippet_selection = false
%w(Given When Then).each do |step_type|
sorted_steps[step_type].each do |step_name|
step_name_text = already_included_snippet_selection ? step_name : "${1:#{step_name}}"
content += %Q{ #{step_type} "#{step_name_text}" do\n pending\n end\n \n}
already_included_snippet_selection = true
end
end
content += "end\n" if create_wrapper_content
content
end


Expand All @@ -133,18 +234,19 @@ def alternate_file(path)
def related_step_files
if is_story_file?(full_file_path)
story_name = full_file_path.match(/\/([^\.\/]*)\.(story|txt)$/).captures.first
steps_file_path = File.dirname(full_file_path) + "/../#{story_name}.rb"

parse_step_group_tags(File.read(steps_file_path))
else
step_files = Dir["#{project_root}/stories/**/*_steps.rb"]
step_files.collect{|f| f.match(/([^\/]*)_steps.rb$/).captures.first }.sort
runner_file_path = File.dirname(full_file_path) + "/../#{story_name}.rb"
return parse_step_group_tags(File.read(runner_file_path)) if File.file?(runner_file_path)
end
step_files = Dir["#{project_root}/stories/**/*_steps.rb"]
step_files.collect{|f| f.match(/([^\/]*)_steps.rb$/).captures.first }.sort
end

def story_runner_file_for(path)
return unless is_story_file?(path)
path.gsub(/\/stories\/([^\.\/]*)\.story$/, '/\1.rb')
if is_story_file?(path)
path.gsub(/\/stories\/([^\.\/]*)\.story$/, '/\1.rb')
else # steps path
path.gsub(/\/steps\/([^\.\/]*)_steps\.rb$/, '/\1.rb')
end
end

def parse_step_group_tags(content)
Expand All @@ -153,29 +255,23 @@ def parse_step_group_tags(content)
end

def full_path_for_step_name(step_name)
File.expand_path(Dir["#{project_root}/**/stories/**/#{step_name}_steps.rb"].first)
possible_files = Dir["#{project_root}/**/stories/**/#{step_name}_steps.rb"]
return if possible_files.empty?
File.expand_path(possible_files.first)
end

def is_story_file?(file_path = full_file_path)
file_path.match(/\.(story|txt)$/)
end


def with_steps_for(*args)
return args
end

def method_missing(method, args)
def steps_for(*args)
yield if block_given?
end

def add_step(type, pattern)
step = Spec::Story::Step.new(pattern){raise "Step doesn't exist."}
@steps << {:file => @full_steps_file_path, :step => step, :type => type,
:pattern => pattern, :tag => @current_step_group_tag,
:line_number => caller[1].match(/:(\d+)/).captures.first.to_i}
end

def Given(pattern)
add_step('given', pattern)
end
Expand Down
17 changes: 16 additions & 1 deletion Support/lib/spec/mate/text_mate_helper.rb
Expand Up @@ -11,6 +11,10 @@ def open_or_prompt(file_path, options = {})
new.open_or_prompt(file_path, options)
end

def silently_create_file_with_contents(file_path, contents)
new.silently_create_file_with_contents(file_path, contents)
end

def snippet_text_for(snippet_name, *replacements)
new.snippet_text_for(snippet_name, replacements)
end
Expand All @@ -20,6 +24,10 @@ def insert_text(text)
end
end

def silently_create_file_with_contents(file_path, contents)
create_file(file_path, contents)
end

def snippet_text_for(snippet_name, *replacements)
snippet_file = File.expand_path(File.dirname(__FILE__) + "/../../../../Snippets/#{snippet_name}.tmSnippet")
content = File.read(snippet_file).match(/<key>content<\/key>\s*<string>([^<]*)<\/string>/m)[1]
Expand Down Expand Up @@ -52,9 +60,16 @@ def open_or_prompt(file_path, options = {})
end

protected
def create_and_open(file_path)
def create_file(file_path, contents = nil)
`mkdir -p "#{File.dirname(file_path)}"`
`touch "#{file_path}"`
if contents
`echo "#{contents.gsub('"','\\"')}" > "#{file_path}"`
end
end

def create_and_open(file_path)
create_file(file_path)
`osascript &>/dev/null -e 'tell app "SystemUIServer" to activate' -e 'tell app "TextMate" to activate'`
`"$TM_SUPPORT_PATH/bin/mate" "#{file_path}"`
end
Expand Down
1 change: 1 addition & 0 deletions info.plist
Expand Up @@ -44,6 +44,7 @@
<string>2BCE4864-D70A-4C52-B49D-FB3220130B74</string>
<string>B269B8F3-3A6D-4069-9E70-DD89A679416A</string>
<string>8E056A1E-AF51-4CFB-949F-47B6BCE6E6EC</string>
<string>A165BB7A-E047-499B-8DF2-CA1FC9049298</string>
<string>21F53BA7-59E2-4284-A989-96D68D39D530</string>
<string>32B65923-8303-4635-BA62-1D8860E59EE4</string>
</array>
Expand Down

0 comments on commit 24ae381

Please sign in to comment.