tps_reporter 0.0.2 → 0.2.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/.gitignore +2 -0
- data/GUIDE.md +54 -0
- data/HISTORY.md +24 -0
- data/README.md +5 -3
- data/bin/tps +42 -11
- data/data/index.haml +317 -39
- data/lib/tps/cli_reporter.rb +11 -3
- data/lib/tps/importer.rb +67 -0
- data/lib/tps/sprint.rb +48 -0
- data/lib/tps/task.rb +132 -4
- data/lib/tps/task_list.rb +22 -2
- data/lib/tps/version.rb +1 -1
- data/lib/tps.rb +1 -0
- data/test/hello.yml +7 -0
- data/test/sprint_test.rb +64 -0
- data/test/sprints.yml +18 -0
- data/test/tps_test.rb +27 -0
- metadata +23 -7
    
        data/.gitignore
    ADDED
    
    
    
        data/GUIDE.md
    ADDED
    
    | @@ -0,0 +1,54 @@ | |
| 1 | 
            +
            Guide
         | 
| 2 | 
            +
            =====
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            Advanced usage guide!
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            Sprint support
         | 
| 7 | 
            +
            --------------
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            First, make a sprints block, preferrably at the top of your `tasks.yml` file.  
         | 
| 10 | 
            +
            List down the sprints.
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            Then simply add the sprint ID to your tasks.
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                # tasks.yml
         | 
| 15 | 
            +
                Sprints:
         | 
| 16 | 
            +
                  s1: Sprint 1 (Nov 1-15)
         | 
| 17 | 
            +
                  s2: Sprint 2 (Nov 16-30)
         | 
| 18 | 
            +
                  s3: Sprint 3 (Dec 1-15)
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                Beta release:
         | 
| 21 | 
            +
                  Account:
         | 
| 22 | 
            +
                    Login: [s1]
         | 
| 23 | 
            +
                    Logout: [s1, done]
         | 
| 24 | 
            +
                    Signup: [s2]
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            It's also recursive--you can put sprints in your parent tasks under `_`:
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                Beta release:
         | 
| 29 | 
            +
                  Blog:
         | 
| 30 | 
            +
                    _: [s3]
         | 
| 31 | 
            +
                    Create posts:
         | 
| 32 | 
            +
                    Read posts:
         | 
| 33 | 
            +
                    Delete posts:
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            Trello card linking support
         | 
| 36 | 
            +
            ---------------------------
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            Simply link the card shortcut under each item using `tr/XXXX`, where *XXXX* is 
         | 
| 39 | 
            +
            the short URL ID for the Trello card. You can see the short ID in Trello by 
         | 
| 40 | 
            +
            clicking "more..." inside the card popup.
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                Beta release:
         | 
| 43 | 
            +
                  Blog: [tr/Xh3pAGp1]
         | 
| 44 | 
            +
                  Account management:
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            You can also link the card numbers, but you have to define the main Trello board 
         | 
| 47 | 
            +
            URL. Simply add `Trello URL: ____` to the top of the file.
         | 
| 48 | 
            +
             | 
| 49 | 
            +
             | 
| 50 | 
            +
                Trello URL: https://trello.com/board/trello-resources/4f84a60f0cbdcb7e7d40e099
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                Beta release:
         | 
| 53 | 
            +
                  Blog: [tr/42]
         | 
| 54 | 
            +
                  Account management: [tr/12, done]
         | 
    
        data/HISTORY.md
    CHANGED
    
    | @@ -1,3 +1,27 @@ | |
| 1 | 
            +
            v0.2.0 - Nov 24, 2012
         | 
| 2 | 
            +
            ---------------------
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            ### New features:
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              * Added support for sprints! See `GUIDE.md` for info.
         | 
| 7 | 
            +
              * Trello card linking support! See `GUIDE.md` again.
         | 
| 8 | 
            +
              * Redesigned HTML output.
         | 
| 9 | 
            +
              * Added "Export mode" in the HTML output.
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            ### Internals:
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              * Implement @task['name'] lookup.
         | 
| 14 | 
            +
              * Implement Task#id.
         | 
| 15 | 
            +
              * Sprints: add a sprint model.
         | 
| 16 | 
            +
              * Task: implement #filter, #filter_by, #contains_sprint?
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            v0.0.3 - Nov 21, 2012
         | 
| 19 | 
            +
            ---------------------
         | 
| 20 | 
            +
             | 
| 21 | 
            +
              * Added support for linking tasks to Trello cards.
         | 
| 22 | 
            +
              * Fixed error when invoking 'tps' without arguments.
         | 
| 23 | 
            +
              * Fixed typo in message when invoked without a tasks.yml file.
         | 
| 24 | 
            +
             | 
| 1 25 | 
             
            v0.0.2 - Feb 04, 2012
         | 
| 2 26 | 
             
            ---------------------
         | 
| 3 27 |  | 
    
        data/README.md
    CHANGED
    
    | @@ -3,7 +3,8 @@ | |
| 3 3 | 
             
            
         | 
| 4 4 |  | 
| 5 5 | 
             
            We often need to make regular reports of things done for our projects at work. I
         | 
| 6 | 
            -
            hate doing these by hand. This tool lets us build these reports from YAML files | 
| 6 | 
            +
            hate doing these by hand. This tool lets us build these reports from YAML files
         | 
| 7 | 
            +
            [such as this][s].
         | 
| 7 8 |  | 
| 8 9 | 
             
            Get started
         | 
| 9 10 | 
             
            -----------
         | 
| @@ -12,8 +13,8 @@ Install TPS (Ruby): | |
| 12 13 |  | 
| 13 14 | 
             
                $ gem install tps_reporter
         | 
