<commit_before>package platform

import (
	"fmt"
	"strings"

	"github.com/scrapli/scrapligo/assets"

	"github.com/scrapli/scrapligo/driver/generic"
	"github.com/scrapli/scrapligo/driver/network"
	"github.com/scrapli/scrapligo/driver/options"

	"github.com/scrapli/scrapligo/util"

	"gopkg.in/yaml.v3"
)

const (
	// AristaEos is a constant representing the platform string/name for Arista EOS devices.
	AristaEos = "arista_eos"
	// CiscoIosxe is a constant representing the platform string/name for Cisco IOSXE devices.
	CiscoIosxe = "cisco_iosxe"
	// CiscoIosxr is a constant representing the platform string/name for Cisco IOSXR devices.
	CiscoIosxr = "cisco_iosxr"
	// CiscoNxos is a constant representing the platform string/name for Cisco NXOS devices.
	CiscoNxos = "cisco_nxos"
	// JuniperJunos is a constant representing the platform string/name for Juniper JunOS devices.
	JuniperJunos = "juniper_junos"
	// NokiaSrl is a constant representing the platform string/name for Nokia SRL/SRLinux devices.
	NokiaSrl = "nokia_srl"
)

// GetPlatformNames is used to get the "core" (as in embedded in assets and used in testing)
// platform names.
func GetPlatformNames() []string {
	return []string{
		AristaEos, CiscoIosxe, CiscoIosxr, CiscoNxos, JuniperJunos, NokiaSrl,
	}
}

func loadPlatformDefinitionFromAssets(f string) ([]byte, error) {
	if !strings.HasSuffix(f, ".yaml") {
		f += ".yaml"
	}

	return assets.Assets.ReadFile(fmt.Sprintf("platforms/%s", f))
}

func loadPlatformDefinition(f string) (*Definition, error) {
	b, err := loadPlatformDefinitionFromAssets(f)
	if err != nil {
		b, err = util.ResolveAtFileOrURL(f)
		if err != nil {
			return nil, err
		}
	}

	pd := &Definition{}

	err = yaml.Unmarshal(b, pd)
	if err != nil {
		return nil, err
	}

	return pd, nil
}

func setDriver(host string, p *Platform, opts ...util.Option) error {
	finalOpts := p.AsOptions()
	finalOpts = append(finalOpts, opts...)

	var err error

	switch p.DriverType {
	case "generic":
		var d *generic.Driver

		d, err = generic.NewDriver(host, finalOpts...)
		if err != nil {
			return err
		}

		p.genericDriver = d
	case "network":
		var d *network.Driver

		d, err = network.NewDriver(host, finalOpts...)
		if err != nil {
			return err
		}

		p.networkDriver = d
	}

	return err
}

// NewPlatformVariant returns an instance of Platform from the platform definition file f. The
// provided variant data is merged back into the "base" platform definition. The host and
// any provided options are stored and will be applied when fetching the generic or network driver
// via the GetGenericDriver or GetNetworkDriver methods.
func NewPlatformVariant(f, variant, host string, opts ...util.Option) (*Platform, error) {
	pd, err := loadPlatformDefinition(f)
	if err != nil {
		return nil, err
	}

	p := pd.Default

	vp, ok := pd.Variants[variant]
	if !ok {
		return nil, fmt.Errorf("%w: no variant '%s' in platform", util.ErrPlatformError, variant)
	}

	p.mergeVariant(vp)

	err = setDriver(host, p, opts...)
	if err != nil {
		return nil, err
	}

	return p, nil
}

// NewPlatform returns an instance of Platform from the platform definition file f. The host and
// any provided options are stored and will be applied when fetching the generic or network driver
// via the GetGenericDriver or GetNetworkDriver methods.
func NewPlatform(f, host string, opts ...util.Option) (*Platform, error) {
	pd, err := loadPlatformDefinition(f)
	if err != nil {
		return nil, err
	}

	p := pd.Default

	err = setDriver(host, p, opts...)
	if err != nil {
		return nil, err
	}

	return p, nil
}<commit_msg>accept yaml bytes or string filepath/url<commit_after>package platform

import (
	"fmt"
	"strings"

	"github.com/scrapli/scrapligo/assets"

	"github.com/scrapli/scrapligo/driver/generic"
	"github.com/scrapli/scrapligo/driver/network"
	"github.com/scrapli/scrapligo/driver/options"

	"github.com/scrapli/scrapligo/util"

	"gopkg.in/yaml.v3"
)

