OLD | NEW |
1 // Copyright 2010 The Go Authors. All rights reserved. | 1 // Copyright 2010 The Go Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style | 2 // Use of this source code is governed by a BSD-style |
3 // license that can be found in the LICENSE file. | 3 // license that can be found in the LICENSE file. |
4 | 4 |
5 // This file contains support functionality for godoc. | 5 // This file contains support functionality for godoc. |
6 | 6 |
7 package main | 7 package main |
8 | 8 |
9 import ( | 9 import ( |
10 » "io" | 10 » pathpkg "path" |
11 » "io/ioutil" | |
12 » "os" | |
13 » "path/filepath" | |
14 » "sort" | |
15 » "strings" | |
16 "sync" | 11 "sync" |
17 "time" | 12 "time" |
18 "unicode/utf8" | 13 "unicode/utf8" |
19 ) | 14 ) |
20 | 15 |
21 // An RWValue wraps a value and permits mutually exclusive | 16 // An RWValue wraps a value and permits mutually exclusive |
22 // access to it and records the time the value was last set. | 17 // access to it and records the time the value was last set. |
23 // | 18 // |
24 type RWValue struct { | 19 type RWValue struct { |
25 mutex sync.RWMutex | 20 mutex sync.RWMutex |
26 value interface{} | 21 value interface{} |
27 timestamp time.Time // time of last set() | 22 timestamp time.Time // time of last set() |
28 } | 23 } |
29 | 24 |
30 func (v *RWValue) set(value interface{}) { | 25 func (v *RWValue) set(value interface{}) { |
31 v.mutex.Lock() | 26 v.mutex.Lock() |
32 v.value = value | 27 v.value = value |
33 v.timestamp = time.Now() | 28 v.timestamp = time.Now() |
34 v.mutex.Unlock() | 29 v.mutex.Unlock() |
35 } | 30 } |
36 | 31 |
37 func (v *RWValue) get() (interface{}, time.Time) { | 32 func (v *RWValue) get() (interface{}, time.Time) { |
38 v.mutex.RLock() | 33 v.mutex.RLock() |
39 defer v.mutex.RUnlock() | 34 defer v.mutex.RUnlock() |
40 return v.value, v.timestamp | 35 return v.value, v.timestamp |
41 } | 36 } |
42 | 37 |
43 // TODO(gri) For now, using os.Getwd() is ok here since the functionality | |
44 // based on this code is not invoked for the appengine version, | |
45 // but this is fragile. Determine what the right thing to do is, | |
46 // here (possibly have some Getwd-equivalent in FileSystem). | |
47 var cwd, _ = os.Getwd() // ignore errors | |
48 | |
49 // canonicalizePaths takes a list of (directory/file) paths and returns | |
50 // the list of corresponding absolute paths in sorted (increasing) order. | |
51 // Relative paths are assumed to be relative to the current directory, | |
52 // empty and duplicate paths as well as paths for which filter(path) is | |
53 // false are discarded. filter may be nil in which case it is not used. | |
54 // | |
55 func canonicalizePaths(list []string, filter func(path string) bool) []string { | |
56 i := 0 | |
57 for _, path := range list { | |
58 path = strings.TrimSpace(path) | |
59 if len(path) == 0 { | |
60 continue // ignore empty paths (don't assume ".") | |
61 } | |
62 // len(path) > 0: normalize path | |
63 if filepath.IsAbs(path) { | |
64 path = filepath.Clean(path) | |
65 } else { | |
66 path = filepath.Join(cwd, path) | |
67 } | |
68 // we have a non-empty absolute path | |
69 if filter != nil && !filter(path) { | |
70 continue | |
71 } | |
72 // keep the path | |
73 list[i] = path | |
74 i++ | |
75 } | |
76 list = list[0:i] | |
77 | |
78 // sort the list and remove duplicate entries | |
79 sort.Strings(list) | |
80 i = 0 | |
81 prev := "" | |
82 for _, path := range list { | |
83 if path != prev { | |
84 list[i] = path | |
85 i++ | |
86 prev = path | |
87 } | |
88 } | |
89 | |
90 return list[0:i] | |
91 } | |
92 | |
93 // writeFileAtomically writes data to a temporary file and then | |
94 // atomically renames that file to the file named by filename. | |
95 // | |
96 func writeFileAtomically(filename string, data []byte) error { | |
97 // TODO(gri) this won't work on appengine | |
98 f, err := ioutil.TempFile(filepath.Split(filename)) | |
99 if err != nil { | |
100 return err | |
101 } | |
102 n, err := f.Write(data) | |
103 f.Close() | |
104 if err != nil { | |
105 return err | |
106 } | |
107 if n < len(data) { | |
108 return io.ErrShortWrite | |
109 } | |
110 return os.Rename(f.Name(), filename) | |
111 } | |
112 | |
113 // isText returns true if a significant prefix of s looks like correct UTF-8; | 38 // isText returns true if a significant prefix of s looks like correct UTF-8; |
114 // that is, if it is likely that s is human-readable text. | 39 // that is, if it is likely that s is human-readable text. |
115 // | 40 // |
116 func isText(s []byte) bool { | 41 func isText(s []byte) bool { |
117 const max = 1024 // at least utf8.UTFMax | 42 const max = 1024 // at least utf8.UTFMax |
118 if len(s) > max { | 43 if len(s) > max { |
119 s = s[0:max] | 44 s = s[0:max] |
120 } | 45 } |
121 for i, c := range string(s) { | 46 for i, c := range string(s) { |
122 if i+utf8.UTFMax > len(s) { | 47 if i+utf8.UTFMax > len(s) { |
(...skipping 16 matching lines...) Expand all Loading... |
139 ".js": false, // must be served raw | 64 ".js": false, // must be served raw |
140 } | 65 } |
141 | 66 |
142 // isTextFile returns true if the file has a known extension indicating | 67 // isTextFile returns true if the file has a known extension indicating |
143 // a text file, or if a significant chunk of the specified file looks like | 68 // a text file, or if a significant chunk of the specified file looks like |
144 // correct UTF-8; that is, if it is likely that the file contains human- | 69 // correct UTF-8; that is, if it is likely that the file contains human- |
145 // readable text. | 70 // readable text. |
146 // | 71 // |
147 func isTextFile(filename string) bool { | 72 func isTextFile(filename string) bool { |
148 // if the extension is known, use it for decision making | 73 // if the extension is known, use it for decision making |
149 » if isText, found := textExt[filepath.Ext(filename)]; found { | 74 » if isText, found := textExt[pathpkg.Ext(filename)]; found { |
150 return isText | 75 return isText |
151 } | 76 } |
152 | 77 |
153 // the extension is not known; read an initial chunk | 78 // the extension is not known; read an initial chunk |
154 // of the file and check if it looks like text | 79 // of the file and check if it looks like text |
155 f, err := fs.Open(filename) | 80 f, err := fs.Open(filename) |
156 if err != nil { | 81 if err != nil { |
157 return false | 82 return false |
158 } | 83 } |
159 defer f.Close() | 84 defer f.Close() |
160 | 85 |
161 var buf [1024]byte | 86 var buf [1024]byte |
162 n, err := f.Read(buf[0:]) | 87 n, err := f.Read(buf[0:]) |
163 if err != nil { | 88 if err != nil { |
164 return false | 89 return false |
165 } | 90 } |
166 | 91 |
167 return isText(buf[0:n]) | 92 return isText(buf[0:n]) |
168 } | 93 } |
OLD | NEW |