plansheet 0.12.0 → 0.15.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +6 -2
- data/Gemfile.lock +1 -1
- data/exe/plansheet +6 -7
- data/lib/plansheet/pool.rb +53 -0
- data/lib/plansheet/project/stringify.rb +49 -0
- data/lib/plansheet/project/yaml.rb +167 -0
- data/lib/plansheet/project.rb +16 -189
- data/lib/plansheet/version.rb +1 -1
- data/lib/plansheet.rb +2 -19
- metadata +5 -3
- data/.rubocop_todo.yml +0 -35
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: f121593269dc11059fe547d8b72d58c2ad1a6685ae4826ac39daaff8b33f7c9a
         | 
| 4 | 
            +
              data.tar.gz: 4020dab046e837f02b64cfc975408240d472b04a26bc6f40e7e552d915d77336
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: ae82c5c2df93d55e520262754cc1c6e8b1b80a9a2fe230c09d00bd7ed4a38790c71b0dfcde5ac178e38112a26284219f21a8d99e48212601bbbcfae98607c0d1
         | 
| 7 | 
            +
              data.tar.gz: 6ca757244abd515dcd86bbedb5a31590a53fbc8720e35167549bfe326c1f96673acef49b0dd1044a0e92f43253ef1442d126872859cebfa0f1185bb06612da59
         | 
    
        data/.rubocop.yml
    CHANGED
    
    | @@ -1,5 +1,3 @@ | |
| 1 | 
            -
            inherit_from: .rubocop_todo.yml
         | 
| 2 | 
            -
             | 
| 3 1 | 
             
            AllCops:
         | 
| 4 2 | 
             
              TargetRubyVersion: 2.6
         | 
| 5 3 | 
             
              NewCops: enable
         | 
| @@ -15,3 +13,9 @@ Style/StringLiteralsInInterpolation: | |
| 15 13 |  | 
| 16 14 | 
             
            Layout/LineLength:
         | 
| 17 15 | 
             
              Max: 120
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            Metrics:
         | 
| 18 | 
            +
              Enabled: false
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            Style/Documentation:
         | 
| 21 | 
            +
              Enabled: false
         | 
    
        data/Gemfile.lock
    CHANGED
    
    
    
        data/exe/plansheet
    CHANGED
    
    | @@ -25,18 +25,17 @@ options = {} | |
| 25 25 | 
             
            parser.parse!(into: options)
         | 
| 26 26 |  | 
| 27 27 | 
             
            config = Plansheet.load_config
         | 
| 28 | 
            +
            pool = Plansheet::Pool.new({ projects_dir: config["projects_dir"] })
         | 
| 28 29 |  | 
| 29 30 | 
             
            if options[:sheet] || options.empty?
         | 
| 30 | 
            -
              project_arr = Plansheet.load_projects_dir config["projects_dir"]
         | 
| 31 | 
            -
             | 
| 32 31 | 
             
              Dir.mkdir config["output_dir"] unless Dir.exist? config["output_dir"]
         | 
| 33 | 
            -
             | 
| 34 | 
            -
              Plansheet::Sheet.new("#{config["output_dir"]}/projects.md", project_arr)
         | 
| 32 | 
            +
              Plansheet::Sheet.new("#{config["output_dir"]}/projects.md", pool.projects)
         | 
| 35 33 | 
             
            elsif options[:sort]
         | 
| 36 | 
            -
               | 
| 34 | 
            +
              # Pool sorts projects, this now just matches old behaviour
         | 
| 35 | 
            +
              pool.write_projects
         | 
| 37 36 | 
             
            elsif options[:cli]
         | 
| 38 | 
            -
               | 
| 39 | 
            -
              project_arr. | 
| 37 | 
            +
              # TODO: add a project filter method
         | 
| 38 | 
            +
              project_arr = pool.projects
         | 
| 40 39 | 
             
              project_arr.delete_if { |x| x.status == "dropped" || x.status == "done" }
         | 
| 41 40 | 
             
              project_arr.select! { |x| x.location == options[:location_filter] } if options[:location_filter]
         | 
| 42 41 | 
             
              project_arr.each do |proj|
         | 
| @@ -0,0 +1,53 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Plansheet
         | 
| 4 | 
            +
              # The "pool" is the aggregated collection of projects, calendar events, etc.
         | 
| 5 | 
            +
              class Pool
         | 
| 6 | 
            +
                attr_accessor :projects
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                def initialize(config)
         | 
| 9 | 
            +
                  @projects_dir = config[:projects_dir]
         | 
| 10 | 
            +
                  # @completed_projects_dir = config(:completed_projects_dir)
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  load_projects_dir(@projects_dir)
         | 
| 13 | 
            +
                  # TODO: Slurp all files
         | 
| 14 | 
            +
                  sort_projects
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                def sort_projects
         | 
| 18 | 
            +
                  @projects.sort!
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                def project_namespaces
         | 
| 22 | 
            +
                  @projects.collect(&:namespace).uniq.sort
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                def projects_in_namespace(namespace)
         | 
| 26 | 
            +
                  @projects.select { |x| x.namespace == namespace }
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                def write_projects
         | 
| 30 | 
            +
                  # TODO: This leaves potential for duplicate projects where empty files
         | 
| 31 | 
            +
                  # are involved once completed project directories are a thing - will need
         | 
