devlogs 1.0.0 → 1.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9df7671be5997a9e311b6a8c10863732b95fb3e6bdb89a7f5ba8476d3cec4653
4
- data.tar.gz: af3731bb2c7134cea231b17a7c940d313d13f21c0721c720029e1084d706f0a4
3
+ metadata.gz: 6c4dc52933d3f7a0ff683b103879fb1bc9ad3f49eff9a93be745b0547339f881
4
+ data.tar.gz: 5442df05a0318ed2369c1679eb4c663d5c7a22e081c3b70cc231a69d405672ef
5
5
  SHA512:
6
- metadata.gz: ffe4be66ace5c88074c0ce3e8a20e0f82a53162e59776be3bfdbf1d72c038d4d73365ca65bf91047b4730a64efeb548f3e1b7f8a8704a0f9554bc427ed0daa39
7
- data.tar.gz: 9f3326e754283f25d307c598aa8824c53aa5686c112ebb9c9301ee7dc424710c572cac02275f72cf204e5e00677c92c03bd7e3f4ee157e7d585c9a3bcd2fc6c9
6
+ metadata.gz: 9ac187c64e5df6da35b990d1109495c1c81bdc7e99e28ec45f6ecccfbb384399a0c2040e387bd6e9b1636a51f20acd2f1e9083b60a8e33f2e77ffd6f79321c71
7
+ data.tar.gz: 360340a212b3c53bc5db51c4f28b80de556dad9b9fc11ac9403b5277eb552e79ae26bb8355134915b5146a05d1c4ebdfa4650e4d2c5f06c6d3714a080dcdd5e3
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- devlogs (1.0.0)
4
+ devlogs (1.1.1)
5
5
  rsync (~> 1.0, >= 1.0.9)
6
6
  thor (~> 1.2.1)
7
7
  tty-prompt (~> 0.23.1)
data/Makefile CHANGED
@@ -9,6 +9,9 @@ setup:
9
9
  install:
10
10
  @bundle exec rake install
11
11
 
12
+ build:
13
+ @bundle exec rake build
14
+
12
15
  release:
13
16
  @bundle exec rake release
14
17
 
data/README.md CHANGED
@@ -1,8 +1,6 @@
1
1
  # devlogs
2
2
  Project based session logging for solo-developers with the option to mirror changes to another directory.
3
3
 
4
- https://stacktrace.one/blog/avoid-project-management-solo-dev/
5
-
6
4
  ![Maintain non-source controlled logs across various projects with mirroring to a single](./docs/mirroring.png)
7
5
 
8
6
  ## Installation
@@ -47,7 +45,8 @@ obsidianvault
47
45
  >> content mirrored here
48
46
  ```
49
47
 
50
- ### Creating entries
48
+ ### Logs
49
+ #### Creating log entries
51
50
  Once you are done for the day or session run the `new` command:
52
51
 
53
52
  ```bash
@@ -66,22 +65,60 @@ Your editor will pop up and you can fill in cliff notes.
66
65
 
67
66
  Save and if you set a mirror it will sync over!
68
67
 
69
- ### Retrieve previous entry
68
+ #### Retrieve previous entry
70
69
  You can use the `last` command to retrieve the most recent entry
71
70
 
72
71
  ```bash
73
72
  devlogs last
74
73
  ```
75
74
 
76
- ## Development
75
+ #### List all log entires
76
+ You can use the `ls` command to retrieve the most recent entry
77
+
78
+ ```bash
79
+ devlogs ls
80
+ ```
81
+
82
+ ### Issues
83
+ Devlogs also allows you to manage issues locally as well. Devlogs creates a separate subdirectory in the `.devlogs` folder which will contain all issues. These files are also synced if the repository is mirrored.
84
+
85
+ #### Creating an Issue
86
+ You can create a new issue via `devlogs new_issue`. You will be prompted to provide some information and then your editor will open and you can fill in some details
87
+
88
+ ```bash
89
+ devlogs new_issue
90
+ ```
77
91
 
