tpope-pickler 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/MIT-LICENSE +20 -0
 - data/README.rdoc +43 -0
 - data/bin/pickler +7 -0
 - data/lib/pickler/tracker/project.rb +46 -0
 - data/lib/pickler/tracker/story.rb +90 -0
 - data/lib/pickler/tracker.rb +77 -0
 - data/lib/pickler.rb +194 -0
 - data/pickler.gemspec +24 -0
 - metadata +77 -0
 
    
        data/MIT-LICENSE
    ADDED
    
    | 
         @@ -0,0 +1,20 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            Copyright (c) 2008 Tim Pope
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            Permission is hereby granted, free of charge, to any person obtaining
         
     | 
| 
      
 4 
     | 
    
         
            +
            a copy of this software and associated documentation files (the
         
     | 
| 
      
 5 
     | 
    
         
            +
            "Software"), to deal in the Software without restriction, including
         
     | 
| 
      
 6 
     | 
    
         
            +
            without limitation the rights to use, copy, modify, merge, publish,
         
     | 
| 
      
 7 
     | 
    
         
            +
            distribute, sublicense, and/or sell copies of the Software, and to
         
     | 
| 
      
 8 
     | 
    
         
            +
            permit persons to whom the Software is furnished to do so, subject to
         
     | 
| 
      
 9 
     | 
    
         
            +
            the following conditions:
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            The above copyright notice and this permission notice shall be
         
     | 
| 
      
 12 
     | 
    
         
            +
            included in all copies or substantial portions of the Software.
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
         
     | 
| 
      
 15 
     | 
    
         
            +
            EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
         
     | 
| 
      
 16 
     | 
    
         
            +
            MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
         
     | 
| 
      
 17 
     | 
    
         
            +
            NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
         
     | 
| 
      
 18 
     | 
    
         
            +
            LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
         
     | 
| 
      
 19 
     | 
    
         
            +
            OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
         
     | 
| 
      
 20 
     | 
    
         
            +
            WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
         
     | 
    
        data/README.rdoc
    ADDED
    
    | 
         @@ -0,0 +1,43 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            = Pickler
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            Synchronize user stories in Pivotal Tracker with Cucumber features.
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            == Getting started
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
              echo "api_token: ..."  > ~/.tracker.yml
         
     | 
| 
      
 8 
     | 
    
         
            +
              echo "project_id: ..." > ~/my/app/features/tracker.yml
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            For details about the Pivotal Tracker API, including where to find your API
         
     | 
| 
      
 11 
     | 
    
         
            +
            token and project id, see http://www.pivotaltracker.com/help/api .
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            The pull and push commands map the story's name into the "Feature: ..." line
         
     | 
| 
      
 14 
     | 
    
         
            +
            and the story's description with an additional two space indent into the
         
     | 
| 
      
 15 
     | 
    
         
            +
            feature's body.  Keep this in mind when entering stories into Pivotal Tracker.
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
            == Usage
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
              pickler pull
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
            Download all well formed stories to the features/ directory.  Previously
         
     | 
| 
      
 22 
     | 
    
         
            +
            unseen stories will be given a numeric filename that you are encouraged to
         
     | 
| 
      
 23 
     | 
    
         
            +
            change.
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
              pickler push
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
            Upload all features with a tracker url in a comment on the first line.
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
              pickler search <query>
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
            List all stories matching the given query.
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
              pickler show <id>
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
            Show details for the story referenced by the id.
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
            == Disclaimer
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
            No warranties, expressed or implied.
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
            Notably, the push and pull commands are quite happy to blindly clobber
         
     | 
| 
      
 42 
     | 
    
         
            +
            features if so instructed.  Pivotal Tracker has a history to recover things
         
     | 
| 
      
 43 
     | 
    
         
            +
            server side.
         
     | 
    
        data/bin/pickler
    ADDED
    
    
| 
         @@ -0,0 +1,46 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class Pickler
         
     | 
| 
      
 2 
     | 
    
         
            +
              class Tracker
         
     | 
| 
      
 3 
     | 
    
         
            +
                class Project < Abstract
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                  attr_reader :tracker
         
     | 
| 
      
 6 
     | 
    
         
            +
                  reader :point_scale, :week_start_day, :name, :iteration_length
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  def initialize(tracker, attributes = {})
         
     | 
| 
      
 9 
     | 
    
         
            +
                    @tracker = tracker
         
     | 
| 
      
 10 
     | 
    
         
            +
                    super(attributes)
         
     | 
| 
      
 11 
     | 
    
         
            +
                  end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                  def story(story_id)
         
     | 
| 
      
 14 
     | 
    
         
            +
                    Story.new(self,tracker.get_xml("/projects/#{id}/stories/#{story_id}")["story"])
         
     | 
| 
      
 15 
     | 
    
         
            +
                  end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                  def stories(*args)
         
     | 
| 
      
 18 
     | 
    
         
            +
                    filter = encode_term(args) if args.any?
         
     | 
| 
      
 19 
     | 
    
         
            +
                    path = "/projects/#{id}/stories"
         
     | 
| 
      
 20 
     | 
    
         
            +
                    path << "?filter=#{CGI.escape(filter)}" if filter
         
     | 
| 
      
 21 
     | 
    
         
            +
                    response = tracker.get_xml(path)
         
     | 
| 
      
 22 
     | 
    
         
            +
                    [response["stories"]["story"]].flatten.compact.map {|s| Story.new(self,s)}
         
     | 