| 32 | 
            +
                  # to keep a list of project files to delete
         | 
| 33 | 
            +
                  project_namespaces.each do |ns|
         | 
| 34 | 
            +
                    # TODO: move this to ProjectYAMLFile
         | 
| 35 | 
            +
                    #
         | 
| 36 | 
            +
                    f = "#{@projects_dir}/#{ns}.yml"
         | 
| 37 | 
            +
                    pyf = ProjectYAMLFile.new f
         | 
| 38 | 
            +
                    pyf.projects = projects_in_namespace(ns)
         | 
| 39 | 
            +
                    pyf.write
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                def load_projects_dir(dir)
         | 
| 44 | 
            +
                  project_arr = []
         | 
| 45 | 
            +
                  projects = Dir.glob("*yml", base: dir)
         | 
| 46 | 
            +
                  projects.each do |l|
         | 
| 47 | 
            +
                    project_arr << ProjectYAMLFile.new(File.join(dir, l)).load_file
         | 
| 48 | 
            +
                  end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                  @projects = project_arr.flatten!
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
              end
         | 
| 53 | 
            +
            end
         | 
| @@ -0,0 +1,49 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Plansheet
         | 
| 4 | 
            +
              class Project
         | 
| 5 | 
            +
                def to_s
         | 
| 6 | 
            +
                  str = String.new
         | 
| 7 | 
            +
                  str << "# "
         | 
| 8 | 
            +
                  str << "#{@namespace} - " if @namespace
         | 
| 9 | 
            +
                  str << "#{@name}\n"
         | 
| 10 | 
            +
                  STRING_PROPERTIES.each do |o|
         | 
| 11 | 
            +
                    str << stringify_string_property(o)
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
                  DATE_PROPERTIES.each do |o|
         | 
| 14 | 
            +
                    str << stringify_string_property(o)
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
                  ARRAY_PROPERTIES.each do |o|
         | 
| 17 | 
            +
                    str << stringify_array_property(o)
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
                  str
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                def stringify_string_property(prop)
         | 
| 23 | 
            +
                  if instance_variable_defined? "@#{prop}"
         | 
| 24 | 
            +
                    "#{prop}: #{instance_variable_get("@#{prop}")}\n"
         | 
| 25 | 
            +
                  else
         | 
| 26 | 
            +
                    ""
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                def stringify_date_property(prop)
         | 
| 31 | 
            +
                  if instance_variable_defined? "@#{prop}"
         | 
| 32 | 
            +
                    "#{prop}: #{instance_variable_get("@#{prop}")}\n"
         | 
| 33 | 
            +
                  else
         | 
| 34 | 
            +
                    ""
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                def stringify_array_property(prop)
         | 
| 39 | 
            +
                  str = String.new
         | 
| 40 | 
            +
                  if instance_variable_defined? "@#{prop}"
         | 
| 41 | 
            +
                    str << "#{prop}:\n"
         | 
| 42 | 
            +
                    instance_variable_get("@#{prop}").each do |t|
         | 
| 43 | 
            +
                      str << "- #{t}\n"
         | 
| 44 | 
            +
                    end
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
                  str
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
            end
         | 
| @@ -0,0 +1,167 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "yaml"
         | 
| 4 | 
            +
            require "date"
         | 
| 5 | 
            +
            require "pathname"
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            module Plansheet
         | 
| 8 | 
            +
              # Once there's some stability in plansheet and dc-kwalify, will pre-load this
         | 
| 9 | 
            +
              # to save the later YAML.load
         | 
| 10 | 
            +
              PROJECT_YAML_SCHEMA = <<~YAML
         | 
| 11 | 
            +
                desc: dc-tasks project schema
         | 
| 12 | 
            +
                type: seq
         | 
| 13 | 
            +
                sequence:
         | 
| 14 | 
            +
                  - type: map
         | 
| 15 | 
            +
                    mapping:
         | 
| 16 | 
            +
                      "project":
         | 
| 17 | 
            +
                        desc: Project name
         | 
| 18 | 
            +
                        type: str
         | 
| 19 | 
            +
                      "namespace":
         | 
| 20 | 
            +
                        desc: Project name
         | 
| 21 | 
            +
                        type: str
         | 
| 22 | 
            +
                      "priority":
         | 
| 23 | 
            +
                        desc: Project priority
         | 
| 24 | 
            +
                        type: str
         | 
| 25 | 
            +
                        enum:
         | 
| 26 | 
            +
                          - high
         | 
| 27 | 
            +
                          - medium
         | 
| 28 | 
            +
                          - low
         | 
| 29 | 
            +
                      "status":
         | 
| 30 | 
            +
                        desc: The current status of the project
         | 
| 31 | 
            +
                        type: str
         | 
| 32 | 
            +
                        enum:
         | 
| 33 | 
            +
                          - wip # project is a work-in-progress
         | 
| 34 | 
            +
                          - ready # project has tasks, ready to go
         | 
| 35 | 
            +
                          - waiting # project in waiting on some external person/event
         | 
| 36 | 
            +
                          - blocked # project is blocked by another project, but otherwise ready/wip
         | 
| 37 | 
            +
                          - planning # project in planning phase (set manually)
         | 
| 38 | 
            +
                          - idea # project is little more than an idea
         | 
| 39 | 
            +
                          - dropped # project has been explicitly dropped, but
         | 