| 14 15 |  | 
| 15 | 
            -
            ...then generate a sample file. (or create `tasks.yml` based on [this | 
| 16 | 
            -
             | 
| 16 | 
            +
            ...then generate a sample file. (or create `tasks.yml` based on [this sample
         | 
| 17 | 
            +
            file.][s])
         | 
| 17 18 |  | 
| 18 19 | 
             
                $ tps sample
         | 
| 19 20 |  | 
| @@ -58,6 +59,7 @@ The metadata is just a simple YAML array that you can conveniently define using | |
| 58 59 | 
             
             - `done`
         | 
| 59 60 | 
             
             - `in progress`
         | 
| 60 61 | 
             
             - `pt/2839478` *(Pivotal tracker ID. Links to a Pivotal tracker story.)*
         | 
| 62 | 
            +
             - `tr/LabxGP3` *(Trello card short name. Links to a Trello card.)*
         | 
| 61 63 | 
             
             - `0pt` *(points; influences percentage. needs to end in __pt__ or __pts__.)*
         | 
| 62 64 | 
             
             - `10%` *(task progress. implies __in progress__.)*
         | 
| 63 65 |  | 
    
        data/bin/tps
    CHANGED
    
    | @@ -3,8 +3,17 @@ $:.unshift File.expand_path('../../lib', __FILE__) | |
| 3 3 | 
             
            require 'tps'
         | 
| 4 4 |  | 
| 5 5 | 
             
            module Params
         | 
| 6 | 
            -
              def extract(what) | 
| 7 | 
            -
             | 
| 6 | 
            +
              def extract(what)
         | 
| 7 | 
            +
                i = index(what) and slice!(i, 2)[1]
         | 
| 8 | 
            +
              end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              def first_is(what)
         | 
| 11 | 
            +
                shift  if first == what
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              def first_matches(what)
         | 
| 15 | 
            +
                shift  if first.downcase == what.downcase[0...first.size]
         | 
| 16 | 
            +
              end
         | 
| 8 17 | 
             
            end
         | 
| 9 18 |  | 
| 10 19 | 
             
            ARGV.extend Params
         | 
| @@ -17,9 +26,12 @@ module TPS::Command | |
| 17 26 | 
             
                puts ""
         | 
| 18 27 | 
             
                puts "Commands:"
         | 
| 19 28 | 
             
                puts "   html                 Builds HTML"
         | 
| 29 | 
            +
                puts "   html --stdout        Builds HTML in stdout"
         | 
| 20 30 | 
             
                puts "   open                 Builds HTML and opens it in the browser"
         | 
| 21 31 | 
             
                puts "   paparazzi            Builds HTML and opens it in Paparazzi (Mac)"
         | 
| 22 32 | 
             
                puts "   print                Prints the report to the console."
         | 
| 33 | 
            +
                puts "   import FILE FORMAT   Import from a file of a given format."
         | 
| 34 | 
            +
                puts "                        (Supported formats: trello)"
         | 
| 23 35 | 
             
                puts ""
         | 
| 24 36 | 
             
                puts "Options (optional):"
         | 
| 25 37 | 
             
                puts "  -f FILE               Specifies the input file. Defaults to tasks.yml."
         | 
| @@ -29,8 +41,14 @@ module TPS::Command | |
| 29 41 |  | 
| 30 42 | 
             
              def html
         | 
| 31 43 | 
             
                t = get_tasks
         | 
| 32 | 
            -
                 | 
| 33 | 
            -
             | 
| 44 | 
            +
                stdout = ARGV.delete('--stdout')
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                if stdout
         | 
| 47 | 
            +
                  puts t.to_html
         | 
| 48 | 
            +
                else
         | 
| 49 | 
            +
                  path = output { |file| file.write t.to_html }
         | 
| 50 | 
            +
                  info "Wrote to '#{path}'."
         | 
| 51 | 
            +
                end
         | 
| 34 52 |  | 
| 35 53 | 
             
                path
         | 
| 36 54 | 
             
              end
         | 
| @@ -65,6 +83,13 @@ module TPS::Command | |
| 65 83 | 
             
                reporter.print
         | 
| 66 84 | 
             
              end
         | 
| 67 85 |  | 
| 86 | 
            +
              def import(file, format=nil)
         | 
| 87 | 
            +
                require 'tps/importer'
         | 
| 88 | 
            +
                import = TPS::Importer.create(file, format)
         | 
| 89 | 
            +
                File.open('tasks.yml', 'w') { |f| f.write import.to_yaml }
         | 
| 90 | 
            +
                puts "Wrote to tasks.yml."
         | 
| 91 | 
            +
              end
         | 
| 92 | 
            +
             | 
| 68 93 | 
             
            private
         | 
| 69 94 | 
             
              def err(str)
         | 
| 70 95 | 
             
                $stdout << "#{str}\n"
         | 
| @@ -92,13 +117,13 @@ private | |
| 92 117 | 
             
                fn = tasks_filename
         | 
| 93 118 | 
             
                if !File.exists?(fn)
         | 
| 94 119 | 
             
                  err "No tasks file found."
         | 
| 95 | 
            -
                  err "Create a sample using ` | 
| 120 | 
            +
                  err "Create a sample using `tps sample`."
         | 
| 96 121 | 
             
                  exit 256
         | 
| 97 122 | 
             
                end
         | 
| 98 123 |  | 
| 99 124 | 
             
                begin
         | 
| 100 125 | 
             
                  TPS::TaskList.new yaml: fn
         | 
| 101 | 
            -
                rescue => e
         | 
| 126 | 
            +
                rescue Psych::SyntaxError => e
         | 
| 102 127 | 
             
                  err "Parse error: #{e.message}"
         | 
| 103 128 | 
             
                  exit 256
         | 
| 104 129 | 
             
                end
         | 
| @@ -125,19 +150,25 @@ private | |
| 125 150 | 
             
              end
         | 
| 126 151 | 
             
            end
         | 
| 127 152 |  | 
| 128 | 
            -
            if ARGV. | 
| 153 | 
            +
            if ARGV.empty?
         | 
| 154 | 
            +
              TPS::Command.help
         | 
| 155 | 
            +
             | 
| 156 | 
            +
            elsif ARGV.first_matches('html')
         | 
| 129 157 | 
             
              TPS::Command.html
         | 
| 130 158 |  | 
| 131 | 
            -
            elsif ARGV. | 
| 159 | 
            +
            elsif ARGV.first_matches('open')
         | 
| 132 160 | 
             
              TPS::Command.open
         | 
| 133 161 |  | 
| 134 | 
            -
            elsif ARGV. | 
| 162 | 
            +
            elsif ARGV.first_matches('sample')
         | 
| 135 163 | 
             
              TPS::Command.sample
         | 
| 136 164 |  | 
| 137 | 
            -
            elsif ARGV. | 
| 165 | 
            +
            elsif ARGV.first_matches('print')
         | 
| 138 166 | 
             
              TPS::Command.print
         | 
| 139 167 |  | 
| 140 | 
            -
            elsif ARGV. | 
| 168 | 
            +
            elsif ARGV.first_matches('import')
         | 
| 169 | 
            +
              TPS::Command.import *ARGV
         | 
| 170 | 
            +
             | 
| 171 | 
            +
            elsif ARGV.first_matches('paparazzi')
         | 
| 141 172 | 
             
              TPS::Command.paparazzi
         | 
| 142 173 |  | 
| 143 174 | 
             
            else
         | 
    
        data/data/index.haml
    CHANGED
    
    | @@ -4,13 +4,45 @@ | |
| 4 4 | 
             
                %script{src: "http://cdnjs.cloudflare.com/ajax/libs/jquery/1.7/jquery.min.js"}
         | 
| 5 5 |  | 
| 6 6 | 
             
                :javascript
         | 
| 7 | 
            -
                  $("td").live(' | 
| 7 | 
            +
                  $("td").live('dblclick', function() {
         | 
| 8 8 | 
             
                    $(this).closest('tr').toggleClass('highlight');
         | 
| 9 9 | 
             
                  });
         | 
| 10 10 |  | 
| 11 | 
            +
                  // Folding
         | 
| 12 | 
            +
                  $(".task .status").live('click', function(e) {
         | 
| 13 | 
            +
                    e.preventDefault();
         | 
| 14 | 
            +
                    e.stopPropagation();
         | 
| 15 | 
            +
                    var id = $(this).closest('tr').attr('id');
         | 
| 16 | 
            +
                    $(".in_"+id).toggle();
         | 
| 17 | 
            +
                  });
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  // Filter by sprints
         | 
| 20 | 
            +
                  $('.sprint-nav a').live('click', function(e) {
         | 
| 21 | 
            +
                    e.preventDefault();
         | 
| 22 | 
            +
                    e.stopPropagation();
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                    var sprint = $(this).attr('data-sprint');
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                    $(".tasks tr")
         | 
| 27 | 
            +
                      .hide()
         | 
| 28 | 
            +
                      .filter('.sprint-'+sprint+', .has_sprint-'+sprint).show();
         | 
| 29 | 
            +
                  });
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  $('[href="#all"]').live('click', function(e) {
         | 
| 32 | 
            +
                    e.preventDefault();
         | 
| 33 | 
            +
                    $('.tasks tr').show();
         | 
| 34 | 
            +
                  });
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  $('[href="#print"]').live('click', function(e) {
         | 
| 37 | 
            +
                    e.preventDefault();
         | 
| 38 | 
            +
                    $('body').addClass('print');
         | 
| 39 | 
            +
                  });
         | 
| 40 | 
            +
             | 
| 41 | 
            +
             | 
| 11 42 | 
             
                %style
         | 
| 12 43 | 
             
                  :plain
         | 
| 13 44 | 
             
                    body {
         | 
| 45 | 
            +
                      background: #eee;
         | 
| 14 46 | 
             
                      padding: 0;
         | 
| 15 47 | 
             
                      margin: 0; }
         | 
| 16 48 |  | 
| @@ -20,13 +52,23 @@ | |
| 20 52 | 
             
                      line-height: 13.5pt;
         | 
| 21 53 | 
             
                      font-size: 9pt; }
         | 
| 22 54 |  | 
| 55 | 
            +
                    h2.table-header {
         | 
| 56 | 
            +
                      padding: 0 0 20px 0;
         | 
| 57 | 
            +
                      margin: 20px auto;
         | 
| 58 | 
            +
                      text-align: center;
         | 
| 59 | 
            +
                      font-family: helvetica neue, sans-serif;
         | 
| 60 | 
            +
                      font-size: 28pt;
         | 
| 61 | 
            +
                      font-weight: 100;
         | 
| 62 | 
            +
                      color: #888;
         | 
| 63 | 
            +
                      text-shadow: 0 1px 0 rgba(250, 250, 250, 0.7); }
         | 
| 64 | 
            +
             | 
| 23 65 | 
             
                    table {
         | 
| 24 | 
            -
                      border: solid  | 
| 66 | 
            +
                      border: solid 10px #fff;
         | 
| 67 | 
            +
                      box-shadow: 0 1px 10px rgba(0, 0, 0, 0.15);
         | 
| 25 68 | 
             
                      padding: 2px;
         | 
| 26 69 |  | 
| 27 70 | 
             
                      background: #fafafa;
         | 
| 28 | 
            -
                      margin: 0;
         | 
| 29 | 
            -
                      width: 600px;
         | 
| 71 | 
            +
                      margin: 0 auto 20px auto;
         | 
| 30 72 | 
             
                      border-collapse: collapse; }
         | 
| 31 73 |  | 
| 32 74 | 
             
                    table td, table th {
         | 
| @@ -34,9 +76,10 @@ | |
| 34 76 | 
             
                      border-top: solid 1px #eee; }
         | 
| 35 77 |  | 
| 36 78 | 
             
                    /* Columns */
         | 
| 37 | 
            -
                    tr>.task { width:  | 
| 38 | 
            -
                    tr>.points { width:  | 
| 39 | 
            -
                    tr>.progress { width:  | 
| 79 | 
            +
                    tr>.task { width: auto; text-align: left; padding-right: 50px; }
         | 
| 80 | 
            +
                    tr>.points { width: 70px; }
         | 
| 81 | 
            +
                    tr>.progress { width: 100px; }
         | 
| 82 | 
            +
                    tr>.sprints { padding-right: 50px; }
         | 
| 40 83 | 
             
                    tr>.owner { display: none; }
         | 
| 41 84 |  | 
| 42 85 | 
             
                    /* Indentation */
         | 
| @@ -46,28 +89,30 @@ | |
| 46 89 | 
             
                    .level-4 .task { padding-left: 100px; }
         | 
| 47 90 |  | 
| 48 91 | 
             
                    /* Overrides for parents */
         | 
| 49 | 
            -
                    .milestone td.task,
         | 
| 50 | 
            -
                    .feature td.task {
         | 
| 92 | 
            +
                    tr.milestone td.task,
         | 
| 93 | 
            +
                    tr.feature td.task {
         | 
| 51 94 | 
             
                      font-weight: bold;
         | 
| 52 95 | 
             
                      font-size: 1.1em; }
         | 
| 53 96 |  | 
| 54 97 | 
             
                    tr.milestone td,
         | 
| 55 98 | 
             
                    tr.feature td {
         | 
| 56 | 
            -
                      border-top: solid 1px # | 
| 99 | 
            +
                      border-top: solid 1px #ccc; }
         | 
| 57 100 |  | 
| 58 | 
            -
                    tr .progress .bar {
         | 
| 101 | 
            +
                    tr.subtask .progress .bar {
         | 
| 59 102 | 
             
                      display: none; }
         | 
| 60 103 |  | 
| 61 | 
            -
                    tr. | 
| 62 | 
            -
                    tr.feature .progress .bar {
         | 
| 63 | 
            -
                      display: block; }
         | 
| 64 | 
            -
             | 
| 65 | 
            -
                    tr td.points>* {
         | 
| 104 | 
            +
                    tr.subtask td.points>* {
         | 
| 66 105 | 
             
                      display: none; }
         | 
| 67 106 |  | 
| 68 | 
            -
                     | 
| 69 | 
            -
                    tr. | 
| 70 | 
            -
                       | 
| 107 | 
            +
                    /* Milestone */
         | 
| 108 | 
            +
                    tr.milestone td {
         | 
| 109 | 
            +
                      line-height: 18pt; }
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                    tr.milestone:not(:first-child) td {
         | 
| 112 | 
            +
                      border-top: solid 3px #777; }
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                    tr.milestone td.task {
         | 
| 115 | 
            +
                      font-size: 1.2em; }
         | 
| 71 116 |  | 
| 72 117 | 
             
                    /* Header */
         | 
| 73 118 | 
             
                    thead {
         | 
| @@ -90,23 +135,24 @@ | |
| 90 135 | 
             
                      background: #fafae0; }
         | 
| 91 136 |  | 
| 92 137 | 
             
                    /* Status box */
         | 
| 93 | 
            -
                     | 
| 138 | 
            +
                    .status {
         | 
| 94 139 | 
             
                      display: inline-block;
         | 
| 95 140 | 
             
                      width: 12px;
         | 
| 96 141 | 
             
                      height: 12px;
         | 
| 97 142 | 
             
                      margin: 0 5px 0 0;
         | 
| 98 143 |  | 
| 99 | 
            -
                      border-radius:  | 
| 144 | 
            +
                      border-radius: 6px;
         | 
| 145 | 
            +
                      box-shadow: inset 0 0 0 1px rgba(0,0,0,0.1), inset 1px 1px 1px rgba(0, 0, 0, 0.1);
         | 
| 100 146 |  | 
| 101 147 | 
             
                      position: relative;
         | 
| 102 148 | 
             
                      top: 2px;
         | 
| 103 149 | 
             
                      background: #ddd; }
         | 
| 104 150 |  | 
| 105 | 
            -
                     | 
| 106 | 
            -
                       | 
| 151 | 
            +
                    .parent .status {
         | 
| 152 | 
            +
                      cursor: pointer; }
         | 
| 107 153 |  | 
| 108 | 
            -
                     | 
| 109 | 
            -
                       | 
| 154 | 
            +
                    .parent .status:hover {
         | 
| 155 | 
            +
                      box-shadow: inset 0 0 0 2px rgba(0, 0, 0, 0.1); }
         | 
| 110 156 |  | 
| 111 157 | 
             
                    /* Progress */
         | 
| 112 158 | 
             
                    .progress .number {
         | 
| @@ -115,25 +161,43 @@ | |
| 115 161 | 
             
                    .progress .bar {
         | 
| 116 162 | 
             
                      height: 10px;
         | 
| 117 163 | 
             
                      border-radius: 5px;
         | 
| 118 | 
            -
                       | 
| 164 | 
            +
                      box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.05), inset 1px 1px 2px rgba(0, 0, 0, 0.1);
         | 
| 165 | 
            +
                      background: #eee; }
         | 
| 119 166 |  | 
| 120 167 | 
             
                    .progress .bar span {
         | 
| 168 | 
            +
                      box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.05), inset -1px -1px 7px rgba(0, 0, 0, 0.1);
         | 
| 169 | 
            +
                      min-width: 10px;
         | 
| 121 170 | 
             
                      display: block;
         | 
| 122 171 | 
             
                      height: 10px;
         | 
| 123 172 | 
             
                      border-radius: 5px;
         | 
| 124 173 | 
             
                      background: #888; }
         | 
| 125 174 |  | 
| 126 | 
            -
                     | 
| 127 | 
            -
                       | 
| 175 | 
            +
                    .meta {
         | 
| 176 | 
            +
                      white-space: nowrap;
         | 
| 177 | 
            +
                      font-size: 8pt;
         | 
| 178 | 
            +
                      font-weight: normal;
         | 
| 128 179 | 
             
                      text-decoration: none;
         | 
| 129 180 |  | 
| 130 | 
            -
                      background: # | 
| 131 | 
            -
                      padding: 1px  | 
| 181 | 
            +
                      background: #eee;
         | 
| 182 | 
            +
                      padding: 1px 5px;
         | 
| 132 183 | 
             
                      border-radius: 2px;
         | 
| 133 | 
            -
                      border-bottom: solid 1px # | 
| 184 | 
            +
                      border-bottom: solid 1px #ddd;
         | 
| 185 | 
            +
                      border-right: solid 1px #ddd;
         | 
| 186 | 
            +
             | 
| 187 | 
            +
                      margin: 0 10px;
         | 
| 188 | 
            +
                      color: #999; }
         | 
| 189 | 
            +
             | 
| 190 | 
            +
                    a.meta:hover {
         | 
| 191 | 
            +
                      background: #ddd; }
         | 
| 134 192 |  | 
| 135 | 
            -
             | 
| 136 | 
            -
                       | 
| 193 | 
            +
                    a.meta:active {
         | 
| 194 | 
            +
                      background: #bbb; }
         | 
| 195 | 
            +
             | 
| 196 | 
            +
                    span.meta {
         | 
| 197 | 
            +
                      background: #ddd;
         | 
| 198 | 
            +
                      border-bottom: solid 1px #ccc;
         | 
| 199 | 
            +
                      border-right: solid 1px #ccc;
         | 
| 200 | 
            +
                      color: #666; }
         | 
| 137 201 |  | 
| 138 202 | 
             
                    /* Progress */
         | 
| 139 203 | 
             
                    td.points {
         | 
| @@ -147,31 +211,244 @@ | |
| 147 211 | 
             
                    td.points .points.total {
         | 
| 148 212 | 
             
                      color: #888; }
         | 
| 149 213 |  | 
| 150 | 
            -
             | 
| 214 | 
            +
                    /* Sprints */
         | 
| 215 | 
            +
                    .sprint-display {
         | 
| 216 | 
            +
                      background: #eee;
         | 
| 217 | 
            +
                      border-radius: 6px;
         | 
| 218 | 
            +
                      height: 12px;
         | 
| 219 | 
            +
                      position: relative; }
         | 
| 220 | 
            +
             | 
| 221 | 
            +
                    .sprint-display {
         | 
| 222 | 
            +
                      overflow: hidden; }
         | 
| 223 | 
            +
             | 
| 224 | 
            +
                    .sprint-progress {
         | 
| 225 | 
            +
                      display: block;
         | 
| 226 | 
            +
                      float: left;
         | 
| 227 | 
            +
             | 
| 228 | 
            +
                      background-color: #eee;
         | 
| 229 | 
            +
                      background: -webkit-linear-gradient(left, #eee, #ddd);
         | 
| 230 | 
            +
                      height: 2px;
         | 
| 231 | 
            +
                      border-top-left-radius: 1px;
         | 
| 232 | 
            +
                      border-bottom-left-radius: 1px;
         | 
| 233 | 
            +
                      margin-top: 8px; }
         | 
| 234 | 
            +
             | 
| 235 | 
            +
                    .sprint-marker {
         | 
| 236 | 
            +
                      display: block;
         | 
| 237 | 
            +
                      float: left;
         | 
| 238 | 
            +
             | 
| 239 | 
            +
                      background: #ccc;
         | 
| 240 | 
            +
             | 
| 241 | 
            +
                      width: 6px;
         | 
| 242 | 
            +
                      height: 6px;
         | 
| 243 | 
            +
                      border-radius: 3px;
         | 
| 244 | 
            +
                      float: left;
         | 
| 151 245 |  | 
| 152 | 
            -
             | 
| 246 | 
            +
                      margin-top: 6px; }
         | 
| 247 | 
            +
             | 
| 248 | 
            +
                    .sprint-label {
         | 
| 249 | 
            +
                      display: block;
         | 
| 250 | 
            +
                      float: left;
         | 
| 251 | 
            +
             | 
| 252 | 
            +
                      font-size: 7pt;
         | 
| 253 | 
            +
                      color: #888;
         | 
| 254 | 
            +
                      padding-left: 5px; }
         | 
| 255 | 
            +
             | 
| 256 | 
            +
                    .status-bg-in_progress {
         | 
| 257 | 
            +
                      background: #ea3; }
         | 
| 258 | 
            +
             | 
| 259 | 
            +
                    .status-bg-done {
         | 
| 260 | 
            +
                      background: #393; }
         | 
| 261 | 
            +
             | 
| 262 | 
            +
                    /* Navigation */
         | 
| 263 | 
            +
                    body {
         | 
| 264 | 
            +
                      padding: 80px 0 50px 0; }
         | 
| 265 | 
            +
             | 
| 266 | 
            +
                    .nav {
         | 
| 267 | 
            +
                      position: fixed;
         | 
| 268 | 
            +
                      top: 0;
         | 
| 269 | 
            +
                      left: 0;
         | 
| 270 | 
            +
                      right: 0;
         | 
| 271 | 
            +
                      z-index: 5;
         | 
| 272 | 
            +
             | 
| 273 | 
            +
                      padding: 0 20px;
         | 
| 274 | 
            +
                      background: rgba(220, 220, 220, 0.95);
         | 
| 275 | 
            +
                      box-shadow: inset 0 -5px 3px -3px rgba(0, 0, 0, 0.06);
         | 
| 276 | 
            +
             | 
| 277 | 
            +
                      font-size: 8pt;
         | 
| 278 | 
            +
                      font-family: sans-serif; }
         | 
| 279 | 
            +
             | 
| 280 | 
            +
                    .nav:after {
         | 
| 281 | 
            +
                      content: '';
         | 
| 282 | 
            +
                      display: table;
         | 
| 283 | 
            +
                      clear: both; }
         | 
| 284 | 
            +
             | 
| 285 | 
            +
                    .nav ul, .nav li, .nav h4 {
         | 
| 286 | 
            +
                      margin: 0;
         | 
| 287 | 
            +
                      padding: 0;
         | 
| 288 | 
            +
                      list-style-type: none; }
         | 
| 289 | 
            +
             | 
| 290 | 
            +
                    .nav ul, .nav li, .nav h4, .nav a, .nav .segment {
         | 
| 291 | 
            +
                      float: left; }
         | 
| 292 | 
            +
             | 
| 293 | 
            +
                    .nav h4 {
         | 
| 294 | 
            +
                      padding: 1px;
         | 
| 295 | 
            +
                      color: #888;
         | 
| 296 | 
            +
                      text-shadow: 0 1px 0 rgba(250, 250, 250, 0.4); }
         | 
| 297 | 
            +
             | 
| 298 | 
            +
                    .nav ul {
         | 
| 299 | 
            +
                      margin-left: 10px; }
         | 
| 300 | 
            +
             | 
| 301 | 
            +
                    .nav .segment {
         | 
| 302 | 
            +
                      margin: 0;
         | 
| 303 | 
            +
                      padding: 10px 20px 10px 10px;
         | 
| 304 | 
            +
                      border-left: solid 1px #ccc;
         | 
| 305 | 
            +
                      box-shadow: inset 1px 0 0 #e0e0e0; }
         | 
| 306 | 
            +
             | 
| 307 | 
            +
                    .nav .segment:first-child {
         | 
| 308 | 
            +
                      box-shadow: none;
         | 
| 309 | 
            +
                      border-left: 0; }
         | 
| 310 | 
            +
             | 
| 311 | 
            +
                    .nav h4,
         | 
| 312 | 
            +
                    .nav a {
         | 
| 313 | 
            +
                      height: 24px;
         | 
| 314 | 
            +
                      line-height: 24px; }
         | 
| 315 | 
            +
             | 
| 316 | 
            +
                    .nav a {
         | 
| 317 | 
            +
                      color: #888;
         | 
| 318 | 
            +
                      text-shadow: 0 1px 0 rgba(250, 250, 250, 0.4);
         | 
| 319 | 
            +
                      text-decoration: none;
         | 
| 320 | 
            +
             | 
| 321 | 
            +
                      display: block;
         | 
| 322 | 
            +
                      padding: 0 10px;
         | 
| 323 | 
            +
                      border: solid 1px rgba(0, 0, 0, 0.1);
         | 
| 324 | 
            +
                      border-radius: 3px; }
         | 
| 325 | 
            +
             | 
| 326 | 
            +
                    .nav ul a {
         | 
| 327 | 
            +
                      border-radius: 0;
         | 
| 328 | 
            +
                      border-left-width: 0; }
         | 
| 329 | 
            +
             | 
| 330 | 
            +
                    .nav a:hover {
         | 
| 331 | 
            +
                      background: rgba(250, 250, 250, 0.2); }
         | 
| 332 | 
            +
             | 
| 333 | 
            +
                    .nav ul li:first-child a {
         | 
| 334 | 
            +
                      border-top-left-radius: 3px;
         | 
| 335 | 
            +
                      border-bottom-left-radius: 3px;
         | 
| 336 | 
            +
                      border-left-width: 1px; }
         | 
| 337 | 
            +
                      
         | 
| 338 | 
            +
                    .nav ul li:last-child a {
         | 
| 339 | 
            +
                      border-top-right-radius: 3px;
         | 
| 340 | 
            +
                      border-bottom-right-radius: 3px; }
         | 
| 341 | 
            +
             | 
| 342 | 
            +
                    /* Print mode */
         | 
| 343 | 
            +
                    body.print {
         | 
| 344 | 
            +
                      padding: 0;
         | 
| 345 | 
            +
                      background: transparent; }
         | 
| 346 | 
            +
             | 
| 347 | 
            +
                    body.print .table-header,
         | 
| 348 | 
            +
                    body.print .nav {
         | 
| 349 | 
            +
                      display: none; }
         | 
| 350 | 
            +
             | 
| 351 | 
            +
                    body.print table {
         | 
| 352 | 
            +
                      box-shadow: none;
         | 
| 353 | 
            +
                      margin-left: 0;
         | 
| 354 | 
            +
                      margin-right: 0;
         | 
| 355 | 
            +
                      border: 0; }
         | 
| 356 | 
            +
             | 
| 357 | 
            +
                    /* Sprint table */
         | 
| 358 | 
            +
                    table.sprints td {
         | 
| 359 | 
            +
                      padding: 10px; }
         | 
| 360 | 
            +
             | 
| 361 | 
            +
                    table.sprints td.progress {
         | 
| 362 | 
            +
                      width: 200px; }
         | 
| 363 | 
            +
             | 
| 364 | 
            +
                    table.sprints td.progress .bar {
         | 
| 365 | 
            +
                      background: #ccc; }
         | 
| 366 | 
            +
             | 
| 367 | 
            +
              %body
         | 
| 368 | 
            +
                .nav
         | 
| 369 | 
            +
                  .segment
         | 
| 370 | 
            +
                    - if list.sprints?
         | 
| 371 | 
            +
                      %h4 Filter by:
         | 
| 372 | 
            +
                      %ul.sprint-nav
         | 
| 373 | 
            +
                        - list.sprints.values.each do |sprint|
         | 
| 374 | 
            +
                          %li
         | 
| 375 | 
            +
                            %a{href: "#sprint-#{sprint.slug}", data: { sprint: sprint.slug }}= sprint.id
         | 
| 376 | 
            +
             | 
| 377 | 
            +
                      %ul
         | 
| 378 | 
            +
                        %li
         | 
| 379 | 
            +
                          %a{href: '#all'} All
         | 
| 380 | 
            +
             | 
| 381 | 
            +
                  .segment
         | 
| 382 | 
            +
                    %ul
         | 
| 383 | 
            +
                      %li
         | 
| 384 | 
            +
                        %a{href: '#print'} Export mode
         | 
| 385 | 
            +
             | 
| 386 | 
            +
             | 
| 387 | 
            +
                - if list.sprints?
         | 
| 388 | 
            +
                  %h2.table-header Schedule
         | 
| 389 | 
            +
             | 
| 390 | 
            +
                  %table.sprints
         | 
| 391 | 
            +
                    - max_points = list.sprints.values.map(&:points).max
         | 
| 392 | 
            +
                    - list.sprints.values.each do |sprint|
         | 
| 393 | 
            +
                      %tr{data: { sprint: sprint.slug }}
         | 
| 394 | 
            +
                        %td.name= sprint.name
         | 
| 395 | 
            +
                        %td.progress
         | 
| 396 | 
            +
                          .bar{style: "width: #{sprint.points*100/max_points}%"}
         | 
| 397 | 
            +
                            - if sprint.percent > 0
         | 
| 398 | 
            +
                              %span{style: "width: #{(sprint.percent*100).to_i}%"}
         | 
| 399 | 
            +
                          %span.number= "#{(sprint.percent*100).to_i}%"
         | 
| 400 | 
            +
                        %td.points
         | 
| 401 | 
            +
                          %span.points.done= sprint.points_done.round(1).to_s.gsub(/\.0+$/,'')
         | 
| 402 | 
            +
                          %span.of of
         | 
| 403 | 
            +
                          %span.points.total= sprint.points.round(1).to_s.gsub(/\.0+$/,'')
         | 
| 404 | 
            +
                  %br
         | 
| 405 | 
            +
             | 
| 406 | 
            +
                %h2.table-header Tasks
         | 
| 407 | 
            +
             | 
| 408 | 
            +
                %table.tasks
         | 
| 153 409 | 
             
                  %thead
         | 
| 154 410 | 
             
                    %tr
         | 
| 155 411 | 
             
                      %th.task Task
         | 
| 156 412 | 
             
                      %th.owner Owner
         | 
| 413 | 
            +
                      - if list.sprints?
         | 
| 414 | 
            +
                        %th.sprints Schedule
         | 
| 157 415 | 
             
                      %th.progress Progress
         | 
| 158 416 | 
             
                      %th.points Points
         | 
| 159 417 |  | 
| 160 418 | 
             
                  - list.walk do |task, recurse|
         | 
| 161 419 |  | 
| 162 | 
            -
                    %tr{ | 
| 420 | 
            +
                    %tr{id: "task_#{task.id}", class: "#{task.css_class}"}
         | 
| 163 421 | 
             
                      %td.task
         | 
| 164 | 
            -
                        %span.status{class: " | 
| 422 | 
            +
                        %span.status{class: "status-bg-#{task.status}"}
         | 
| 165 423 | 
             
                        = task
         | 
| 166 424 | 
             
                        - if task.pivotal_id
         | 
| 167 | 
            -
                          %a.meta{href: task.pivotal_url}= " | 
| 425 | 
            +
                          %a.meta{href: task.pivotal_url, target: '_blank'}= "PT ##{task.pivotal_id}"
         | 
| 426 | 
            +
                        - if task.trello_id
         | 
| 427 | 
            +
                          %a.meta{href: task.trello_url, target: '_blank'}= "Trello"
         | 
| 428 | 
            +
             | 
| 429 | 
            +
                        - if task.tags.any?
         | 
| 430 | 
            +
                          - task.tags.each do |tag|
         | 
| 431 | 
            +
                            %span.meta= tag
         | 
| 432 | 
            +
             | 
| 433 | 
            +
                        - if task.leaf? && task.points != 1
         | 
| 434 | 
            +
                          %span.meta=  ("%.2f" % task.points).gsub(/\.?0+$/,'') + "pts"
         | 
| 168 435 |  | 
| 169 436 | 
             
                      %td.owner
         | 
| 170 437 | 
             
                        = task.owner
         | 
| 171 438 |  | 
| 439 | 
            +
                      - if list.sprints?
         | 
| 440 | 
            +
                        %td.sprints
         | 
| 441 | 
            +
                          - if task.sprint?
         | 
| 442 | 
            +
                            %span.sprint-display
         | 
| 443 | 
            +
                              %span.sprint-progress{style: "width: #{45*task.sprint.index+6}px"}
         | 
| 444 | 
            +
                              %span.sprint-marker{class: "status-bg-#{task.status}"}
         | 
| 445 | 
            +
                              %span.sprint-label
         | 
| 446 | 
            +
                                = task.sprint.id
         | 
| 447 | 
            +
             | 
| 172 448 | 
             
                      %td.progress
         | 
| 173 449 | 
             
                        .bar
         | 
| 174 | 
            -
                           | 
| 450 | 
            +
                          - if task.percent > 0
         | 
| 451 | 
            +
                            %span{style: "width: #{(task.percent*100).to_i}%"}
         | 
| 175 452 | 
             
                        %span.number= "#{(task.percent*100).to_i}%"
         | 
| 176 453 |  | 
| 177 454 | 
             
                      %td.points
         | 
| @@ -180,3 +457,4 @@ | |
| 180 457 | 
             
                        %span.points.total= task.points.round(1).to_s.gsub(/\.0+$/,'')
         | 
| 181 458 |  | 
| 182 459 | 
             
                    - recurse.call  if recurse
         | 
| 460 | 
            +
             | 
    
        data/lib/tps/cli_reporter.rb
    CHANGED
    
    | @@ -25,15 +25,23 @@ module TPS | |
| 25 25 | 
             
                  # Columns
         | 
| 26 26 | 
             
                  c1 = "%s %s %s" % [ indent, status, task.name ]
         | 
| 27 27 | 
             
                  c2 = if task.feature? || task.milestone?
         | 
| 28 | 
            -
                         progress
         | 
| 28 | 
            +
                         "%6s %s" % [ points, progress ]
         | 
| 29 29 | 
             
                      else
         | 
| 30 | 
            -
                        ' '* | 
| 30 | 
            +
                        ' '*19
         | 
| 31 31 | 
             
                      end
         | 
| 32 32 |  | 
| 33 33 | 
             
                  pref = c("-"*80, 30)+"\n"  if task.feature?
         | 
| 34 34 |  | 
| 35 35 | 
             
                  # Put together
         | 
| 36 | 
            -
                  "#{pref}" + "%- | 
| 36 | 
            +
                  "#{pref}" + "%-88s%s\n" % [ c1, c2 ]
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                def points
         | 
| 40 | 
            +
                  [
         | 
| 41 | 
            +
                    "%3i" % [ task.points_done ],
         | 
| 42 | 
            +
                    c("/", 30),
         | 
| 43 | 
            +
                    c("%-2i" % [ task.points ], 32)
         | 
| 44 | 
            +
                  ].join ''
         | 
| 37 45 | 
             
                end
         | 
| 38 46 |  | 
| 39 47 | 
             
                def color
         | 
    
        data/lib/tps/importer.rb
    ADDED
    
    | @@ -0,0 +1,67 @@ | |
| 1 | 
            +
            require 'json'
         | 
| 2 | 
            +
            require 'yaml'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module TPS
         | 
| 5 | 
            +
              module Importer
         | 
| 6 | 
            +
                def self.create(file, format=nil)
         | 
| 7 | 
            +
                  TPS::Importer::Trello.new file
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                class Base
         | 
| 11 | 
            +
                  def initialize(file)
         | 
| 12 | 
            +
                    @file = file
         | 
| 13 | 
            +
                    @tree = Hash.new
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  def to_yaml
         | 
| 17 | 
            +
                    YAML::dump @tree
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                class Trello < Base
         | 
| 22 | 
            +
                  def initialize(file)
         | 
| 23 | 
            +
                    super
         | 
| 24 | 
            +
                    @data = JSON.parse(File.read(@file))
         | 
| 25 | 
            +
                    @tree = Hash.new
         | 
| 26 | 
            +
                    work!
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  def milestone
         | 
| 30 | 
            +
                    name = @data['name'] + ' milestone'
         | 
| 31 | 
            +
                    @tree[name] ||= Hash.new
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  def archived
         | 
| 35 | 
            +
                    name = @data['name'] + ' archived milestone'
         | 
| 36 | 
            +
                    @tree[name] ||= Hash.new
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  def work!
         | 
| 40 | 
            +
                    milestone
         | 
| 41 | 
            +
                    archived
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                    @lists ||= Hash.new
         | 
| 44 | 
            +
                    @data['lists'].each do |list|
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                      parent = list['closed'] ? archived : milestone
         | 
| 47 | 
            +
                      parent[list['name']] ||= Hash.new
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                      @lists[list['id']] = parent[list['name']]
         | 
| 50 | 
            +
                    end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                    @data['cards'].each do |card|
         | 
| 53 | 
            +
                      parent = @lists[card['idList']]
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                      labels = card['labels'].map { |l| l['name'].downcase }
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                      value = Array.new
         | 
| 58 | 
            +
                      value << 'done'  if labels.include?("done")
         | 
| 59 | 
            +
                      value << 'in progress'  if labels.include?("in progress")
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                      parent[card['name']] = value.empty? ? nil : value
         | 
| 62 | 
            +
                    end
         | 
| 63 | 
            +
                  end
         | 
| 64 | 
            +
                end
         | 
| 65 | 
            +
              end
         | 
| 66 | 
            +
            end
         | 
| 67 | 
            +
             | 
    
        data/lib/tps/sprint.rb
    ADDED
    
    | @@ -0,0 +1,48 @@ | |
| 1 | 
            +
            module TPS
         | 
| 2 | 
            +
              class Sprint
         | 
| 3 | 
            +
                attr_accessor :id
         | 
| 4 | 
            +
                attr_accessor :name
         | 
| 5 | 
            +
                attr_accessor :list
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                def initialize(id, name, list)
         | 
| 8 | 
            +
                  @id = id
         | 
| 9 | 
            +
                  @name = name
         | 
| 10 | 
            +
                  @list = list
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                # Returns the numeric index of the sprint.
         | 
| 14 | 
            +
                #
         | 
| 15 | 
            +
                #     s1.index  #=> 0
         | 
| 16 | 
            +
                #     s2.index  #=> 1
         | 
| 17 | 
            +
                #
         | 
| 18 | 
            +
                def index
         | 
| 19 | 
            +
                  list.sprints.keys.index id
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                def points
         | 
| 23 | 
            +
                  sublist ? sublist.points : 0.0
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                def points_done
         | 
| 27 | 
            +
                  sublist ? sublist.points_done : 0.0
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                def percent
         | 
| 31 | 
            +
                  sublist ? sublist.percent : 0.0
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                def sublist
         | 
| 35 | 
            +
                  @sublist ||= list.filter_by_sprint(self)
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                def slug
         | 
| 39 | 
            +
                  slugify id
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
              private
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                def slugify(str)
         | 
| 45 | 
            +
                  str.scan(/[A-Za-z0-9]+/).join('_').downcase
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
              end
         | 
| 48 | 
            +
            end
         | 
    
        data/lib/tps/task.rb
    CHANGED
    
    | @@ -7,12 +7,18 @@ module TPS | |
| 7 7 | 
             
                attr_reader :tasks    # Array of Tasks
         | 
| 8 8 | 
             
                attr_reader :owner
         | 
| 9 9 | 
             
                attr_reader :pivotal_id
         | 
| 10 | 
            +
                attr_reader :trello_id
         | 
| 10 11 | 
             
                attr_reader :parent
         | 
| 12 | 
            +
                attr_reader :tags
         | 
| 13 | 
            +
                attr_reader :list     # the root TaskList
         | 
| 14 | 
            +
                attr_reader :id
         | 
| 11 15 |  | 
| 12 | 
            -
                def initialize(parent, name, data=nil)
         | 
| 16 | 
            +
                def initialize(parent, name, data=nil, list)
         | 
| 13 17 | 
             
                  @name   = name
         | 
| 14 18 | 
             
                  @tasks  = Array.new
         | 
| 19 | 
            +
                  @tags   = Array.new
         | 
| 15 20 | 
             
                  @parent = parent
         | 
| 21 | 
            +
                  @list   = list
         | 
| 16 22 |  | 
| 17 23 | 
             
                  if data.is_a?(Array)
         | 
| 18 24 | 
             
                    tags = data
         | 
| @@ -38,6 +44,9 @@ module TPS | |
| 38 44 | 
             
                    # [pt/28394] -- Pivotal tracker
         | 
| 39 45 | 
             
                    elsif t =~ /^pt\/(.*)$/i
         | 
| 40 46 | 
             
                      @pivotal_id = $1.strip
         | 
| 47 | 
            +
                    # [tl/28394] -- Trello
         | 
| 48 | 
            +
                    elsif t =~ /^tr\/(.*)$/i
         | 
| 49 | 
            +
                      @trello_id = $1.strip
         | 
| 41 50 | 
             
                    # [50%] -- percentage
         | 
| 42 51 | 
             
                    elsif t =~ /^([\d\.]+)%$/
         | 
| 43 52 | 
             
                      @status = :in_progress
         | 
| @@ -45,13 +54,20 @@ module TPS | |
| 45 54 | 
             
                    # [0pt] -- points
         | 
| 46 55 | 
             
                    elsif t =~ /^([\d\.]+)pts?/i
         | 
| 47 56 | 
             
                      @points = $1.strip.to_f
         | 
| 57 | 
            +
                    # [-all] -- tags
         | 
| 58 | 
            +
                    elsif %w[- #].include?(t[0])
         | 
| 59 | 
            +
                      @tags.push t[1..-1]
         | 
| 60 | 
            +
                    # Sprint name
         | 
| 61 | 
            +
                    elsif list && list.sprints[t]
         | 
| 62 | 
            +
                      @sprint = list.sprints[t]
         | 
| 48 63 | 
             
                    end
         | 
| 49 64 | 
             
                  end
         | 
| 50 65 |  | 
| 51 | 
            -
                  @tasks = tasks.map { |task, data| Task.new self, task, data }  if tasks
         | 
| 66 | 
            +
                  @tasks = tasks.map { |task, data| Task.new self, task, data, self.list }  if tasks
         | 
| 52 67 |  | 
| 53 | 
            -
                   | 
| 54 | 
            -
             | 
| 68 | 
            +
                  @milestone = root? && is_milestone?(@name)
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                  @id = list.get_id  if list
         | 
| 55 71 | 
             
                end
         | 
| 56 72 |  | 
| 57 73 | 
             
                def status
         | 
| @@ -109,10 +125,109 @@ module TPS | |
| 109 125 | 
             
                  tasks.any?
         | 
| 110 126 | 
             
                end
         | 
| 111 127 |  | 
| 128 | 
            +
                def sprint?
         | 
| 129 | 
            +
                  !! sprint
         | 
| 130 | 
            +
                end
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                def sprint
         | 
| 133 | 
            +
                  @sprint || (parent && parent.sprint)
         | 
| 134 | 
            +
                end
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                def contains_sprint?(sprint)
         | 
| 137 | 
            +
                  contains? { |t| t.sprint == sprint }
         | 
| 138 | 
            +
                end
         | 
| 139 | 
            +
             | 
| 140 | 
            +
                # Filters a task list by tasks that match a given block, preserving its
         | 
| 141 | 
            +
                # ancestors even if they don't match.
         | 
| 142 | 
            +
                #
         | 
| 143 | 
            +
                #     @list.contains? { |t| t.done? }
         | 
| 144 | 
            +
                #     @list.contains? { |t| t.sprint == sprint }
         | 
| 145 | 
            +
                #
         | 
| 146 | 
            +
                def contains?(&blk)
         | 
| 147 | 
            +
                  blk.call(self) || tasks.detect { |t| t.contains?(&blk) }
         | 
| 148 | 
            +
                end
         | 
| 149 | 
            +
             | 
| 150 | 
            +
                def ancestor?(&blk)
         | 
| 151 | 
            +
                  blk.call(self) || parent && parent.ancestor?(&blk)
         | 
| 152 | 
            +
                end
         | 
| 153 | 
            +
             | 
| 154 | 
            +
                # Filters a task tree to tasks of a given criteria, preserving its
         | 
| 155 | 
            +
                # ancestors even if they don't match.
         | 
| 156 | 
            +
                #
         | 
| 157 | 
            +
                #     @list.filter { |t| t.done? }
         | 
| 158 | 
            +
                #
         | 
| 159 | 
            +
                def filter(&blk)
         | 
| 160 | 
            +
                  filter_by { |t| t.contains?(&blk) || t.ancestor?(&blk) }
         | 
| 161 | 
            +
                end
         | 
| 162 | 
            +
             | 
| 163 | 
            +
                # Returns a list of a task's ancestors, excluding the list.
         | 
| 164 | 
            +
                def breadcrumbs(include_self=true)
         | 
| 165 | 
            +
                  arr = []
         | 
| 166 | 
            +
                  arr += [self]  if include_self
         | 
| 167 | 
            +
                  arr = parent.breadcrumbs + arr  if parent
         | 
| 168 | 
            +
                  arr
         | 
| 169 | 
            +
                end
         | 
| 170 | 
            +
             | 
| 171 | 
            +
                def css_class
         | 
| 172 | 
            +
                  [
         | 
| 173 | 
            +
                    "level-#{level}",
         | 
| 174 | 
            +
                    "status-#{status}",
         | 
| 175 | 
            +
                    (tasks? ? 'parent' : 'leaf'),
         | 
| 176 | 
            +
                    ('root' if root?),
         | 
| 177 | 
            +
                    ('feature' if feature?),
         | 
| 178 | 
            +
                    ('milestone' if milestone?),
         | 
| 179 | 
            +
                    ('subtask' if subtask?),
         | 
| 180 | 
            +
                    ("sprint-#{sprint.slug}" if sprint?),
         | 
| 181 | 
            +
                    sprint_css_classes,
         | 
| 182 | 
            +
                    breadcrumbs(false).map { |t| "in_task_#{t.id}" }
         | 
| 183 | 
            +
                  ].flatten.compact.join(' ')
         | 
| 184 | 
            +
                end
         | 
| 185 | 
            +
             | 
| 186 | 
            +
                def sprint_css_classes
         | 
| 187 | 
            +
                  list.sprints.values.map { |sprint|
         | 
| 188 | 
            +
                    "has_sprint-#{sprint.slug}"  if contains_sprint?(sprint)
         | 
| 189 | 
            +
                  }.compact
         | 
| 190 | 
            +
                end
         | 
| 191 | 
            +
             | 
| 192 | 
            +
                def subtask?
         | 
| 193 | 
            +
                  !feature? && !milestone?
         | 
| 194 | 
            +
                end
         | 
| 195 | 
            +
             | 
| 196 | 
            +
                def filter_by_sprint(sprint)
         | 
| 197 | 
            +
                  filter { |t| t.sprint == sprint }
         | 
| 198 | 
            +
                end
         | 
| 199 | 
            +
             | 
| 200 | 
            +
                # Works like #filter, but only preserves ancestors if they match.
         | 
| 201 | 
            +
                def filter_by(&blk)
         | 
| 202 | 
            +
                  return nil  unless blk.call(self)
         | 
| 203 | 
            +
                  task = self.dup
         | 
| 204 | 
            +
                  task.instance_variable_set :@tasks, tasks.map { |t| t.filter_by(&blk) }.compact
         | 
| 205 | 
            +
                  task
         | 
| 206 | 
            +
                end
         | 
| 207 | 
            +
             | 
| 112 208 | 
             
                def pivotal_url
         | 
| 113 209 | 
             
                  "https://www.pivotaltracker.com/story/show/#{pivotal_id}"  if pivotal_id
         | 
| 114 210 | 
             
                end
         | 
| 115 211 |  | 
| 212 | 
            +
                # Looks up a task.
         | 
| 213 | 
            +
                #
         | 
| 214 | 
            +
                #     @list['Login']
         | 
| 215 | 
            +
                #
         | 
| 216 | 
            +
                def [](name)
         | 
| 217 | 
            +
                  tasks.detect { |t| t.name == name }
         | 
| 218 | 
            +
                end
         | 
| 219 | 
            +
             | 
| 220 | 
            +
                def trello_url
         | 
| 221 | 
            +
                  if trello_id
         | 
| 222 | 
            +
                    id = trello_id.match(/([A-Za-z0-9]+)$/) && $1.strip
         | 
| 223 | 
            +
                    if list.trello_board_url && id.match(/^[0-9]+/)
         | 
| 224 | 
            +
                      "#{list.trello_board_url}/#{id}"
         | 
| 225 | 
            +
                    else
         | 
| 226 | 
            +
                      "https://trello.com/c/#{id}"
         | 
| 227 | 
            +
                    end
         | 
| 228 | 
            +
                  end
         | 
| 229 | 
            +
                end
         | 
| 230 | 
            +
             | 
| 116 231 | 
             
                def percent
         | 
| 117 232 | 
             
                  if done?
         | 
| 118 233 | 
             
                    1.0
         | 
| @@ -142,6 +257,10 @@ module TPS | |
| 142 257 | 
             
                  !! @milestone
         | 
| 143 258 | 
             
                end
         | 
| 144 259 |  | 
| 260 | 
            +
                def leaf?
         | 
| 261 | 
            +
                  tasks.empty?
         | 
| 262 | 
            +
                end
         | 
| 263 | 
            +
             | 
| 145 264 | 
             
                # - list.walk do |task, recurse|
         | 
| 146 265 | 
             
                #   %ul
         | 
| 147 266 | 
             
                #     %li
         | 
| @@ -162,5 +281,14 @@ module TPS | |
| 162 281 | 
             
                  tpl = Tilt.new(template)
         | 
| 163 282 | 
             
                  tpl.evaluate({}, list: self)
         | 
| 164 283 | 
             
                end
         | 
| 284 | 
            +
             | 
| 285 | 
            +
              private
         | 
| 286 | 
            +
             | 
| 287 | 
            +
                def is_milestone?(str)
         | 
| 288 | 
            +
                  str = str.to_s.downcase
         | 
| 289 | 
            +
                  str.match(/^milestone|milestone$/i) ||
         | 
| 290 | 
            +
                    str.match(/^release|release$/i) ||
         | 
| 291 | 
            +
                    str.match(/^version|version$/i)
         | 
| 292 | 
            +
                end
         | 
| 165 293 | 
             
              end
         | 
| 166 294 | 
             
            end
         | 
    
        data/lib/tps/task_list.rb
    CHANGED
    
    | @@ -1,7 +1,12 @@ | |
| 1 1 | 
             
            module TPS
         | 
| 2 | 
            +
              # The root node.
         | 
| 2 3 | 
             
              class TaskList < Task
         | 
| 4 | 
            +
                attr_accessor :tasks
         | 
| 5 | 
            +
                attr_accessor :sprints
         | 
| 6 | 
            +
                attr_accessor :trello_board_url
         | 
| 7 | 
            +
             | 
| 3 8 | 
             
                def initialize(options)
         | 
| 4 | 
            -
                  super nil, nil
         | 
| 9 | 
            +
                  super nil, nil, nil, nil
         | 
| 5 10 |  | 
| 6 11 | 
             
                  data = if options[:yaml]
         | 
| 7 12 | 
             
                           YAML::load_file options[:yaml]
         | 
| @@ -11,7 +16,22 @@ module TPS | |
| 11 16 | 
             
                          options
         | 
| 12 17 | 
             
                         end
         | 
| 13 18 |  | 
| 14 | 
            -
                   | 
| 19 | 
            +
                  sprint_data = data.delete('Sprints') || {}
         | 
| 20 | 
            +
                  @sprints = Hash[*sprint_data.map { |id, name| [id, Sprint.new(id, name, self)] }.flatten]
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  @trello_board_url = data.delete('Trello URL')
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  @tasks = data.map { |task, data| Task.new nil, task, data, self }
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                def sprints?
         | 
| 28 | 
            +
                  sprints.any?
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                # Returns a fresh ID. (internal)
         | 
| 32 | 
            +
                def get_id
         | 
| 33 | 
            +
                  @task_count ||= 0
         | 
| 34 | 
            +
                  @task_count += 1
         | 
| 15 35 | 
             
                end
         | 
| 16 36 | 
             
              end
         | 
| 17 37 | 
             
            end
         | 
    
        data/lib/tps/version.rb
    CHANGED
    
    
    
        data/lib/tps.rb
    CHANGED
    
    
    
        data/test/hello.yml
    CHANGED
    
    
    
        data/test/sprint_test.rb
    ADDED
    
    | @@ -0,0 +1,64 @@ | |
| 1 | 
            +
            require File.expand_path('../test_helper', __FILE__)
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class SprintTest < UnitTest
         | 
| 4 | 
            +
              setup do
         | 
| 5 | 
            +
                @list = TPS::TaskList.new yaml: f('sprints.yml')
         | 
| 6 | 
            +
                @milestone = @list.tasks.first
         | 
| 7 | 
            +
                @s1 = @list.sprints['s1']
         | 
| 8 | 
            +
                @s2 = @list.sprints['s2']
         | 
| 9 | 
            +
              end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              test "Sprints" do
         | 
| 12 | 
            +
                assert @list.sprints.size == 2
         | 
| 13 | 
            +
                assert @s1.name == 'Sprint one'
         | 
| 14 | 
            +
                assert @s2.name == 'Sprint two'
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              test "Sprint model attributes" do
         | 
| 18 | 
            +
                sprint = @list.sprints['s1']
         | 
| 19 | 
            +
                assert sprint.name == 'Sprint one'
         | 
| 20 | 
            +
                assert sprint.list == @list
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              test "Tasks should be assigned to sprints" do
         | 
| 24 | 
            +
                assert @list['Version 1']['Account']['Login'].sprint == @s1
         | 
| 25 | 
            +
              end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
              test "Task#contains_sprint?" do
         | 
| 28 | 
            +
                assert @list.contains_sprint?(@s1)
         | 
| 29 | 
            +
                assert @list.contains_sprint?(@s2)
         | 
| 30 | 
            +
              end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
              test "Task#contains_sprint? part 2" do
         | 
| 33 | 
            +
                task = @list['Version 1']['Account']['Login']
         | 
| 34 | 
            +
                assert task.contains_sprint?(@s1)
         | 
| 35 | 
            +
                assert ! task.contains_sprint?(@s2)
         | 
| 36 | 
            +
              end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
              test "Task#filter_by_sprint" do
         | 
| 39 | 
            +
                list = @list.filter_by_sprint(@s1)
         | 
| 40 | 
            +
                assert ! list['Version 1']['Account']['Login'].nil?
         | 
| 41 | 
            +
                assert list['Version 1']['Account']['Signup'].nil?
         | 
| 42 | 
            +
              end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
              test "Sub-tasks of a sprint task" do
         | 
| 45 | 
            +
                list = @list.filter_by_sprint(@s1)
         | 
| 46 | 
            +
                task = list['Version 1']['Comments']['Creating']
         | 
| 47 | 
            +
                assert ! task.nil?
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
              test "Sprint#index" do
         | 
| 51 | 
            +
                assert @list.sprints['s1'].index == 0
         | 
| 52 | 
            +
                assert @list.sprints['s2'].index == 1
         | 
| 53 | 
            +
              end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
              test "Sprint#points" do
         | 
| 56 | 
            +
                assert @s1.points == 5
         | 
| 57 | 
            +
              end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
              test "Sprint#points_done" do
         | 
| 60 | 
            +
                assert @s1.points_done == 3
         | 
| 61 | 
            +
              end
         | 
| 62 | 
            +
            end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
             
         | 
    
        data/test/sprints.yml
    ADDED
    
    
    
        data/test/tps_test.rb
    CHANGED
    
    | @@ -61,6 +61,12 @@ class MyTest < UnitTest | |
| 61 61 | 
             
                assert_equal 0.25, task.percent
         | 
| 62 62 | 
             
              end
         | 
| 63 63 |  | 
| 64 | 
            +
              test "Progress override" do
         | 
| 65 | 
            +
                task = @milestone.tasks[7]
         | 
| 66 | 
            +
                assert_equal 0.2, task.percent
         | 
| 67 | 
            +
                assert_equal 0.4, task.points_done
         | 
| 68 | 
            +
              end
         | 
| 69 | 
            +
             | 
| 64 70 | 
             
              test "Milestone" do
         | 
| 65 71 | 
             
                assert @milestone.milestone?
         | 
| 66 72 | 
             
              end
         | 
| @@ -68,4 +74,25 @@ class MyTest < UnitTest | |
| 68 74 | 
             
              test "HTML works" do
         | 
| 69 75 | 
             
                assert @list.to_html
         | 
| 70 76 | 
             
              end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
              test "Lookup" do
         | 
| 79 | 
            +
                assert @list['Milestone 1'] == @milestone
         | 
| 80 | 
            +
              end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
              test "Task#breadcrumbs" do
         | 
| 83 | 
            +
                crumbs = @list['Milestone 1']['User login']['Signup'].breadcrumbs
         | 
| 84 | 
            +
                assert crumbs == [
         | 
| 85 | 
            +
                  @list['Milestone 1'],
         | 
| 86 | 
            +
                  @list['Milestone 1']['User login'],
         | 
| 87 | 
            +
                  @list['Milestone 1']['User login']['Signup']
         | 
| 88 | 
            +
                ]
         | 
| 89 | 
            +
              end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
              test "Task#breadcrumbs(false)" do
         | 
| 92 | 
            +
                crumbs = @list['Milestone 1']['User login']['Signup'].breadcrumbs(false)
         | 
| 93 | 
            +
                assert crumbs == [
         | 
| 94 | 
            +
                  @list['Milestone 1'],
         | 
| 95 | 
            +
                  @list['Milestone 1']['User login']
         | 
| 96 | 
            +
                ]
         | 
| 97 | 
            +
              end
         | 
| 71 98 | 
             
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: tps_reporter
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0.0 | 
| 4 | 
            +
              version: 0.2.0
         | 
| 5 5 | 
             
              prerelease: 
         | 
| 6 6 | 
             
            platform: ruby
         | 
| 7 7 | 
             
            authors:
         | 
| @@ -9,11 +9,11 @@ authors: | |
| 9 9 | 
             
            autorequire: 
         | 
| 10 10 | 
             
            bindir: bin
         | 
| 11 11 | 
             
            cert_chain: []
         | 
| 12 | 
            -
            date: 2012- | 
| 12 | 
            +
            date: 2012-11-24 00:00:00.000000000 Z
         | 
| 13 13 | 
             
            dependencies:
         | 
| 14 14 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 15 15 | 
             
              name: tilt
         | 
| 16 | 
            -
              requirement:  | 
| 16 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 17 17 | 
             
                none: false
         | 
| 18 18 | 
             
                requirements:
         | 
| 19 19 | 
             
                - - ! '>='
         | 
| @@ -21,10 +21,15 @@ dependencies: | |
| 21 21 | 
             
                    version: '0'
         | 
| 22 22 | 
             
              type: :runtime
         | 
| 23 23 | 
             
              prerelease: false
         | 
| 24 | 
            -
              version_requirements:  | 
| 24 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 25 | 
            +
                none: false
         | 
| 26 | 
            +
                requirements:
         | 
| 27 | 
            +
                - - ! '>='
         | 
| 28 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 29 | 
            +
                    version: '0'
         | 
| 25 30 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 26 31 | 
             
              name: contest
         | 
| 27 | 
            -
              requirement:  | 
| 32 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 28 33 | 
             
                none: false
         | 
| 29 34 | 
             
                requirements:
         | 
| 30 35 | 
             
                - - ! '>='
         | 
| @@ -32,7 +37,12 @@ dependencies: | |
| 32 37 | 
             
                    version: '0'
         | 
| 33 38 | 
             
              type: :development
         | 
| 34 39 | 
             
              prerelease: false
         | 
| 35 | 
            -
              version_requirements:  | 
| 40 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 41 | 
            +
                none: false
         | 
| 42 | 
            +
                requirements:
         | 
| 43 | 
            +
                - - ! '>='
         | 
| 44 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 45 | 
            +
                    version: '0'
         | 
| 36 46 | 
             
            description: A YAML-powered, simple command-line task report builder.
         | 
| 37 47 | 
             
            email:
         | 
| 38 48 | 
             
            - rico@sinefunc.com
         | 
| @@ -41,6 +51,8 @@ executables: | |
| 41 51 | 
             
            extensions: []
         | 
| 42 52 | 
             
            extra_rdoc_files: []
         | 
| 43 53 | 
             
            files:
         | 
| 54 | 
            +
            - .gitignore
         | 
| 55 | 
            +
            - GUIDE.md
         | 
| 44 56 | 
             
            - HISTORY.md
         | 
| 45 57 | 
             
            - README.md
         | 
| 46 58 | 
             
            - Rakefile
         | 
| @@ -49,10 +61,14 @@ files: | |
| 49 61 | 
             
            - data/sample.yml
         | 
| 50 62 | 
             
            - lib/tps.rb
         | 
| 51 63 | 
             
            - lib/tps/cli_reporter.rb
         | 
| 64 | 
            +
            - lib/tps/importer.rb
         | 
| 65 | 
            +
            - lib/tps/sprint.rb
         | 
| 52 66 | 
             
            - lib/tps/task.rb
         | 
| 53 67 | 
             
            - lib/tps/task_list.rb
         | 
| 54 68 | 
             
            - lib/tps/version.rb
         | 
| 55 69 | 
             
            - test/hello.yml
         | 
| 70 | 
            +
            - test/sprint_test.rb
         | 
| 71 | 
            +
            - test/sprints.yml
         | 
| 56 72 | 
             
            - test/test_helper.rb
         | 
| 57 73 | 
             
            - test/tps_test.rb
         | 
| 58 74 | 
             
            - tps_reporter.gemspec
         | 
| @@ -76,7 +92,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 76 92 | 
             
                  version: '0'
         | 
| 77 93 | 
             
            requirements: []
         | 
| 78 94 | 
             
            rubyforge_project: 
         | 
| 79 | 
            -
            rubygems_version: 1.8. | 
| 95 | 
            +
            rubygems_version: 1.8.23
         | 
| 80 96 | 
             
            signing_key: 
         | 
| 81 97 | 
             
            specification_version: 3
         | 
| 82 98 | 
             
            summary: Task progress sheet reporter.
         |