til-rb 0.0.2 → 0.0.7
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/README.md +66 -0
- data/bin/til +16 -1
- data/lib/til-rb.rb +1 -0
- data/lib/til/core.rb +57 -45
- data/lib/til/readme_updater.rb +85 -0
- data/lib/til/version.rb +1 -1
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 929173ad31be6464b75779784d2c1bb1d37f04d548b1869783e156bc1a13cb11
|
4
|
+
data.tar.gz: 351871aa23247e031ab56b163a3e3684b8cc995d8fe0dc65b6c529b851c51332
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ce2a20ab053f5e3ed0f8bb68cd085beb60e87a5f45c39e9af95626714eb309275f6464b9d5690ad6b7eedf34b6d476999b890ac16e72db8b1fc2dc0a838eedb8
|
7
|
+
data.tar.gz: c418c12ae3bf2ee02255350c330b6271a07d0bcd053ea2b45acf762b852e5e0eb04daf4676bbec9980bfe803f2e6b83c598bd39e51075527fbf8233b9f2f4087
|
data/README.md
CHANGED
@@ -5,3 +5,69 @@
|
|
5
5
|
A work-in-progress tool to maintain a TIL repo.
|
6
6
|
|
7
7
|
Inspiration: https://github.com/jbranchaud/til
|
8
|
+
|
9
|
+
See it in action below:
|
10
|
+
|
11
|
+