| 40 | 
            +
                                    # want to keep around for reference, etc
         | 
| 41 | 
            +
                          - done # project is finished, but want to keep around
         | 
| 42 | 
            +
                                 # for reference, etc.
         | 
| 43 | 
            +
                      "location":
         | 
| 44 | 
            +
                        desc: Location
         | 
| 45 | 
            +
                        type: str
         | 
| 46 | 
            +
                      "notes":
         | 
| 47 | 
            +
                        desc: Free-form notes string
         | 
| 48 | 
            +
                        type: str
         | 
| 49 | 
            +
                      "time_estimate":
         | 
| 50 | 
            +
                        desc: The estimated amount of time before a project is completed
         | 
| 51 | 
            +
                        type: str
         | 
| 52 | 
            +
                      "frequency":
         | 
| 53 | 
            +
                        desc: The amount of time before a recurring project moves to ready status again from when it was last done (WIP)
         | 
| 54 | 
            +
                        type: str
         | 
| 55 | 
            +
                        pattern: /\\d+[dwDW]/
         | 
| 56 | 
            +
                      "lead_time":
         | 
| 57 | 
            +
                        desc: The amount of time before a recurring project is "due" moved to ready where the project (sort of a deferral mechanism) (WIP)
         | 
| 58 | 
            +
                        type: str
         | 
| 59 | 
            +
                        pattern: /\\d+[dwDW]/
         | 
| 60 | 
            +
                      "due":
         | 
| 61 | 
            +
                        desc: Due date of the task
         | 
| 62 | 
            +
                        type: date
         | 
| 63 | 
            +
                      "defer":
         | 
| 64 | 
            +
                        desc: Defer task until this day
         | 
| 65 | 
            +
                        type: date
         | 
| 66 | 
            +
                      "completed_on":
         | 
| 67 | 
            +
                        desc: When the (non-recurring) project was completed
         | 
| 68 | 
            +
                        type: date
         | 
| 69 | 
            +
                      "created_on":
         | 
| 70 | 
            +
                        desc: When the project was created
         | 
| 71 | 
            +
                        type: date
         | 
| 72 | 
            +
                      "starts_on":
         | 
| 73 | 
            +
                        desc: For ICS (WIP)
         | 
| 74 | 
            +
                        type: date
         | 
| 75 | 
            +
                      "last_reviewed":
         | 
| 76 | 
            +
                        desc: When the project was last reviewed (WIP)
         | 
| 77 | 
            +
                        type: date
         | 
| 78 | 
            +
                      "last_done":
         | 
| 79 | 
            +
                        desc: When the recurring project was last completed (WIP)
         | 
| 80 | 
            +
                        type: date
         | 
| 81 | 
            +
                      "dependencies":
         | 
| 82 | 
            +
                        desc: The names of projects that need to be completed before this project can be started/completed
         | 
| 83 | 
            +
                        type: seq
         | 
| 84 | 
            +
                        sequence:
         | 
| 85 | 
            +
                          - type: str
         | 
| 86 | 
            +
                      "externals":
         | 
| 87 | 
            +
                        desc: List of external commitments, ie who else cares about project completion?
         | 
| 88 | 
            +
                        type: seq
         | 
| 89 | 
            +
                        sequence:
         | 
| 90 | 
            +
                          - type: str
         | 
| 91 | 
            +
                      "urls":
         | 
| 92 | 
            +
                        desc: List of URLs that may be pertinent
         | 
| 93 | 
            +
                        type: seq
         | 
| 94 | 
            +
                        sequence:
         | 
| 95 | 
            +
                          - type: str
         | 
| 96 | 
            +
                      "tasks":
         | 
| 97 | 
            +
                        desc: List of tasks to do
         | 
| 98 | 
            +
                        type: seq
         | 
| 99 | 
            +
                        sequence:
         | 
| 100 | 
            +
                          - type: str
         | 
| 101 | 
            +
                      "done":
         | 
| 102 | 
            +
                        desc: List of tasks which have been completed
         | 
| 103 | 
            +
                        type: seq
         | 
| 104 | 
            +
                        sequence:
         | 
| 105 | 
            +
                          - type: str
         | 
| 106 | 
            +
                      "tags":
         | 
| 107 | 
            +
                        desc: List of tags (WIP)
         | 
| 108 | 
            +
                        type: seq
         | 
| 109 | 
            +
                        sequence:
         | 
| 110 | 
            +
                          - type: str
         | 
| 111 | 
            +
              YAML
         | 
| 112 | 
            +
              PROJECT_SCHEMA = YAML.safe_load(PROJECT_YAML_SCHEMA)
         | 
| 113 | 
            +
             | 
| 114 | 
            +
              class ProjectYAMLFile
         | 
| 115 | 
            +
                attr_accessor :projects
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                def initialize(path)
         | 
| 118 | 
            +
                  @path = path
         | 
| 119 | 
            +
                  # TODO: this won't GC, inline validation instead?
         | 
| 120 | 
            +
                end
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                def load_file
         | 
| 123 | 
            +
                  # Handle pre-Ruby 3.1 psych versions (this is brittle)
         | 
| 124 | 
            +
                  @raw = if Psych::VERSION.split(".")[0].to_i >= 4
         | 
| 125 | 
            +
                           YAML.load_file(@path, permitted_classes: [Date])
         | 
| 126 | 
            +
                         else
         | 
