github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/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 string(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  }