wd
All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
cmdln.c
Go to the documentation of this file.
1 /*
2  Copyright 2013 John Bailey
3 
4  Licensed under the Apache License, Version 2.0 (the "License");
5  you may not use this file except in compliance with the License.
6  You may obtain a copy of the License at
7 
8  http://www.apache.org/licenses/LICENSE-2.0
9 
10  Unless required by applicable law or agreed to in writing, software
11  distributed under the License is distributed on an "AS IS" BASIS,
12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  See the License for the specific language governing permissions and
14  limitations under the License.
15 */
16 
17 #include "cmdln.h"
18 
19 #define VERSION_STRING "wd v1.1 by dev@brightsilence.com\n https://github.com/bright-tools/wd"
20 #define UNRECOGNISED_ARG_STRING "Unrecognised command line argument"
21 #define NEED_PARAMETER_STRING "No parameter specified for argument"
22 #define INCOMPATIBLE_OP_STRING "Parameter incompatible with other arguments"
23 #define UNRECOGNISED_PARAM_STRING "Parameter to argument not recognised"
24 
25 /* Windows is quite happy with a forward slash in the path */
26 #define DEFAULT_LIST_FILE "/.wd_list"
27 #define ENV_VAR_NAME "WD_OPTS"
28 
29 #include <stdio.h>
30 #include <time.h>
31 
32 /* Supporting get_home() */
33 #include <unistd.h>
34 #if defined _WIN32
35 #include <shlobj.h>
36 #else
37 #include <stdlib.h>
38 #include <pwd.h>
39 #include <sys/types.h>
40 #include <string.h>
41 #endif
42 /* !Supporting get_home() */
43 
44 static void get_home( config_container_t* const p_config )
45 {
46  int success = 0;
47 
48 #if defined _WIN32
49  char homedir[MAX_PATH];
50  success = SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PROFILE, NULL, 0, homedir));
51 #else
52  /* Try and retrieve the home from the environment first */
53  char *homedir = getenv("HOME");
54 
55  if( homedir == NULL ) {
56  /* Couldn't get user's home directory from the environment so
57  try the user database */
58  uid_t uid = getuid();
59  struct passwd *pw = getpwuid(uid);
60  if (pw != NULL) {
61  homedir = pw->pw_dir;
62  }
63  }
64  if( homedir != NULL ) {
65  success = 1;
66  }
67 #endif
68 
69  if( success ) {
70  /* Stitch the home directory and default filename together into list_fn
71  */
72 
73  size_t home_size = strlen( homedir );
74  size_t complete = home_size +
75  strlen( DEFAULT_LIST_FILE ) +
76  1U;
77  p_config->list_fn = realloc( p_config->list_fn, complete );
78  strcpy( p_config->list_fn, homedir );
79  strcpy( &(p_config->list_fn[ home_size ]), DEFAULT_LIST_FILE);
80  p_config->list_fn[ complete -1 ] = 0;
81  } else {
82  /* TODO: Deal with not having a home directory */
83  }
84 }
85 
86 void init_cmdln( config_container_t* const p_config ) {
87  p_config->wd_oper = WD_OPER_NONE;
88  p_config->wd_prompt = 0;
89  p_config->wd_store_access = 0;
90  p_config->wd_bookmark_name = NULL;
91  p_config->wd_dir_form = WD_DIRFORM_NONE;
92  p_config->wd_now_time = time(NULL);
93  p_config->wd_entity_type = WD_ENTITY_ANY;
94  p_config->list_fn = NULL;
95  p_config->wd_output_all = 1;
96 
97  /* TODO: Consider only doing this if the file has not been specified on the
98  command line for efficiency reasons */
99  get_home( p_config );
100 }
101 
102 static void show_help( const char* const p_cmd ) {
103  fprintf(stdout,
104  "%s [-v] [-h] [-t] [-f <fn>] [-r [dir] [-p]] [-a [dir]] [-d] [-l] [-s <c>]\n"
105  " -v : Show version information\n"
106  " -h : Show usage help\n"
107  " -d : Dump bookmark list\n"
108  " -t : Store access times for bookmarks\n"
109  " -l : List directories & bookmark names (generally for use in tab\n"
110  " expansion)\n"
111  " -s <c> : Format paths for cygwin\n"
112  " -n <nam> : Get bookmark directory with specified shortcut name\n"
113  " -p : Prompt for input (can be used with -r instead of specifying\n"
114  " directory\n"
115  " -f <fn> : Use file <fn> for storing bookmarks\n"
116  " -r [dir] : Remove specified directory or current directory if none\n"
117  " -a [dir] : Add specified directory or current directory if none\n"
118  " specified\n",
119  p_cmd );
120  /* TODO: Complete the description */
121  /* TODO: Add option to sort dump alphabetically by directory, by name,
122  by date added */
123 }
124 
125 static int process_opts( config_container_t* const p_config, const int argc, char* const argv[], const int p_cmd_line ) {
126  int arg_loop;
127  int ret_val = 1;
128 
129  /* Start loop at 1 - index 0 is name of executable */
130  for( arg_loop = 1; arg_loop < argc; arg_loop++ )
131  {
132  char* this_arg = argv[ arg_loop ];
133  DEBUG_OUT("process_opts: %s",this_arg);
134  if( p_cmd_line && ( 0 == strcmp( this_arg, "-v" ) )) {
135  fprintf( stdout, "%s\n", VERSION_STRING );
136  } else if( p_cmd_line && ( 0 == strcmp( this_arg, "-h" )) ) {
137  show_help( argv[0] );
138  } else if( p_cmd_line && ( 0 == strcmp( this_arg, "-p" )) ) {
139  p_config->wd_prompt = 1;
140  } else if( 0 == strcmp( this_arg, "-t" ) ) {
141  p_config->wd_store_access = 1;
142  } else if( 0 == strcmp( this_arg, "-z" ) ) {
143  if(( arg_loop + 1 ) < argc ) {
144  arg_loop++;
145  sscanf(argv[arg_loop],"%ld",(long int*)(&p_config->wd_now_time));
146  } else {
147  fprintf( stdout, "%s: %s\n", NEED_PARAMETER_STRING, this_arg );
148  ret_val = 0;
149  }
150  } else if( 0 == strcmp( this_arg, "-e" ) ) {
151  if(( arg_loop + 1 ) < argc ) {
152  arg_loop++;
153  /* TODO: Validate length of argument? */
154  switch( argv[ arg_loop ][0] ) {
155  case 'a':
156  p_config->wd_entity_type = WD_ENTITY_ANY;
157  p_config->wd_output_all = 0;
158  break;
159  case 'd':
160  p_config->wd_entity_type = WD_ENTITY_DIR;
161  p_config->wd_output_all = 0;
162  break;
163  case 'f':
164  p_config->wd_entity_type = WD_ENTITY_FILE;
165  p_config->wd_output_all = 0;
166  break;
167  case 'A':
168  p_config->wd_entity_type = WD_ENTITY_ANY;
169  p_config->wd_output_all = 1;
170  break;
171  case 'D':
172  p_config->wd_entity_type = WD_ENTITY_DIR;
173  p_config->wd_output_all = 1;
174  break;
175  case 'F':
176  p_config->wd_entity_type = WD_ENTITY_FILE;
177  p_config->wd_output_all = 1;
178  break;
179 
180  default:
181  fprintf( stdout, "%s: %s\n", UNRECOGNISED_PARAM_STRING, this_arg );
182  ret_val = 0;
183  break;
184  }
185  } else {
186  fprintf( stdout, "%s: %s\n", NEED_PARAMETER_STRING, this_arg );
187  ret_val = 0;
188  }
189  } else if( 0 == strcmp( this_arg, "-s" ) ) {
190  if(( arg_loop + 1 ) < argc ) {
191  arg_loop++;
192  /* TODO: Validate length of argument? */
193  switch( argv[ arg_loop ][0] ) {
194  case 'c':
195  p_config->wd_dir_form = WD_DIRFORM_CYGWIN;
196  break;
197  case 'w':
198  p_config->wd_dir_form = WD_DIRFORM_WINDOWS;
199  break;
200  default:
201  fprintf( stdout, "%s: %s\n", UNRECOGNISED_PARAM_STRING, this_arg );
202  ret_val = 0;
203  break;
204  }
205  } else {
206  fprintf( stdout, "%s: %s\n", NEED_PARAMETER_STRING, this_arg );
207  ret_val = 0;
208  }
209  } else if( p_cmd_line && ( 0 == strcmp( this_arg, "-d" )) ) {
210  p_config->wd_oper = WD_OPER_DUMP;
211  } else if( p_cmd_line && ( 0 == strcmp( this_arg, "-l" )) ) {
212  p_config->wd_oper = WD_OPER_LIST;
213  } else if( p_cmd_line && (( 0 == strcmp( this_arg, "-n" )) ||
214  ( 0 == strcmp( this_arg, "-g" )))) {
215  if((( arg_loop + 1 ) < argc ) &&
216  ( argv[ arg_loop + 1 ][0] != '-' )) {
217  arg_loop++;
218  if( 0 == strcmp( this_arg, "-n" ) ) {
219  p_config->wd_oper = WD_OPER_GET_BY_BM_NAME;
220  } else {
221  p_config->wd_oper = WD_OPER_GET;
222  }
223  p_config->wd_bookmark_name = argv[ arg_loop ];
224  } else {
225  fprintf( stdout, "%s: %s\n", NEED_PARAMETER_STRING, this_arg );
226  ret_val = 0;
227  }
228  } else if( p_cmd_line &&
229  (( 0 == strcmp( this_arg, "-a" )) ||
230  ( 0 == strcmp( this_arg, "-r" ))) ) {
231  if( p_config->wd_oper == WD_OPER_NONE ) {
232  switch( this_arg[ 1 ] ) {
233  case 'a':
234  p_config->wd_oper = WD_OPER_ADD;
235  break;
236  case 'r':
237  p_config->wd_oper = WD_OPER_REMOVE;
238  break;
239  }
240 
241  /* Check to see if there's an argument to this command */
242  if((( arg_loop + 1 ) < argc ) &&
243  ( argv[ arg_loop + 1 ][0] != '-' )) {
244  arg_loop++;
245 
246  /* TODO: Add a switch to prevent the path being made
247  absolute? */
248 #if defined _WIN32
249  GetFullPathName( argv[ arg_loop ],
250  MAXPATHLEN,
251  p_config->wd_oper_dir,
252  NULL );
253 #else
254  realpath( argv[ arg_loop ], p_config->wd_oper_dir );
255 #endif
256 
257  /* TODO: This only really makes sense for an add operation
258  */
259  if((( arg_loop + 1 ) < argc ) &&
260  ( argv[ arg_loop + 1 ][0] != '-' )) {
261  arg_loop++;
262  p_config->wd_bookmark_name = argv[ arg_loop ];
263  }
264  } else {
265  /* No argument .. use the CWD */
266  getcwd( p_config->wd_oper_dir, MAXPATHLEN );
267  }
268  } else {
269  fprintf( stdout, "%s: %s\n", INCOMPATIBLE_OP_STRING, this_arg );
270 
271  /* Check to see if there's an argument to this command */
272  if((( arg_loop + 1 ) < argc ) &&
273  ( argv[ arg_loop + 1 ][0] != '-' )) {
274  arg_loop++;
275  }
276  }
277  } else if( 0 == strcmp( this_arg, "-f" ) ) {
278  arg_loop++;
279  if( arg_loop < argc ) {
280  /* To be consistent, list_fn always points to malloc'd memory
281  rather than just pointing it to the parameter string */
282  p_config->list_fn = realloc( p_config->list_fn, strlen( argv[ arg_loop ] ));
283  strcpy( p_config->list_fn, argv[ arg_loop ] );
284  } else {
285  fprintf( stdout, "%s: %s\n", NEED_PARAMETER_STRING, this_arg );
286  ret_val = 0;
287  }
288  } else {
289  fprintf( stdout, "%s:\n %s\n", UNRECOGNISED_ARG_STRING, this_arg );
290  ret_val = 0;
291  }
292  }
293 
294  return ret_val;
295 }
296 
297 int process_env( config_container_t* const p_config )
298 {
299  char *opts = getenv(ENV_VAR_NAME);
300  int ret_val = 1;
301 
302  /* Retrieved environment string OK? */
303  if( opts != NULL ) {
304  /* +1 to take the NULL terminator into account - when we're looping
305  through the string below we want to include this, hence we factor
306  it in at this point */
307  const size_t opts_len = strlen( opts ) + 1U;
308  char* opt_copy = (char*) malloc( opts_len );
309  size_t breaks = 0;
310  size_t loop = 0;
311  char** argv;
312  int waiting;
313  char *src, *dest;
314 
315  DEBUG_OUT(ENV_VAR_NAME ": %s",opts);
316 
317  /* TODO: Deal with quotes in strings */
318 
319  /* Copy the environment string to a fresh memory area so that we can
320  maniupulate it. Replace spaces with null terminators to break string
321  into individual arguments and keep a count of the amount we've done
322  */
323  for( loop = 0, src = opts, dest = opt_copy ;
324  loop < opts_len;
325  loop++, src++, dest++ )
326  {
327  if( *src == ' ' )
328  {
329  *dest = 0;
330  breaks++;
331  } else {
332  *dest = *src;
333  }
334  }
335 
336  /* Allocate memory for pointers to the arguments
337  +1 to take into account the fact that the first argument has special
338  meaning
339  +1 to take into account the final, un-counted element in the loop
340  above */
341  argv = malloc( sizeof( char*[ breaks + 2 ] ));
342  /* First argument is the name of the program */
343  argv[0] = ENV_VAR_NAME;
344  breaks = 1;
345  waiting = 1;
346 
347  /* Loop through the broken up string, populating argv with pointers to
348  the start of each argument */
349  for( loop = 0;
350  loop < opts_len;
351  loop++ )
352  {
353  /* Are we waiting to find the start of a new string and this is a
354  non-null character?
355  Using this mechanism deals with multiple consecutive spaces/NULLs
356  in the string and prevents argv having entries pointing to NULL
357  strings */
358  if( waiting && ( opt_copy[ loop ] != '0' )) {
359  argv[breaks++] = &(opt_copy[loop]);
360  DEBUG_OUT(ENV_VAR_NAME ": opt - %s",argv[breaks-1]);
361  waiting = 0;
362  } else if( opt_copy[loop ] == 0 ) {
363  waiting = 1;
364  }
365  }
366 
367  DEBUG_OUT(ENV_VAR_NAME ": opt count - %d",breaks);
368  /* Finally, process the array of options */
369  ret_val = process_opts( p_config, breaks, argv, 0 );
370 
371  free( opt_copy );
372  free( argv );
373  }
374 
375  return ret_val;
376 }
377 
378 int process_cmdln( config_container_t* const p_config, const int argc, char* const argv[] ) {
379  return process_opts( p_config, argc, argv, 1 );
380 }
char * wd_bookmark_name
Name of a bookmark read from the command line on which operations should be performed.
Definition: cmdln.h:59
int wd_output_all
Control whether or not all items should be output regardless of whether or not they seem to point to ...
Definition: cmdln.h:73
#define VERSION_STRING
Definition: cmdln.c:19
#define DEBUG_OUT(...)
Definition: cmdln.h:86
#define UNRECOGNISED_PARAM_STRING
Definition: cmdln.c:23
time_t wd_now_time
Time to use as the current time when manipulating datestamps.
Definition: cmdln.h:68
#define INCOMPATIBLE_OP_STRING
Definition: cmdln.c:22
#define ENV_VAR_NAME
Definition: cmdln.c:27
wd_oper_t wd_oper
Type of operation which the command line has instructed should be performed.
Definition: cmdln.h:49
void init_cmdln(config_container_t *const p_config)
Initialise the specified config with default values.
Definition: cmdln.c:86
char * list_fn
Directory containing list of bookmarks.
Definition: cmdln.h:53
int process_cmdln(config_container_t *const p_config, const int argc, char *const argv[])
Definition: cmdln.c:378
char wd_oper_dir[MAXPATHLEN]
Directory read from the command line upon which operations should be performed.
Definition: cmdln.h:56
#define UNRECOGNISED_ARG_STRING
Definition: cmdln.c:20
#define NEED_PARAMETER_STRING
Definition: cmdln.c:21
#define DEFAULT_LIST_FILE
Definition: cmdln.c:26
int wd_store_access
Indicate whether or not access times should be stored in the bookmarks.
Definition: cmdln.h:64
int wd_prompt
Indicate whether or not to run in &quot;interactive&quot; mode.
Definition: cmdln.h:61
wd_dir_format_t wd_dir_form
Format in which file paths should be output.
Definition: cmdln.h:51
wd_entity_t wd_entity_type
Control which types of entity should be included in the output.
Definition: cmdln.h:70
int process_env(config_container_t *const p_config)
Definition: cmdln.c:297