| 127 | 
            +
                           YAML.load_file(@path)
         | 
| 128 | 
            +
                         end
         | 
| 129 | 
            +
             | 
| 130 | 
            +
                  validate_schema
         | 
| 131 | 
            +
                  @raw ||= []
         | 
| 132 | 
            +
                  @projects = @raw.map do |proj|
         | 
| 133 | 
            +
                    proj["namespace"] = namespace
         | 
| 134 | 
            +
                    Project.new proj
         | 
| 135 | 
            +
                  end
         | 
| 136 | 
            +
                  @projects
         | 
| 137 | 
            +
                end
         | 
| 138 | 
            +
             | 
| 139 | 
            +
                def namespace
         | 
| 140 | 
            +
                  # TODO: yikes
         | 
| 141 | 
            +
                  ::Pathname.new(@path).basename.to_s.gsub(/\.yml$/, "")
         | 
| 142 | 
            +
                end
         | 
| 143 | 
            +
             | 
| 144 | 
            +
                def validate_schema
         | 
| 145 | 
            +
                  validator = Kwalify::Validator.new(Plansheet::PROJECT_SCHEMA)
         | 
| 146 | 
            +
                  errors = validator.validate(@raw)
         | 
| 147 | 
            +
                  # Check YAML validity
         | 
| 148 | 
            +
                  return unless errors && !errors.empty?
         | 
| 149 | 
            +
             | 
| 150 | 
            +
                  $stderr.write "Schema errors in #{@path}:\n"
         | 
| 151 | 
            +
                  errors.each { |err| puts "- [#{err.path}] #{err.message}" }
         | 
| 152 | 
            +
                  abort
         | 
| 153 | 
            +
                end
         | 
| 154 | 
            +
             | 
| 155 | 
            +
                def sort!
         | 
| 156 | 
            +
                  @projects.sort!
         | 
| 157 | 
            +
                end
         | 
| 158 | 
            +
             | 
| 159 | 
            +
                def write
         | 
| 160 | 
            +
                  File.write @path, yaml_dump
         | 
| 161 | 
            +
                end
         | 
| 162 | 
            +
             | 
| 163 | 
            +
                def yaml_dump
         | 
| 164 | 
            +
                  YAML.dump(@projects.map { |x| x.to_h.except("namespace") })
         | 
| 165 | 
            +
                end
         | 
| 166 | 
            +
              end
         | 
| 167 | 
            +
            end
         | 
    
        data/lib/plansheet/project.rb
    CHANGED
    
    | @@ -2,6 +2,8 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            require "yaml"
         | 
| 4 4 | 
             
            require "date"
         | 
| 5 | 
            +
            require_relative "project/yaml"
         | 
| 6 | 
            +
            require_relative "project/stringify"
         | 
| 5 7 |  | 
| 6 8 | 
             
            module Plansheet
         | 
| 7 9 | 
             
              PROJECT_STATUS_PRIORITY = {
         | 
| @@ -21,107 +23,6 @@ module Plansheet | |
| 21 23 | 
             
                "low" => 3
         | 
| 22 24 | 
             
              }.freeze
         | 
| 23 25 |  | 
| 24 | 
            -
              # Once there's some stability in plansheet and dc-kwalify, will pre-load this
         | 
| 25 | 
            -
              # to save the later YAML.load
         | 
| 26 | 
            -
              PROJECT_YAML_SCHEMA = <<~YAML
         | 
| 27 | 
            -
                desc: dc-tasks project schema
         | 
| 28 | 
            -
                type: seq
         | 
| 29 | 
            -
                sequence:
         | 
| 30 | 
            -
                  - type: map
         | 
| 31 | 
            -
                    mapping:
         | 
| 32 | 
            -
                      "project":
         | 
| 33 | 
            -
                        desc: Project name
         | 
| 34 | 
            -
                        type: str
         | 
| 35 | 
            -
                        required: yes
         | 
| 36 | 
            -
                      "priority":
         | 
| 37 | 
            -
                        desc: Project priority
         | 
| 38 | 
            -
                        type: str
         | 
| 39 | 
            -
                        enum:
         | 
| 40 | 
            -
                          - high
         | 
| 41 | 
            -
                          - medium
         | 
| 42 | 
            -
                          - low
         | 
| 43 | 
            -
                      "status":
         | 
| 44 | 
            -
                        desc: The current status of the project
         | 
| 45 | 
            -
                        type: str
         | 
| 46 | 
            -
                        enum:
         | 
| 47 | 
            -
                          - wip # project is a work-in-progress
         | 
| 48 | 
            -
                          - ready # project has tasks, ready to go
         | 
| 49 | 
            -
                          - waiting # project in waiting on some external person/event
         | 
| 50 | 
            -
                          - blocked # project is blocked by another project, but otherwise ready/wip
         | 
| 51 | 
            -
                          - planning # project in planning phase (set manually)
         | 
| 52 | 
            -
                          - idea # project is little more than an idea
         | 
| 53 | 
            -
                          - dropped # project has been explicitly dropped, but
         | 
| 54 | 
            -
                                    # want to keep around for reference, etc
         | 
| 55 | 
            -
                          - done # project is finished, but want to keep around
         | 
| 56 | 
            -
                                 # for reference, etc.
         | 
| 57 | 
            -
                      "location":
         | 
| 58 | 
            -
                        desc: Location
         | 
