better regexp for grabbing field names
[migration-tools.git] / mig
1 #!/usr/bin/perl -w
2 ###############################################################################
3 =pod
4
5 =head1 NAME
6
7 mig - git-like program for tracking and manipulating legacy data files for
8 migrations
9
10 =head1 SYNOPSIS
11
12 B<mig> <command> [argument] [...]
13
14 =head1 DESCRIPTION
15
16 B<mig> is used to track and manipulate CSV or CSV-like text files exported from
17 legacy systems for migration into Evergreen.  It can be a wrapper for some
18 other migration tools and tracks state using a PostgreSQL table in a given
19 migration schema.
20
21 It makes use of certain environment variables that may be set by the B<mig-env>
22 tool: PGHOST, PGPORT, PGUSER, PGDATABASE, MIGSCHEMA, and MIGWORKDIR
23
24 For most commands, if the current working directory falls outside of the
25 directory specified by MIGWORKDIR, then mig will assume that environment is
26 also incorrect and bail before doing any actual work.
27
28 ~/.pgpass should also be configured, as B<mig> will not prompt for a database
29 password.
30
31 Only the B<env> and B<help> commands work without the MIGSCHEMA environment
32 variable being set.
33
34 =head1 OVERVIEW
35
36 Using B<mig> should go something like this:
37
38 =over 15
39
40 =item mig env create m_foo # Sets up the environment
41
42 =item mig env use m_foo # Spawns a shell using the configured environment
43
44 =item mig init # creates the m_foo schema in the database if needed, and other tables
45
46 =item mig add patrons.tsv # tracks an incoming data file; repeat for additional files
47
48 =item mig iconv patrons.tsv # convert it to UTF8, creating patrons.tsv.utf8
49
50 =item mig clean patrons.tsv # cleans the file, creating patrons.tsv.utf8.clean
51
52 =item mig link patrons.tsv actor_usr # makes the soon-to-be staging table a child of m_foo.actor_usr
53
54 =item mig convert patrons.tsv # creates a .sql file for staging the data
55
56 =item mig stage patrons.tsv # load said .sql file
57
58 =item mig mapper patrons.tsv # interactive tool for analyzing/mapping the staging table
59
60 =item mig analysis patrons.tsv # writes a summary .tsv file of mapped/flagged fields from the staging table
61
62 =item mig map patrons.tsv # apply configured mappings
63
64 =item mig write_prod patrons.tsv # creates a .sql file for pushing the staging data into production
65
66 =back
67
68 =head1 COMMANDS
69
70 =over 15
71
72 =item B<help> [command]
73
74 Display this very same documentation, or specific documentation for one of the
75 commands listed here.
76
77 =item B<env> <create|use|show> <schema>
78
79 Invokes B<mig-env> with the same arguments.  I<mig-env> can set important
80 environment variables and spawn a shell with those variables, and it also does
81 some directory creation and symlinking.
82
83 =item B<init>
84
85 Create or re-create the PostgreSQL tracking table for the schema specified by
86 the MIGSCHEMA environment variable.  If needed, create the migration schema
87 itself and run migration_tools.init() and build() if the migration_tools schema
88 exists.
89
90 =item B<status> [file] [...]
91
92 Show status information for either the specified files or all tracked files if
93 no argument is given.
94
95 =item B<add> [--no-headers|--headers] <file> [file|--no-headers|--headers] [...]
96
97 Add the specified files to the migration tracker.  Until --no-headers is
98 specified, the tracker will assume the files have headers.
99
100 You can do crazy stuff like
101 B<mig add file1 --no-headers file2 file3 --headers file4>
102
103 =item B<remove> <file> [file] [...]
104
105 Remove the specified files from the migration tracker.
106
107 =item B<iconv> <file> [other arguments...]
108
109 Attempts to invoke B<iconv> on the specified tracked file, placing the output in
110 <file>.utf8
111
112 If given no other arguments, the invocation will lool like
113
114 =over 5
115
116 iconv -f ISO-8859-1 -t UTF-8 -o <file>.utf8 <file>
117
118 =back
119
120 otherwise, the arguments will be passed through like so
121
122 =over 5
123
124 iconv [other arguments...] -o <file>.utf8 <file>
125
126 =back
127
128 =item B<skip-iconv> <file>
129
130 If this is used instead of B<iconv>, then B<mig> will look for an existing
131 <file>.utf8 and use it instead of attempting to create one.
132
133 =item B<clean> <file> [other arguments...]
134
135 Attempts to invoke B<clean_csv> on the iconv-converted specified tracked file,
136 placing the output in <file>.utf8.clean
137
138 If given no other arguments, the invocation will lool like
139
140 =over 5
141
142 clean_csv --config scripts/clean.conf --fix --apply <--create-headers> <file>
143
144 =back
145
146 otherwise, the arguments will be passed through like so
147
148 =over 5
149
150 clean_csv [other arguments...] <file>
151
152 =back
153
154 =item B<skip-clean> <file>
155
156 If this is used instead of B<clean>, then B<mig> will look for an existing
157 <file>.utf8.clean and use it instead of attempting to create one.
158
159 =item B<link> <file> <parent table>
160
161 Associate the specified file with a parent table within the migration schema.
162
163 Linking multiple files to the same parent table is not allowed currently.
164
165 =item B<unlink> <file>
166
167 Removes any association between the specified file and a parent table within
168 the migration schema.
169
170 =item B<convert> <file>
171
172 Attempts to invoke B<csv2sql> on the .utf8.clean version of the specified
173 tracked file, creating either [file].utf8.clean.stage.sql or
174 <parent table>_stage.sql depending on whether the file has been linked to a
175 parent table within the migration schema or not.
176
177 If given no other arguments, the invocation will lool like
178
179 =over 5
180
181 csv2sql --config scripts/clean.conf --add-x-migrate --schema <MIGSCHEMA> [--parent <PARENT TABLE>] -o <[<file>.utf8.clean.stage.sql]|[parent_table_stage.sql]> <FILE>.utf8.clean
182
183 =back
184
185 otherwise, the arguments will be passed through like so
186
187 =over 5
188
189 csv2sql [other arguments...] -o <[<file>.utf8.clean.stage.sql]|[parent_table_stage.sql]> <file>.utf8.clean
190
191 =back
192
193 =item B<stage> <file> [other arguments...]
194
195 Load the SQL-converted version of the specified file into the migration schema.
196
197 Extra arguments are passed to the underlying call to psql
198
199 =item B<mapper> <file>
200
201 Interactive session for analyzing, flagging, and mapping legacy field data to
202 Evergreen fields.
203
204 Upon exit, generate either [file].clean.map.sql or <parent table>_map.sql. The
205 SQL generated will be UPDATE's for setting the Evergreen-specific columns for a
206 given file's staging tables, and TRUNCATE's and INSERT's for auxilary tables.
207 The files will have \include hooks for pulling in additional mapping files
208 (for example, end-user mappings for circ modifiers, etc.)
209
210 =item B<analysis> [file]
211
212 Writes a MIGSCHEMA.tsv file containing a break-down of mapped and flagged
213 fields from the specified file, or all staged files if no file is specified.
214
215 The main goal of the tsv file is to present end-user mappable data for circ
216 modifiers, shelving locations, patron profiles, etc.  We use spreadsheets for
217 this now but may move to a dedicated UI in the future.
218
219 =item B<map> [file]
220
221 Applies the mapping sql to the migration schema for the specified mapped file,
222 or for all mapped files if no file is specified.
223
224 =item B<write_prod> [file]
225
226 Generates <parent table>_prod.sql for the specified linked and mapped file, or
227 all such files if no file is specified.
228
229 =item B<sql> [arguments...]
230
231 A wrapper around the psql command.  At some point the plan is to shove mig-tracked variables into psql sessions.
232
233 =back
234
235 =cut
236
237 ###############################################################################
238
239 use strict;
240 use Switch;
241 use Env qw(
242     HOME PGHOST PGPORT PGUSER PGDATABASE MIGSCHEMA
243     MIGBASEWORKDIR MIGBASEGITDIR MIGGITDIR MIGWORKDIR
244 );
245 use Pod::Usage;
246 use FindBin;
247 my $mig_bin = "$FindBin::Bin/mig-bin/";
248 use lib "$FindBin::Bin/mig-bin";
249 use Mig;
250
251 pod2usage(-verbose => 2) if ! $ARGV[0];
252 switch($ARGV[0]) {
253     case "help" {
254         if (defined $ARGV[1]) {
255             my $cmd = $mig_bin . "mig-$ARGV[1]";
256             if (-e $cmd) {
257                 system( $mig_bin . "mig-$ARGV[1]", '--help' );
258             } else {
259                 pod2usage(-verbose => 2);
260             }
261         } else {
262             pod2usage(-verbose => 2);
263         }
264     }
265     case "env" {
266         standard_invocation(@ARGV);
267     }
268     case "init" {
269         Mig::die_if_no_env_migschema();
270         standard_invocation(@ARGV);
271     }
272     case "status" {
273         Mig::die_if_no_env_migschema();
274         standard_invocation(@ARGV);
275     }
276     case "add" {
277         Mig::die_if_no_env_migschema();
278         standard_invocation(@ARGV);
279     }
280     case "remove" {
281         Mig::die_if_no_env_migschema();
282         standard_invocation(@ARGV);
283     }
284     case "iconv" {
285         Mig::die_if_no_env_migschema();
286         standard_invocation(@ARGV);
287     }
288     case "skip-iconv" {
289         Mig::die_if_no_env_migschema();
290         standard_invocation(@ARGV);
291     }
292     case "clean" {
293         Mig::die_if_no_env_migschema();
294         standard_invocation(@ARGV);
295     }
296     case "skip-clean" {
297         Mig::die_if_no_env_migschema();
298         standard_invocation(@ARGV);
299     }
300     case "link" {
301         Mig::die_if_no_env_migschema();
302         standard_invocation(@ARGV);
303     }
304     case "unlink" {
305         Mig::die_if_no_env_migschema();
306         standard_invocation(@ARGV);
307     }
308     case "convert" {
309         Mig::die_if_no_env_migschema();
310         standard_invocation(@ARGV);
311     }
312     case "stage" {
313         Mig::die_if_no_env_migschema();
314         standard_invocation(@ARGV);
315     }
316     case "mapper" {
317         Mig::die_if_no_env_migschema();
318         standard_invocation(@ARGV);
319     }
320     case "quicksheet" {
321         Mig::die_if_no_env_migschema();
322         standard_invocation(@ARGV);
323     }
324     case "sql" {
325         Mig::die_if_no_env_migschema();
326         standard_invocation(@ARGV);
327     }
328     case "map" {
329         Mig::die_if_no_env_migschema();
330     }
331     case "load" {
332         Mig::die_if_no_env_migschema();
333     }
334     case "wdir" {
335         print "$MIGWORKDIR\n";
336     }
337     case "gdir" {
338         print "$MIGBASEGITDIR\n";
339     }
340     case "sdir" {
341         print "$MIGGITDIR\n";
342     }
343     else {
344         pod2usage(1);
345     }
346 }
347
348 sub standard_invocation {
349     my $cmd = shift;
350     system( $mig_bin . "mig-$cmd", @_ );
351 }
352
353