const (
	// AristaEos is a constant representing the platform string/name for Arista EOS devices.
	AristaEos = "arista_eos"
	// CiscoIosxe is a constant representing the platform string/name for Cisco IOSXE devices.
	CiscoIosxe = "cisco_iosxe"
	// CiscoIosxr is a constant representing the platform string/name for Cisco IOSXR devices.
	CiscoIosxr = "cisco_iosxr"
	// CiscoNxos is a constant representing the platform string/name for Cisco NXOS devices.
	CiscoNxos = "cisco_nxos"
	// JuniperJunos is a constant representing the platform string/name for Juniper JunOS devices.
	JuniperJunos = "juniper_junos"
	// NokiaSrl is a constant representing the platform string/name for Nokia SRL/SRLinux devices.
	NokiaSrl = "nokia_srl"
)

// GetPlatformNames is used to get the "core" (as in embedded in assets and used in testing)
// platform names.
func GetPlatformNames() []string {
	return []string{
		AristaEos, CiscoIosxe, CiscoIosxr, CiscoNxos, JuniperJunos, NokiaSrl,
	}
}

func loadPlatformDefinitionFromAssets(f string) ([]byte, error) {
	if !strings.HasSuffix(f, ".yaml") {
		f += ".yaml"
	}

	return assets.Assets.ReadFile(fmt.Sprintf("platforms/%s", f))
}

func loadPlatformDefinition(f string) (*Definition, error) {
	b, err := loadPlatformDefinitionFromAssets(f)
	if err != nil {
		b, err = util.ResolveAtFileOrURL(f)
		if err != nil {
			return nil, err
		}
	}

	return loadPlatformDefinitionFromBytes(b)
}

func loadPlatformDefinitionFromBytes(b []byte) (*Definition, error) {
	pd := &Definition{}

	err := yaml.Unmarshal(b, pd)
	if err != nil {
		return nil, err
	}

	return pd, nil
}

func setDriver(host string, p *Platform, opts ...util.Option) error {
	finalOpts := p.AsOptions()
	finalOpts = append(finalOpts, opts...)

	var err error

	switch p.DriverType {
	case "generic":
		var d *generic.Driver

		d, err = generic.NewDriver(host, finalOpts...)
		if err != nil {
			return err
		}

		p.genericDriver = d
	case "network":
		var d *network.Driver

		d, err = network.NewDriver(host, finalOpts...)
		if err != nil {
			return err
		}

		p.networkDriver = d
	}

	return err
}

// NewPlatformVariant returns an instance of Platform from the platform definition f where f may be
// a string representing a filepath or URL, or a byte slice of an already loaded YAML definition.
// The provided variant data is merged back into the "base" platform definition. The host and
// any provided options are stored and will be applied when fetching the generic or network driver
// via the GetGenericDriver or GetNetworkDriver methods.
func NewPlatformVariant(
	f interface{},
	variant, host string,
	opts ...util.Option,
) (*Platform, error) {
	var pd *Definition

	var err error

	switch t := f.(type) {
	case string:
		pd, err = loadPlatformDefinition(t)
	case []byte:
		pd, err = loadPlatformDefinitionFromBytes(t)
	}

	if err != nil {
		return nil, err
	}

	p := pd.Default

	vp, ok := pd.Variants[variant]
	if !ok {
		return nil, fmt.Errorf("%w: no variant '%s' in platform", util.ErrPlatformError, variant)
	}

	p.mergeVariant(vp)

	err = setDriver(host, p, opts...)
	if err != nil {
		return nil, err
	}

	return p, nil
}

// NewPlatform returns an instance of Platform from the platform definition f where f may be
// a string representing a filepath or URL, or a byte slice of an already loaded YAML definition.
// The host and any provided options are stored and will be applied when fetching the generic or
// network driver via the GetGenericDriver or GetNetworkDriver methods.
func NewPlatform(f interface{}, host string, opts ...util.Option) (*Platform, error) {
	var pd *Definition

	var err error

	switch t := f.(type) {
	case string:
		pd, err = loadPlatformDefinition(t)
	case []byte:
		pd, err = loadPlatformDefinitionFromBytes(t)
	}

	if err != nil {
		return nil, err
	}

	p := pd.Default

	err = setDriver(host, p, opts...)
	if err != nil {
		return nil, err
	}

	return p, nil
}
{'fraction_non_alphanumeric': 0.10709726831758999, 'fraction_numerical': 0.0002552974214960429, 'mean_word_length': 3.8038013488657265, 'pattern_counts': {'":': 4, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2605642', 'n_tokens_mistral': 2726, 'n_tokens_neox': 2587, 'n_words': 869}