92
+ #### List all issues
93
+ You can use the `ls_issues` command to retrieve the most recent entry
94
+
95
+ ```bash
96
+ devlogs ls_issues
97
+ ```
98
+
99
+ ### Custom Templates
100
+ Devlogs initializes the log repository with two custom templates that you can edit freely `.log_template.erb.md` and `.issue_template.erb.md`. These are [Embedded Ruby Files](https://en.wikipedia.org/wiki/ERuby) meaning they can access certain variables.
101
+
102
+ #### Log Template
103
+ | Variable Name | Value |
104
+ | --- | --- |
105
+ | Time | The current date, hour and minute time |
106
+
107
+ #### Issue Template
108
+ | Variable Name | Value |
109
+ | --- | --- |
110
+ | Time | The current date, hour and minute time |
111
+ | Issue Title | The provided issue title input from the issue creation prompt |
112
+ | Description | The provided description input from the issue creation prompt |
113
+ | Reproduction Steps | The provided input for reproduction steps from the issue creation prompt |
114
+
115
+ ## Development
78
116
  After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
79
117
 
80
118
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
81
119
 
82
120
  ## Contributing
83
-
84
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/devlogs.
121
+ Bug reports and pull requests are welcome on GitHub at https://github.com/aquaflamingo/devlogs.
85
122
 
86
123
  ## License
87
124
 
data/lib/devlogs/cli.rb CHANGED
@@ -4,7 +4,7 @@ require_relative "version"
4
4
  require_relative "repository"
5
5
  require_relative "editor"
6
6
  require_relative "pager"
7
- require_relative "prompt_utils"
7
+ require_relative "helper/tty_prompt_helper"
8
8
  require_relative "repository/initializer"
9
9
  require "thor"
10
10
 
@@ -13,7 +13,7 @@ module Devlogs
13
13
  # The CLI devlogs CLI
14
14
  #
15
15
  class CLI < Thor
16
- include PromptUtils
16
+ include TTYPromptHelper
17
17
 
18
18
  package_name "devlogs"
19
19
 
@@ -40,10 +40,10 @@ module Devlogs
40
40
  puts "Creating devlogs repository"
41
41
 
42
42
  Repository::Initializer.run(
43
- {
43
+ {
44
44
  force: options.force?,
45
- dirpath: options.dirpath,
46
- },
45
+ dirpath: options.dirpath
46
+ }
47
47
  )
48
48
 
49
49
  puts "Created devlogs repository"
@@ -64,6 +64,8 @@ module Devlogs
64
64
  puts File.read(last_entry)
65
65
  end
66
66
  end
67
+
68
+ # FIXME: Add logs sub command
67
69
  #
68
70
  # Creates a devlogs entry in the repository and syncs changes
69
71
  # to the mirrored directory if set
@@ -76,6 +78,39 @@ module Devlogs
76
78
  repo.sync
77
79
  end
78
80
 
81
+ # FIXME: Add logs sub command
82
+ #
83
+ # Creates a devlogs entry in the repository and syncs changes
84
+ # to the mirrored directory if set
85
+ #
86
+ desc "new_issue", "Create a new devlogs entry" # [4]
87
+ def new_issue
88
+ repo.create_issue
89
+ repo.sync
90
+ end
91
+
92
+ #
93
+ # Lists repository issues
94
+ #
95
+ desc "ls_issues", "Lists the repository issues and allows you to select"
96
+ def ls_issues
97
+ issues = repo.ls_issues
98
+
99
+ if issues.empty?
100
+ puts "No issues present in this repository"
101
+ exit 0
102
+ end
103
+
104
+ # Use the file names as visible keys for the prompt
105
+ issue_names = issues.map { |e| File.basename(e) }
106
+
107
+ # Build the TTY:Prompt
108
+ result = build_select_prompt(data: issue_names, text: "Select an issue issue...")
109
+
110
+ # Open in paging program
111
+ Pager.open(issues[result])
112
+ end
113
+
79
114
  #
80
115
  # Lists repository logs
81
116
  #
@@ -83,7 +118,7 @@ module Devlogs
83
118
  def ls
84
119
  entries = repo.ls
85
120
 
86
- if entries.size < 1
121
+ if entries.empty?
87
122
  puts "No logs present in this repository"
88
123
  exit 0
89
124
  end
@@ -103,7 +138,7 @@ module Devlogs
103
138
  # Helper method for repository loading
104
139
  #
105
140
  def repo
106
- # FIXME: Need to add in path specification here
141
+ # FIXME: Need to add in path specification here
107
142
  @repo ||= Repository.load
108
143
  end
109
144
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TimeHelper
4
+ # Example: 11-22-2022__13h43m
5
+ TIME_FORMAT_FILE_PREFIX = "%m-%d-%Y__%kh%Mm"
6
+
7
+ def current_time(format: TIME_FORMAT_FILE_PREFIX)
8
+ time = Time.new
9
+ time.strftime(format)
10
+ end
11
+ end
@@ -5,7 +5,7 @@ require "tty-prompt"
5
5
  #
6
6
  # Utility module for tty-prompt library
7
7
  #
8
- module PromptUtils
8
+ module TTYPromptHelper
9
9
  #
10
10
  # Builds a basic select prompt using the provided data
11
11
  #
@@ -22,4 +22,10 @@ module PromptUtils
22
22
  end
23
23
  end
24
24
  end
25
+
26
+ module Validator
27
+ def self.length_range(min: 0, max: 99)
28
+ ->(input) { input.size > min && input.size <= max }
29
+ end
30
+ end
25
31
  end
@@ -3,9 +3,10 @@
3
3
  require "erb"
4
4
 
5
5
  #
6
- # LogTemplate is a class that represents the rendered log template
6
+ # ErbTemplateRenderer is a base class for rendering arbitrary
7
+ # ERB templates.
7
8
  #
8
- class LogTemplate
9
+ class ErbTemplateRenderer
9
10
  attr_reader :time
10
11
 
11
12
  TIME_FORMAT_TEXT_ENTRY = "%m-%d-%Y %k:%M"
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "erb_template_renderer"
4
+
5
+ #
6
+ # IssueTemplateRenderer captures issue information and
7
+ # renders it within a given ERB template
8
+ #
9
+ class IssueTemplateRenderer < ErbTemplateRenderer
10
+ attr_reader :title, :description, :reproduction
11
+
12
+ def initialize(template_file_path, info = {})
13
+ super(template_file_path)
14
+
15
+ @title = info[:display_title]
16
+ @description = info[:description]
17
+ @reproduction = info[:reproduction]
18
+ end
19
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "erb_template_renderer"
4
+
5
+ #
6
+ # LogTemplateRenderer captures log information and
7
+ # renders it within a given ERB template
8
+ #
9
+ class LogTemplateRenderer < ErbTemplateRenderer
10
+ end
@@ -4,7 +4,7 @@
4
4
  class Repository
5
5
  class Config
6
6
  # FIXME: Need to figure out file path
7
- attr_reader :name, :description, :mirror, :file_path, :template_file_path
7
+ attr_reader :name, :description, :mirror, :file_path, :template_file_path, :short_code
8
8
 
9
9
  # Configuration associated with the Mirror
10
10
  MirrorConfig = Struct.new(:use_mirror, :path, keyword_init: true)
@@ -13,6 +13,7 @@ class Repository
13
13
  @file_path = path
14
14
  @template_file_path = opts[:template_file_path]
15
15
  @name = opts[:name]
16
+ @short_code = opts[:short_code]
16
17
  @description = opts[:description]
17
18
  @mirror = MirrorConfig.new(opts[:mirror])
18
19
  end
@@ -0,0 +1,186 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Repository
4
+ class ConfigBuilder
5
+ def initialize(dirpath)
6
+ @config_store = if dirpath
7
+ Repository::ConfigStore.new(dir: dirpath)
8
+ else
9
+ Repository::ConfigStore.new
10
+ end
11
+ end
12
+
13
+ def build
14
+ config_info = prompt_for_info
15
+
16
+ DraftConfig.new(@config_store, config_info)
17
+ end
18
+
19
+ private
20
+
21
+ # Creates an interactive prompt for user input
22
+ #
23
+ # @returns [Hash]
24
+ def prompt_for_info
25
+ prompt = TTY::Prompt.new
26
+
27
+ prompt.collect do |_p|
28
+ # Project name
29
+ key(:name).ask("What is the project name?") do |q|
30
+ q.required true
31
+ end
32
+
33
+ # Project description
34
+ key(:desc).ask("What is the project description?") do |q|
35
+ q.required true
36
+ end
37
+
38
+ # Project short code, e.g. RLP
39
+ key(:short_code).ask("What is the project short code (3 letters)?") do |q|
40
+ q.required true
41
+
42
+ q.validate ->(input) { input.size.positive? && input.size <= 3 }
43
+
44
+ q.messages[:valid?] = "Short code must be 3 letters or less"
45
+ end
46
+
47
+ key(:mirror) do
48
+ key(:use_mirror).ask("Do you want to mirror these logs?", convert: :boolean)
49
+ key(:path).ask("Path to mirror directory: ")
50
+ end
51
+ end
52
+ end
53
+
54
+ class DraftConfig
55
+ INFO_FILE_SUFFIX = "devlogs.info.md"
56
+ LOG_TEMPLATE_FILE_NAME = "__log_template.erb.md"
57
+ ISSUE_TEMPLATE_FILE_NAME = "__issue_template.erb.md"
58
+
59
+ def initialize(config_store, config_info)
60
+ @config_store = config_store
61
+ @config_info = config_info
62
+ end
63
+
64
+ #
65
+ # Initiates the write process of devlogs repository
66
+ #
67
+ # @param force [Boolean]
68
+ #
69
+ def save!(force: false)
70
+ exists = File.exist?(@config_store.file_path)
71
+
72
+ if exists && !force
73
+ puts "Log repository already exists in aborting..."
74
+ raise RuntimeError
75
+ end
76
+
77
+ create_config_store_dir
78
+ save_config_file
79
+ save_info_file
80
+ save_log_template_file
81
+
82
+ create_issue_dir
83
+ save_issue_template_file
84
+ save_data_file
85
+ end
86
+
87
+ private
88
+
89
+ #
90
+ # Creates the configuration store directory
91
+ #
92
+ def create_config_store_dir
93
+ # Create the draft_config directory
94
+ FileUtils.mkdir_p(@config_store.dir)
95
+ end
96
+
97
+ #
98
+ # Creates the issue directory
99
+ #
100
+ def create_issue_dir
101
+ # Create the draft_config directory
102
+ FileUtils.mkdir_p(@config_store.issue_dir_path)
103
+ end
104
+
105
+ #
106
+ # Saves the .devlogs.config.yml file
107
+ #
108
+ def save_config_file
109
+ # Create draft_config file
110
+ File.open(@config_store.file_path, "w") do |f|
111
+ f.write @config_info.to_yaml
112
+ end
113
+ end
114
+
115
+ #
116
+ # Saves the .info file
117
+ #
118
+ def save_info_file
119
+ # Replace spaces in project name with underscores
120
+ sanitized_project_name = @config_info[:name].gsub(/ /, "_").downcase
121
+
122
+ # Create the info file
123
+ info_file_name = "#{sanitized_project_name}.#{INFO_FILE_SUFFIX}"
124
+ info_file = File.join(@config_store.dir, info_file_name)
125
+
126
+ File.open(info_file, "w") do |f|
127
+ f.puts "# #{@config_info[:name]}"
128
+ f.puts (@config_info[:desc]).to_s
129
+ end
130
+ end
131
+
132
+ #
133
+ # Copies the log template to the config store directory
134
+ #
135
+ def save_log_template_file
136
+ # Copy the default template file inside the gem into the repository
137
+ default_log_template_path = get_template_path(LOG_TEMPLATE_FILE_NAME)
138
+
139
+ draft_config_log_template_file_path = File.join(@config_store.dir, Repository::ConfigStore::LOG_TEMPLATE_FILE)
140
+
141
+ FileUtils.cp(default_log_template_path, draft_config_log_template_file_path)
142
+ end
143
+
144
+ #
145
+ # Copies the log template to the config store directory
146
+ #
147
+ def save_issue_template_file
148
+ default_iss_template_path = get_template_path(ISSUE_TEMPLATE_FILE_NAME)
149
+
150
+ draft_config_iss_template_file_path = File.join(@config_store.dir, Repository::ConfigStore::ISSUE_TEMPLATE_FILE)
151
+
152
+ FileUtils.cp(default_iss_template_path, draft_config_iss_template_file_path)
153
+ end
154
+
155
+ #
156
+ # Creates a .devlogs.data.yml file
157
+ #
158
+ def save_data_file
159
+ data_file = File.join(@config_store.dir, Repository::ConfigStore::DATA_FILE)
160
+
161
+ #
162
+ # MARK: Default Data
163
+ #
164
+ data_info = {
165
+ issues: {
166
+ index: 1
167
+ },
168
+ logs: {},
169
+ repository: {}
170
+ }
171
+
172
+ File.open(data_file, "w") do |f|
173
+ f.puts data_info.to_yaml
174
+ end
175
+ end
176
+
177
+ # Gets the template file path embedded in the gem from the library root
178
+ #
179
+ # @returns [String]
180
+ #
181
+ def get_template_path(file_name)
182
+ File.join(Devlogs.lib_root, "templates", file_name)
183
+ end
184
+ end
185
+ end
186
+ end
@@ -8,12 +8,14 @@ require_relative "config"
8
8
  # A per repository configuration storage directory
9
9
  class Repository
10
10
  class ConfigStore
11
- attr_reader :dir, :values
11
+ attr_reader :dir
12
12
 
13
13
  CONFIG_FILE = ".devlogs.config.yml"
14
+ DATA_FILE = ".devlogs.data.yml"
15
+ ISSUE_TEMPLATE_FILE = ".issue_template.erb.md"
16
+ LOG_TEMPLATE_FILE = ".log_template.erb.md"
14
17
 
15
- # TODO: should be part of configuration
16
- TEMPLATE_FILE = ".log_template.erb.md"
18
+ ISSUE_DIR = "issues"
17
19
  DEFAULT_DIRECTORY_PATH = "."
18
20
  DEFAULT_DIRECTORY_NAME = ".devlogs"
19
21
 
@@ -25,15 +27,55 @@ class Repository
25
27
  @values ||= load_values_from_config_file
26
28
  end
27
29
 
30
+ #
31
+ # Retrieves the data file
32
+ #
33
+ # @returns [String]
34
+ #
35
+ def data_file_path
36
+ File.join(@dir, DATA_FILE)
37
+ end
38
+
39
+ #
40
+ # Retrieves .devlogs.config.yml file path
41
+ #
42
+ # @returns [String]
43
+ #
28
44
  def file_path
29
45
  File.join(@dir, CONFIG_FILE)
30
46
  end
31
47
 
48
+ #
49
+ # The template File
50
+ #
51
+ # @returns [String]
52
+ # FIXME: rename to log_template_file_path
32
53
  def template_file_path
33
- File.join(@dir, TEMPLATE_FILE)
54
+ File.join(@dir, LOG_TEMPLATE_FILE)
55
+ end
56
+
57
+ #
58
+ # The issue template file path:
59
+ #
60
+ # @returns [String]
61
+ #
62
+ def issue_template_file_path
63
+ File.join(@dir, ISSUE_TEMPLATE_FILE)
64
+ end
65
+
66
+ #
67
+ # Issue directory path
68
+ #
69
+ # @returns [String]
70
+ #
71
+ def issue_dir_path
72
+ File.join(@dir, ISSUE_DIR)
34
73
  end
35
74
 
36
75
  class << self
76
+ #
77
+ # Initialization utility method
78
+ #
37
79
  def load_from(path = File.join(DEFAULT_DIRECTORY_PATH, DEFAULT_DIRECTORY_NAME))
38
80
  exists = File.exist?(path)
39
81
 
@@ -1,86 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "config_store"
4
+ require_relative "config_builder"
4
5
 
5
6
  # Initialize is an execution object which initializes a Repository on the
6
7
  # filesystem
7
8
  class Repository
8
9
  class Initializer
9
- INFO_FILE_SUFFIX = "devlogs.info.md"
10
- LOG_TEMPLATE_FILE_NAME = "__log_template.erb.md"
11
-
12
10
  # Creates a new devlogs repository at the provided path
13
11
  def self.run(opts = {})
14
- new_config = if opts[:dirpath]
15
- Repository::ConfigStore.new(dir: opts[:dirpath])
16
- else
17
- Repository::ConfigStore.new
18
- end
19
-
20
- exists = File.exist?(new_config.file_path)
21
-
22
- if exists && !opts[:force]
23
- puts "Log repository already exists in #{new_config.file_path}. Aborting..."
24
- raise RuntimeError
25
- end
26
-
27
- results = prompt_for_info
28
-
29
- # Create the new_config directory
30
- FileUtils.mkdir_p(new_config.dir)
31
-
32
- # Create new_config file
33
- File.open(new_config.file_path, "w") do |f|
34
- f.write results.to_yaml
35
- end
36
-
37
- # Replace spaces in project name with underscores
38
- sanitized_project_name = results[:name].gsub(/ /, "_").downcase
39
-
40
- # Create the info file
41
- info_file_name = "#{sanitized_project_name}.devlogs.info.md"
42
- info_file = File.join(new_config.dir, info_file_name)
43
-
44
- File.open(info_file, "w") do |f|
45
- f.puts "# #{results[:name]}"
46
- f.puts (results[:desc]).to_s
47
- end
12
+ new_config = Repository::ConfigBuilder.new(opts[:dirpath])
48
13
 
49
- # Copy the default template file inside the gem into the repository
50
- default_template_path = get_template_path
51
-
52
- new_config_template_file_path = File.join(new_config.dir, Repository::ConfigStore::TEMPLATE_FILE)
53
-
54
- FileUtils.cp(default_template_path, new_config_template_file_path)
55
- end
56
-
57
- # Creates an interactive prompt for user input
58
- #
59
- # @returns [Hash]
60
- def self.prompt_for_info
61
- prompt = TTY::Prompt.new
62
-
63
- prompt.collect do |_p|
64
- # Project name
65
- key(:name).ask("What is the project name?") do |q|
66
- q.required true
67
- end
68
-
69
- # Project description
70
- key(:desc).ask("What is the project description?") do |q|
71
- q.required true
72
- end
73
-
74
- key(:mirror) do
75
- key(:use_mirror).ask("Do you want to mirror these logs?", convert: :boolean)
76
- key(:path).ask("Path to mirror directory: ")
77
- end
78
- end
79
- end
14
+ draft = new_config.build
80
15
 
81
- # Gets the template file path embedded in the gem from the library root
82
- def self.get_template_path
83
- File.join(Devlogs.lib_root, "templates", LOG_TEMPLATE_FILE_NAME)
16
+ draft.save!(force: opts[:force])
84
17
  end
85
18
  end
86
19
  end
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../helper/time_helper"
4
+ require_relative "../helper/tty_prompt_helper"
5
+ require_relative "../render/issue_template_renderer"
6
+
7
+ #
8
+ # IssueManager is an abstraction class to orchestrate the internals
9
+ # of issue management and creation for a repository
10
+ #
11
+ class IssueManager
12
+ include TTYPromptHelper
13
+ include TimeHelper
14
+
15
+ VALID_DIRECTION = %i[asc desc].freeze
16
+ ISSUE_SEPARATOR = "__"
17
+
18
+ #
19
+ # @param [Repository::ConfigStore]
20
+ #
21
+ def initialize(repo_config_store)
22
+ @config_store = repo_config_store
23
+ end
24
+
25
+ #
26
+ # Lists the issue entries present in the repository
27
+ #
28
+ # @param direction [Symbol] ascending or descending
29
+ #
30
+ def list(direction = :desc)
31
+ raise ArgumentError, "Must be one of: " + VALID_DIRECTION unless VALID_DIRECTION.include?(direction.to_sym)
32
+
33
+ # Anything with the SHORTCODE- prefix
34
+ #
35
+ # i.e. RLP-1, RLP-2, et cetera
36
+ #
37
+ short_code_pattern = "#{config_values.short_code}-*"
38
+
39
+ #
40
+ # pattern: RLP-*
41
+ #
42
+ glob_pattern = File.join(@config_store.issue_dir_path, short_code_pattern)
43
+
44
+ Dir.glob(glob_pattern).sort_by do |fpath|
45
+ # i.e. [RLP-1, title_of_issue.md]
46
+ issue_tag, = File.basename(fpath).split(ISSUE_SEPARATOR)
47
+
48
+ # i.e. [RLP, 1]
49
+ _, issue_num = issue_tag.split("-")
50
+
51
+ if direction == :asc
52
+ issue_num.to_i
53
+ else
54
+ -1 * issue_num.to_i
55
+ end
56
+ end
57
+ end
58
+
59
+ #
60
+ # Adds a new entry to the repository
61
+ #
62
+ # @returns [String] entry file path
63
+ #
64
+ def create
65
+ info = issue_info_prompt
66
+
67
+ issue = compose_issue(info)
68
+
69
+ issue_file_path = File.join(@config_store.issue_dir_path, issue[:file_name])
70
+
71
+ template = IssueTemplateRenderer.new(@config_store.issue_template_file_path, issue)
72
+
73
+ unless File.exist?(issue_file_path)
74
+ # Add default boiler plate if the file does not exist yet
75
+ File.open(issue_file_path, "w") do |f|
76
+ f.write template.render
77
+ end
78
+
79
+ increment_issue_index!
80
+ end
81
+
82
+ issue_file_path
83
+ end
84
+
85
+ private
86
+
87
+ #
88
+ # Sanitizes and composes the content for display
89
+ #
90
+ # @returns [Hash]
91
+ def compose_issue(info = {})
92
+ # RLP-n
93
+ short_code_issue = compute_short_code
94
+
95
+ # RLP-1: User Validation Fails.md
96
+ display_title = "#{short_code_issue}: #{info[:title]}"
97
+
98
+ # rlp_1__user_validation_fails.md
99
+ file_name_title = snakify(info[:title])
100
+
101
+ issue_file_name = "#{short_code_issue}#{ISSUE_SEPARATOR}#{file_name_title}.md".downcase
102
+
103
+ {
104
+ display_title: display_title,
105
+ file_name: issue_file_name,
106
+ description: info[:description].join(""),
107
+ reproduction: info[:reproduction].join("")
108
+ }
109
+ end
110
+
111
+ #
112
+ # Increments the issue index in the data file by one
113
+ #
114
+ def increment_issue_index!
115
+ data = YAML.load_file(@config_store.data_file_path)
116
+
117
+ data[:issues][:index] += 1
118
+
119
+ File.open(@config_store.data_file_path, "w") do |f|
120
+ f.write data.to_yaml
121
+ end
122
+ end
123
+
124
+ #
125
+ # Gets TTY input for issue data
126
+ #
127
+ # @return [Hash]
128
+ #
129
+ def issue_info_prompt
130
+ prompt = TTY::Prompt.new
131
+
132
+ prompt.collect do
133
+ key(:title).ask("What is the issue title?") do |q|
134
+ q.required true
135
+ q.validate Validator.length_range(min: 0, max: 25)
136
+ q.messages[:valid?] = "Title cannot be empty and may be maximum 25 characters"
137
+ end
138
+
139
+ key(:description).multiline("Describe the issue: ") do |q|
140
+ q.default "There is an issue with..."
141
+ q.help "Press ctrl+d to end"
142
+ end
143
+
144
+ key(:reproduction).multiline("Describe the reproduction steps: ") do |q|
145
+ q.default "To reproduce the issue..."
146
+ q.help "Press ctrl+d to end"
147
+ end
148
+ end
149
+ end
150
+
151
+ #
152
+ # Convenience method for accessing config store values
153
+ #
154
+ # @returns [Repository::Config]
155
+ #
156
+ def config_values
157
+ @config_store.values
158
+ end
159
+
160
+ # Transforms a string with spaces or hyphens to
161
+ # an snake case String
162
+ #
163
+ # @param input [String]
164
+ # @returns [String]
165
+ #
166
+ def snakify(input)
167
+ input.gsub(/[ -]/, "_").downcase
168
+ end
169
+
170
+ #
171
+ # Reads the .data.yml file in the repository to
172
+ # get the latest issue index number
173
+ #
174
+ # @returns [String]
175
+ #
176
+ def compute_short_code
177
+ data = YAML.load_file(@config_store.data_file_path)
178
+
179
+ issue_index = data[:issues][:index]
180
+
181
+ "#{config_values.short_code}-#{issue_index}".upcase
182
+ end
183
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../helper/time_helper"
4
+ require_relative "../render/log_template_renderer"
5
+
6
+ #
7
+ # LogManager is an abstraction class to orchestrate the internals
8
+ # of issue management and creation for a repository
9
+ #
10
+ class LogManager
11
+ include TimeHelper
12
+ LOG_FILE_SUFFIX = "log.md"
13
+ VALID_DIRECTION = %i[asc desc].freeze
14
+
15
+ def initialize(repo_config_store)
16
+ @config_store = repo_config_store
17
+ end
18
+
19
+ # Lists the log entries present in the repository
20
+ #
21
+ # @param direction [Symbol] ascending or descending
22
+ #
23
+ def list(direction = :desc)
24
+ raise ArgumentError, "Must be one of: " + VALID_DIRECTION unless VALID_DIRECTION.include?(direction.to_sym)
25
+
26
+ # Anything with the _log.md suffix
27
+ glob_pattern = File.join(@config_store.dir, "*_#{LOG_FILE_SUFFIX}")
28
+
29
+ Dir.glob(glob_pattern).sort_by do |fpath|
30
+ # The date is joined by two underscores to the suffix
31
+ date, = File.basename(fpath).split("_#{LOG_FILE_SUFFIX}")
32
+
33
+ time_ms = Time.strptime(date, TimeHelper::TIME_FORMAT_FILE_PREFIX).to_i
34
+
35
+ if direction == :asc
36
+ time_ms
37
+ else
38
+ -time_ms
39
+ end
40
+ end
41
+ end
42
+
43
+ #
44
+ # Adds a new entry to the repository
45
+ #
46
+ # @returns [String] entry file path
47
+ #
48
+ def create
49
+ entry_file_name = "#{current_time}_#{LOG_FILE_SUFFIX}"
50
+
51
+ entry_file_path = File.join(@config_store.dir, entry_file_name)
52
+
53
+ template = LogTemplateRenderer.new(@config_store.template_file_path)
54
+
55
+ unless File.exist?(entry_file_path)
56
+ # Add default boiler plate if the file does not exist yet
57
+ File.open(entry_file_path, "w") do |f|
58
+ f.write template.render
59
+ end
60
+ end
61
+
62
+ entry_file_path
63
+ end
64
+ end
@@ -8,17 +8,13 @@ require "time"
8
8
  require_relative "repository/config_store"
9
9
  require_relative "editor"
10
10
  require_relative "repository/sync_manager"
11
- require_relative "log_template"
11
+ require_relative "helper/time_helper"
12
+ require_relative "repository/log_manager"
13
+ require_relative "repository/issue_manager"
12
14
 
13
- # Repostiroy is an accessor object for the devlogs directory
15
+ # Repository is an accessor object for the devlogs directory
14
16
  class Repository
15
- # Example: 11-22-2022_1343
16
- TIME_FORMAT_FILE_PREFIX = "%m-%d-%Y__%kh%Mm"
17
-
18
- LOG_FILE_SUFFIX = "log.md"
19
- ISSUE_FILE_PREFIX = "iss"
20
-
21
- VALID_DIRECTION = %i[asc desc].freeze
17
+ include TimeHelper
22
18
 
23
19
  # Initializes a .devlogs repository with the supplied configuration
24
20
  #
@@ -31,27 +27,21 @@ class Repository
31
27
  #
32
28
  # @returns nil
33
29
  def create
34
- time = Time.new
35
- time_prefix = time.strftime(TIME_FORMAT_FILE_PREFIX)
36
-
37
- entry_file_name = "#{time_prefix}_#{LOG_FILE_SUFFIX}"
30
+ entry_file_path = log_manager.create
38
31
 
39
- # FIXME: Need to figure out file path
40
- entry_file_path = File.join(@config_store.dir, entry_file_name)
32
+ Editor.open(entry_file_path)
41
33
 
42
- # FIXME: Need to figure out file path
43
- template = LogTemplate.new(@config_store.template_file_path)
34
+ puts "Writing entry to #{entry_file_path}.."
35
+ end
44
36
 
45
- unless File.exist?(entry_file_path)
46
- # Add default boiler plate if the file does not exist yet
47
- File.open(entry_file_path, "w") do |f|
48
- f.write template.render
49
- end
50
- end
37
+ #
38
+ # @returns nil
39
+ def create_issue
40
+ issue_file_path = issue_manager.create
51
41
 
52
- Editor.open(entry_file_path)
42
+ Editor.open(issue_file_path)
53
43
 
54
- puts "Writing entry to #{entry_file_path}.."
44
+ puts "Writing issue to #{issue_file_path}.."
55
45
  end
56
46
 
57
47
  # Syncs the directory changes to the (optional) mirror repository
@@ -64,21 +54,12 @@ class Repository
64
54
 
65
55
  # Lists the files in the repository
66
56
  def ls(direction = :desc)
67
- raise ArgumentError, "Must be one of: " + VALID_DIRECTION unless VALID_DIRECTION.include?(direction.to_sym)
68
-
69
- Dir.glob(File.join(@config_store.dir, "*_#{LOG_FILE_SUFFIX}")).sort_by do |fpath|
70
- # The date is joined by two underscores to the suffix
71
- date, = File.basename(fpath).split("__")
72
-
73
- time_ms = Time.strptime(date, "%m-%d-%Y").to_i
57
+ log_manager.list(direction)
58
+ end
74
59
 
75
- # Descending
76
- if direction == :asc
77
- time_ms
78
- else
79
- -time_ms
80
- end
81
- end
60
+ # Lists the issues in the repository
61
+ def ls_issues(direction = :desc)
62
+ issue_manager.list(direction)
82
63
  end
83
64
 
84
65
  class << self
@@ -98,4 +79,12 @@ class Repository
98
79
  def sync_manager
99
80
  @sync_manager ||= Repository::SyncManager.new(@config_store)
100
81
  end
82
+
83
+ def log_manager
84
+ @log_manager ||= LogManager.new(@config_store)
85
+ end
86
+
87
+ def issue_manager
88
+ @issue_manager ||= IssueManager.new(@config_store)
89
+ end
101
90
  end
@@ -0,0 +1,13 @@
1
+ [[@INFO]]
2
+ Tags:
3
+ Links:
4
+
5
+ ---
6
+
7
+ # <%= title %>
8
+
9
+ ## Problem
10
+ <%= description %>
11
+
12
+ ## Reproduction Steps
13
+ <%= reproduction %>
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Devlogs
4
- VERSION = "1.0.0"
4
+ VERSION = "1.1.1"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: devlogs
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - aquaflamingo
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-09-17 00:00:00.000000000 Z
11
+ date: 2022-09-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rsync
@@ -99,14 +99,21 @@ files:
99
99
  - lib/devlogs/editor.rb
100
100
  - lib/devlogs/executable.rb
101
101
  - lib/devlogs/gem.rb
102
- - lib/devlogs/log_template.rb
102
+ - lib/devlogs/helper/time_helper.rb
103
+ - lib/devlogs/helper/tty_prompt_helper.rb
103
104
  - lib/devlogs/pager.rb
104
- - lib/devlogs/prompt_utils.rb
105
+ - lib/devlogs/render/erb_template_renderer.rb
106
+ - lib/devlogs/render/issue_template_renderer.rb
107
+ - lib/devlogs/render/log_template_renderer.rb
105
108
  - lib/devlogs/repository.rb
106
109
  - lib/devlogs/repository/config.rb
110
+ - lib/devlogs/repository/config_builder.rb
107
111
  - lib/devlogs/repository/config_store.rb
108
112
  - lib/devlogs/repository/initializer.rb
113
+ - lib/devlogs/repository/issue_manager.rb
114
+ - lib/devlogs/repository/log_manager.rb
109
115
  - lib/devlogs/repository/sync_manager.rb
116
+ - lib/devlogs/templates/__issue_template.erb.md
110
117
  - lib/devlogs/templates/__log_template.erb.md
111
118
  - lib/devlogs/version.rb
112
119
  homepage: http://github.com/aquaflamingo/devlogs