]> git.lizzy.rs Git - rust.git/blob - src/changes.rs
Merge pull request #48 from oli-obk/newlines
[rust.git] / src / changes.rs
1 // Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11
12 // TODO
13 // print to files
14 // tests
15
16 use strings::string_buffer::StringBuffer;
17 use std::collections::HashMap;
18 use syntax::codemap::{CodeMap, Span, BytePos};
19 use std::fmt;
20 use std::fs::File;
21 use std::io::{Write, stdout};
22 use WriteMode;
23 use NEWLINE_STYLE;
24 use NewlineStyle;
25
26 // This is basically a wrapper around a bunch of Ropes which makes it convenient
27 // to work with libsyntax. It is badly named.
28 pub struct ChangeSet<'a> {
29     file_map: HashMap<String, StringBuffer>,
30     codemap: &'a CodeMap,
31     file_spans: Vec<(u32, u32)>,
32 }
33
34 impl<'a> ChangeSet<'a> {
35     // Create a new ChangeSet for a given libsyntax CodeMap.
36     pub fn from_codemap(codemap: &'a CodeMap) -> ChangeSet<'a> {
37         let mut result = ChangeSet {
38             file_map: HashMap::new(),
39             codemap: codemap,
40             file_spans: Vec::with_capacity(codemap.files.borrow().len()),
41         };
42
43         for f in codemap.files.borrow().iter() {
44             // Use the length of the file as a heuristic for how much space we
45             // need. I hope that at some stage someone rounds this up to the next
46             // power of two. TODO check that or do it here.
47             result.file_map.insert(f.name.clone(),
48                                    StringBuffer::with_capacity(f.src.as_ref().unwrap().len()));
49
50             result.file_spans.push((f.start_pos.0, f.end_pos.0));
51         }
52
53         result.file_spans.sort();
54
55         result
56     }
57
58     pub fn filespans_for_span(&self, start: BytePos, end: BytePos) -> Vec<(u32, u32)> {
59         assert!(start.0 <= end.0);
60
61         if self.file_spans.len() == 0 {
62             return Vec::new();
63         }
64
65         // idx is the index into file_spans which indicates the current file, we
66         // with the file start denotes.
67         let mut idx = match self.file_spans.binary_search(&(start.0, ::std::u32::MAX)) {
68             Ok(i) => i,
69             Err(0) => 0,
70             Err(i) => i - 1,
71         };
72
73         let mut result = Vec::new();
74         let mut start = start.0;
75         loop {
76             let cur_file = &self.file_spans[idx];
77             idx += 1;
78
79             if idx >= self.file_spans.len() || start >= end.0 {
80                 if start < end.0 {
81                     result.push((start, end.0));
82                 }
83                 return result;
84             }
85
86             let end = ::std::cmp::min(cur_file.1 - 1, end.0);
87             if start < end {
88                 result.push((start, end));
89             }
90             start = self.file_spans[idx].0;
91         }
92     }
93
94     pub fn push_str(&mut self, filename: &str, text: &str) {
95         let buf = self.file_map.get_mut(&*filename).unwrap();
96         buf.push_str(text)
97     }
98
99     pub fn push_str_span(&mut self, span: Span, text: &str) {
100         let file_name = self.codemap.span_to_filename(span);
101         self.push_str(&file_name, text)
102     }
103
104     pub fn get_mut(&mut self, file_name: &str) -> &mut StringBuffer {
105         self.file_map.get_mut(file_name).unwrap()
106     }
107
108     pub fn cur_offset(&mut self, filename: &str) -> usize {
109         self.file_map[&*filename].cur_offset()
110     }
111
112     pub fn cur_offset_span(&mut self, span: Span) -> usize {
113         let filename = self.codemap.span_to_filename(span);
114         self.cur_offset(&filename)
115     }
116
117     // Return an iterator over the entire changed text.
118     pub fn text<'c>(&'c self) -> FileIterator<'c, 'a> {
119         FileIterator {
120             change_set: self,
121             keys: self.file_map.keys().collect(),
122             cur_key: 0,
123         }
124     }
125
126     // Append a newline to the end of each file.
127     pub fn append_newlines(&mut self) {
128         for (_, s) in self.file_map.iter_mut() {
129             s.push_str("\n");
130         }
131     }
132
133     pub fn write_all_files(&self,
134                            mode: WriteMode)
135                            -> Result<(HashMap<String, String>), ::std::io::Error> {
136         let mut result = HashMap::new();
137         for filename in self.file_map.keys() {
138             let one_result = try!(self.write_file(filename, mode));
139             if let Some(r) = one_result {
140                 result.insert(filename.clone(), r);
141             }
142         }
143
144         Ok(result)
145     }
146
147     pub fn write_file(&self,
148                       filename: &str,
149                       mode: WriteMode)
150                       -> Result<Option<String>, ::std::io::Error> {
151         let text = &self.file_map[filename];
152
153         // prints all newlines either as `\n` or as `\r\n`
154         fn write_system_newlines<T>(
155             mut writer: T,
156             text: &StringBuffer)
157             -> Result<(), ::std::io::Error>
158             where T: Write,
159         {
160             match NEWLINE_STYLE {
161                 NewlineStyle::Unix => write!(writer, "{}", text),
162                 NewlineStyle::Windows => {
163                     for (c, _) in text.chars() {
164                         match c {
165                             '\n' => try!(write!(writer, "\r\n")),
166                             '\r' => continue,
167                             c => try!(write!(writer, "{}", c)),
168                         }
169                     }
170                     Ok(())
171                 },
172             }
173         }
174
175         match mode {
176             WriteMode::Overwrite => {
177                 // Do a little dance to make writing safer - write to a temp file
178                 // rename the original to a .bk, then rename the temp file to the
179                 // original.
180                 let tmp_name = filename.to_owned() + ".tmp";
181                 let bk_name = filename.to_owned() + ".bk";
182                 {
183                     // Write text to temp file
184                     let tmp_file = try!(File::create(&tmp_name));
185                     try!(write_system_newlines(tmp_file, text));
186                 }
187
188                 try!(::std::fs::rename(filename, bk_name));
189                 try!(::std::fs::rename(tmp_name, filename));
190             }
191             WriteMode::NewFile(extn) => {
192                 let filename = filename.to_owned() + "." + extn;
193                 let file = try!(File::create(&filename));
194                 try!(write_system_newlines(file, text));
195             }
196             WriteMode::Display => {
197                 println!("{}:\n", filename);
198                 let stdout = stdout();
199                 let stdout_lock = stdout.lock();
200                 try!(write_system_newlines(stdout_lock, text));
201             }
202             WriteMode::Return(_) => {
203                 // io::Write is not implemented for String, working around with Vec<u8>
204                 let mut v = Vec::new();
205                 try!(write_system_newlines(&mut v, text));
206                 // won't panic, we are writing correct utf8
207                 return Ok(Some(String::from_utf8(v).unwrap()));
208             }
209         }
210
211         Ok(None)
212     }
213 }
214
215 // Iterates over each file in the ChangSet. Yields the filename and the changed
216 // text for that file.
217 pub struct FileIterator<'c, 'a: 'c> {
218     change_set: &'c ChangeSet<'a>,
219     keys: Vec<&'c String>,
220     cur_key: usize,
221 }
222
223 impl<'c, 'a> Iterator for FileIterator<'c, 'a> {
224     type Item = (&'c str, &'c StringBuffer);
225
226     fn next(&mut self) -> Option<(&'c str, &'c StringBuffer)> {
227         if self.cur_key >= self.keys.len() {
228             return None;
229         }
230
231         let key = self.keys[self.cur_key];
232         self.cur_key += 1;
233         return Some((&key, &self.change_set.file_map[&*key]))
234     }
235 }
236
237 impl<'a> fmt::Display for ChangeSet<'a> {
238     // Prints the entire changed text.
239     fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
240         for (f, r) in self.text() {
241             try!(write!(fmt, "{}:\n", f));
242             try!(write!(fmt, "{}\n\n", r));
243         }
244         Ok(())
245     }
246 }