-
Notifications
You must be signed in to change notification settings - Fork 1
/
sync_docs
executable file
·216 lines (183 loc) · 5.89 KB
/
sync_docs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
#!/usr/bin/env ruby
# frozen_string_literal: true
require "yaml"
require "faraday"
require "faraday/retry"
require "faraday/multipart"
require "listen"
CATEGORY_ID = ENV["DOCS_CATEGORY_ID"].to_i
DATA_EXPLORER_QUERY_ID = ENV["DOCS_DATA_EXPLORER_QUERY_ID"].to_i
DOCS_TARGET = ENV["DOCS_TARGET"]
DOCS_API_KEY = ENV["DOCS_API_KEY"]
VERBOSE = ARGV.include?("-v")
WATCH = ARGV.include?("--watch")
DRY_RUN = ARGV.include?("--dry-run")
require_relative "lib/local_doc"
require_relative "lib/api"
require_relative "lib/util"
docs = []
puts "Reading local docs..."
BASE = "#{__dir__}/docs/"
Dir
.glob("**/*.md", base: BASE)
.each do |path|
next if path.end_with?("index.md")
content = File.read(File.join(BASE, path))
frontmatter, content = Util.parse_md(content)
if frontmatter["id"].nil? || frontmatter["title"].nil?
puts "Skipping '#{path}' because it has no id or title in frontmatter"
next
end
docs.push(LocalDoc.new(frontmatter:, path:, content:))
end
puts "Validating local docs..."
docs
.group_by { |doc| doc.external_id }
.each do |id, docs|
if docs.size > 1
puts "- duplicate external_id '#{id}' found in:"
docs.each { |doc| puts "- #{doc.path}" }
exit 1
end
end
puts "Fetching remote info via data-explorer..."
remote_topics = API.fetch_current_state
puts "Mapping to existing topics..."
map_to_remote =
lambda do
docs.each do |doc|
puts "- checking '#{doc.external_id}'..." if VERBOSE
if topic_info = remote_topics.find { |t| t[:external_id] == doc.external_id }
doc.topic_id = topic_info[:topic_id]
doc.first_post_id = topic_info[:first_post_id]
doc.remote_title = topic_info[:title]
doc.remote_content = topic_info[:raw]
doc.remote_deleted = topic_info[:deleted_at]
puts " found topic_id: #{doc.topic_id}" if VERBOSE
else
puts " not found" if VERBOSE
end
end
end
map_to_remote.call
puts "Deleting topics if necessary..."
cat_desc_topic = remote_topics.find { |t| t[:is_index_topic] }
if cat_desc_topic.nil?
puts "Docs category is missing an index topic"
exit 1
end
cat_desc_topic_id = cat_desc_topic[:topic_id]
remote_topics
.reject { |remote_doc| remote_doc[:deleted_at] }
.reject { |remote_doc| docs.any? { |doc| doc.topic_id == remote_doc[:topic_id] } }
.reject { |remote_doc| remote_doc[:topic_id] == cat_desc_topic_id }
.each do |remote_doc|
id = remote_doc[:topic_id]
puts "- deleting topic #{id}..."
API.trash_topic(topic_id: id)
end
puts "Restoring topics if necessary..."
docs
.filter(&:remote_deleted)
.each do |doc|
puts "- restoring '#{doc.external_id}'..."
API.restore_topic(topic_id: doc.topic_id)
end
puts "Creating missing topics..."
created_any = false
docs.each do |doc|
next if doc.topic_id
created_any = true
puts "- creating '#{doc.external_id}'"
API.create_topic(
external_id: doc.external_id,
raw: doc.content_with_uploads,
category: CATEGORY_ID,
title: doc.frontmatter["title"]
)
rescue Faraday::UnprocessableEntityError => e
puts " 422 error: #{e.response[:body]}"
raise e
end
if created_any
puts "Re-fetching remote info..."
remote_topics = API.fetch_current_state
map_to_remote.call
end
puts "Updating content..."
docs.each do |doc|
if doc.topic_id.nil?
next if DRY_RUN
raise "Topic ID not found for '#{doc.external_id}'. Something went wrong with creating it?"
end
if doc.content_with_uploads.strip == doc.remote_content.strip &&
doc.frontmatter["title"] == doc.remote_title
puts "- no changes required for '#{doc.external_id}' (topic_id: #{doc.topic_id})" if VERBOSE
next
end
puts "- updating '#{doc.external_id}' (topic_id: #{doc.topic_id})..."
API.edit_post(
post_id: doc.first_post_id,
raw: doc.content_with_uploads,
title: doc.frontmatter["title"],
category: CATEGORY_ID
)
rescue Faraday::UnprocessableEntityError => e
puts " 422 error: #{e.response[:body]}"
raise e
end
puts "Building index..."
_, index_content = Util.parse_md(File.read("#{BASE}index.md"))
index_content += "\n\n"
docs
.group_by { |doc| doc.section }
.each do |section, section_docs|
if section
section_frontmatter, _ = Util.parse_md(File.read("#{BASE}#{section}/index.md"))
index_content += "## #{section_frontmatter["title"]}\n\n"
end
section_docs.each do |doc|
index_content +=
"- #{doc.frontmatter["short_title"]}: [#{doc.frontmatter["title"]}](/t/-/#{doc.topic_id}?silent=true)\n"
end
index_content += "\n"
end
index_post_info = remote_topics.find { |t| t[:topic_id] == cat_desc_topic_id }
if index_post_info[:raw].strip == index_content.strip
puts "- no changes required for index"
else
puts "- updating index..."
API.edit_post(post_id: index_post_info[:first_post_id], raw: index_content)
end
if WATCH
puts "Watching for changes to files..."
Listen
.to("#{__dir__}/docs") do |modified, added, removed|
if added.size > 0 || removed.size > 0
puts "Files added/removed. Restarting sync..."
exec("ruby", "#{__dir__}/sync_docs", *ARGV)
end
modified.each do |path|
relative = path.sub(BASE, "")
doc = docs.find { |d| d.path == relative }
raise "Modified file not recognized: #{relative}" if doc.nil?
print "- updating '#{doc.external_id}' (topic_id: #{doc.topic_id})..."
new_frontmatter, new_content = Util.parse_md(File.read(path))
if %w[id short_title].any? { |key| doc.frontmatter[key] != new_frontmatter[key] }
puts "Frontmatter changed. Restarting sync..."
exec("ruby", "#{__dir__}/sync_docs", *ARGV)
end
doc.content, doc.frontmatter = new_content, new_frontmatter
API.edit_post(
post_id: doc.first_post_id,
raw: doc.content_with_uploads,
title: doc.frontmatter["title"]
)
puts " done"
end
end
.start
sleep
else
puts "Done."
end