| 59 | 
            -
                        type: str
         | 
| 60 | 
            -
                      "notes":
         | 
| 61 | 
            -
                        desc: Free-form notes string
         | 
| 62 | 
            -
                        type: str
         | 
| 63 | 
            -
                      "time_estimate":
         | 
| 64 | 
            -
                        desc: The estimated amount of time before a project is completed
         | 
| 65 | 
            -
                        type: str
         | 
| 66 | 
            -
                      "frequency":
         | 
| 67 | 
            -
                        desc: The amount of time before a recurring project moves to ready status again from when it was last done (WIP)
         | 
| 68 | 
            -
                        type: str
         | 
| 69 | 
            -
                        pattern: /\\d+[dwDW]/
         | 
| 70 | 
            -
                      "lead_time":
         | 
| 71 | 
            -
                        desc: The amount of time before a recurring project is "due" moved to ready where the project (sort of a deferral mechanism) (WIP)
         | 
| 72 | 
            -
                        type: str
         | 
| 73 | 
            -
                        pattern: /\\d+[dwDW]/
         | 
| 74 | 
            -
                      "due":
         | 
| 75 | 
            -
                        desc: Due date of the task
         | 
| 76 | 
            -
                        type: date
         | 
| 77 | 
            -
                      "defer":
         | 
| 78 | 
            -
                        desc: Defer task until this day
         | 
| 79 | 
            -
                        type: date
         | 
| 80 | 
            -
                      "created_on":
         | 
| 81 | 
            -
                        desc: When the project was created
         | 
| 82 | 
            -
                        type: date
         | 
| 83 | 
            -
                      "starts_on":
         | 
| 84 | 
            -
                        desc: For ICS (WIP)
         | 
| 85 | 
            -
                        type: date
         | 
| 86 | 
            -
                      "last_reviewed":
         | 
| 87 | 
            -
                        desc: When the project was last reviewed (WIP)
         | 
| 88 | 
            -
                        type: date
         | 
| 89 | 
            -
                      "last_done":
         | 
| 90 | 
            -
                        desc: When the recurring project was last completed (WIP)
         | 
| 91 | 
            -
                        type: date
         | 
| 92 | 
            -
                      "dependencies":
         | 
| 93 | 
            -
                        desc: The names of projects that need to be completed before this project can be started/completed
         | 
| 94 | 
            -
                        type: seq
         | 
| 95 | 
            -
                        sequence:
         | 
| 96 | 
            -
                          - type: str
         | 
| 97 | 
            -
                      "externals":
         | 
| 98 | 
            -
                        desc: List of external commitments, ie who else cares about project completion?
         | 
| 99 | 
            -
                        type: seq
         | 
| 100 | 
            -
                        sequence:
         | 
| 101 | 
            -
                          - type: str
         | 
| 102 | 
            -
                      "urls":
         | 
| 103 | 
            -
                        desc: List of URLs that may be pertinent
         | 
| 104 | 
            -
                        type: seq
         | 
| 105 | 
            -
                        sequence:
         | 
| 106 | 
            -
                          - type: str
         | 
| 107 | 
            -
                      "tasks":
         | 
| 108 | 
            -
                        desc: List of tasks to do
         | 
| 109 | 
            -
                        type: seq
         | 
| 110 | 
            -
                        sequence:
         | 
| 111 | 
            -
                          - type: str
         | 
| 112 | 
            -
                      "done":
         | 
| 113 | 
            -
                        desc: List of tasks which have been completed
         | 
| 114 | 
            -
                        type: seq
         | 
| 115 | 
            -
                        sequence:
         | 
| 116 | 
            -
                          - type: str
         | 
| 117 | 
            -
                      "tags":
         | 
| 118 | 
            -
                        desc: List of tags (WIP)
         | 
| 119 | 
            -
                        type: seq
         | 
| 120 | 
            -
                        sequence:
         | 
| 121 | 
            -
                          - type: str
         | 
| 122 | 
            -
              YAML
         | 
| 123 | 
            -
              PROJECT_SCHEMA = YAML.safe_load(PROJECT_YAML_SCHEMA)
         | 
| 124 | 
            -
             | 
| 125 26 | 
             
              def self.parse_date_duration(str)
         | 
| 126 27 | 
             
                return Regexp.last_match(1).to_i if str.strip.match(/(\d+)[dD]/)
         | 
| 127 28 | 
             
                return (Regexp.last_match(1).to_i * 7) if str.strip.match(/(\d+)[wW]/)
         | 
| @@ -147,16 +48,19 @@ module Plansheet | |
| 147 48 | 
             
                  status
         | 
| 148 49 | 
             
                ].map { |x| "compare_#{x}".to_sym }.freeze
         | 
| 149 50 | 
             
                # NOTE: The order of these affects presentation!
         | 
| 51 | 
            +
                # namespace is derived from file name
         | 
| 150 52 | 
             
                STRING_PROPERTIES = %w[priority status location notes time_estimate frequency lead_time].freeze
         | 
| 151 | 
            -
                DATE_PROPERTIES = %w[due defer created_on starts_on last_done last_reviewed].freeze
         | 
| 53 | 
            +
                DATE_PROPERTIES = %w[due defer completed_on created_on starts_on last_done last_reviewed].freeze
         | 
| 152 54 | 
             
                ARRAY_PROPERTIES = %w[dependencies externals urls tasks done tags].freeze
         | 
