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 +4 -4
- data/Gemfile.lock +1 -1
- data/Makefile +3 -0
- data/README.md +44 -7
- data/lib/devlogs/cli.rb +42 -7
- data/lib/devlogs/helper/time_helper.rb +11 -0
- data/lib/devlogs/{prompt_utils.rb → helper/tty_prompt_helper.rb} +7 -1
- data/lib/devlogs/{log_template.rb → render/erb_template_renderer.rb} +3 -2
- data/lib/devlogs/render/issue_template_renderer.rb +19 -0
- data/lib/devlogs/render/log_template_renderer.rb +10 -0
- data/lib/devlogs/repository/config.rb +2 -1
- data/lib/devlogs/repository/config_builder.rb +186 -0
- data/lib/devlogs/repository/config_store.rb +46 -4
- data/lib/devlogs/repository/initializer.rb +4 -71
- data/lib/devlogs/repository/issue_manager.rb +183 -0
- data/lib/devlogs/repository/log_manager.rb +64 -0
- data/lib/devlogs/repository.rb +28 -39
- data/lib/devlogs/templates/__issue_template.erb.md +13 -0
- data/lib/devlogs/version.rb +1 -1
- metadata +11 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6c4dc52933d3f7a0ff683b103879fb1bc9ad3f49eff9a93be745b0547339f881
|
4
|
+
data.tar.gz: 5442df05a0318ed2369c1679eb4c663d5c7a22e081c3b70cc231a69d405672ef
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9ac187c64e5df6da35b990d1109495c1c81bdc7e99e28ec45f6ecccfbb384399a0c2040e387bd6e9b1636a51f20acd2f1e9083b60a8e33f2e77ffd6f79321c71
|
7
|
+
data.tar.gz: 360340a212b3c53bc5db51c4f28b80de556dad9b9fc11ac9403b5277eb552e79ae26bb8355134915b5146a05d1c4ebdfa4650e4d2c5f06c6d3714a080dcdd5e3
|
data/Gemfile.lock
CHANGED
data/Makefile
CHANGED
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
|

|
7
5
|
|
8
6
|
## Installation
|
@@ -47,7 +45,8 @@ obsidianvault
|
|
47
45
|
>> content mirrored here
|
48
46
|
```
|
49
47
|
|
50
|
-
###
|
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
|
-
|
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
|
-
|
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 "
|
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
|
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.
|
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
|
@@ -5,7 +5,7 @@ require "tty-prompt"
|
|
5
5
|
#
|
6
6
|
# Utility module for tty-prompt library
|
7
7
|
#
|
8
|
-
module
|
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
|
-
#
|
6
|
+
# ErbTemplateRenderer is a base class for rendering arbitrary
|
7
|
+
# ERB templates.
|
7
8
|
#
|
8
|
-
class
|
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
|
@@ -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
|
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
|
-
|
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,
|
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 =
|
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
|
-
|
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
|
-
|
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
|
data/lib/devlogs/repository.rb
CHANGED
@@ -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 "
|
11
|
+
require_relative "helper/time_helper"
|
12
|
+
require_relative "repository/log_manager"
|
13
|
+
require_relative "repository/issue_manager"
|
12
14
|
|
13
|
-
#
|
15
|
+
# Repository is an accessor object for the devlogs directory
|
14
16
|
class Repository
|
15
|
-
|
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
|
-
|
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
|
-
|
40
|
-
entry_file_path = File.join(@config_store.dir, entry_file_name)
|
32
|
+
Editor.open(entry_file_path)
|
41
33
|
|
42
|
-
|
43
|
-
|
34
|
+
puts "Writing entry to #{entry_file_path}.."
|
35
|
+
end
|
44
36
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
end
|
50
|
-
end
|
37
|
+
#
|
38
|
+
# @returns nil
|
39
|
+
def create_issue
|
40
|
+
issue_file_path = issue_manager.create
|
51
41
|
|
52
|
-
Editor.open(
|
42
|
+
Editor.open(issue_file_path)
|
53
43
|
|
54
|
-
puts "Writing
|
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
|
-
|
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
|
-
|
76
|
-
|
77
|
-
|
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
|
data/lib/devlogs/version.rb
CHANGED
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.
|
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-
|
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/
|
102
|
+
- lib/devlogs/helper/time_helper.rb
|
103
|
+
- lib/devlogs/helper/tty_prompt_helper.rb
|
103
104
|
- lib/devlogs/pager.rb
|
104
|
-
- lib/devlogs/
|
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
|