|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
### Step 1: Install the gem
|
16
|
+
|
17
|
+
```
|
18
|
+
gem install til-rb
|
19
|
+
```
|
20
|
+
|
21
|
+
You will also need `fzf` to run `til`, it's available on homebrew, so unless you already installed it, you'll have to run
|
22
|
+
|
23
|
+
```
|
24
|
+
brew install fzf
|
25
|
+
```
|
26
|
+
|
27
|
+
_fzf is technically not a hard requirement, we really could have a slightly different workflow if it's not available.
|
28
|
+
Given that I currently am the only user, there's no need to change this at the moment, but if you'd like to use this
|
29
|
+
gem without `fzf`, let me know and I'll happily work on it!_
|
30
|
+
|
31
|
+
### Step 2: Create a GitHub repo
|
32
|
+
|
33
|
+
You need a GitHub repo to store your TILs. The gem has pretty strict expectations about the format of the README.md
|
34
|
+
file, so I recommend forking [this repo](https://github.com/pjambet/til-template), or just copying the content to a
|
35
|
+
fresh new repo.
|
36
|
+
|
37
|
+
Note: The format expectations mentioned above are the following:
|
38
|
+
|
39
|
+
- A "categories" section as defined by a leading markdown separator `---`, followed by a blank line, the `###
|
40
|
+
Categories` title, and a list of all the categories.
|
41
|
+
- A links section as defined by a leading markdown separator `---`, followed by a blank line and a series of markdown
|
42
|
+
titles for each categories present in the previous section.
|
43
|
+
|
44
|
+
### Step 3: Add the environment variables
|
45
|
+
|
46
|
+
Add the following variables to your environment:
|
47
|
+
|
48
|
+
- `GH_TOKEN`: The only required scope is `public_repo` if your TIL repo is public and `repo` if it is private. You can
|
49
|
+
create a token [in the GitHub
|
50
|
+
settings](https://github.com/settings/tokens/new?scopes=public_repo&description=Token%20for%20til-rb)
|
51
|
+
- `GH_REPO`: The repo name, e.g. `pjambet/til`
|
52
|
+
|
53
|
+
You might want to add those to either your `.bashrc` or `.zshrc` but please be careful in case you share those publicly
|
54
|
+
as the token is private and *must not* be shared publicly.
|
55
|
+
|
56
|
+
Note: An earlier version of this gem used different names, `GH_TOKEN` & `GH_REPO`, it still works, but is not the
|
57
|
+
recommended approach anymore, see #2.
|
58
|
+
|
59
|
+
### Step 4:
|
60
|
+
|
61
|
+
Run `til` from the command line
|
62
|
+
|
63
|
+
## Future improvements
|
64
|
+
|
65
|
+
- An `init` command that will create a repo for you
|
66
|
+
- A `configure` command that will store the token and the repo name in a file in `~/.config/til/config` or `~/.til`
|
67
|
+
|
68
|
+
## Known issues
|
69
|
+
|
70
|
+
The current version (0.0.5) deletes the temporary file before attempting to create the new commit, so if anything goes
|
71
|
+
wrong there, the content of the file will be lost.
|
72
|
+
This will be fixed soon, by only deleting the temporary file after the commit was created, but please keep that in mind
|
73
|
+
if you typed a long TIL and definitely don't want to lose its content.
|
data/bin/til
CHANGED
@@ -1,5 +1,20 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
require 'til-rb'
|
4
|
+
require 'optparse'
|
4
5
|
|
5
|
-
|
6
|
+
options = {}
|
7
|
+
OptionParser.new do |opts|
|
8
|
+
opts.banner = 'Usage: til [options]'
|
9
|
+
|
10
|
+
opts.on('-t', '--title=TITLE', 'Set the title from the command line') do |title|
|
11
|
+
options[:title] = title
|
12
|
+
end
|
13
|
+
|
14
|
+
opts.on('-h', '--help', 'Prints this help') do
|
15
|
+
puts opts
|
16
|
+
exit
|
17
|
+
end
|
18
|
+
end.parse!
|
19
|
+
|
20
|
+
Til::Core.run options: options
|
data/lib/til-rb.rb
CHANGED
data/lib/til/core.rb
CHANGED
@@ -1,10 +1,14 @@
|
|
1
1
|
require 'octokit'
|
2
2
|
require 'tempfile'
|
3
|
+
require 'readline'
|
3
4
|
|
4
5
|
module Til
|
5
6
|
class Core
|
6
7
|
|
7
|
-
|
8
|
+
GH_TOKEN_ENV_VAR_NAME = 'TIL_RB_GITHUB_TOKEN'
|
9
|
+
GH_REPO_ENV_VAR_NAME = 'TIL_RB_GITHUB_REPO'
|
10
|
+
|
11
|
+
def self.run(options: {})
|
8
12
|
# Exit if `fzf` is not available
|
9
13
|
# Optionally print a spinner
|
10
14
|
# Grab the list of existing categories
|
@@ -17,17 +21,18 @@ module Til
|
|
17
21
|
# Create the new file
|
18
22
|
# Create a new commit
|
19
23
|
# Output a link to the file and the link to edit it
|
20
|
-
til = new
|
24
|
+
til = new(options: options)
|
21
25
|
til.run
|
22
26
|
end
|
23
27
|
|
24
|
-
def initialize(kernel: Kernel, process: Process, env: ENV, github_client: nil, stderr: $stderr)
|
28
|
+
def initialize(options: {}, kernel: Kernel, process: Process, env: ENV, github_client: nil, stderr: $stderr)
|
29
|
+
@options = options
|
25
30
|
@kernel = kernel
|
26
31
|
@process = process
|
27
32
|
@env = env
|
28
33
|
@stderr = stderr
|
29
34
|
@github_client = github_client
|
30
|
-
@repo_name =
|
35
|
+
@repo_name = nil
|
31
36
|
@new_category = false
|
32
37
|
end
|
33
38
|
|
@@ -37,7 +42,10 @@ module Til
|
|
37
42
|
check_environment_variables
|
38
43
|
existing_categories = fetch_existing_categories
|
39
44
|
selected_category = prompt_fzf(existing_categories)
|
40
|
-
|
45
|
+
if @new_category
|
46
|
+
selected_category = prompt_for_new_category
|
47
|
+
end
|
48
|
+
prepopulate_tempfile(selected_category, @options[:title])
|
41
49
|
open_editor
|
42
50
|
til_content = read_file
|
43
51
|
commit_new_til(selected_category, til_content)
|
@@ -54,17 +62,25 @@ module Til
|
|
54
62
|
end
|
55
63
|
|
56
64
|
def check_environment_variables
|
57
|
-
if @env[
|
58
|
-
|
65
|
+
if @env[GH_TOKEN_ENV_VAR_NAME].nil? || @env[GH_TOKEN_ENV_VAR_NAME] == ''
|
66
|
+
if @env['GH_TOKEN'].nil? || @env['GH_TOKEN'] == ''
|
67
|
+
raise "The #{GH_TOKEN_ENV_VAR_NAME} (with the public_repo or repo scope) environment variable is required"
|
68
|
+
else
|
69
|
+
@stderr.puts "Using GH_TOKEN is deprecated, use #{GH_TOKEN_ENV_VAR_NAME} instead"
|
70
|
+
end
|
59
71
|
end
|
60
72
|
|
61
|
-
if
|
62
|
-
|
73
|
+
if @env[GH_REPO_ENV_VAR_NAME].nil? || @env[GH_REPO_ENV_VAR_NAME] == ''
|
74
|
+
if @env['GH_REPO'].nil? || @env['GH_REPO'] == ''
|
75
|
+
raise "The #{GH_REPO_ENV_VAR_NAME} environment variable is required"
|
76
|
+
else
|
77
|
+
@stderr.puts "Using GH_REPO is deprecated, use #{GH_REPO_ENV_VAR_NAME} instead"
|
78
|
+
end
|
63
79
|
end
|
64
80
|
end
|
65
81
|
|
66
82
|
def fetch_existing_categories
|
67
|
-
existing_categories = github_client.contents(
|
83
|
+
existing_categories = github_client.contents(repo_name, path: '').filter do |c|
|
68
84
|
c['type'] == 'dir'
|
69
85
|
end
|
70
86
|
|
@@ -76,7 +92,11 @@ module Til
|
|
76
92
|
end
|
77
93
|
|
78
94
|
def github_client
|
79
|
-
@github_client ||= Octokit::Client.new(access_token: @env['GH_TOKEN'])
|
95
|
+
@github_client ||= Octokit::Client.new(access_token: @env[GH_TOKEN_ENV_VAR_NAME] || @env['GH_TOKEN'])
|
96
|
+
end
|
97
|
+
|
98
|
+
def repo_name
|
99
|
+
@repo_name ||= (@env[GH_REPO_ENV_VAR_NAME] || @env['GH_REPO'])
|
80
100
|
end
|
81
101
|
|
82
102
|
def prompt_fzf(categories)
|
@@ -99,6 +119,10 @@ module Til
|
|
99
119
|
throw :exit
|
100
120
|
end
|
101
121
|
|
122
|
+
def prompt_for_new_category
|
123
|
+
Readline.readline("New category > ").downcase
|
124
|
+
end
|
125
|
+
|
102
126
|
def prepopulate_tempfile(selected_category, title = 'Title Placeholder')
|
103
127
|
@tempfile = Tempfile.new('til.md')
|
104
128
|
@tempfile.write("# #{title}")
|
@@ -108,7 +132,7 @@ module Til
|
|
108
132
|
end
|
109
133
|
|
110
134
|
def open_editor
|
111
|
-
editor = ENV['VISUAL'] || ENV['EDITOR']
|
135
|
+
editor = ENV['VISUAL'] || ENV['EDITOR'] || 'vi'
|
112
136
|
system(*editor.split, @tempfile.path)
|
113
137
|
end
|
114
138
|
|
@@ -118,22 +142,26 @@ module Til
|
|
118
142
|
content
|
119
143
|
end
|
120
144
|
|
145
|
+
def new_filename(commit_title)
|
146
|
+
today = Time.now.strftime '%Y-%m-%d'
|
147
|
+
name = CGI.escape(commit_title.split.map(&:downcase).join('-'))
|
148
|
+
"#{today}_#{name}.md"
|
149
|
+
end
|
150
|
+
|
121
151
|
def commit_new_til(category, content)
|
122
152
|
commit_title = content.lines[0].chomp
|
123
153
|
if commit_title.start_with?('#')
|
124
154
|
commit_title = commit_title[1..].strip
|
125
155
|
end
|
126
|
-
|
127
|
-
name = commit_title.split.map(&:downcase).join('-')
|
128
|
-
filename = "#{today}_#{name}.md"
|
156
|
+
filename = new_filename(commit_title)
|
129
157
|
|
130
|
-
ref = github_client.ref
|
131
|
-
commit = github_client.commit
|
132
|
-
tree = github_client.tree
|
133
|
-
readme = github_client.readme
|
158
|
+
ref = github_client.ref repo_name, 'heads/master'
|
159
|
+
commit = github_client.commit repo_name, ref.object.sha
|
160
|
+
tree = github_client.tree repo_name, commit.commit.tree.sha, recursive: true
|
161
|
+
readme = github_client.readme repo_name
|
134
162
|
readme_content = Base64.decode64 readme.content
|
135
163
|
|
136
|
-
blob = github_client.create_blob
|
164
|
+
blob = github_client.create_blob repo_name, content
|
137
165
|
blobs = tree.tree.filter { |object|
|
138
166
|
object[:type] == 'blob' && object[:path] != 'README.md'
|
139
167
|
}.map { |object|
|
@@ -141,42 +169,26 @@ module Til
|
|
141
169
|
}
|
142
170
|
|
143
171
|
updated_readme_content = update_readme_content(category, commit_title, filename, readme_content)
|
144
|
-
new_readme_blob = github_client.create_blob
|
172
|
+
new_readme_blob = github_client.create_blob repo_name, updated_readme_content
|
145
173
|
blobs << { path: 'README.md', mode: '100644', type: 'blob', sha: new_readme_blob }
|
146
174
|
|
147
175
|
blobs << { path: "#{category}/#{filename}", mode: '100644', type: 'blob', sha: blob }
|
148
176
|
|
149
|
-
tree = github_client.create_tree
|
150
|
-
commit = github_client.create_commit
|
151
|
-
github_client.update_ref
|
177
|
+
tree = github_client.create_tree repo_name, blobs
|
178
|
+
commit = github_client.create_commit repo_name, commit_title, tree.sha, ref.object.sha
|
179
|
+
github_client.update_ref repo_name, 'heads/master', commit.sha
|
152
180
|
|
153
|
-
puts "You can see your new TIL at : https://github.com/
|
154
|
-
puts "You can edit your new TIL at : https://github.com/
|
181
|
+
puts "You can see your new TIL at : https://github.com/#{repo_name}/blob/master/#{category}/#{filename}"
|
182
|
+
puts "You can edit your new TIL at : https://github.com/#{repo_name}/edit/master/#{category}/#{filename}"
|
155
183
|
end
|
156
184
|
|
157
185
|
def update_readme_content(category, commit_title, filename, readme_content)
|
158
|
-
|
159
|
-
eend = readme_content.index('---', readme_content.index('---') + 1) - 1
|
160
|
-
|
161
|
-
# [["[Git](#git)", "Git", "git"], ["[Qux](#qux)", "Qux", "qux"]]
|
162
|
-
categories = readme_content[beginning..eend].scan(/(\[(\w+)\]\(#(\w+)\))/)
|
186
|
+
updater = Til::ReadmeUpdater.new(readme_content)
|
163
187
|
|
164
188
|
if @new_category
|
189
|
+
updater.add_item_for_new_category(category, commit_title, filename)
|
165
190
|
else
|
166
|
-
|
167
|
-
|
168
|
-
loc_in_page = readme_content.index("### #{existing_cat[1]}")
|
169
|
-
next_cat_location = readme_content.index('###', loc_in_page + 1)
|
170
|
-
|
171
|
-
new_line = "- [#{commit_title}](#{category}/#{filename})"
|
172
|
-
new_readme_content = ''
|
173
|
-
if next_cat_location
|
174
|
-
breakpoint = next_cat_location - 2
|
175
|
-
new_readme_content = readme_content[0..breakpoint] + new_line + readme_content[breakpoint..]
|
176
|
-
else
|
177
|
-
new_readme_content = readme_content + new_line + '\n'
|
178
|
-
end
|
179
|
-
new_readme_content
|
191
|
+
updater.add_item_for_existing_category(category, commit_title, filename)
|
180
192
|
end
|
181
193
|
end
|
182
194
|
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Til
|
2
|
+
class ReadmeUpdater
|
3
|
+
|
4
|
+
def initialize(initial_content)
|
5
|
+
@initial_content = initial_content
|
6
|
+
end
|
7
|
+
|
8
|
+
def add_item_for_existing_category(category, item_title, filename)
|
9
|
+
beginning = @initial_content.index('### Categories') + '### Categories'.length
|
10
|
+
eend = @initial_content.index('---', @initial_content.index('---') + 1) - 1
|
11
|
+
|
12
|
+
# [["[Git](#git)", "Git", "git"], ["[Qux](#qux)", "Qux", "qux"]]
|
13
|
+
categories = @initial_content[beginning..eend].scan(/(\[(\w+)\]\(#(\w+)\))/)
|
14
|
+
|
15
|
+
existing_cat = categories.find { |c| c[2] == category }
|
16
|
+
|
17
|
+
loc_in_page = @initial_content.index("### #{existing_cat[1]}")
|
18
|
+
next_cat_location = @initial_content.index('###', loc_in_page + 1)
|
19
|
+
|
20
|
+
new_line = "- [#{item_title}](#{category}/#{filename})"
|
21
|
+
new_readme_content = ''
|
22
|
+
if next_cat_location
|
23
|
+
breakpoint = next_cat_location - 2
|
24
|
+
new_readme_content = @initial_content[0..breakpoint] + new_line + @initial_content[breakpoint..]
|
25
|
+
else
|
26
|
+
new_readme_content = @initial_content + new_line + "\n"
|
27
|
+
end
|
28
|
+
new_readme_content
|
29
|
+
end
|
30
|
+
|
31
|
+
def add_item_for_new_category(category, item_title, filename)
|
32
|
+
# TODO: We'll need some form of validation on the category name
|
33
|
+
beginning = @initial_content.index('### Categories') + '### Categories'.length
|
34
|
+
first_dashdashdash = @initial_content.index('---')
|
35
|
+
eend = @initial_content.index('---', first_dashdashdash + 1) - 1
|
36
|
+
|
37
|
+
# [["[Git](#git)", "Git", "git"], ["[Qux](#qux)", "Qux", "qux"]]
|
38
|
+
categories = @initial_content[beginning..eend].scan(/(\[(\w+)\]\(#(\w+)\))/)
|
39
|
+
|
40
|
+
insert_at = categories.bsearch_index do |category_triplet|
|
41
|
+
category_triplet[2] >= category
|
42
|
+
end
|
43
|
+
|
44
|
+
if insert_at.nil?
|
45
|
+
# It's the last category
|
46
|
+
insert_at = categories.length
|
47
|
+
end
|
48
|
+
|
49
|
+
categories.insert(insert_at, ["[#{category.capitalize}](\##{category})", category.capitalize, category])
|
50
|
+
|
51
|
+
new_categories_formatted = categories.map do |category|
|
52
|
+
"* #{category[0]}"
|
53
|
+
end.join("\n")
|
54
|
+
|
55
|
+
new_categories_formatted.prepend("### Categories\n\n")
|
56
|
+
|
57
|
+
category_sections_found = 0
|
58
|
+
current_search_index = eend + 1 + 3
|
59
|
+
|
60
|
+
while category_sections_found < insert_at
|
61
|
+
current_search_index = @initial_content.index('###', current_search_index + 1)
|
62
|
+
category_sections_found += 1
|
63
|
+
end
|
64
|
+
|
65
|
+
next_bound = @initial_content.index('###', current_search_index + 1)
|
66
|
+
|
67
|
+
new_line = "- [#{item_title}](#{category}/#{filename})"
|
68
|
+
|
69
|
+
if next_bound
|
70
|
+
new_readme_content = @initial_content[0..(first_dashdashdash + 2)] \
|
71
|
+
+ "\n\n#{new_categories_formatted}\n" \
|
72
|
+
+ @initial_content[eend..(next_bound - 2)] \
|
73
|
+
+ "\n### #{category.capitalize}\n\n#{new_line}\n\n" \
|
74
|
+
+ @initial_content[next_bound..]
|
75
|
+
else
|
76
|
+
new_readme_content = @initial_content[0..(first_dashdashdash + 2)] \
|
77
|
+
+ "\n\n#{new_categories_formatted}\n" \
|
78
|
+
+ @initial_content[eend..] \
|
79
|
+
+ "\n### #{category.capitalize}\n\n#{new_line}\n"
|
80
|
+
end
|
81
|
+
|
82
|
+
new_readme_content
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/lib/til/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: til-rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pierre Jambet
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: 1.11.2
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: timecop
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.9.1
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.9.1
|
41
55
|
description: til-rb helps you manage a repo of TILs similar to https://github.com/jbranchaud/til
|
42
56
|
email: hello@pjam.me
|
43
57
|
executables:
|
@@ -50,8 +64,9 @@ files:
|
|
50
64
|
- bin/til
|
51
65
|
- lib/til-rb.rb
|
52
66
|
- lib/til/core.rb
|
67
|
+
- lib/til/readme_updater.rb
|
53
68
|
- lib/til/version.rb
|
54
|
-
homepage: https://
|
69
|
+
homepage: https://github.com/pjambet/til-rb/
|
55
70
|
licenses:
|
56
71
|
- MIT
|
57
72
|
metadata: {}
|