package puppet import ( "bufio" "os" "strings" ) // StdinReader returns a buffered reader over f and true only when f actually // carries piped/redirected data. Terminals and character devices such as // /dev/null return false, and an empty pipe or empty file (immediate EOF on // peek) also returns false. This mirrors node-lookup's no-TTY behaviour: when // invoked without a real pipe the caller can fall back to arguments instead of // blocking on or silently consuming empty input. func StdinReader(f *os.File) (*bufio.Reader, bool) { fi, err := f.Stat() if err != nil { return nil, false } if (fi.Mode() & os.ModeCharDevice) != 0 { return nil, false // terminal or /dev/null } r := bufio.NewReader(f) if _, err := r.Peek(1); err != nil { return nil, false // empty pipe / empty file (EOF) } return r, true } // ReadHosts resolves the list of hostnames to act on. Explicit args win; failing // that it reads the first whitespace-separated field of each non-empty line from // stdin (so `node-lookup -R | pburl` and `node-lookup -1 | pburl` both work). // Order is preserved and duplicates are removed. Returns nil when neither args // nor piped stdin data are present. func ReadHosts(stdin *os.File, args []string) []string { if len(args) > 0 { return dedupe(args) } r, ok := StdinReader(stdin) if !ok { return nil } var hosts []string sc := bufio.NewScanner(r) for sc.Scan() { fields := strings.Fields(sc.Text()) if len(fields) == 0 { continue } hosts = append(hosts, fields[0]) } return dedupe(hosts) } func dedupe(in []string) []string { seen := make(map[string]struct{}, len(in)) out := make([]string, 0, len(in)) for _, s := range in { if _, ok := seen[s]; ok { continue } seen[s] = struct{}{} out = append(out, s) } return out }