| 
      
 23 
     | 
    
         
            +
                  end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                  def new_story(attributes = {}, &block)
         
     | 
| 
      
 26 
     | 
    
         
            +
                    Story.new(self, attributes, &block)
         
     | 
| 
      
 27 
     | 
    
         
            +
                  end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                  def deliver_all_finished_stories
         
     | 
| 
      
 30 
     | 
    
         
            +
                    request_xml(:put,"/projects/#{id}/stories_deliver_all_finished")
         
     | 
| 
      
 31 
     | 
    
         
            +
                  end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                  private
         
     | 
| 
      
 34 
     | 
    
         
            +
                  def encode_term(term)
         
     | 
| 
      
 35 
     | 
    
         
            +
                    case term
         
     | 
| 
      
 36 
     | 
    
         
            +
                    when Array   then term.map {|v| encode_term(v)}.join(" ")
         
     | 
| 
      
 37 
     | 
    
         
            +
                    when Hash    then term.map {|k,v| encode_term("#{k}:#{v}")}.join(" ")
         
     | 
| 
      
 38 
     | 
    
         
            +
                    when /^\S+$/, Symbol then term
         
     | 
| 
      
 39 
     | 
    
         
            +
                    when /^(\S+?):(.*)$/ then %{#$1:"#$2"}
         
     | 
| 
      
 40 
     | 
    
         
            +
                    else %{"#{term}"}
         
     | 
| 
      
 41 
     | 
    
         
            +
                    end
         
     | 
| 
      
 42 
     | 
    
         
            +
                  end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                end
         
     | 
| 
      
 45 
     | 
    
         
            +
              end
         
     | 
| 
      
 46 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,90 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class Pickler
         
     | 
| 
      
 2 
     | 
    
         
            +
              class Tracker
         
     | 
| 
      
 3 
     | 
    
         
            +
                class Story < Abstract
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                  TYPES = %w(bug feature chore release)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  STATES = %w(unstarted started finished delivered rejected accepted)
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  attr_reader :project
         
     | 
| 
      
 9 
     | 
    
         
            +
                  reader :created_at, :iteration, :url, :labels
         
     | 
| 
      
 10 
     | 
    
         
            +
                  accessor :current_state, :name, :description, :estimate, :owned_by, :requested_by, :story_type
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                  def initialize(project, attributes = {})
         
     | 
| 
      
 13 
     | 
    
         
            +
                    @project = project
         
     | 
| 
      
 14 
     | 
    
         
            +
                    super(attributes)
         
     | 
| 
      
 15 
     | 
    
         
            +
                  end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                  def tracker
         
     | 
| 
      
 18 
     | 
    
         
            +
                    project.tracker
         
     | 
| 
      
 19 
     | 
    
         
            +
                  end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                  def to_s
         
     | 
| 
      
 22 
     | 
    
         
            +
                    to_s = "# #{url}\n#{story_type.capitalize}: #{name}\n"
         
     | 
| 
      
 23 
     | 
    
         
            +
                    description_lines.each do |line|
         
     | 
| 
      
 24 
     | 
    
         
            +
                      to_s << "  #{line}".rstrip << "\n"
         
     | 
| 
      
 25 
     | 
    
         
            +
                    end
         
     | 
| 
      
 26 
     | 
    
         
            +
                    to_s
         
     | 
| 
      
 27 
     | 
    
         
            +
                  end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                  def to_s=(body)
         
     | 
| 
      
 30 
     | 
    
         
            +
                    body = body.sub(/\A# .*\n/,'')
         
     | 
| 
      
 31 
     | 
    
         
            +
                    if body =~ /\A(\w+): (.*)/
         
     | 
| 
      
 32 
     | 
    
         
            +
                      self.story_type = $1.downcase
         
     | 
| 
      
 33 
     | 
    
         
            +
                      self.name = $2
         
     | 
| 
      
 34 
     | 
    
         
            +
                      description = $'
         
     | 
| 
      
 35 
     | 
    
         
            +
                    else
         
     | 
| 
      
 36 
     | 
    
         
            +
                      self.story_type = "feature"
         
     | 
| 
      
 37 
     | 
    
         
            +
                      self.name = body[/.*/]
         
     | 
| 
      
 38 
     | 
    
         
            +
                      description = $'
         
     | 
| 
      
 39 
     | 
    
         
            +
                    end
         
     | 
| 
      
 40 
     | 
    
         
            +
                    self.description = description.gsub(/\A\n+|\n+\Z/,'') + "\n"
         
     | 
| 
      
 41 
     | 
    
         
            +
                    if description_lines.all? {|l| l.empty? || l =~ /^  /}
         
     | 
| 
      
 42 
     | 
    
         
            +
                      self.description.gsub!(/^  /,'')
         
     | 
| 
      
 43 
     | 
    
         
            +
                    end
         
     | 
| 
      
 44 
     | 
    
         
            +
                    self
         
     | 
| 
      
 45 
     | 
    
         
            +
                  end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                  def description_lines
         
     | 
| 
      
 48 
     | 
    
         
            +
                    array = []
         
     | 
| 
      
 49 
     | 
    
         
            +
                    description.to_s.each_line do |line|
         
     | 
| 
      
 50 
     | 
    
         
            +
                      array << line.chomp
         
     | 
| 
      
 51 
     | 
    
         
            +
                    end
         
     | 
| 
      
 52 
     | 
    
         
            +
                    array
         
     | 
| 
      
 53 
     | 
    
         
            +
                  end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                  def notes
         
     | 
| 
      
 56 
     | 
    
         
            +
                    [@attributes["notes"]].flatten.compact
         
     | 
| 
      
 57 
     | 
    
         
            +
                  end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                  def to_xml
         
     | 
| 
      
 60 
     | 
    
         
            +
                    hash = @attributes.except("id","url","iteration","notes","labels")
         
     | 
| 
      
 61 
     | 
    
         
            +
                    hash["labels"] = Array(@attributes["labels"]).join(", ")
         
     | 
| 
      
 62 
     | 
    
         
            +
                    hash.to_xml(:root => "story")
         
     | 
| 
      
 63 
     | 
    
         
            +
                  end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                  def destroy
         
     | 
| 
      
 66 
     | 
    
         
            +
                    if id
         
     | 
| 
      
 67 
     | 
    
         
            +
                      request = tracker.request_xml(:delete, "/projects/#{project.id}/stories/#{id}", to_xml)
         
     | 
| 
      
 68 
     | 
    
         
            +
                      raise Error, request["message"], caller if request["success"] != "true"
         
     | 
| 
      
 69 
     | 
    
         
            +
                      @attributes["id"] = nil
         
     | 
| 
      
 70 
     | 
    
         
            +
                      self
         
     | 
| 
      
 71 
     | 
    
         
            +
                    end
         
     | 
| 
      
 72 
     | 
    
         
            +
                  end
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                  def save
         
     | 
| 
      
 75 
     | 
    
         
            +
                    if id
         
     | 
| 
      
 76 
     | 
    
         
            +
                      request = tracker.request_xml(:put, "/projects/#{project.id}/stories/#{id}", to_xml)
         
     | 
| 
      
 77 
     | 
    
         
            +
                    else
         
     | 
| 
      
 78 
     | 
    
         
            +
                      request = tracker.request_xml(:post, "/projects/#{project.id}/stories", to_xml)
         
     | 
| 
      
 79 
     | 
    
         
            +
                    end
         
     | 
| 
      
 80 
     | 
    
         
            +
                    if request["success"] == "true"
         
     | 
| 
      
 81 
     | 
    
         
            +
                      initialize(project, request["story"])
         
     | 
| 
      
 82 
     | 
    
         
            +
                      true
         
     | 
| 
      
 83 
     | 
    
         
            +
                    else
         
     | 
| 
      
 84 
     | 
    
         
            +
                      Array(request["errors"]["error"])
         
     | 
| 
      
 85 
     | 
    
         
            +
                    end
         
     | 
| 
      
 86 
     | 
    
         
            +
                  end
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                end
         
     | 
| 
      
 89 
     | 
    
         
            +
              end
         
     | 
| 
      
 90 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,77 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class Pickler
         
     | 
| 
      
 2 
     | 
    
         
            +
              class Tracker
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
                ADDRESS = 'www.pivotaltracker.com'
         
     | 
| 
      
 5 
     | 
    
         
            +
                BASE_PATH = '/services/v1'
         
     | 
| 
      
 6 
     | 
    
         
            +
                SEARCH_KEYS = %w(label type state requester owner mywork id includedone)
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                class Error < Pickler::Error; end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                attr_reader :token
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                def initialize(token)
         
     | 
| 
      
 13 
     | 
    
         
            +
                  require 'active_support'
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @token = token
         
     | 
| 
      
 15 
     | 
    
         
            +
                end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                def request(method, path, *args)
         
     | 
| 
      
 18 
     | 
    
         
            +
                  require 'net/http'
         
     | 
| 
      
 19 
     | 
    
         
            +
                  Net::HTTP.start(ADDRESS) do |http|
         
     | 
| 
      
 20 
     | 
    
         
            +
                    headers = {
         
     | 
| 
      
 21 
     | 
    
         
            +
                      "Token"        => @token,
         
     | 
| 
      
 22 
     | 
    
         
            +
                      "Accept"       => "application/xml",
         
     | 
| 
      
 23 
     | 
    
         
            +
                      "Content-type" => "application/xml"
         
     | 
| 
      
 24 
     | 
    
         
            +
                    }
         
     | 
| 
      
 25 
     | 
    
         
            +
                    klass = Net::HTTP.const_get(method.to_s.capitalize)
         
     | 
| 
      
 26 
     | 
    
         
            +
                    http.request(klass.new("#{BASE_PATH}#{path}", headers), *args)
         
     | 
| 
      
 27 
     | 
    
         
            +
                  end
         
     | 
| 
      
 28 
     | 
    
         
            +
                end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                def request_xml(method, path, *args)
         
     | 
| 
      
 31 
     | 
    
         
            +
                  response = request(method,path,*args)
         
     | 
| 
      
 32 
     | 
    
         
            +
                  raise response.inspect if response["Content-type"].split(/; */).first != "application/xml"
         
     | 
| 
      
 33 
     | 
    
         
            +
                  Hash.from_xml(response.body)["response"]
         
     | 
| 
      
 34 
     | 
    
         
            +
                end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                def get_xml(path)
         
     | 
| 
      
 37 
     | 
    
         
            +
                  response = request_xml(:get, path)
         
     | 
| 
      
 38 
     | 
    
         
            +
                  unless response["success"] == "true"
         
     | 
| 
      
 39 
     | 
    
         
            +
                    if response["message"]
         
     | 
| 
      
 40 
     | 
    
         
            +
                      raise Error, response["message"], caller
         
     | 
| 
      
 41 
     | 
    
         
            +
                    else
         
     | 
| 
      
 42 
     | 
    
         
            +
                      raise "#{path}: #{response.inspect}"
         
     | 
| 
      
 43 
     | 
    
         
            +
                    end
         
     | 
| 
      
 44 
     | 
    
         
            +
                  end
         
     | 
| 
      
 45 
     | 
    
         
            +
                  response
         
     | 
| 
      
 46 
     | 
    
         
            +
                end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                def project(id)
         
     | 
| 
      
 49 
     | 
    
         
            +
                  Project.new(self,get_xml("/projects/#{id}")["project"].merge("id" => id.to_i))
         
     | 
| 
      
 50 
     | 
    
         
            +
                end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                class Abstract
         
     | 
| 
      
 53 
     | 
    
         
            +
                  def initialize(attributes)
         
     | 
| 
      
 54 
     | 
    
         
            +
                    @attributes = (attributes || {}).stringify_keys
         
     | 
| 
      
 55 
     | 
    
         
            +
                    yield self if block_given?
         
     | 
| 
      
 56 
     | 
    
         
            +
                  end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                  def self.reader(*methods)
         
     | 
| 
      
 59 
     | 
    
         
            +
                    methods.each do |method|
         
     | 
| 
      
 60 
     | 
    
         
            +
                      define_method(method) { @attributes[method.to_s] }
         
     | 
| 
      
 61 
     | 
    
         
            +
                    end
         
     | 
| 
      
 62 
     | 
    
         
            +
                  end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                  def self.accessor(*methods)
         
     | 
| 
      
 65 
     | 
    
         
            +
                    reader(*methods)
         
     | 
| 
      
 66 
     | 
    
         
            +
                    methods.each do |method|
         
     | 
| 
      
 67 
     | 
    
         
            +
                      define_method("#{method}=") { |v| @attributes[method.to_s] = v }
         
     | 
| 
      
 68 
     | 
    
         
            +
                    end
         
     | 
| 
      
 69 
     | 
    
         
            +
                  end
         
     | 
| 
      
 70 
     | 
    
         
            +
                  reader :id
         
     | 
| 
      
 71 
     | 
    
         
            +
                end
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
              end
         
     | 
| 
      
 74 
     | 
    
         
            +
            end
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
            require 'pickler/tracker/project'
         
     | 
| 
      
 77 
     | 
    
         
            +
            require 'pickler/tracker/story'
         
     | 
    
        data/lib/pickler.rb
    ADDED
    
    | 
         @@ -0,0 +1,194 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'yaml'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            class Pickler
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
              class Error < RuntimeError
         
     | 
| 
      
 6 
     | 
    
         
            +
              end
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
              def self.config
         
     | 
| 
      
 9 
     | 
    
         
            +
                @config ||= {'api_token' => ENV["TRACKER_API_TOKEN"]}.merge(
         
     | 
| 
      
 10 
     | 
    
         
            +
                  if File.exist?(path = File.expand_path('~/.tracker.yml'))
         
     | 
| 
      
 11 
     | 
    
         
            +
                    YAML.load_file(path)
         
     | 
| 
      
 12 
     | 
    
         
            +
                  end || {}
         
     | 
| 
      
 13 
     | 
    
         
            +
                )
         
     | 
| 
      
 14 
     | 
    
         
            +
              end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
              def self.run(argv)
         
     | 
| 
      
 17 
     | 
    
         
            +
                pickler = new(Dir.getwd)
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                case first = argv.shift
         
     | 
| 
      
 20 
     | 
    
         
            +
                when 'show', /^\d+$/
         
     | 
| 
      
 21 
     | 
    
         
            +
                  story = pickler.project.story(first == 'show' ? argv.shift : first)
         
     | 
| 
      
 22 
     | 
    
         
            +
                  puts story
         
     | 
| 
      
 23 
     | 
    
         
            +
                when 'search'
         
     | 
| 
      
 24 
     | 
    
         
            +
                  stories = pickler.project.stories(*argv).group_by {|s| s.current_state}
         
     | 
| 
      
 25 
     | 
    
         
            +
                  first = true
         
     | 
| 
      
 26 
     | 
    
         
            +
                  states = Tracker::Story::STATES
         
     | 
| 
      
 27 
     | 
    
         
            +
                  states -= %w(unstarted accepted) if argv.empty?
         
     | 
| 
      
 28 
     | 
    
         
            +
                  states.each do |state|
         
     | 
| 
      
 29 
     | 
    
         
            +
                    next unless stories[state]
         
     | 
| 
      
 30 
     | 
    
         
            +
                    puts unless first
         
     | 
| 
      
 31 
     | 
    
         
            +
                    first = false
         
     | 
| 
      
 32 
     | 
    
         
            +
                    puts state.upcase
         
     | 
| 
      
 33 
     | 
    
         
            +
                    puts '-' * state.length
         
     | 
| 
      
 34 
     | 
    
         
            +
                    stories[state].each do |story|
         
     | 
| 
      
 35 
     | 
    
         
            +
                      puts "[#{story.id}] #{story.story_type.capitalize}: #{story.name}"
         
     | 
| 
      
 36 
     | 
    
         
            +
                    end
         
     | 
| 
      
 37 
     | 
    
         
            +
                  end
         
     | 
| 
      
 38 
     | 
    
         
            +
                when 'push'
         
     | 
| 
      
 39 
     | 
    
         
            +
                  pickler.push(*argv)
         
     | 
| 
      
 40 
     | 
    
         
            +
                when 'pull'
         
     | 
| 
      
 41 
     | 
    
         
            +
                  pickler.pull(*argv)
         
     | 
| 
      
 42 
     | 
    
         
            +
                when 'help', '--help', '-h', '', nil
         
     | 
| 
      
 43 
     | 
    
         
            +
                  puts 'pickler commands: show <id>, search <query>, push, pull'
         
     | 
| 
      
 44 
     | 
    
         
            +
                else
         
     | 
| 
      
 45 
     | 
    
         
            +
                  $stderr.puts "pickler: unknown command #{first}"
         
     | 
| 
      
 46 
     | 
    
         
            +
                  exit 1
         
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
      
 48 
     | 
    
         
            +
              rescue Pickler::Error
         
     | 
| 
      
 49 
     | 
    
         
            +
                $stderr.puts "#$!"
         
     | 
| 
      
 50 
     | 
    
         
            +
                exit 1
         
     | 
| 
      
 51 
     | 
    
         
            +
              rescue Interrupt
         
     | 
| 
      
 52 
     | 
    
         
            +
                $stderr.puts "Interrupted!"
         
     | 
| 
      
 53 
     | 
    
         
            +
                exit 130
         
     | 
| 
      
 54 
     | 
    
         
            +
              end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
              attr_reader :directory
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
              def initialize(path = '.')
         
     | 
| 
      
 59 
     | 
    
         
            +
                @lang = 'en'
         
     | 
| 
      
 60 
     | 
    
         
            +
                @directory = File.expand_path(path)
         
     | 
| 
      
 61 
     | 
    
         
            +
                until File.directory?(File.join(@directory,'features'))
         
     | 
| 
      
 62 
     | 
    
         
            +
                  if @directory == File.dirname(@directory)
         
     | 
| 
      
 63 
     | 
    
         
            +
                    raise Error, 'Project not found.  Make sure you have a features/ directory.', caller
         
     | 
| 
      
 64 
     | 
    
         
            +
                  end
         
     | 
| 
      
 65 
     | 
    
         
            +
                  @directory = File.dirname(@directory)
         
     | 
| 
      
 66 
     | 
    
         
            +
                end
         
     | 
| 
      
 67 
     | 
    
         
            +
              end
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
              def features_path(*subdirs)
         
     | 
| 
      
 70 
     | 
    
         
            +
                File.join(@directory,'features',*subdirs)
         
     | 
| 
      
 71 
     | 
    
         
            +
              end
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
              def config_file
         
     | 
| 
      
 74 
     | 
    
         
            +
                features_path('tracker.yml')
         
     | 
| 
      
 75 
     | 
    
         
            +
              end
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
              def config
         
     | 
| 
      
 78 
     | 
    
         
            +
                @config ||= File.exist?(config_file) && YAML.load_file(config_file) || {}
         
     | 
| 
      
 79 
     | 
    
         
            +
                self.class.config.merge(@config)
         
     | 
| 
      
 80 
     | 
    
         
            +
              end
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
              def parser
         
     | 
| 
      
 83 
     | 
    
         
            +
                require 'cucumber'
         
     | 
| 
      
 84 
     | 
    
         
            +
                require "cucumber/treetop_parser/feature_#@lang"
         
     | 
| 
      
 85 
     | 
    
         
            +
                Cucumber.load_language(@lang)
         
     | 
| 
      
 86 
     | 
    
         
            +
                @parser ||= Cucumber::TreetopParser::FeatureParser.new
         
     | 
| 
      
 87 
     | 
    
         
            +
              end
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
              def project_id
         
     | 
| 
      
 90 
     | 
    
         
            +
                config["project_id"] || (self.class.config["projects"]||{})[File.basename(@directory)]
         
     | 
| 
      
 91 
     | 
    
         
            +
              end
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
      
 93 
     | 
    
         
            +
              def project
         
     | 
| 
      
 94 
     | 
    
         
            +
                @project ||= Dir.chdir(@directory) do
         
     | 
| 
      
 95 
     | 
    
         
            +
                  unless token = config['api_token']
         
     | 
| 
      
 96 
     | 
    
         
            +
                    raise Error, 'echo api_token: ... > ~/.tracker.yml'
         
     | 
| 
      
 97 
     | 
    
         
            +
                  end
         
     | 
| 
      
 98 
     | 
    
         
            +
                  unless id = project_id
         
     | 
| 
      
 99 
     | 
    
         
            +
                    raise Error, 'echo project_id: ... > features/tracker.yml'
         
     | 
| 
      
 100 
     | 
    
         
            +
                  end
         
     | 
| 
      
 101 
     | 
    
         
            +
                  Tracker.new(token).project(id)
         
     | 
| 
      
 102 
     | 
    
         
            +
                end
         
     | 
| 
      
 103 
     | 
    
         
            +
              end
         
     | 
| 
      
 104 
     | 
    
         
            +
             
     | 
| 
      
 105 
     | 
    
         
            +
              def scenario_word
         
     | 
| 
      
 106 
     | 
    
         
            +
                parser
         
     | 
| 
      
 107 
     | 
    
         
            +
                Cucumber.language['scenario']
         
     | 
| 
      
 108 
     | 
    
         
            +
              end
         
     | 
| 
      
 109 
     | 
    
         
            +
             
     | 
| 
      
 110 
     | 
    
         
            +
              def remote_features
         
     | 
| 
      
 111 
     | 
    
         
            +
                project.stories(scenario_word, :includedone => true).select do |s|
         
     | 
| 
      
 112 
     | 
    
         
            +
                  s.to_s =~ /^\s*#{Regexp.escape(scenario_word)}:/ && parser.parse(s.to_s)
         
     | 
| 
      
 113 
     | 
    
         
            +
                end
         
     | 
| 
      
 114 
     | 
    
         
            +
              end
         
     | 
| 
      
 115 
     | 
    
         
            +
             
     | 
| 
      
 116 
     | 
    
         
            +
              def local_features
         
     | 
| 
      
 117 
     | 
    
         
            +
                Dir[features_path('**','*.feature')].map {|f| LocalFeature.new(self,f)}
         
     | 
| 
      
 118 
     | 
    
         
            +
              end
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
              def pull
         
     | 
| 
      
 121 
     | 
    
         
            +
                l = local_features
         
     | 
| 
      
 122 
     | 
    
         
            +
                remote_features.each do |remote|
         
     | 
| 
      
 123 
     | 
    
         
            +
                  body = "# http://www.pivotaltracker.com/story/show/#{remote.id}\n" <<
         
     | 
| 
      
 124 
     | 
    
         
            +
                  normalize_feature(remote.to_s)
         
     | 
| 
      
 125 
     | 
    
         
            +
                  if local = l.detect {|f| f.id == remote.id}
         
     | 
| 
      
 126 
     | 
    
         
            +
                    filename = local.filename
         
     | 
| 
      
 127 
     | 
    
         
            +
                  else
         
     | 
| 
      
 128 
     | 
    
         
            +
                    next if remote.current_state == 'unstarted'
         
     | 
| 
      
 129 
     | 
    
         
            +
                    filename = features_path("#{remote.id}.feature")
         
     | 
| 
      
 130 
     | 
    
         
            +
                  end
         
     | 
| 
      
 131 
     | 
    
         
            +
                  File.open(filename,'w') {|f| f.puts body}
         
     | 
| 
      
 132 
     | 
    
         
            +
                end
         
     | 
| 
      
 133 
     | 
    
         
            +
                nil
         
     | 
| 
      
 134 
     | 
    
         
            +
              end
         
     | 
| 
      
 135 
     | 
    
         
            +
             
     | 
| 
      
 136 
     | 
    
         
            +
              def push
         
     | 
| 
      
 137 
     | 
    
         
            +
                local_features.select do |local|
         
     | 
| 
      
 138 
     | 
    
         
            +
                  next unless local.id
         
     | 
| 
      
 139 
     | 
    
         
            +
                  remote = local.story
         
     | 
| 
      
 140 
     | 
    
         
            +
                  next if remote.to_s == local.to_s
         
     | 
| 
      
 141 
     | 
    
         
            +
                  remote.to_s = local.to_s
         
     | 
| 
      
 142 
     | 
    
         
            +
                  remote.save
         
     | 
| 
      
 143 
     | 
    
         
            +
                end
         
     | 
| 
      
 144 
     | 
    
         
            +
              end
         
     | 
| 
      
 145 
     | 
    
         
            +
             
     | 
| 
      
 146 
     | 
    
         
            +
              protected
         
     | 
| 
      
 147 
     | 
    
         
            +
             
     | 
| 
      
 148 
     | 
    
         
            +
              def normalize_feature(body)
         
     | 
| 
      
 149 
     | 
    
         
            +
                return body unless ast = parser.parse(body)
         
     | 
| 
      
 150 
     | 
    
         
            +
                feature = ast.compile
         
     | 
| 
      
 151 
     | 
    
         
            +
                new = ''
         
     | 
| 
      
 152 
     | 
    
         
            +
                (feature.header.chomp << "\n").each_line do |l|
         
     | 
| 
      
 153 
     | 
    
         
            +
                  new << '  ' unless new.empty?
         
     | 
| 
      
 154 
     | 
    
         
            +
                  new << l.strip << "\n"
         
     | 
| 
      
 155 
     | 
    
         
            +
                end
         
     | 
| 
      
 156 
     | 
    
         
            +
                feature.scenarios.each do |scenario|
         
     | 
| 
      
 157 
     | 
    
         
            +
                  new << "\n  Scenario: #{scenario.name}\n"
         
     | 
| 
      
 158 
     | 
    
         
            +
                  scenario.steps.each do |step|
         
     | 
| 
      
 159 
     | 
    
         
            +
                    new << "    #{step.keyword} #{step.name}\n"
         
     | 
| 
      
 160 
     | 
    
         
            +
                  end
         
     | 
| 
      
 161 
     | 
    
         
            +
                end
         
     | 
| 
      
 162 
     | 
    
         
            +
                new
         
     | 
| 
      
 163 
     | 
    
         
            +
              end
         
     | 
| 
      
 164 
     | 
    
         
            +
             
     | 
| 
      
 165 
     | 
    
         
            +
              class LocalFeature
         
     | 
| 
      
 166 
     | 
    
         
            +
                attr_reader :pickler, :filename
         
     | 
| 
      
 167 
     | 
    
         
            +
             
     | 
| 
      
 168 
     | 
    
         
            +
                def initialize(pickler, filename)
         
     | 
| 
      
 169 
     | 
    
         
            +
                  @pickler = pickler
         
     | 
| 
      
 170 
     | 
    
         
            +
                  @filename = filename
         
     | 
| 
      
 171 
     | 
    
         
            +
                end
         
     | 
| 
      
 172 
     | 
    
         
            +
             
     | 
| 
      
 173 
     | 
    
         
            +
                def to_s
         
     | 
| 
      
 174 
     | 
    
         
            +
                  File.read(@filename)
         
     | 
| 
      
 175 
     | 
    
         
            +
                end
         
     | 
| 
      
 176 
     | 
    
         
            +
             
     | 
| 
      
 177 
     | 
    
         
            +
                def id
         
     | 
| 
      
 178 
     | 
    
         
            +
                  unless defined?(@id)
         
     | 
| 
      
 179 
     | 
    
         
            +
                    @id = if id = to_s[%r{#\s*http://www\.pivotaltracker\.com/\S*?/(\d+)},1]
         
     | 
| 
      
 180 
     | 
    
         
            +
                            id.to_i
         
     | 
| 
      
 181 
     | 
    
         
            +
                          end
         
     | 
| 
      
 182 
     | 
    
         
            +
                  end
         
     | 
| 
      
 183 
     | 
    
         
            +
                  @id
         
     | 
| 
      
 184 
     | 
    
         
            +
                end
         
     | 
| 
      
 185 
     | 
    
         
            +
             
     | 
| 
      
 186 
     | 
    
         
            +
                def story
         
     | 
| 
      
 187 
     | 
    
         
            +
                  @pickler.project.story(id) if id
         
     | 
| 
      
 188 
     | 
    
         
            +
                end
         
     | 
| 
      
 189 
     | 
    
         
            +
             
     | 
| 
      
 190 
     | 
    
         
            +
              end
         
     | 
| 
      
 191 
     | 
    
         
            +
             
     | 
| 
      
 192 
     | 
    
         
            +
            end
         
     | 
| 
      
 193 
     | 
    
         
            +
             
     | 
| 
      
 194 
     | 
    
         
            +
            require 'pickler/tracker'
         
     | 
    
        data/pickler.gemspec
    ADDED
    
    | 
         @@ -0,0 +1,24 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            Gem::Specification.new do |s|
         
     | 
| 
      
 2 
     | 
    
         
            +
              s.name                = "pickler"
         
     | 
| 
      
 3 
     | 
    
         
            +
              s.version             = "0.0.0"
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
              s.summary             = "PIvotal traCKer Liaison to cucumbER"
         
     | 
| 
      
 6 
     | 
    
         
            +
              s.description         = "Synchronize between Cucumber and Pivotal Tracker"
         
     | 
| 
      
 7 
     | 
    
         
            +
              s.authors             = ["Tim Pope"]
         
     | 
| 
      
 8 
     | 
    
         
            +
              s.email               = "ruby@tpope.i"+'nfo'
         
     | 
| 
      
 9 
     | 
    
         
            +
              s.homepage            = "http://github.com/tpope/pickler"
         
     | 
| 
      
 10 
     | 
    
         
            +
              s.default_executable  = "pickler"
         
     | 
| 
      
 11 
     | 
    
         
            +
              s.executables         = ["pickler"]
         
     | 
| 
      
 12 
     | 
    
         
            +
              s.files               = [
         
     | 
| 
      
 13 
     | 
    
         
            +
                "README.rdoc",
         
     | 
| 
      
 14 
     | 
    
         
            +
                "MIT-LICENSE",
         
     | 
| 
      
 15 
     | 
    
         
            +
                "pickler.gemspec",
         
     | 
| 
      
 16 
     | 
    
         
            +
                "bin/pickler",
         
     | 
| 
      
 17 
     | 
    
         
            +
                "lib/pickler.rb",
         
     | 
| 
      
 18 
     | 
    
         
            +
                "lib/pickler/tracker.rb",
         
     | 
| 
      
 19 
     | 
    
         
            +
                "lib/pickler/tracker/project.rb",
         
     | 
| 
      
 20 
     | 
    
         
            +
                "lib/pickler/tracker/story.rb"
         
     | 
| 
      
 21 
     | 
    
         
            +
              ]
         
     | 
| 
      
 22 
     | 
    
         
            +
              s.add_dependency("activesupport", [">= 2.0.0"])
         
     | 
| 
      
 23 
     | 
    
         
            +
              s.add_dependency("cucumber", [">= 0.1.9"])
         
     | 
| 
      
 24 
     | 
    
         
            +
            end
         
     | 
    
        metadata
    ADDED
    
    | 
         @@ -0,0 +1,77 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            --- !ruby/object:Gem::Specification 
         
     | 
| 
      
 2 
     | 
    
         
            +
            name: tpope-pickler
         
     | 
| 
      
 3 
     | 
    
         
            +
            version: !ruby/object:Gem::Version 
         
     | 
| 
      
 4 
     | 
    
         
            +
              version: 0.0.0
         
     | 
| 
      
 5 
     | 
    
         
            +
            platform: ruby
         
     | 
| 
      
 6 
     | 
    
         
            +
            authors: 
         
     | 
| 
      
 7 
     | 
    
         
            +
            - Tim Pope
         
     | 
| 
      
 8 
     | 
    
         
            +
            autorequire: 
         
     | 
| 
      
 9 
     | 
    
         
            +
            bindir: bin
         
     | 
| 
      
 10 
     | 
    
         
            +
            cert_chain: []
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            date: 2008-10-27 00:00:00 -07:00
         
     | 
| 
      
 13 
     | 
    
         
            +
            default_executable: pickler
         
     | 
| 
      
 14 
     | 
    
         
            +
            dependencies: 
         
     | 
| 
      
 15 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency 
         
     | 
| 
      
 16 
     | 
    
         
            +
              name: activesupport
         
     | 
| 
      
 17 
     | 
    
         
            +
              version_requirement: 
         
     | 
| 
      
 18 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement 
         
     | 
| 
      
 19 
     | 
    
         
            +
                requirements: 
         
     | 
| 
      
 20 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 21 
     | 
    
         
            +
                  - !ruby/object:Gem::Version 
         
     | 
| 
      
 22 
     | 
    
         
            +
                    version: 2.0.0
         
     | 
| 
      
 23 
     | 
    
         
            +
                version: 
         
     | 
| 
      
 24 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency 
         
     | 
| 
      
 25 
     | 
    
         
            +
              name: cucumber
         
     | 
| 
      
 26 
     | 
    
         
            +
              version_requirement: 
         
     | 
| 
      
 27 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement 
         
     | 
| 
      
 28 
     | 
    
         
            +
                requirements: 
         
     | 
| 
      
 29 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 30 
     | 
    
         
            +
                  - !ruby/object:Gem::Version 
         
     | 
| 
      
 31 
     | 
    
         
            +
                    version: 0.1.9
         
     | 
| 
      
 32 
     | 
    
         
            +
                version: 
         
     | 
| 
      
 33 
     | 
    
         
            +
            description: Synchronize between Cucumber and Pivotal Tracker
         
     | 
| 
      
 34 
     | 
    
         
            +
            email: ruby@tpope.info
         
     | 
| 
      
 35 
     | 
    
         
            +
            executables: 
         
     | 
| 
      
 36 
     | 
    
         
            +
            - pickler
         
     | 
| 
      
 37 
     | 
    
         
            +
            extensions: []
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
            extra_rdoc_files: []
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
            files: 
         
     | 
| 
      
 42 
     | 
    
         
            +
            - README.rdoc
         
     | 
| 
      
 43 
     | 
    
         
            +
            - MIT-LICENSE
         
     | 
| 
      
 44 
     | 
    
         
            +
            - pickler.gemspec
         
     | 
| 
      
 45 
     | 
    
         
            +
            - bin/pickler
         
     | 
| 
      
 46 
     | 
    
         
            +
            - lib/pickler.rb
         
     | 
| 
      
 47 
     | 
    
         
            +
            - lib/pickler/tracker.rb
         
     | 
| 
      
 48 
     | 
    
         
            +
            - lib/pickler/tracker/project.rb
         
     | 
| 
      
 49 
     | 
    
         
            +
            - lib/pickler/tracker/story.rb
         
     | 
| 
      
 50 
     | 
    
         
            +
            has_rdoc: false
         
     | 
| 
      
 51 
     | 
    
         
            +
            homepage: http://github.com/tpope/pickler
         
     | 
| 
      
 52 
     | 
    
         
            +
            post_install_message: 
         
     | 
| 
      
 53 
     | 
    
         
            +
            rdoc_options: []
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
            require_paths: 
         
     | 
| 
      
 56 
     | 
    
         
            +
            - lib
         
     | 
| 
      
 57 
     | 
    
         
            +
            required_ruby_version: !ruby/object:Gem::Requirement 
         
     | 
| 
      
 58 
     | 
    
         
            +
              requirements: 
         
     | 
| 
      
 59 
     | 
    
         
            +
              - - ">="
         
     | 
| 
      
 60 
     | 
    
         
            +
                - !ruby/object:Gem::Version 
         
     | 
| 
      
 61 
     | 
    
         
            +
                  version: "0"
         
     | 
| 
      
 62 
     | 
    
         
            +
              version: 
         
     | 
| 
      
 63 
     | 
    
         
            +
            required_rubygems_version: !ruby/object:Gem::Requirement 
         
     | 
| 
      
 64 
     | 
    
         
            +
              requirements: 
         
     | 
| 
      
 65 
     | 
    
         
            +
              - - ">="
         
     | 
| 
      
 66 
     | 
    
         
            +
                - !ruby/object:Gem::Version 
         
     | 
| 
      
 67 
     | 
    
         
            +
                  version: "0"
         
     | 
| 
      
 68 
     | 
    
         
            +
              version: 
         
     | 
| 
      
 69 
     | 
    
         
            +
            requirements: []
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
            rubyforge_project: 
         
     | 
| 
      
 72 
     | 
    
         
            +
            rubygems_version: 1.2.0
         
     | 
| 
      
 73 
     | 
    
         
            +
            signing_key: 
         
     | 
| 
      
 74 
     | 
    
         
            +
            specification_version: 2
         
     | 
| 
      
 75 
     | 
    
         
            +
            summary: PIvotal traCKer Liaison to cucumbER
         
     | 
| 
      
 76 
     | 
    
         
            +
            test_files: []
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     |