Commit dab0c5b096c0c52ff8eaddd85c0f8b5df164e087
1 parent
5e811526
Added --version, -v, --help and made configuration more flexible.
Can have .blimp.conf in more locations, overrides work. Location of blimpstore added to .blimp.conf. Added config dump, made verbosity work correctly. Fixes #4.
Showing
1 changed file
with
113 additions
and
57 deletions
blimp.nim
... | ... | @@ -15,34 +15,44 @@ import md5, os, osproc, parseopt2, strutils, parsecfg, streams, lapp, subexes |
15 | 15 | # The file is copied over into: |
16 | 16 | # <homedir>/blimpStore/<originalfilename>-<md5sum> |
17 | 17 | # |
18 | -# Configuration is in: | |
18 | +# Configuration is in these locations in order: | |
19 | +# ./.blimp.conf | |
19 | 20 | # <gitroot>/.blimp.conf |
20 | 21 | # ~/blimpstore/.blimp.conf |
22 | +# ~/.blimp.conf | |
23 | + | |
24 | +const | |
25 | + versionMajor* = 0 | |
26 | + versionMinor* = 2 | |
27 | + versionPatch* = 1 | |
28 | + versionAsString* = $versionMajor & "." & $versionMinor & "." & $versionPatch | |
21 | 29 | |
22 | 30 | var |
23 | - blimpStore, uploadCommandFormat, downloadCommandFormat, deleteCommandFormat: string | |
24 | - remoteBlimpStore: string = nil | |
31 | + blimpStore, remoteBlimpStore, uploadCommandFormat, downloadCommandFormat, deleteCommandFormat, rsyncPassword: string = nil | |
25 | 32 | verbose: bool |
26 | 33 | |
27 | 34 | let |
28 | 35 | defaultConfig = """ |
29 | 36 | [rsync] |
30 | 37 | # Set this to your remote rsync daemon area |
31 | -remote = "blimp@some-rsync-server.com::blimpstore" | |
38 | +remote = "blimpuser@some-rsync-server.com::blimpstore" | |
39 | +password = "some-good-rsync-password-for-blimpuser" | |
32 | 40 | |
33 | 41 | # The following three formats should not need editing |
34 | 42 | # $1 is filename, $2 is remote and $3 is the local blimpstore |
35 | -upload = "rsync --password-file ~/blimp.pass -avzP $3/$1 $2/" | |
36 | -download = "rsync --password-file ~/blimp.pass -avzP $2/$1 $3/" | |
43 | +upload = "rsync --password-file $3/.blimp.pass -avzP $3/$1 $2/" | |
44 | +download = "rsync --password-file $3/.blimp.pass -avzP $2/$1 $3/" | |
37 | 45 | # This deletes a single file from destination, that is already deleted in source |
38 | -delete = "rsync --password-file ~/blimp.pass -dv --delete --existing --ignore-existing --include '$1' --exclude '*' $3/ $2" | |
46 | +delete = "rsync --password-file $3/.blimp.pass -dv --delete --existing --ignore-existing --include '$1' --exclude '*' $3/ $2" | |
39 | 47 | """ |
40 | 48 | |
41 | -# Load blimp.conf file, overkill for now but... | |
49 | +# Load a blimp.conf file | |
42 | 50 | proc parseConfFile(filename: string) = |
43 | 51 | var f = newFileStream(filename, fmRead) |
44 | 52 | if f != nil: |
53 | + if verbose: echo "Reading config: " & filename | |
45 | 54 | var p: CfgParser |
55 | + | |
46 | 56 | open(p, f, filename) |
47 | 57 | while true: |
48 | 58 | var e = next(p) |
... | ... | @@ -53,14 +63,18 @@ proc parseConfFile(filename: string) = |
53 | 63 | continue # Ignore |
54 | 64 | of cfgKeyValuePair: |
55 | 65 | case e.key |
66 | + of "blimpstore": | |
67 | + if blimpStore.isNil: blimpStore = e.value | |
56 | 68 | of "remote": |
57 | - remoteBlimpStore = e.value | |
69 | + if remoteBlimpStore.isNil: remoteBlimpStore = e.value | |
70 | + of "password": | |
71 | + if rsyncPassword.isNil: rsyncPassword = e.value | |
58 | 72 | of "upload": |
59 | - uploadCommandFormat = e.value | |
73 | + if uploadCommandFormat.isNil: uploadCommandFormat = e.value | |
60 | 74 | of "download": |
61 | - downloadCommandFormat = e.value | |
75 | + if downloadCommandFormat.isNil: downloadCommandFormat = e.value | |
62 | 76 | of "delete": |
63 | - deleteCommandFormat = e.value | |
77 | + if deleteCommandFormat.isNil: deleteCommandFormat = e.value | |
64 | 78 | else: |
65 | 79 | quit("Unknown configuration: " & e.key) |
66 | 80 | of cfgOption: |
... | ... | @@ -74,12 +88,19 @@ proc run(cmd: string): auto = |
74 | 88 | if verbose: echo(cmd) |
75 | 89 | execCmd(cmd) |
76 | 90 | |
91 | +# Every rsync command, make sure we have a password file | |
92 | +proc rsyncRun(cmd: string): auto = | |
93 | + writeFile(blimpStore / ".blimp.pass", rsyncPassword) | |
94 | + if execCmd("chmod 600 " & blimpStore / ".blimp.pass") != 0: | |
95 | + quit("Failed to chmod 600 " & blimpStore / ".blimp.pass") | |
96 | + run(cmd) | |
97 | + | |
77 | 98 | # Upload a file to the remote master blimpStore |
78 | 99 | proc uploadFile(blimpFilename: string) = |
79 | 100 | if remoteBlimpStore.isNil: |
80 | 101 | echo("Remote blimpstore not set in configuration file, skipping uploading content:\n\t" & blimpFilename) |
81 | 102 | return |
82 | - let errorCode = run(format(uploadCommandFormat, blimpFilename, remoteBlimpStore, blimpStore)) | |
103 | + let errorCode = rsyncRun(format(uploadCommandFormat, blimpFilename, remoteBlimpStore, blimpStore)) | |
83 | 104 | if errorCode != 0: |
84 | 105 | quit("Something went wrong uploading " & blimpFilename & " to " & remoteBlimpStore, 2) |
85 | 106 | |
... | ... | @@ -87,7 +108,7 @@ proc uploadFile(blimpFilename: string) = |
87 | 108 | proc downloadFile(blimpFilename: string) = |
88 | 109 | if remoteBlimpStore.isNil: |
89 | 110 | quit("Remote blimpstore not set in configuration file, can not download content:\n\t" & blimpFilename) |
90 | - let errorCode = run(format(downloadCommandFormat, blimpFilename, remoteBlimpStore, blimpStore)) | |
111 | + let errorCode = rsyncRun(format(downloadCommandFormat, blimpFilename, remoteBlimpStore, blimpStore)) | |
91 | 112 | if errorCode != 0: |
92 | 113 | quit("Something went wrong downloading " & blimpFilename & " from " & remoteBlimpStore, 3) |
93 | 114 | |
... | ... | @@ -95,7 +116,7 @@ proc downloadFile(blimpFilename: string) = |
95 | 116 | proc remoteDeleteFile(blimpFilename: string) = |
96 | 117 | if remoteBlimpStore.isNil: |
97 | 118 | return |
98 | - let errorCode = run(format(deleteCommandFormat, blimpFilename, remoteBlimpStore, blimpStore)) | |
119 | + let errorCode = rsyncRun(format(deleteCommandFormat, blimpFilename, remoteBlimpStore, blimpStore)) | |
99 | 120 | if errorCode != 0: |
100 | 121 | quit("Something went wrong deleting " & blimpFilename & " from " & remoteBlimpStore, 3) |
101 | 122 | |
... | ... | @@ -130,36 +151,40 @@ proc blimpFileName(filename: string): string= |
130 | 151 | |
131 | 152 | # Get hash and compute blimpFilename |
132 | 153 | proc computeBlimpFilename(filename: string): string = |
133 | - result = blimpFilename(filename) | |
134 | - if result.isNil: | |
135 | - var content: string | |
136 | - try: | |
137 | - content = readFile(filename) | |
138 | - except: | |
139 | - quit("Failed opening file: " & filename, 1) | |
140 | - let hash = getMD5(content) | |
141 | - result = filename & "-" & hash | |
154 | + var content: string | |
155 | + try: | |
156 | + content = readFile(filename) | |
157 | + except: | |
158 | + quit("Failed opening file: " & filename, 1) | |
159 | + let hash = getMD5(content) | |
160 | + result = filename & "-" & hash | |
142 | 161 | |
143 | 162 | # Copy original file to blimpStore and replace with hash stub in git. |
144 | 163 | proc deflate(filename: string) = |
145 | - let blimpFilename = computeBlimpFilename(filename) | |
146 | - copyToBlimpStore(filename, blimpFilename) | |
147 | - writeFile(filename, "hash:" & blimpFilename) | |
148 | - echo("\t" & filename & " deflated.") | |
164 | + if verbose: echo "Deflating " & filename | |
165 | + var blimpFilename = blimpFilename(filename) | |
166 | + if not blimpFilename.isNil: | |
167 | + echo("\t" & filename & " is already deflated, skipping.") | |
168 | + else: | |
169 | + blimpFilename = computeBlimpFilename(filename) | |
170 | + copyToBlimpStore(filename, blimpFilename) | |
171 | + writeFile(filename, "hash:" & blimpFilename) | |
172 | + if verbose: echo("\t" & filename & " deflated.") | |
149 | 173 | |
150 | 174 | proc isInBlimpStore(filename: string): bool = |
151 | 175 | let blimpFilename = blimpFilename(filename) |
152 | 176 | if not blimpFilename.isNil: |
153 | 177 | return true |
154 | - | |
178 | + | |
155 | 179 | # Parse out hash from hash stub and copy back original content from blimpStore. |
156 | 180 | proc inflate(filename: string) = |
181 | + if verbose: echo "Inflating " & filename | |
157 | 182 | let blimpFilename = blimpFilename(filename) |
158 | 183 | if blimpFilename.isNil: |
159 | - echo("\t" & filename & " is not deflated, skipping.", 5) | |
184 | + echo("\t" & filename & " is not deflated, skipping.") | |
160 | 185 | else: |
161 | 186 | copyFromBlimpStore(blimpfilename, filename) |
162 | - echo("\t" & filename & " inflated.") | |
187 | + if verbose: echo("\t" & filename & " inflated.") | |
163 | 188 | |
164 | 189 | # Inflates file first (if deflated) and then removes current content for it, |
165 | 190 | # both locally and in remote. |
... | ... | @@ -172,68 +197,99 @@ proc remove(filename: string) = |
172 | 197 | deleteFromBlimpStore(blimpfilename, filename) |
173 | 198 | echo("\t" & filename & " content removed from blimpstore locally and remotely.") |
174 | 199 | |
175 | -# Find git root dir or fall back on current dir | |
200 | +# Find git root dir or nil | |
176 | 201 | proc gitRoot(): string = |
177 | 202 | try: |
178 | 203 | let tup = execCmdEx("git rev-parse --show-toplevel") |
179 | 204 | if tup[1] == 0: |
180 | - result = tup[0] | |
205 | + result = strip(tup[0]) | |
181 | 206 | else: |
182 | - result = getCurrentDir() | |
207 | + result = nil | |
183 | 208 | except: |
184 | - result = getCurrentDir() | |
209 | + result = nil | |
210 | + | |
211 | +proc setupBlimpStore() = | |
212 | + try: | |
213 | + if not existsDir(blimpStore): | |
214 | + createDir(blimpStore) | |
215 | + except: | |
216 | + quit("Could not create " & blimpStore & " directory.", 1) | |
217 | + | |
218 | + try: | |
219 | + if not existsFile(blimpStore / ".blimp.conf"): | |
220 | + writeFile(blimpStore / ".blimp.conf", defaultConfig) | |
221 | + except: | |
222 | + quit("Could not create .blimp.conf config file in " & blimpStore & " directory.", 1) | |
223 | + | |
224 | +proc dumpConfig() = | |
225 | + echo "\nDump of configuration:" | |
226 | + echo "\tblimpStore: " & blimpStore | |
227 | + echo "\tremoteBlimpStore: " & remoteBlimpStore | |
228 | + echo "\tuploadCommandFormat: " & uploadCommandFormat | |
229 | + echo "\tdownloadCommandFormat: " & downloadCommandFormat | |
230 | + echo "\tdeleteCommandFormat: " & deleteCommandFormat | |
231 | + echo "\trsyncPassword: " & rsyncPassword | |
232 | + echo "\n" | |
185 | 233 | |
186 | 234 | let help = """ |
187 | 235 | blimp [options] <command> <filenames...> |
236 | + -h,--help Show this | |
237 | + --version Show version of blimp | |
188 | 238 | -v,--verbose Verbosity |
189 | - <command> (string) (d)eflate, (i)nflate, delete, (c)heck, (r)ecover | |
239 | + <command> (string) (d)eflate, (i)nflate, remove | |
190 | 240 | <filenames> (string...) One or more filepaths to inflate/deflate |
191 | 241 | |
192 | 242 | Edit ~/blimpstore/.blimp.conf or <gitroot>/.blimp.conf and set a proper |
193 | - remote and also create ~/blimp.pass with the proper rsync password to use. | |
243 | + remote and the proper rsync password to use. | |
194 | 244 | |
195 | 245 | Deflate is run before you add the big file to the index for committing. |
196 | 246 | Deflate will replace the file contents with a hash, and copy the |
197 | - real content to ~/blimpstore, and if configured also upload it to | |
247 | + real content to your local blimpstore, and if configured also upload it to | |
198 | 248 | remote, using rsync. |
199 | 249 | |
200 | 250 | Inflate will bring back the original content by copying from |
201 | - ~/blimpstore, and if its not there, first downloading from the remote. | |
251 | + your local blimpstore, and if its not there, first downloading from the remote. | |
202 | 252 | Use this whenever you need to work/edit the big file - in order to get |
203 | 253 | its real content. |
204 | 254 | |
205 | 255 | Remove (no single character shortcut) will remove the file(s) content |
206 | - both from the local ~/blimpstore and from the remote. This only removes | |
256 | + both from the local blimpstore and from the remote. This only removes | |
207 | 257 | the current content version, not older versions. The file itself is first |
208 | 258 | inflated, if needed, and not deleted. This only "unblimps" the file. |
209 | 259 | """ |
210 | 260 | |
211 | 261 | ################################ main ##################################### |
212 | 262 | |
213 | -# Hardwired to "blimpstore" directory in home dir. | |
214 | -blimpStore = getHomeDir() / "blimpstore" | |
263 | +# Using lapp to get args, on parsing failure this will show usage automatically | |
264 | +var args = parse(help) | |
265 | +verbose = args["verbose"].asBool | |
266 | + | |
267 | +# Parse configuration files, may shadow and override each other | |
268 | +parseConfFile(getCurrentDir() / ".blimp.conf") | |
269 | +if not gitRoot().isNil: | |
270 | + parseConfFile(gitRoot() / ".blimp.conf") | |
215 | 271 | |
216 | -# Make sure we have the dir, or create it. | |
217 | -try: | |
218 | - if not existsDir(blimpStore): | |
219 | - createDir(blimpStore) | |
220 | - if not existsFile(blimpStore / ".blimp.conf"): | |
221 | - writeFile(blimpStore / ".blimp.conf", defaultConfig) | |
222 | -except: | |
223 | - quit("Could not create " & blimpStore & " directory.", 1) | |
272 | +# If we haven't gotten a blimpstore yet, we set a default one | |
273 | +if blimpStore.isNil: | |
274 | + blimpStore = getHomeDir() / "blimpstore" | |
224 | 275 | |
276 | +if existsDir(blimpStore): | |
277 | + parseConfFile(blimpStore / ".blimp.conf") | |
278 | +parseConfFile(getHomeDir() / ".blimp.conf") | |
225 | 279 | |
226 | -# Parse configuration files if they exist | |
227 | -# The one in gitroot overrides settings from the store. | |
228 | -parseConfFile(blimpStore / ".blimp.conf") | |
229 | -parseConfFile(gitRoot() / ".blimp.conf") | |
280 | +if verbose: dumpConfig() | |
230 | 281 | |
282 | +# These two are special, they short out | |
283 | +if args.showHelp: quit(help) | |
284 | +if args.showVersion: quit("blimp version: " & versionAsString) | |
231 | 285 | |
232 | -# Using lapp to get args | |
233 | -let args = parse(help) | |
234 | 286 | let command = args["command"].asString |
235 | 287 | let filenames = args["filenames"].asSeq |
236 | -verbose = args["verbose"].asBool | |
288 | + | |
289 | +# Make sure the local blimpstore is setup. | |
290 | +setupBlimpStore() | |
291 | + | |
292 | + | |
237 | 293 | |
238 | 294 | # Do the deed |
239 | 295 | if command == "d" or command == "deflate": | ... | ... |