chiark / gitweb /
Merge branch 'issue29'
[disorder] / lib / filepart.c
1 /*
2  * This file is part of DisOrder
3  * Copyright (C) 2005, 2007 Richard Kettlewell
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  * 
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  * 
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 /** @file lib/filepart.c
19  * @brief Filename parsing
20  */
21
22 #include "common.h"
23
24 #include "filepart.h"
25 #include "mem.h"
26
27 /** @brief Parse a filename
28  * @param path Filename to parse
29  * @param dirnamep Where to put directory name, or NULL
30  * @param basenamep Where to put basename, or NULL
31  */
32 static void parse_filename(const char *path,
33                            char **dirnamep,
34                            char **basenamep) {
35   const char *s, *e = path + strlen(path);
36
37   /* Strip trailing slashes.  We never take these into account. */
38   while(e > path && e[-1] == '/')
39     --e;
40   if(e == path) {
41     /* The path is empty or contains only slashes */
42     if(*path) {
43       if(dirnamep)
44         *dirnamep = xstrdup("/");
45       if(basenamep)
46         *basenamep = xstrdup("/");
47     } else {
48       if(dirnamep)
49         *dirnamep = xstrdup("");
50       if(basenamep)
51         *basenamep = xstrdup("");
52     }
53   } else {
54     /* The path isn't empty and has more than just slashes.  e therefore now
55      * points at the end of the basename. */
56     s = e;
57     while(s > path && s[-1] != '/')
58       --s;
59     /* Now s points at the start of the basename */
60     if(basenamep)
61       *basenamep = xstrndup(s, e - s);
62     if(s > path) {
63       --s;
64       /* s must now be pointing at a '/' before the basename */
65       assert(*s == '/');
66       while(s > path && s[-1] == '/')
67         --s;
68       /* Now s must be pointing at the last '/' after the dirname */
69       assert(*s == '/');
70       if(s == path) {
71         /* If we reached the start we must be at the root */
72         if(dirnamep)
73           *dirnamep = xstrdup("/");
74       } else {
75         /* There's more than just the root here */
76         if(dirnamep)
77           *dirnamep = xstrndup(path, s - path);
78       }
79     } else {
80       /* There wasn't a slash */
81       if(dirnamep)
82         *dirnamep = xstrdup(".");
83     }
84   }
85 }
86
87 /** @brief Return the directory part of @p path
88  * @param path Path to parse
89  * @return Directory part of @p path
90  *
91  * Extracts the directory part of @p path.  This is a simple lexical
92  * transformation and no canonicalization is performed.  The result will only
93  * ever end "/" if it is the root directory.  The result will be "." if there
94  * is no directory part.
95  */
96 char *d_dirname(const char *path) {
97   char *d = 0;
98
99   parse_filename(path, &d, 0);
100   assert(d != 0);
101   return d;
102 }
103
104 /** @brief Return the basename part of @p path
105  * @param path Path to parse
106  * @return Base part of @p path
107  *
108  * Extracts the base part of @p path.  This is a simple lexical transformation
109  * and no canonicalization is performed.  The result is always newly allocated
110  * even if compares equal to @p path.
111  */
112 char *d_basename(const char *path) {
113   char *b = 0;
114
115   parse_filename(path, 0, &b);
116   assert(b != 0);
117   return b;
118 }
119
120 /** @brief Find the extension part of @p path
121  * @param path Path to parse
122  * @return Start of extension in @p path, or NULL
123  *
124  * The return value points into @p path and points at the "." at the start of
125  * the path.  If the basename has no extension the result is NULL.  Extensions
126  * are assumed to only contain the ASCII digits and letters.
127  *
128  * See also extension().
129  */
130 static const char *find_extension(const char *path) {
131   const char *q = path + strlen(path);
132   
133   while(q > path && strchr("abcdefghijklmnopqrstuvwxyz"
134                         "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
135                         "0123456789", *q))
136     --q;
137   return *q == '.' ? q : 0;
138 }
139
140 /** @brief Strip the extension from @p path
141  * @param path Path to parse
142  * @return @p path with extension removed, or @p path
143  *
144  * The extension is defined exactly as for find_extension().  The result might
145  * or might not point into @p path.
146  */
147 const char *strip_extension(const char *path) {
148   const char *q = find_extension(path);
149
150   return q ? xstrndup(path, q - path) : path;
151 }
152
153 /** @brief Find the extension part of @p path
154  * @param path Path to parse
155  * @return Start of extension in @p path, or ""
156  *
157  * The return value may points into @p path and if so points at the "." at the
158  * start of the path.  If the basename has no extension the result is "".
159  * Extensions are assumed to only contain the ASCII digits and letters.
160  *
161  * See also find_extension().
162  */
163 const char *extension(const char *path) {
164   const char *q = find_extension(path);
165
166   return q ? q : "";
167 }
168
169 /*
170 Local Variables:
171 c-basic-offset:2
172 comment-column:40
173 fill-column:79
174 indent-tabs-mode:nil
175 End:
176 */