| 153 55 |  | 
| 154 56 | 
             
                ALL_PROPERTIES = STRING_PROPERTIES + DATE_PROPERTIES + ARRAY_PROPERTIES
         | 
| 155 57 |  | 
| 156 58 | 
             
                attr_reader :name, *ALL_PROPERTIES
         | 
| 59 | 
            +
                attr_accessor :namespace
         | 
| 157 60 |  | 
| 158 61 | 
             
                def initialize(options)
         | 
| 159 62 | 
             
                  @name = options["project"]
         | 
| 63 | 
            +
                  @namespace = options["namespace"]
         | 
| 160 64 |  | 
| 161 65 | 
             
                  ALL_PROPERTIES.each do |o|
         | 
| 162 66 | 
             
                    instance_variable_set("@#{o}", options[o]) if options[o]
         | 
| @@ -240,8 +144,8 @@ module Plansheet | |
| 240 144 | 
             
                def status
         | 
| 241 145 | 
             
                  return @status if @status
         | 
| 242 146 | 
             
                  return recurring_status if recurring?
         | 
| 243 | 
            -
                  return task_based_status if  | 
| 244 | 
            -
                   | 
| 147 | 
            +
                  return task_based_status if @tasks || @done
         | 
| 148 | 
            +
                  return "done" if @completed_on && @tasks.nil?
         | 
| 245 149 |  | 
| 246 150 | 
             
                  "idea"
         | 
| 247 151 | 
             
                end
         | 
| @@ -260,18 +164,19 @@ module Plansheet | |
| 260 164 |  | 
| 261 165 | 
             
                def recurring_status
         | 
| 262 166 | 
             
                  # add frequency to last_done
         | 
| 263 | 
            -
                   | 
| 264 | 
            -
                    # This recurring project is being done for the first time
         | 
| 265 | 
            -
                    task_based_status
         | 
| 266 | 
            -
                  else
         | 
| 167 | 
            +
                  if @last_done
         | 
| 267 168 | 
             
                    # This project has been done once before
         | 
| 268 169 | 
             
                    subsequent_recurring_status
         | 
| 170 | 
            +
                  else
         | 
| 171 | 
            +
                    # This recurring project is being done for the first time
         | 
| 172 | 
            +
                    task_based_status
         | 
| 269 173 | 
             
                  end
         | 
| 270 174 | 
             
                end
         | 
| 271 175 |  | 
| 272 176 | 
             
                def subsequent_recurring_status
         | 
| 273 177 | 
             
                  return "done" if @lead_time && defer > Date.today
         | 
| 274 178 | 
             
                  return "done" if due > Date.today
         | 
| 179 | 
            +
             | 
| 275 180 | 
             
                  task_based_status
         | 
| 276 181 | 
             
                end
         | 
| 277 182 |  | 
| @@ -284,6 +189,7 @@ module Plansheet | |
| 284 189 | 
             
                def due
         | 
| 285 190 | 
             
                  return @due if @due
         | 
| 286 191 | 
             
                  return recurring_due_date if recurring?
         | 
| 192 | 
            +
             | 
| 287 193 | 
             
                  nil
         | 
| 288 194 | 
             
                end
         | 
| 289 195 |  | 
| @@ -298,6 +204,7 @@ module Plansheet | |
| 298 204 | 
             
                def defer
         | 
| 299 205 | 
             
                  return @defer if @defer
         | 
| 300 206 | 
             
                  return lead_time_deferral if @lead_time && due
         | 
| 207 | 
            +
             | 
| 301 208 | 
             
                  nil
         | 
| 302 209 | 
             
                end
         | 
| 303 210 |  | 
| @@ -314,50 +221,8 @@ module Plansheet | |
| 314 221 | 
             
                  status == "dropped" || status == "done"
         | 
| 315 222 | 
             
                end
         | 
| 316 223 |  | 
| 317 | 
            -
                def to_s
         | 
| 318 | 
            -
                  str = String.new
         | 
| 319 | 
            -
                  str << "# #{@name}\n"
         | 
| 320 | 
            -
                  STRING_PROPERTIES.each do |o|
         | 
| 321 | 
            -
                    str << stringify_string_property(o)
         | 
| 322 | 
            -
                  end
         | 
| 323 | 
            -
                  DATE_PROPERTIES.each do |o|
         | 
| 324 | 
            -
                    str << stringify_string_property(o)
         | 
| 325 | 
            -
                  end
         | 
| 326 | 
            -
                  ARRAY_PROPERTIES.each do |o|
         | 
| 327 | 
            -
                    str << stringify_array_property(o)
         | 
| 328 | 
            -
                  end
         | 
| 329 | 
            -
                  str
         | 
| 330 | 
            -
                end
         | 
| 331 | 
            -
             | 
| 332 | 
            -
                def stringify_string_property(prop)
         | 
| 333 | 
            -
                  if instance_variable_defined? "@#{prop}"
         | 
| 334 | 
            -
                    "#{prop}: #{instance_variable_get("@#{prop}")}\n"
         | 
| 335 | 
            -
                  else
         | 
| 336 | 
            -
                    ""
         | 
| 337 | 
            -
                  end
         | 
| 338 | 
            -
                end
         | 
| 339 | 
            -
             | 
| 340 | 
            -
                def stringify_date_property(prop)
         | 
