devlogs 0.3.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/Gemfile.lock +1 -1
- data/README.md +48 -11
- data/lib/devlogs/cli.rb +54 -9
- data/lib/devlogs/gem.rb +7 -0
- 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/render/erb_template_renderer.rb +36 -0
- 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 +37 -0
- data/lib/devlogs/repository/config_builder.rb +186 -0
- data/lib/devlogs/repository/config_store.rb +96 -0
- data/lib/devlogs/repository/initializer.rb +19 -0
- data/lib/devlogs/repository/issue_manager.rb +183 -0
- data/lib/devlogs/repository/log_manager.rb +64 -0
- data/lib/devlogs/repository/sync_manager.rb +59 -0
- data/lib/devlogs/repository.rb +40 -185
- data/lib/devlogs/templates/__issue_template.erb.md +13 -0
- data/lib/devlogs/templates/__log_template.erb.md +5 -0
- data/lib/devlogs/version.rb +1 -1
- data/lib/devlogs.rb +1 -0
- metadata +17 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 710e8aea6a7ce1d312e3f94c36bb01c718358e94d46be5745956ffa66e902c77
|
4
|
+
data.tar.gz: f02110b5872d1cc117aad8b4e5cd5e5baabdb160336a9a300d96b16ad08aca5a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 70776c55ba2df5ac5c14d96e6a773a30821e8ceb7fbcffeb9af65c005cd54cf04ec161dff61b8f0285f1324921ca1fb7d92aa05ce8ce48056423c5a7b3a1f28e
|
7
|
+
data.tar.gz: 371ede787d87a78a2de645734e8a0953467acb7a9e64a01c5d887057b894eefe3530649c2d482462d05119fc28fec9c5e6aeb37705944aab9641881c39ebb6ee
|
data/.gitignore
CHANGED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -23,12 +23,12 @@ Or install it yourself as:
|
|
23
23
|
|
24
24
|
## Usage
|
25
25
|
### Initialize
|
26
|
-
Inside your project initialize the `
|
26
|
+
Inside your project initialize the `.devlogs` repository:
|
27
27
|
```bash
|
28
28
|
$ devlogs init
|
29
29
|
```
|
30
30
|
|
31
|
-
Follow the prompts to setup the project configuration located in
|
31
|
+
Follow the prompts to setup the project configuration located in the _default_ `.devlogs/.devlogs.config`. (You can optionally set where you want to initialize the repository via --dirpath)
|
32
32
|
|
33
33
|
You can setup a mirror directory path in the configuration stage to sync changes to another directory on your machine, for example to Obsidian.md.
|
34
34
|
|
@@ -36,7 +36,7 @@ Example:
|
|
36
36
|
|
37
37
|
```
|
38
38
|
myproject
|
39
|
-
|
39
|
+
.devlogs
|
40
40
|
>> content
|
41
41
|
```
|
42
42
|
|
@@ -47,11 +47,12 @@ obsidianvault
|
|
47
47
|
>> content mirrored here
|
48
48
|
```
|
49
49
|
|
50
|
-
###
|
51
|
-
|
50
|
+
### Logs
|
51
|
+
#### Creating log entries
|
52
|
+
Once you are done for the day or session run the `new` command:
|
52
53
|
|
53
54
|
```bash
|
54
|
-
devlogs
|
55
|
+
devlogs new
|
55
56
|
```
|
56
57
|
|
57
58
|
Your editor will pop up and you can fill in cliff notes.
|
@@ -66,24 +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
|
-
|
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
|
-
|
77
|
+
#### List all log entires
|
78
|
+
You can use the `ls` command to retrieve the most recent entry
|
77
79
|
|
78
|
-
|
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
|
+
```
|
93
|
+
|
94
|
+
#### List all issues
|
95
|
+
You can use the `ls_issues` command to retrieve the most recent entry
|
79
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
|
80
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.
|
81
119
|
|
82
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).
|
83
121
|
|
84
122
|
## Contributing
|
85
|
-
|
86
|
-
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.
|
87
124
|
|
88
125
|
## License
|
89
126
|
|
data/lib/devlogs/cli.rb
CHANGED
@@ -4,7 +4,8 @@ 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
|
+
require_relative "repository/initializer"
|
8
9
|
require "thor"
|
9
10
|
|
10
11
|
module Devlogs
|
@@ -12,7 +13,7 @@ module Devlogs
|
|
12
13
|
# The CLI devlogs CLI
|
13
14
|
#
|
14
15
|
class CLI < Thor
|
15
|
-
include
|
16
|
+
include TTYPromptHelper
|
16
17
|
|
17
18
|
package_name "devlogs"
|
18
19
|
|
@@ -33,16 +34,19 @@ module Devlogs
|
|
33
34
|
# Initializes a +devlogs+ repository with a configuration
|
34
35
|
#
|
35
36
|
desc "init", "Initialize a developer logs for project"
|
36
|
-
method_options force: :boolean
|
37
|
+
method_options force: :boolean
|
38
|
+
method_options dirpath: :string
|
37
39
|
def init
|
38
40
|
puts "Creating devlogs repository"
|
39
41
|
|
40
|
-
Repository::
|
41
|
-
{
|
42
|
-
|
42
|
+
Repository::Initializer.run(
|
43
|
+
{
|
44
|
+
force: options.force?,
|
45
|
+
dirpath: options.dirpath
|
46
|
+
}
|
43
47
|
)
|
44
48
|
|
45
|
-
puts "Created devlogs"
|
49
|
+
puts "Created devlogs repository"
|
46
50
|
end
|
47
51
|
|
48
52
|
#
|
@@ -60,18 +64,53 @@ module Devlogs
|
|
60
64
|
puts File.read(last_entry)
|
61
65
|
end
|
62
66
|
end
|
67
|
+
|
68
|
+
# FIXME: Add logs sub command
|
63
69
|
#
|
64
70
|
# Creates a devlogs entry in the repository and syncs changes
|
65
71
|
# to the mirrored directory if set
|
66
72
|
#
|
67
|
-
desc "
|
68
|
-
def
|
73
|
+
desc "new", "Create a new devlogs entry" # [4]
|
74
|
+
def new
|
69
75
|
puts "Creating new entry..."
|
70
76
|
repo.create
|
71
77
|
|
72
78
|
repo.sync
|
73
79
|
end
|
74
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
|
+
|
75
114
|
#
|
76
115
|
# Lists repository logs
|
77
116
|
#
|
@@ -79,6 +118,11 @@ module Devlogs
|
|
79
118
|
def ls
|
80
119
|
entries = repo.ls
|
81
120
|
|
121
|
+
if entries.empty?
|
122
|
+
puts "No logs present in this repository"
|
123
|
+
exit 0
|
124
|
+
end
|
125
|
+
|
82
126
|
# Use the file names as visible keys for the prompt
|
83
127
|
entry_names = entries.map { |e| File.basename(e) }
|
84
128
|
|
@@ -94,6 +138,7 @@ module Devlogs
|
|
94
138
|
# Helper method for repository loading
|
95
139
|
#
|
96
140
|
def repo
|
141
|
+
# FIXME: Need to add in path specification here
|
97
142
|
@repo ||= Repository.load
|
98
143
|
end
|
99
144
|
end
|
data/lib/devlogs/gem.rb
ADDED
@@ -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
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "erb"
|
4
|
+
|
5
|
+
#
|
6
|
+
# ErbTemplateRenderer is a base class for rendering arbitrary
|
7
|
+
# ERB templates.
|
8
|
+
#
|
9
|
+
class ErbTemplateRenderer
|
10
|
+
attr_reader :time
|
11
|
+
|
12
|
+
TIME_FORMAT_TEXT_ENTRY = "%m-%d-%Y %k:%M"
|
13
|
+
|
14
|
+
def initialize(template_file_path)
|
15
|
+
@time = Time.new.strftime(TIME_FORMAT_TEXT_ENTRY)
|
16
|
+
@template_file_path = template_file_path
|
17
|
+
end
|
18
|
+
|
19
|
+
#
|
20
|
+
# Runs the ERB rendering using the provided template file template_file_path
|
21
|
+
#
|
22
|
+
# @returns [String]
|
23
|
+
#
|
24
|
+
def render
|
25
|
+
erb = ERB.new(File.read(@template_file_path))
|
26
|
+
erb.result(get_binding)
|
27
|
+
end
|
28
|
+
|
29
|
+
# rubocop:disable
|
30
|
+
#
|
31
|
+
# For ERB
|
32
|
+
#
|
33
|
+
def get_binding
|
34
|
+
binding
|
35
|
+
end
|
36
|
+
end
|
@@ -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,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# The repository's configuration values located in the yml file
|
4
|
+
class Repository
|
5
|
+
class Config
|
6
|
+
# FIXME: Need to figure out file path
|
7
|
+
attr_reader :name, :description, :mirror, :file_path, :template_file_path, :short_code
|
8
|
+
|
9
|
+
# Configuration associated with the Mirror
|
10
|
+
MirrorConfig = Struct.new(:use_mirror, :path, keyword_init: true)
|
11
|
+
|
12
|
+
def initialize(path, opts = {})
|
13
|
+
@file_path = path
|
14
|
+
@template_file_path = opts[:template_file_path]
|
15
|
+
@name = opts[:name]
|
16
|
+
@short_code = opts[:short_code]
|
17
|
+
@description = opts[:description]
|
18
|
+
@mirror = MirrorConfig.new(opts[:mirror])
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns whether or not the devlogs repository is configured to mirror
|
22
|
+
#
|
23
|
+
# @returns [Boolean]
|
24
|
+
def mirror?
|
25
|
+
@mirror.use_mirror
|
26
|
+
end
|
27
|
+
|
28
|
+
# Ensures no weird double trailing slash path values
|
29
|
+
def path(with_trailing: false)
|
30
|
+
if with_trailing
|
31
|
+
@file_path[-1] == "/" ? @path_value : @path_value + "/"
|
32
|
+
else
|
33
|
+
@file_path
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
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
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "yaml"
|
4
|
+
require "rsync"
|
5
|
+
|
6
|
+
require_relative "config"
|
7
|
+
|
8
|
+
# A per repository configuration storage directory
|
9
|
+
class Repository
|
10
|
+
class ConfigStore
|
11
|
+
attr_reader :dir
|
12
|
+
|
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"
|
17
|
+
|
18
|
+
ISSUE_DIR = "issues"
|
19
|
+
DEFAULT_DIRECTORY_PATH = "."
|
20
|
+
DEFAULT_DIRECTORY_NAME = ".devlogs"
|
21
|
+
|
22
|
+
def initialize(dir: File.join(DEFAULT_DIRECTORY_PATH, DEFAULT_DIRECTORY_NAME))
|
23
|
+
@dir = dir
|
24
|
+
end
|
25
|
+
|
26
|
+
def values
|
27
|
+
@values ||= load_values_from_config_file
|
28
|
+
end
|
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
|
+
#
|
44
|
+
def file_path
|
45
|
+
File.join(@dir, CONFIG_FILE)
|
46
|
+
end
|
47
|
+
|
48
|
+
#
|
49
|
+
# The template File
|
50
|
+
#
|
51
|
+
# @returns [String]
|
52
|
+
# FIXME: rename to log_template_file_path
|
53
|
+
def template_file_path
|
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)
|
73
|
+
end
|
74
|
+
|
75
|
+
class << self
|
76
|
+
#
|
77
|
+
# Initialization utility method
|
78
|
+
#
|
79
|
+
def load_from(path = File.join(DEFAULT_DIRECTORY_PATH, DEFAULT_DIRECTORY_NAME))
|
80
|
+
exists = File.exist?(path)
|
81
|
+
|
82
|
+
raise "no repository found #{path}" unless exists
|
83
|
+
|
84
|
+
new(dir: path)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def load_values_from_config_file
|
91
|
+
yml = YAML.load_file(File.join(file_path))
|
92
|
+
|
93
|
+
Repository::Config.new(file_path, yml)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "config_store"
|
4
|
+
require_relative "config_builder"
|
5
|
+
|
6
|
+
# Initialize is an execution object which initializes a Repository on the
|
7
|
+
# filesystem
|
8
|
+
class Repository
|
9
|
+
class Initializer
|
10
|
+
# Creates a new devlogs repository at the provided path
|
11
|
+
def self.run(opts = {})
|
12
|
+
new_config = Repository::ConfigBuilder.new(opts[:dirpath])
|
13
|
+
|
14
|
+
draft = new_config.build
|
15
|
+
|
16
|
+
draft.save!(force: opts[:force])
|
17
|
+
end
|
18
|
+
end
|
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
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rsync"
|
4
|
+
|
5
|
+
# FIXME: Create module
|
6
|
+
class Repository
|
7
|
+
#
|
8
|
+
# SyncManager is an abstraction class for managing any necessity to sync
|
9
|
+
# files on the file system using Rsync.
|
10
|
+
#
|
11
|
+
class SyncManager
|
12
|
+
#
|
13
|
+
# @param config_store [Repository::ConfigStore]
|
14
|
+
#
|
15
|
+
def initialize(config_store)
|
16
|
+
@config_store = config_store
|
17
|
+
end
|
18
|
+
|
19
|
+
# Run rsync with -a to copy directories recursively
|
20
|
+
|
21
|
+
# Use trailing slash to avoid sub-directory
|
22
|
+
# See rsync manual page
|
23
|
+
#
|
24
|
+
# @throws Error if sync fails
|
25
|
+
def run
|
26
|
+
dest_path = @config_store.values.mirror.path
|
27
|
+
src_path = config_store_path_with_trailing
|
28
|
+
|
29
|
+
runner.run("-av", src_path, dest_path) do |result|
|
30
|
+
if result.success?
|
31
|
+
puts "Mirror sync complete."
|
32
|
+
result.changes.each do |change|
|
33
|
+
puts "#{change.filename} (#{change.summary})"
|
34
|
+
end
|
35
|
+
else
|
36
|
+
raise "Failed to sync: #{result.error}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
#
|
44
|
+
# Utility method for getting access to the runner program
|
45
|
+
# @returns [Rsync]
|
46
|
+
#
|
47
|
+
def runner
|
48
|
+
@runner ||= Rsync
|
49
|
+
end
|
50
|
+
|
51
|
+
# Depending on the runner (Rsync) program,
|
52
|
+
# you may need a trailing slash on the directory path
|
53
|
+
#
|
54
|
+
# @returns [String]
|
55
|
+
def config_store_path_with_trailing
|
56
|
+
@config_store.dir[-1] == "/" ? @config_store.dir : @config_store.dir + "/"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/devlogs/repository.rb
CHANGED
@@ -2,61 +2,46 @@
|
|
2
2
|
|
3
3
|
require "fileutils"
|
4
4
|
require "tty-prompt"
|
5
|
-
require "yaml"
|
6
|
-
require "rsync"
|
7
5
|
require "pry"
|
8
6
|
require "time"
|
7
|
+
|
8
|
+
require_relative "repository/config_store"
|
9
9
|
require_relative "editor"
|
10
|
+
require_relative "repository/sync_manager"
|
11
|
+
require_relative "helper/time_helper"
|
12
|
+
require_relative "repository/log_manager"
|
13
|
+
require_relative "repository/issue_manager"
|
10
14
|
|
11
|
-
#
|
15
|
+
# Repository is an accessor object for the devlogs directory
|
12
16
|
class Repository
|
13
|
-
|
14
|
-
|
15
|
-
# TODO: should be part of configuration
|
16
|
-
DEFAULT_LOG_SUFFIX = "devlogs.md"
|
17
|
-
DEFAULT_DIRECTORY_PATH = "."
|
18
|
-
DEFAULT_DIRECTORY_NAME = "_devlogs"
|
19
|
-
|
20
|
-
# Example: 11-22-2022_1343
|
21
|
-
DEFAULT_TIME_FORMAT_FILE_PREFIX = "%m-%d-%Y__%kh%Mm"
|
22
|
-
DEFAULT_TIME_FORMAT_TEXT_ENTRY = "%m-%d-%Y %k:%M"
|
17
|
+
include TimeHelper
|
23
18
|
|
24
|
-
|
25
|
-
|
26
|
-
# Initializes a _devlogs repository with the supplied configuration
|
27
|
-
# @param repo_config [Repository::Config]
|
19
|
+
# Initializes a .devlogs repository with the supplied configuration
|
28
20
|
#
|
29
|
-
def initialize(
|
30
|
-
@
|
21
|
+
def initialize(repo_config_store)
|
22
|
+
@config_store = repo_config_store
|
23
|
+
@repo_config = @config_store.values
|
31
24
|
end
|
32
25
|
|
33
|
-
# Creates a new
|
26
|
+
# Creates a new .devlogs entry for recording session completion
|
34
27
|
#
|
35
28
|
# @returns nil
|
36
29
|
def create
|
37
|
-
|
38
|
-
prefix = time.strftime(DEFAULT_TIME_FORMAT_FILE_PREFIX)
|
39
|
-
|
40
|
-
entry_file_name = "#{prefix}_#{DEFAULT_LOG_SUFFIX}"
|
30
|
+
entry_file_path = log_manager.create_entry
|
41
31
|
|
42
|
-
|
32
|
+
Editor.open(entry_file_path)
|
43
33
|
|
44
|
-
|
45
|
-
|
46
|
-
File.open(entry_file_path, "w") do |f|
|
47
|
-
f.write <<~ENDOFFILE
|
48
|
-
# #{time.strftime(DEFAULT_TIME_FORMAT_TEXT_ENTRY)}
|
49
|
-
Tags: #dev, #log
|
34
|
+
puts "Writing entry to #{entry_file_path}.."
|
35
|
+
end
|
50
36
|
|
51
|
-
|
37
|
+
#
|
38
|
+
# @returns nil
|
39
|
+
def create_issue
|
40
|
+
issue_file_path = issue_manager.create
|
52
41
|
|
53
|
-
|
54
|
-
end
|
55
|
-
end
|
42
|
+
Editor.open(issue_file_path)
|
56
43
|
|
57
|
-
|
58
|
-
|
59
|
-
puts "Writing entry to #{entry_file_path}.."
|
44
|
+
puts "Writing issue to #{issue_file_path}.."
|
60
45
|
end
|
61
46
|
|
62
47
|
# Syncs the directory changes to the (optional) mirror repository
|
@@ -64,42 +49,17 @@ class Repository
|
|
64
49
|
#
|
65
50
|
# @returns nil
|
66
51
|
def sync
|
67
|
-
if @
|
68
|
-
# Run rsync with -a to copy directories recursively
|
69
|
-
|
70
|
-
# Use trailing slash to avoid sub-directory
|
71
|
-
# See rsync manual page
|
72
|
-
|
73
|
-
Rsync.run("-av", @config.path(with_trailing: true), @config.mirror.path) do |result|
|
74
|
-
if result.success?
|
75
|
-
puts "Mirror sync complete."
|
76
|
-
result.changes.each do |change|
|
77
|
-
puts "#{change.filename} (#{change.summary})"
|
78
|
-
end
|
79
|
-
else
|
80
|
-
raise "Failed to sync: #{result.error}"
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
52
|
+
sync_manager.run if @repo_config.mirror?
|
84
53
|
end
|
85
54
|
|
86
55
|
# Lists the files in the repository
|
87
56
|
def ls(direction = :desc)
|
88
|
-
|
89
|
-
|
90
|
-
Dir.glob(File.join(@config.path, "*_#{DEFAULT_LOG_SUFFIX}")).sort_by do |fpath|
|
91
|
-
# The date is joined by two underscores to the suffix
|
92
|
-
date, = File.basename(fpath).split("__")
|
93
|
-
|
94
|
-
time_ms = Time.strptime(date, "%m-%d-%Y").to_i
|
57
|
+
log_manager.list(direction)
|
58
|
+
end
|
95
59
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
else
|
100
|
-
-time_ms
|
101
|
-
end
|
102
|
-
end
|
60
|
+
# Lists the issues in the repository
|
61
|
+
def ls_issues(direction = :desc)
|
62
|
+
issue_manager.list(direction)
|
103
63
|
end
|
104
64
|
|
105
65
|
class << self
|
@@ -107,129 +67,24 @@ class Repository
|
|
107
67
|
#
|
108
68
|
# @returns [Repository]
|
109
69
|
#
|
110
|
-
def load(path = File.join(DEFAULT_DIRECTORY_PATH, DEFAULT_DIRECTORY_NAME))
|
111
|
-
|
112
|
-
|
113
|
-
raise "no repository found #{path}" unless exists
|
114
|
-
|
115
|
-
cfg = YAML.load_file(File.join(path, CONFIG_FILE))
|
116
|
-
|
117
|
-
cfg[:path] = path
|
118
|
-
|
119
|
-
repo_config = Config.hydrate(cfg)
|
70
|
+
def load(path = File.join(Repository::ConfigStore::DEFAULT_DIRECTORY_PATH, Repository::ConfigStore::DEFAULT_DIRECTORY_NAME))
|
71
|
+
store = Repository::ConfigStore.load_from(path)
|
120
72
|
|
121
|
-
new(
|
73
|
+
new(store)
|
122
74
|
end
|
123
75
|
end
|
124
76
|
|
125
|
-
|
126
|
-
# in memory for access.
|
127
|
-
class Config
|
128
|
-
attr_reader :name, :description, :mirror, :path_value
|
77
|
+
private
|
129
78
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
def initialize(name, desc, mirror, p)
|
134
|
-
@name = name
|
135
|
-
@description = desc
|
136
|
-
@mirror = MirrorConfig.new(mirror)
|
137
|
-
@path_value = p
|
138
|
-
end
|
139
|
-
|
140
|
-
# Returns whether or not the devlogs repository is configured to mirror
|
141
|
-
#
|
142
|
-
# @returns [Boolean]
|
143
|
-
def mirror?
|
144
|
-
@mirror.use_mirror
|
145
|
-
end
|
146
|
-
|
147
|
-
def path(with_trailing: false)
|
148
|
-
if with_trailing
|
149
|
-
@path_value[-1] == "/" ? @path_value : @path_value + "/"
|
150
|
-
else
|
151
|
-
@path_value
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
# Utility method to build a configuration from a Hash
|
156
|
-
#
|
157
|
-
# @returns [Repository::Config]
|
158
|
-
def self.hydrate(cfg)
|
159
|
-
new(cfg[:name], cfg[:description], cfg[:mirror], cfg[:path])
|
160
|
-
end
|
79
|
+
def sync_manager
|
80
|
+
@sync_manager ||= Repository::SyncManager.new(@config_store)
|
161
81
|
end
|
162
82
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
# Creates a new devlogs repository at the provided path
|
167
|
-
def self.run(opts = {}, path = File.join(DEFAULT_DIRECTORY_PATH, DEFAULT_DIRECTORY_NAME))
|
168
|
-
exists = File.exist?(path)
|
169
|
-
|
170
|
-
if exists && !opts[:force]
|
171
|
-
puts "Log repository already exists in #{path}. Aborting..."
|
172
|
-
raise RuntimeError
|
173
|
-
end
|
174
|
-
|
175
|
-
results = prompt_for_info
|
176
|
-
|
177
|
-
FileUtils.mkdir_p(path)
|
178
|
-
config_file = File.join(path, CONFIG_FILE)
|
179
|
-
|
180
|
-
# Replace spaces in project name with underscores
|
181
|
-
sanitized_project_name = results[:name].gsub(/ /, "_").downcase
|
182
|
-
|
183
|
-
info_file_name = "#{sanitized_project_name}_devlogs.info.md"
|
184
|
-
info_file = File.join(path, info_file_name)
|
185
|
-
|
186
|
-
# Create config file
|
187
|
-
File.open(config_file, "w") do |f|
|
188
|
-
f.write results.to_yaml
|
189
|
-
end
|
190
|
-
|
191
|
-
# Create the info file
|
192
|
-
File.open(info_file, "w") do |f|
|
193
|
-
f.puts "# #{results[:name]}"
|
194
|
-
f.puts (results[:desc]).to_s
|
195
|
-
end
|
196
|
-
|
197
|
-
# Git ignore if specified
|
198
|
-
if results[:gitignore]
|
199
|
-
gitignore = File.join(path, ".gitignore")
|
200
|
-
|
201
|
-
File.open(gitignore, "a") do |f|
|
202
|
-
f.puts DEFAULT_DIRECTORY_NAME
|
203
|
-
end
|
204
|
-
end
|
205
|
-
end
|
83
|
+
def log_manager
|
84
|
+
@log_manager ||= LogManager.new(@config_store)
|
85
|
+
end
|
206
86
|
|
207
|
-
|
208
|
-
|
209
|
-
# @returns [Hash]
|
210
|
-
def self.prompt_for_info
|
211
|
-
prompt = TTY::Prompt.new
|
212
|
-
|
213
|
-
prompt.collect do |_p|
|
214
|
-
# Project name
|
215
|
-
key(:name).ask("What is the project name?") do |q|
|
216
|
-
q.required true
|
217
|
-
end
|
218
|
-
|
219
|
-
# Project description
|
220
|
-
key(:desc).ask("What is the project description?") do |q|
|
221
|
-
q.required true
|
222
|
-
end
|
223
|
-
|
224
|
-
key(:mirror) do
|
225
|
-
key(:use_mirror).ask("Do you want to mirror these logs?", convert: :boolean)
|
226
|
-
key(:path).ask("Path to mirror directory: ")
|
227
|
-
end
|
228
|
-
|
229
|
-
key(:gitignore).ask("Do you want to gitignore the devlogs repository?") do |q|
|
230
|
-
q.required true
|
231
|
-
end
|
232
|
-
end
|
233
|
-
end
|
87
|
+
def issue_manager
|
88
|
+
@issue_manager ||= IssueManager.new(@config_store)
|
234
89
|
end
|
235
90
|
end
|
data/lib/devlogs/version.rb
CHANGED
data/lib/devlogs.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:
|
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-
|
11
|
+
date: 2022-09-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rsync
|
@@ -98,9 +98,23 @@ files:
|
|
98
98
|
- lib/devlogs/cli.rb
|
99
99
|
- lib/devlogs/editor.rb
|
100
100
|
- lib/devlogs/executable.rb
|
101
|
+
- lib/devlogs/gem.rb
|
102
|
+
- lib/devlogs/helper/time_helper.rb
|
103
|
+
- lib/devlogs/helper/tty_prompt_helper.rb
|
101
104
|
- lib/devlogs/pager.rb
|
102
|
-
- 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
|
103
108
|
- lib/devlogs/repository.rb
|
109
|
+
- lib/devlogs/repository/config.rb
|
110
|
+
- lib/devlogs/repository/config_builder.rb
|
111
|
+
- lib/devlogs/repository/config_store.rb
|
112
|
+
- lib/devlogs/repository/initializer.rb
|
113
|
+
- lib/devlogs/repository/issue_manager.rb
|
114
|
+
- lib/devlogs/repository/log_manager.rb
|
115
|
+
- lib/devlogs/repository/sync_manager.rb
|
116
|
+
- lib/devlogs/templates/__issue_template.erb.md
|
117
|
+
- lib/devlogs/templates/__log_template.erb.md
|
104
118
|
- lib/devlogs/version.rb
|
105
119
|
homepage: http://github.com/aquaflamingo/devlogs
|
106
120
|
licenses:
|