code: func ParseModuleSource(source string) (*Module, error) {
  	// See if there is a friendly host prefix.
  	host, rest := ParseFriendlyHost(source)
  	if host != nil {
  		if !host.Valid() || disallowed[host.Display()] {
  			return nil, ErrInvalidModuleSource
  		}
  	}
  
  	matches := moduleSourceRe.FindStringSubmatch(rest)
  	if len(matches) < 4 {
  		return nil, ErrInvalidModuleSource
  	}
  
  	m := &Module{
  		RawHost:      host,
  		RawNamespace: matches[1],
  		RawName:      matches[2],
  		RawProvider:  matches[3],
  	}
  
  	if len(matches) == 5 {
  		m.RawSubmodule = matches[4]
  	}
  
  	return m, nil
  }
docs: // ParseModuleSource attempts to parse source as a Terraform registry module
  // source. If the string is not found to be in a valid format,
  // ErrInvalidModuleSource is returned. Note that this can only be used on
  // "input" strings, e.g. either ones supplied by the user or potentially
  // normalised but in Display form (unicode). It will fail to parse a source with
  // a punycoded domain since this is not permitted input from a user. If you have
  // an already normalized string internally, you can compare it without parsing
  // by comparing with the normalized version of the subject with the normal
  // string equality operator.