E-MailRelay

gbatchfile.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2021 Graeme Walker <graeme_walker@users.sourceforge.net>
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16// ===
17///
18/// \file gbatchfile.cpp
19///
20
21#include "gdef.h"
22#include "gbatchfile.h"
23#include "gfile.h"
24#include "garg.h"
25#include "gstr.h"
26#include "glog.h"
27#include "gassert.h"
28#include <stdexcept>
29#include <fstream>
30
31 G::BatchFile::BatchFile( const Path & path )
32{
33 std::ifstream stream ;
34 File::open( stream , path , File::Text() ) ;
35 if( !stream.good() )
36 throw Error( "cannot open batch file" , path.str() ) ;
37 m_line = readFrom( stream , path.str() , true ) ;
38 m_args = split( m_line ) ;
39}
40
41 G::BatchFile::BatchFile( const Path & path , std::nothrow_t )
42{
43 std::ifstream stream ;
44 File::open( stream , path , File::Text() ) ;
45 if( stream.good() )
46 {
47 m_line = readFrom( stream , path.str() , false ) ;
48 m_args = split( m_line ) ;
49 }
50}
51
52 G::BatchFile::BatchFile( std::istream & stream , const std::string & name )
53{
54 m_line = readFrom( stream , name , true ) ;
55 m_args = split( m_line ) ;
56}
57
58 bool G::BatchFile::ignorable( const std::string & trimmed_line )
59{
60 return
61 trimmed_line.empty() ||
62 Str::lower(trimmed_line+" ").find("@echo ") == 0U ||
63 Str::lower(trimmed_line+" ").find("rem ") == 0U ;
64}
65
66 bool G::BatchFile::relevant( const std::string & trimmed_line )
67{
68 return ! ignorable( trimmed_line ) ;
69}
70
71std::string G::BatchFile::join( const std::string & file_name , unsigned int line_number )
72{
73 std::ostringstream ss ;
74 ss << file_name << "(" << line_number << ")" ;
75 return ss.str() ;
76}
77
78std::string G::BatchFile::readFrom( std::istream & stream , const std::string & stream_name , bool strict )
79{
80 std::string line ;
81 unsigned int line_number = 1U ;
82 for( ; stream.good() ; line_number++ )
83 {
84 std::string s = Str::readLineFrom( stream ) ;
85 if( !stream ) break ;
86 Str::trim( s , Str::ws() ) ;
87 Str::replaceAll( s , "\t" , " " ) ;
88 s = Str::unique( s , ' ' ) ;
89 if( relevant(s) )
90 {
91 if( line.empty() )
92 line = s ;
93 else
94 throw Error( "too many lines in batch file" , join(stream_name,line_number) ) ;
95 }
96 }
97
98 if( strict && line.empty() )
99 throw Error( "batch file is empty" , join(stream_name,line_number) ) ;
100
101 // strip off any "start" prefix -- allow the "start" to have a quoted window
102 // title but dont expect any "start" options such as "/min"
103 if( !line.empty() )
104 {
105 using size_type = std::string::size_type ;
106 size_type const npos = std::string::npos ;
107 std::string ws = sv_to_string( Str::ws() ) ;
108
109 std::string start = "start " ;
110 size_type start_pos = Str::lower(line).find(start) ;
111 size_type command_pos = start_pos == npos ? 0U : line.find_first_not_of( ws , start_pos+start.size() ) ;
112
113 bool named = start_pos != npos && line.at(command_pos) == '"' ;
114 if( named )
115 {
116 std::size_t name_start_pos = command_pos ;
117 std::size_t name_end_pos = line.find( '\"' , name_start_pos+1U ) ;
118 if( name_end_pos == npos )
119 throw Error( "mismatched quotes in batch file" , stream_name ) ;
120 if( (name_end_pos+2U) >= line.size() || line.at(name_end_pos+1U) != ' ' )
121 throw Error( "invalid window name in batch file" , stream_name ) ;
122
123 m_name = line.substr( name_start_pos+1U , name_end_pos-(name_start_pos+1U) ) ;
124 dequote( m_name ) ;
125 Str::trim( m_name , Str::ws() ) ;
126
127 command_pos = line.find_first_not_of( ws , name_end_pos+2U ) ;
128 }
129
130 if( command_pos != npos )
131 line.erase( 0U , command_pos ) ;
132 }
133
134 // percent characters are doubled up in batch files so un-double them here
135 Str::replaceAll( line , "%%" , "%" ) ;
136
137 return line ;
138}
139
140std::string G::BatchFile::line() const
141{
142 return m_line ;
143}
144
145std::string G::BatchFile::name() const
146{
147 return m_name ;
148}
149
150 const G::StringArray & G::BatchFile::args() const
151{
152 return m_args ;
153}
154
155std::size_t G::BatchFile::lineArgsPos() const
156{
157 // cf. G::Str::dequote()
158 const std::string ws = sv_to_string( Str::ws() ) ;
159 const char qq = '\"' ;
160 const char esc = '\\' ;
161 bool in_quote = false ;
162 bool escaped = false ;
163 std::size_t i = 0U ;
164 for( ; i < m_line.size() ; i++ )
165 {
166 char c = m_line[i] ;
167 if( c == esc && !escaped )
168 {
169 escaped = true ;
170 }
171 else
172 {
173 if( c == qq && !escaped && !in_quote )
174 in_quote = true ;
175 else if( c == qq && !escaped )
176 in_quote = false ;
177 else if( ws.find(c) != std::string::npos && !in_quote )
178 break ;
179 }
180 }
181 return i ;
182}
183
184 void G::BatchFile::dequote( std::string & s )
185{
186 if( s.size() >= 2U && s.find('\"') == 0U && (s.rfind('\"')+1U) == s.size() )
187 s = s.substr( 1U , s.size()-2U ) ;
188}
189
190 void G::BatchFile::write( const Path & path , const StringArray & args , const std::string & name_in )
191{
192 G_ASSERT( !args.empty() ) ;
193 if( args.empty() )
194 throw Error( "invalid contents for startup batch file" ) ;
195
196 std::string name = name_in ;
197 if( name.empty() )
198 {
199 name = args.at(0U) ;
200 dequote( name ) ;
201 name = Path(name).withoutExtension().basename() ;
202 }
203
204 std::ofstream stream ;
205 File::open( stream , path ) ;
206 if( !stream.good() )
207 throw Error( "cannot create batch file" , path.str() ) ;
208
209 stream << "start \"" << name << "\"" ;
210 for( const auto & arg : args )
211 {
212 stream << " " << percents(quote(arg)) ;
213 }
214 stream << "\r\n" ;
215
216 stream.close() ;
217 if( stream.fail() )
218 throw Error( "cannot write batch file" , path.str() ) ;
219}
220
221 G::StringArray G::BatchFile::split( const std::string & line )
222{
223 // get G::Arg to deal with the quotes
224 Arg args ;
225 if( !line.empty() )
226 args.parse( line ) ;
227 return args.array() ;
228}
229
230std::string G::BatchFile::percents( const std::string & s )
231{
232 std::string result( s ) ;
233 Str::replaceAll( result , "%" , "%%" ) ;
234 return result ;
235}
236
237std::string G::BatchFile::quote( const std::string & s )
238{
239 return
240 s.find('\"') == std::string::npos && s.find_first_of(" \t") != std::string::npos ?
241 "\"" + s + "\"" : s ;
242}
243
G::Arg
A class which holds a represention of the argc/argv command line array, and supports simple command-l...
Definition: garg.h:44
G::Arg::array
StringArray array(unsigned int shift=0U) const
Returns the arguments as a string array, with an optional shift.
Definition: garg.cpp:85
G::Arg::parse
void parse(HINSTANCE hinstance, const std::string &command_line_tail)
Parses the given command-line tail, splitting it up into an array of tokens.
Definition: garg.cpp:57
G::BatchFile::write
static void write(const Path &, const StringArray &args, const std::string &start_window_name=std::string())
Writes a startup batch file, including a "start" prefix.
Definition: gbatchfile.cpp:190
G::BatchFile::args
const StringArray & args() const
Returns the startup command-line broken up into de-quoted pieces.
Definition: gbatchfile.cpp:150
G::BatchFile::lineArgsPos
std::size_t lineArgsPos() const
Returns the position in line() where the arguments start.
Definition: gbatchfile.cpp:155
G::BatchFile::name
std::string name() const
Returns the "start" window name, if any.
Definition: gbatchfile.cpp:145
G::BatchFile::BatchFile
BatchFile(const Path &)
Constructor that reads from a file.
Definition: gbatchfile.cpp:31
G::BatchFile::line
std::string line() const
Returns the main command-line from within the batchfile, with normalised spaces, without any "start" ...
Definition: gbatchfile.cpp:140
G::File::Text
An overload discriminator for G::File::open().
Definition: gfile.h:62
G::File::open
static void open(std::ofstream &, const Path &)
Calls open() on the given output file stream.
Definition: gfile_unix.cpp:55
G::Path
A Path object represents a file system path.
Definition: gpath.h:72
G::Path::basename
std::string basename() const
Returns the rightmost part of the path, ignoring "." parts.
Definition: gpath.cpp:328
G::Path::withoutExtension
Path withoutExtension() const
Returns a path without the basename extension, if any.
Definition: gpath.cpp:346
G::Path::str
std::string str() const
Returns the path string.
Definition: gpath.h:215
G::Str::ws
static string_view ws()
Returns a string of standard whitespace characters.
Definition: gstr.cpp:1255
G::Str::unique
static std::string unique(const std::string &s, char c, char r)
Returns a string with repeated 'c' characters replaced by one 'r' character.
Definition: gstr.cpp:1472
G::Str::replaceAll
static unsigned int replaceAll(std::string &s, const std::string &from, const std::string &to)
Does a global replace on string 's', replacing all occurrences of sub-string 'from' with 'to'.
Definition: gstr.cpp:287
G::Str::lower
static std::string lower(const std::string &s)
Returns a copy of 's' in which all Latin-1 upper-case characters have been replaced by lower-case cha...
Definition: gstr.cpp:741
G::Str::readLineFrom
static std::string readLineFrom(std::istream &stream, const std::string &eol=std::string())
Reads a line from the stream using the given line terminator.
Definition: gstr.cpp:924
G::Str::trim
static std::string & trim(std::string &s, string_view ws)
Trims both ends of s, taking off any of the 'ws' characters.
Definition: gstr.cpp:359
garg.h
gassert.h
gbatchfile.h
gfile.h
glog.h
gstr.h
G::StringArray
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstrings.h:31