|
1 /* Title: Pure/System/standard_system.scala |
|
2 Author: Makarius |
|
3 |
|
4 Standard system operations, with basic Cygwin/Posix compatibility. |
|
5 */ |
|
6 |
|
7 package isabelle |
|
8 |
|
9 import java.util.regex.Pattern |
|
10 import java.util.Locale |
|
11 import java.io.{BufferedWriter, OutputStreamWriter, FileOutputStream, |
|
12 BufferedInputStream, FileInputStream, BufferedReader, InputStreamReader, |
|
13 File, IOException} |
|
14 |
|
15 import scala.io.Source |
|
16 import scala.util.matching.Regex |
|
17 import scala.collection.mutable |
|
18 |
|
19 |
|
20 object Standard_System |
|
21 { |
|
22 val charset = "UTF-8" |
|
23 |
|
24 |
|
25 /* permissive UTF-8 decoding */ |
|
26 |
|
27 // see also http://en.wikipedia.org/wiki/UTF-8#Description |
|
28 // overlong encodings enable byte-stuffing |
|
29 |
|
30 def decode_permissive_utf8(text: CharSequence): String = |
|
31 { |
|
32 val buf = new java.lang.StringBuilder(text.length) |
|
33 var code = -1 |
|
34 var rest = 0 |
|
35 def flush() |
|
36 { |
|
37 if (code != -1) { |
|
38 if (rest == 0 && Character.isValidCodePoint(code)) |
|
39 buf.appendCodePoint(code) |
|
40 else buf.append('\uFFFD') |
|
41 code = -1 |
|
42 rest = 0 |
|
43 } |
|
44 } |
|
45 def init(x: Int, n: Int) |
|
46 { |
|
47 flush() |
|
48 code = x |
|
49 rest = n |
|
50 } |
|
51 def push(x: Int) |
|
52 { |
|
53 if (rest <= 0) init(x, -1) |
|
54 else { |
|
55 code <<= 6 |
|
56 code += x |
|
57 rest -= 1 |
|
58 } |
|
59 } |
|
60 for (i <- 0 until text.length) { |
|
61 val c = text.charAt(i) |
|
62 if (c < 128) { flush(); buf.append(c) } |
|
63 else if ((c & 0xC0) == 0x80) push(c & 0x3F) |
|
64 else if ((c & 0xE0) == 0xC0) init(c & 0x1F, 1) |
|
65 else if ((c & 0xF0) == 0xE0) init(c & 0x0F, 2) |
|
66 else if ((c & 0xF8) == 0xF0) init(c & 0x07, 3) |
|
67 } |
|
68 flush() |
|
69 buf.toString |
|
70 } |
|
71 |
|
72 |
|
73 /* basic file operations */ |
|
74 |
|
75 def with_tmp_file[A](prefix: String)(body: File => A): A = |
|
76 { |
|
77 val file = File.createTempFile(prefix, null) |
|
78 try { body(file) } finally { file.delete } |
|
79 } |
|
80 |
|
81 def read_file(file: File): String = |
|
82 { |
|
83 val buf = new StringBuilder(file.length.toInt) |
|
84 val reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), charset)) |
|
85 var c = reader.read |
|
86 while (c != -1) { |
|
87 buf.append(c.toChar) |
|
88 c = reader.read |
|
89 } |
|
90 reader.close |
|
91 buf.toString |
|
92 } |
|
93 |
|
94 def write_file(file: File, text: CharSequence) |
|
95 { |
|
96 val writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), charset)) |
|
97 try { writer.append(text) } |
|
98 finally { writer.close } |
|
99 } |
|
100 |
|
101 |
|
102 /* shell processes */ |
|
103 |
|
104 def raw_execute(env: Map[String, String], redirect: Boolean, args: String*): Process = |
|
105 { |
|
106 val cmdline = new java.util.LinkedList[String] |
|
107 for (s <- args) cmdline.add(s) |
|
108 |
|
109 val proc = new ProcessBuilder(cmdline) |
|
110 proc.environment.clear |
|
111 for ((x, y) <- env) proc.environment.put(x, y) |
|
112 proc.redirectErrorStream(redirect) |
|
113 |
|
114 try { proc.start } |
|
115 catch { case e: IOException => error(e.getMessage) } |
|
116 } |
|
117 |
|
118 def process_output(proc: Process): (String, Int) = |
|
119 { |
|
120 proc.getOutputStream.close |
|
121 val output = Source.fromInputStream(proc.getInputStream, charset).mkString // FIXME |
|
122 val rc = |
|
123 try { proc.waitFor } |
|
124 finally { |
|
125 proc.getInputStream.close |
|
126 proc.getErrorStream.close |
|
127 proc.destroy |
|
128 Thread.interrupted |
|
129 } |
|
130 (output, rc) |
|
131 } |
|
132 } |
|
133 |
|
134 |
|
135 class Standard_System |
|
136 { |
|
137 val platform_root = if (Platform.is_windows) Cygwin.check_root() else "/" |
|
138 override def toString = platform_root |
|
139 |
|
140 |
|
141 /* jvm_path */ |
|
142 |
|
143 private val Cygdrive = new Regex("/cygdrive/([a-zA-Z])($|/.*)") |
|
144 |
|
145 def jvm_path(posix_path: String): String = |
|
146 if (Platform.is_windows) { |
|
147 val result_path = new StringBuilder |
|
148 val rest = |
|
149 posix_path match { |
|
150 case Cygdrive(drive, rest) => |
|
151 result_path ++= (drive + ":" + File.separator) |
|
152 rest |
|
153 case path if path.startsWith("/") => |
|
154 result_path ++= platform_root |
|
155 path |
|
156 case path => path |
|
157 } |
|
158 for (p <- rest.split("/") if p != "") { |
|
159 val len = result_path.length |
|
160 if (len > 0 && result_path(len - 1) != File.separatorChar) |
|
161 result_path += File.separatorChar |
|
162 result_path ++= p |
|
163 } |
|
164 result_path.toString |
|
165 } |
|
166 else posix_path |
|
167 |
|
168 |
|
169 /* posix_path */ |
|
170 |
|
171 private val Platform_Root = new Regex("(?i)" + |
|
172 Pattern.quote(platform_root) + """(?:\\+|\z)(.*)""") |
|
173 |
|
174 private val Drive = new Regex("""([a-zA-Z]):\\*(.*)""") |
|
175 |
|
176 def posix_path(jvm_path: String): String = |
|
177 if (Platform.is_windows) { |
|
178 jvm_path.replace('/', '\\') match { |
|
179 case Platform_Root(rest) => "/" + rest.replace('\\', '/') |
|
180 case Drive(letter, rest) => |
|
181 "/cygdrive/" + letter.toLowerCase(Locale.ENGLISH) + |
|
182 (if (rest == "") "" else "/" + rest.replace('\\', '/')) |
|
183 case path => path.replace('\\', '/') |
|
184 } |
|
185 } |
|
186 else jvm_path |
|
187 } |