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