devlogs 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9df7671be5997a9e311b6a8c10863732b95fb3e6bdb89a7f5ba8476d3cec4653
4
- data.tar.gz: af3731bb2c7134cea231b17a7c940d313d13f21c0721c720029e1084d706f0a4
3
+ metadata.gz: 710e8aea6a7ce1d312e3f94c36bb01c718358e94d46be5745956ffa66e902c77
4
+ data.tar.gz: f02110b5872d1cc117aad8b4e5cd5e5baabdb160336a9a300d96b16ad08aca5a
5
5
  SHA512:
6
- metadata.gz: ffe4be66ace5c88074c0ce3e8a20e0f82a53162e59776be3bfdbf1d72c038d4d73365ca65bf91047b4730a64efeb548f3e1b7f8a8704a0f9554bc427ed0daa39
7
- data.tar.gz: 9f3326e754283f25d307c598aa8824c53aa5686c112ebb9c9301ee7dc424710c572cac02275f72cf204e5e00677c92c03bd7e3f4ee157e7d585c9a3bcd2fc6c9
6
+ metadata.gz: 70776c55ba2df5ac5c14d96e6a773a30821e8ceb7fbcffeb9af65c005cd54cf04ec161dff61b8f0285f1324921ca1fb7d92aa05ce8ce48056423c5a7b3a1f28e
7
+ data.tar.gz: 371ede787d87a78a2de645734e8a0953467acb7a9e64a01c5d887057b894eefe3530649c2d482462d05119fc28fec9c5e6aeb37705944aab9641881c39ebb6ee
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.0)
5
5
  rsync (~> 1.0, >= 1.0.9)
6
6
  thor (~> 1.2.1)
7
7
  tty-prompt (~> 0.23.1)
data/README.md CHANGED
@@ -47,7 +47,8 @@ obsidianvault
47
47
  >> content mirrored here
48
48
  ```
49
49
 
50
- ### Creating entries
50
+ ### Logs
51
+ #### Creating log entries
51
52
  Once you are done for the day or session run the `new` command:
52
53
 
53
54
  ```bash
@@ -66,22 +67,60 @@ Your editor will pop up and you can fill in cliff notes.
66
67
 
67
68
  Save and if you set a mirror it will sync over!
68
69
 
69
- ### Retrieve previous entry
70
+ #### Retrieve previous entry
70
71
  You can use the `last` command to retrieve the most recent entry
71
72
 
72
73
  ```bash
73
74
  devlogs last
74
75
  ```
75
76
 
76
- ## Development
77
+ #### List all log entires
78
+ You can use the `ls` command to retrieve the most recent entry
79
+
80
+ ```bash
81
+ devlogs ls
82
+ ```
83
+
84
+ ### Issues
85
+ 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.
86
+
87
+ #### Creating an Issue
88
+ 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
89
+
90
+ ```bash
91
+ devlogs new_issue
92
+ ```
77
93
 
94
+ #### List all issues
95
+ You can use the `ls_issues` command to retrieve the most recent entry
96
+
97
+ ```bash
98
+ devlogs ls_issues
99
+ ```
100
+
101
+ ### Custom Templates
102
+ 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.
103
+
104
+ #### Log Template
105
+ | Variable Name | Value |
106
+ | --- | --- |
107
+ | Time | The current date, hour and minute time |
108
+
109
+ #### Issue Template
110
+ | Variable Name | Value |
111
+ | --- | --- |
112
+ | Time | The current date, hour and minute time |
113
+ | Issue Title | The provided issue title input from the issue creation prompt |
114
+ | Description | The provided description input from the issue creation prompt |
115
+ | Reproduction Steps | The provided input for reproduction steps from the issue creation prompt |
116
+
117
+ ## Development
78
118
  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
119
 
80
120
  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
121
 
82
122
  ## Contributing
83
-
84
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/devlogs.
123
+ Bug reports and pull requests are welcome on GitHub at https://github.com/aquaflamingo/devlogs.
85
124
 
86
125
  ## License
87
126
 
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_entry
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.0"
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.0
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-19 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