| 341 | 
            -
                  if instance_variable_defined? "@#{prop}"
         | 
| 342 | 
            -
                    "#{prop}: #{instance_variable_get("@#{prop}")}\n"
         | 
| 343 | 
            -
                  else
         | 
| 344 | 
            -
                    ""
         | 
| 345 | 
            -
                  end
         | 
| 346 | 
            -
                end
         | 
| 347 | 
            -
             | 
| 348 | 
            -
                def stringify_array_property(prop)
         | 
| 349 | 
            -
                  str = String.new
         | 
| 350 | 
            -
                  if instance_variable_defined? "@#{prop}"
         | 
| 351 | 
            -
                    str << "#{prop}:\n"
         | 
| 352 | 
            -
                    instance_variable_get("@#{prop}").each do |t|
         | 
| 353 | 
            -
                      str << "- #{t}\n"
         | 
| 354 | 
            -
                    end
         | 
| 355 | 
            -
                  end
         | 
| 356 | 
            -
                  str
         | 
| 357 | 
            -
                end
         | 
| 358 | 
            -
             | 
| 359 224 | 
             
                def to_h
         | 
| 360 | 
            -
                  h = { "project" => @name }
         | 
| 225 | 
            +
                  h = { "project" => @name, "namespace" => @namespace }
         | 
| 361 226 | 
             
                  ALL_PROPERTIES.each do |prop|
         | 
| 362 227 | 
             
                    h[prop] = instance_variable_get("@#{prop}") if instance_variable_defined?("@#{prop}")
         | 
| 363 228 | 
             
                  end
         | 
| @@ -366,42 +231,4 @@ module Plansheet | |
| 366 231 | 
             
                  h
         | 
| 367 232 | 
             
                end
         | 
| 368 233 | 
             
              end
         | 
| 369 | 
            -
             | 
| 370 | 
            -
              class ProjectYAMLFile
         | 
| 371 | 
            -
                attr_reader :projects
         | 
| 372 | 
            -
             | 
| 373 | 
            -
                def initialize(path)
         | 
| 374 | 
            -
                  @path = path
         | 
| 375 | 
            -
                  # TODO: this won't GC, inline validation instead?
         | 
| 376 | 
            -
             | 
| 377 | 
            -
                  # Handle pre-Ruby 3.1 psych versions (this is brittle)
         | 
| 378 | 
            -
                  @raw = if Psych::VERSION.split(".")[0].to_i >= 4
         | 
| 379 | 
            -
                           YAML.load_file(path, permitted_classes: [Date])
         | 
| 380 | 
            -
                         else
         | 
| 381 | 
            -
                           YAML.load_file(path)
         | 
| 382 | 
            -
                         end
         | 
| 383 | 
            -
             | 
| 384 | 
            -
                  validate_schema
         | 
| 385 | 
            -
                  @projects = @raw.map { |proj| Project.new proj }
         | 
| 386 | 
            -
                end
         | 
| 387 | 
            -
             | 
| 388 | 
            -
                def validate_schema
         | 
| 389 | 
            -
                  validator = Kwalify::Validator.new(Plansheet::PROJECT_SCHEMA)
         | 
| 390 | 
            -
                  errors = validator.validate(@raw)
         | 
| 391 | 
            -
                  # Check YAML validity
         | 
| 392 | 
            -
                  return unless errors && !errors.empty?
         | 
| 393 | 
            -
             | 
| 394 | 
            -
                  $stderr.write "Schema errors in #{@path}:\n"
         | 
| 395 | 
            -
                  errors.each { |err| puts "- [#{err.path}] #{err.message}" }
         | 
| 396 | 
            -
                  abort
         | 
| 397 | 
            -
                end
         | 
| 398 | 
            -
             | 
| 399 | 
            -
                def sort!
         | 
| 400 | 
            -
                  @projects.sort!
         | 
| 401 | 
            -
                end
         | 
| 402 | 
            -
             | 
| 403 | 
            -
                def yaml_dump
         | 
| 404 | 
            -
                  YAML.dump(@projects.map(&:to_h))
         | 
| 405 | 
            -
                end
         | 
| 406 | 
            -
              end
         | 
| 407 234 | 
             
            end
         | 
    
        data/lib/plansheet/version.rb
    CHANGED
    
    
    
        data/lib/plansheet.rb
    CHANGED
    
    | @@ -2,6 +2,7 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            require_relative "plansheet/version"
         | 
| 4 4 | 
             
            require_relative "plansheet/project"
         | 
| 5 | 
            +
            require_relative "plansheet/pool"
         | 
| 5 6 | 
             
            require_relative "plansheet/sheet"
         | 
| 6 7 | 
             
            require "yaml"
         | 
| 7 8 | 
             
            require "kwalify"
         | 
| @@ -9,28 +10,10 @@ require "kwalify" | |
| 9 10 | 
             
            module Plansheet
         | 
| 10 11 | 
             
              class Error < StandardError; end
         | 
| 11 12 |  | 
| 13 | 
            +
              # TODO: config schema validation
         | 
| 12 14 | 
             
              def self.load_config
         | 
| 13 15 | 
             
                YAML.load_file "#{Dir.home}/.plansheet.yml"
         | 
| 14 16 | 
             
              rescue StandardError
         | 
| 15 17 | 
             
                abort "unable to load plansheet config file"
         | 
