github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/network/devicenames.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package network 5 6 import ( 7 "sort" 8 "strconv" 9 "unicode/utf8" 10 ) 11 12 const ( 13 EOF = iota 14 LITERAL 15 NUMBER 16 ) 17 18 type token int 19 20 type deviceNameScanner struct { 21 src string 22 23 // scanning state 24 ch rune // current character 25 offset int // character offset 26 rdOffset int // reading offset (position of next ch) 27 } 28 29 type deviceName struct { 30 name string 31 tokens []int 32 } 33 34 type devices []deviceName 35 36 func (d devices) Len() int { 37 return len(d) 38 } 39 40 func (d devices) Less(i, j int) bool { 41 if r := intCompare(d[i].tokens, d[j].tokens); r == -1 { 42 return true 43 } else { 44 return false 45 } 46 } 47 48 func (d devices) Swap(i, j int) { 49 d[i], d[j] = d[j], d[i] 50 } 51 52 // adapted from runtime/noasm.go 53 func intCompare(s1, s2 []int) int { 54 l := len(s1) 55 if len(s2) < l { 56 l = len(s2) 57 } 58 if l == 0 || &s1[0] == &s2[0] { 59 goto samebytes 60 } 61 for i := 0; i < l; i++ { 62 c1, c2 := s1[i], s2[i] 63 if c1 < c2 { 64 return -1 65 } 66 if c1 > c2 { 67 return +1 68 } 69 } 70 samebytes: 71 if len(s1) < len(s2) { 72 return -1 73 } 74 if len(s1) > len(s2) { 75 return +1 76 } 77 return 0 78 } 79 80 func (s *deviceNameScanner) init(src string) { 81 s.src = src 82 s.ch = ' ' 83 s.offset = 0 84 s.rdOffset = 0 85 s.next() 86 } 87 88 func (s *deviceNameScanner) next() { 89 if s.rdOffset < len(s.src) { 90 s.offset = s.rdOffset 91 r, w := rune(s.src[s.rdOffset]), 1 92 s.rdOffset += w 93 s.ch = r 94 } else { 95 s.offset = len(s.src) 96 s.ch = -1 // EOF 97 } 98 } 99 100 func (s *deviceNameScanner) peek() rune { 101 if s.rdOffset < len(s.src) { 102 r, _ := rune(s.src[s.rdOffset]), 1 103 return r 104 } 105 return -1 106 } 107 108 func isDigit(ch rune) bool { 109 return '0' <= ch && ch <= '9' 110 } 111 112 func (s *deviceNameScanner) scanNumber() string { 113 // Treat leading zeros as discrete numbers as this aids the 114 // natural sort ordering. We also only parse whole numbers; 115 // floating point values are considered an integer- and 116 // fractional-part. 117 118 if s.ch == '0' && s.peek() == '0' { 119 s.next() 120 return "0" 121 } 122 123 cur := s.offset 124 125 for isDigit(s.ch) { 126 s.next() 127 } 128 129 return s.src[cur:s.offset] 130 } 131 132 func (s *deviceNameScanner) scan() (tok token, lit string) { 133 switch ch := s.ch; { 134 case -1 == ch: 135 return EOF, "" 136 case '0' <= ch && ch <= '9': 137 return NUMBER, s.scanNumber() 138 default: 139 lit = string(s.ch) 140 s.next() 141 return LITERAL, lit 142 } 143 } 144 145 func parseDeviceName(src string) deviceName { 146 var s deviceNameScanner 147 148 s.init(src) 149 150 d := deviceName{name: src} 151 152 for { 153 tok, lit := s.scan() 154 switch tok { 155 case EOF: 156 return d 157 case LITERAL: 158 x, _ := utf8.DecodeRuneInString(lit) 159 d.tokens = append(d.tokens, int(x)) 160 case NUMBER: 161 val, _ := strconv.Atoi(lit) 162 d.tokens = append(d.tokens, val) 163 } 164 } 165 } 166 167 func parseDeviceNames(args ...string) devices { 168 devices := make(devices, 0) 169 170 for _, a := range args { 171 devices = append(devices, parseDeviceName(a)) 172 } 173 174 return devices 175 } 176 177 // NaturallySortDeviceNames returns an ordered list of names based on 178 // a natural ordering where 'natural' is an ordering of the string 179 // value in alphabetical order, execept that multi-digit numbers are 180 // ordered as a single character. 181 // 182 // For example, sorting: 183 // 184 // [ br-eth10 br-eth1 br-eth2 ] 185 // 186 // would sort as: 187 // 188 // [ br-eth1 br-eth2 br-eth10 ] 189 // 190 // In purely alphabetical sorting "br-eth10" would be sorted before 191 // "br-eth2" because "1" is sorted as smaller than "2", while in 192 // natural sorting "br-eth2" is sorted before "br-eth10" because "2" 193 // is sorted as smaller than "10". 194 // 195 // This also extends to multiply repeated numbers (e.g., VLANs). 196 // 197 // For example, sorting: 198 // 199 // [ br-eth2 br-eth10.10 br-eth200.0 br-eth1.0 br-eth2.0 ] 200 // 201 // would sort as: 202 // 203 // [ br-eth1.0 br-eth2 br-eth2.0 br-eth10.10 br-eth200.0 ] 204 // 205 func NaturallySortDeviceNames(names ...string) []string { 206 if names == nil { 207 return nil 208 } 209 210 devices := parseDeviceNames(names...) 211 sort.Sort(devices) 212 sortedNames := make([]string, len(devices)) 213 214 for i, v := range devices { 215 sortedNames[i] = v.name 216 } 217 218 return sortedNames 219 }