| 16 18 | 
             
              end
         | 
| 17 | 
            -
             | 
| 18 | 
            -
              def self.resort_projects_in_dir(dir)
         | 
| 19 | 
            -
                project_files = Dir.glob("#{dir}/*yml")
         | 
| 20 | 
            -
                project_files.each do |f|
         | 
| 21 | 
            -
                  pyf = ProjectYAMLFile.new(f)
         | 
| 22 | 
            -
                  pyf.sort!
         | 
| 23 | 
            -
                  File.write(f, pyf.yaml_dump)
         | 
| 24 | 
            -
                end
         | 
| 25 | 
            -
              end
         | 
| 26 | 
            -
             | 
| 27 | 
            -
              def self.load_projects_dir(dir)
         | 
| 28 | 
            -
                project_arr = []
         | 
| 29 | 
            -
                projects = Dir.glob("*yml", base: dir)
         | 
| 30 | 
            -
                projects.each do |l|
         | 
| 31 | 
            -
                  project_arr << ProjectYAMLFile.new(File.join(dir, l)).projects
         | 
| 32 | 
            -
                end
         | 
| 33 | 
            -
             | 
| 34 | 
            -
                project_arr.flatten!
         | 
| 35 | 
            -
              end
         | 
| 36 19 | 
             
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: plansheet
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 0.15.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - David Crosby
         | 
| 8 8 | 
             
            autorequire:
         | 
| 9 9 | 
             
            bindir: exe
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2022-06- | 
| 11 | 
            +
            date: 2022-06-09 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: dc-kwalify
         | 
| @@ -33,7 +33,6 @@ extensions: [] | |
| 33 33 | 
             
            extra_rdoc_files: []
         | 
| 34 34 | 
             
            files:
         | 
| 35 35 | 
             
            - ".rubocop.yml"
         | 
| 36 | 
            -
            - ".rubocop_todo.yml"
         | 
| 37 36 | 
             
            - CODE_OF_CONDUCT.md
         | 
| 38 37 | 
             
            - Gemfile
         | 
| 39 38 | 
             
            - Gemfile.lock
         | 
| @@ -44,7 +43,10 @@ files: | |
| 44 43 | 
             
            - examples/backpack.yml
         | 
| 45 44 | 
             
            - exe/plansheet
         | 
| 46 45 | 
             
            - lib/plansheet.rb
         | 
| 46 | 
            +
            - lib/plansheet/pool.rb
         | 
| 47 47 | 
             
            - lib/plansheet/project.rb
         | 
| 48 | 
            +
            - lib/plansheet/project/stringify.rb
         | 
| 49 | 
            +
            - lib/plansheet/project/yaml.rb
         | 
| 48 50 | 
             
            - lib/plansheet/sheet.rb
         | 
| 49 51 | 
             
            - lib/plansheet/version.rb
         | 
| 50 52 | 
             
            homepage: https://dafyddcrosby.com
         | 
    
        data/.rubocop_todo.yml
    DELETED
    
    | @@ -1,35 +0,0 @@ | |
| 1 | 
            -
            # This configuration was generated by
         | 
| 2 | 
            -
            # `rubocop --auto-gen-config`
         | 
| 3 | 
            -
            # on 2022-06-04 17:25:54 UTC using RuboCop version 1.29.1.
         | 
| 4 | 
            -
            # The point is for the user to remove these configuration records
         | 
| 5 | 
            -
            # one by one as the offenses are removed from the code base.
         | 
| 6 | 
            -
            # Note that changes in the inspected code, or installation of new
         | 
| 7 | 
            -
            # versions of RuboCop, may require this file to be generated again.
         | 
| 8 | 
            -
             | 
| 9 | 
            -
            # Offense count: 1
         | 
| 10 | 
            -
            # Configuration parameters: CountComments, CountAsOne.
         | 
| 11 | 
            -
            Metrics/ClassLength:
         | 
| 12 | 
            -
              Enabled: false
         | 
| 13 | 
            -
             | 
| 14 | 
            -
            # Offense count: 1
         | 
| 15 | 
            -
            # Configuration parameters: IgnoredMethods.
         | 
| 16 | 
            -
            Metrics/CyclomaticComplexity:
         | 
| 17 | 
            -
              Enabled: false
         | 
| 18 | 
            -
             | 
| 19 | 
            -
            # Offense count: 2
         | 
| 20 | 
            -
            # Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods.
         | 
| 21 | 
            -
            Metrics/MethodLength:
         | 
| 22 | 
            -
              Enabled: false
         | 
| 23 | 
            -
             | 
| 24 | 
            -
            # Offense count: 1
         | 
| 25 | 
            -
            # Configuration parameters: IgnoredMethods.
         | 
| 26 | 
            -
            Metrics/PerceivedComplexity:
         | 
| 27 | 
            -
              Enabled: false
         | 
| 28 | 
            -
             | 
| 29 | 
            -
            # Offense count: 2
         | 
| 30 | 
            -
            # Configuration parameters: AllowedConstants.
         | 
| 31 | 
            -
            Style/Documentation:
         | 
| 32 | 
            -
              Exclude:
         | 
| 33 | 
            -
                - 'test/**/*'
         | 
| 34 | 
            -
                - 'lib/plansheet.rb'
         | 
| 35 | 
            -
                - 'lib/plansheet/project.rb'
         |