2016-03-10 21:15:55 +00:00
package main
import (
2016-07-20 22:08:24 +00:00
"bytes"
2016-07-20 22:09:27 +00:00
"encoding/json"
2016-03-10 21:15:55 +00:00
"fmt"
2016-06-28 20:40:35 +00:00
"io"
"io/ioutil"
2016-03-10 21:15:55 +00:00
"os"
"strings"
2016-03-24 20:31:36 +00:00
2017-11-03 15:10:58 +00:00
"github.com/sirupsen/logrus"
2022-04-04 20:06:48 +00:00
cli "github.com/urfave/cli/v2"
2016-03-31 18:03:56 +00:00
"github.com/vbatts/go-mtree"
2016-03-24 20:31:36 +00:00
)
2016-11-16 19:15:42 +00:00
func main ( ) {
2022-04-04 20:06:48 +00:00
app := cli . NewApp ( )
app . Name = mtree . AppName
app . Usage = "map a directory hierarchy"
app . Version = mtree . Version
// cli docs --> https://github.com/urfave/cli/blob/master/docs/v2/manual.md
app . Flags = [ ] cli . Flag {
// Flags common with mtree(8)
& cli . BoolFlag {
Name : "create" ,
Aliases : [ ] string { "c" } ,
Usage : "Create a directory hierarchy spec" ,
} ,
2022-04-04 20:25:30 +00:00
& cli . StringSliceFlag {
2022-04-04 20:06:48 +00:00
Name : "file" ,
Aliases : [ ] string { "f" } ,
Usage : "Directory hierarchy spec to validate" ,
} ,
& cli . StringFlag {
Name : "path" ,
Aliases : [ ] string { "p" } ,
Usage : "Root path that the hierarchy spec is relative to" ,
} ,
& cli . StringFlag {
Name : "add-keywords" ,
Aliases : [ ] string { "K" } ,
Usage : "Add the specified (delimited by comma or space) keywords to the current set of keywords" ,
} ,
& cli . StringFlag {
Name : "use-keywords" ,
Aliases : [ ] string { "k" } ,
Usage : "Use only the specified (delimited by comma or space) keywords as the current set of keywords" ,
} ,
& cli . BoolFlag {
Name : "directory-only" ,
Aliases : [ ] string { "d" } ,
Usage : "Ignore everything except directory type files" ,
} ,
& cli . BoolFlag {
Name : "update-attributes" ,
Aliases : [ ] string { "u" } ,
Usage : "Modify the owner, group, permissions and xattrs of files, symbolic links and devices, to match the provided specification. This is not compatible with '-T'." ,
} ,
// Flags unique to gomtree
& cli . BoolFlag {
Name : "debug" ,
Usage : "Output debug info to STDERR" ,
Value : false ,
} ,
& cli . BoolFlag {
Name : "list-keywords" ,
Usage : "List the keywords available" ,
} ,
& cli . BoolFlag {
Name : "list-used" ,
Usage : "List all the keywords found in a validation manifest" ,
} ,
& cli . BoolFlag {
Name : "bsd-keywords" ,
Usage : "Only operate on keywords that are supported by upstream mtree(8)" ,
} ,
& cli . StringFlag {
Name : "tar" ,
Aliases : [ ] string { "T" } ,
Value : "" ,
Usage : ` Use tar archive to create or validate a directory hierarchy spec ("-" indicates stdin) ` ,
} ,
& cli . StringFlag {
Name : "result-format" ,
Value : "bsd" ,
Usage : "output the validation results using the given format (bsd, json, path)" ,
} ,
}
app . Action = func ( c * cli . Context ) error {
return mainApp ( c )
}
if err := app . Run ( os . Args ) ; err != nil {
2017-06-15 18:54:58 +00:00
logrus . Fatal ( err )
2016-11-10 01:41:20 +00:00
}
}
2022-04-04 20:06:48 +00:00
func mainApp ( c * cli . Context ) error {
if c . Bool ( "debug" ) {
2016-07-20 17:28:08 +00:00
os . Setenv ( "DEBUG" , "1" )
2017-06-15 18:54:58 +00:00
logrus . SetLevel ( logrus . DebugLevel )
2016-07-20 17:28:08 +00:00
}
2016-07-26 18:21:44 +00:00
// -list-keywords
2022-04-04 20:06:48 +00:00
if c . Bool ( "list-keywords" ) {
2016-03-24 20:31:36 +00:00
fmt . Println ( "Available keywords:" )
for k := range mtree . KeywordFuncs {
2016-07-26 18:21:44 +00:00
fmt . Print ( " " )
fmt . Print ( k )
if mtree . Keyword ( k ) . Default ( ) {
fmt . Print ( " (default)" )
2016-03-10 21:15:55 +00:00
}
2016-07-26 18:21:44 +00:00
if ! mtree . Keyword ( k ) . Bsd ( ) {
fmt . Print ( " (not upstream)" )
}
fmt . Print ( "\n" )
2016-03-24 20:31:36 +00:00
}
2016-11-16 19:15:42 +00:00
return nil
2016-03-24 20:31:36 +00:00
}
2016-07-26 18:21:44 +00:00
// --result-format
2022-04-04 20:06:48 +00:00
formatFunc , ok := formats [ c . String ( "result-format" ) ]
2016-07-20 22:08:24 +00:00
if ! ok {
2022-04-04 20:06:48 +00:00
return fmt . Errorf ( "invalid output format: %s" , c . String ( "result-format" ) )
2016-07-20 22:08:24 +00:00
}
2016-07-26 18:31:39 +00:00
var (
2016-10-30 13:48:49 +00:00
err error
2016-11-18 00:47:31 +00:00
tmpKeywords [ ] mtree . Keyword
currentKeywords [ ] mtree . Keyword
2016-07-26 18:31:39 +00:00
)
2016-10-30 13:48:49 +00:00
2016-03-24 20:31:36 +00:00
// -k <keywords>
2022-04-04 20:06:48 +00:00
if c . String ( "use-keywords" ) != "" {
tmpKeywords = splitKeywordsArg ( c . String ( "use-keywords" ) )
2016-11-18 00:47:31 +00:00
if ! mtree . InKeywordSlice ( "type" , tmpKeywords ) {
tmpKeywords = append ( [ ] mtree . Keyword { "type" } , tmpKeywords ... )
2016-07-15 13:59:18 +00:00
}
2016-03-24 20:31:36 +00:00
} else {
2022-04-04 20:06:48 +00:00
if c . String ( "tar" ) != "" {
2016-07-26 18:31:39 +00:00
tmpKeywords = mtree . DefaultTarKeywords [ : ]
2016-07-22 22:01:54 +00:00
} else {
2016-07-26 18:31:39 +00:00
tmpKeywords = mtree . DefaultKeywords [ : ]
2016-07-22 22:01:54 +00:00
}
2016-03-24 20:31:36 +00:00
}
2016-07-26 18:31:39 +00:00
2016-03-24 20:31:36 +00:00
// -K <keywords>
2022-04-04 20:06:48 +00:00
if c . String ( "add-keywords" ) != "" {
for _ , kw := range splitKeywordsArg ( c . String ( "add-keywords" ) ) {
2016-11-18 00:47:31 +00:00
if ! mtree . InKeywordSlice ( kw , tmpKeywords ) {
2016-07-26 18:31:39 +00:00
tmpKeywords = append ( tmpKeywords , kw )
}
}
}
// -bsd-keywords
2022-04-04 20:06:48 +00:00
if c . Bool ( "bsd-keywords" ) {
2016-07-26 18:31:39 +00:00
for _ , k := range tmpKeywords {
if mtree . Keyword ( k ) . Bsd ( ) {
currentKeywords = append ( currentKeywords , k )
} else {
fmt . Fprintf ( os . Stderr , "INFO: ignoring %q as it is not an upstream keyword\n" , k )
2016-07-25 22:29:10 +00:00
}
}
2016-07-26 18:31:39 +00:00
} else {
currentKeywords = tmpKeywords
2016-03-24 20:31:36 +00:00
}
2016-10-30 13:48:49 +00:00
// Check mutual exclusivity of keywords.
// TODO(cyphar): Abstract this inside keywords.go.
2016-11-18 00:47:31 +00:00
if mtree . InKeywordSlice ( "tar_time" , currentKeywords ) && mtree . InKeywordSlice ( "time" , currentKeywords ) {
2016-11-16 19:15:42 +00:00
return fmt . Errorf ( "tar_time and time are mutually exclusive keywords" )
2016-10-30 13:48:49 +00:00
}
// If we're doing a comparison, we always are comparing between a spec and
// state DH. If specDh is nil, we are generating a new one.
var (
specDh * mtree . DirectoryHierarchy
stateDh * mtree . DirectoryHierarchy
2016-11-18 00:47:31 +00:00
specKeywords [ ] mtree . Keyword
2016-10-30 13:48:49 +00:00
)
2016-03-24 20:31:36 +00:00
// -f <file>
2022-04-04 20:25:30 +00:00
if len ( c . StringSlice ( "file" ) ) > 0 && ! c . Bool ( "create" ) {
2016-03-24 20:31:36 +00:00
// load the hierarchy, if we're not creating a new spec
2022-04-04 20:25:30 +00:00
fh , err := os . Open ( c . StringSlice ( "file" ) [ 0 ] )
2016-03-24 20:31:36 +00:00
if err != nil {
2016-11-16 19:15:42 +00:00
return err
2016-03-24 20:31:36 +00:00
}
2016-10-30 13:48:49 +00:00
specDh , err = mtree . ParseSpec ( fh )
2016-03-24 20:31:36 +00:00
fh . Close ( )
if err != nil {
2016-11-16 19:15:42 +00:00
return err
2016-03-24 20:31:36 +00:00
}
2016-10-30 13:48:49 +00:00
// We can't check against more fields than in the specKeywords list, so
// currentKeywords can only have a subset of specKeywords.
2016-11-16 19:43:05 +00:00
specKeywords = specDh . UsedKeywords ( )
2016-03-24 20:31:36 +00:00
}
2016-07-29 14:18:17 +00:00
// -list-used
2022-04-04 20:06:48 +00:00
if c . Bool ( "list-used" ) {
2016-10-30 13:48:49 +00:00
if specDh == nil {
2016-11-16 19:15:42 +00:00
return fmt . Errorf ( "no specification provided. please provide a validation manifest" )
2016-07-29 14:18:17 +00:00
}
2016-10-30 13:48:49 +00:00
2022-04-04 20:06:48 +00:00
if c . String ( "result-format" ) == "json" {
2022-04-04 20:25:30 +00:00
for _ , file := range c . StringSlice ( "file" ) {
// if they're asking for json, give it to them
data := map [ string ] [ ] mtree . Keyword { file : specKeywords }
buf , err := json . MarshalIndent ( data , "" , " " )
if err != nil {
return err
}
fmt . Println ( string ( buf ) )
gomtree: `-list-used` can output JSON
Piggybacking on `-result-format`:
```bash
$ tar c .git/ | gomtree -c -T - > git.mtree
$ gomtree -result-format=json -list-used -f ./git.mtree
{
"./git.mtree": [
"type",
"mode",
"uid",
"gid",
"tar_time",
"size"
]
}
$ tar c .git/ | gomtree -c -T - -K sha512digest > git.mtree
$ gomtree -result-format=json -list-used -f ./git.mtree
{
"./git.mtree": [
"type",
"mode",
"uid",
"gid",
"tar_time",
"size",
"sha512digest"
]
}
```
Signed-off-by: Vincent Batts <vbatts@hashbangbash.com>
2016-08-11 18:50:20 +00:00
}
} else {
2022-04-04 20:25:30 +00:00
for _ , file := range c . StringSlice ( "file" ) {
fmt . Printf ( "Keywords used in [%s]:\n" , file )
for _ , kw := range specKeywords {
fmt . Printf ( " %s" , kw )
if _ , ok := mtree . KeywordFuncs [ kw ] ; ! ok {
fmt . Print ( " (unsupported)" )
}
fmt . Printf ( "\n" )
gomtree: `-list-used` can output JSON
Piggybacking on `-result-format`:
```bash
$ tar c .git/ | gomtree -c -T - > git.mtree
$ gomtree -result-format=json -list-used -f ./git.mtree
{
"./git.mtree": [
"type",
"mode",
"uid",
"gid",
"tar_time",
"size"
]
}
$ tar c .git/ | gomtree -c -T - -K sha512digest > git.mtree
$ gomtree -result-format=json -list-used -f ./git.mtree
{
"./git.mtree": [
"type",
"mode",
"uid",
"gid",
"tar_time",
"size",
"sha512digest"
]
}
```
Signed-off-by: Vincent Batts <vbatts@hashbangbash.com>
2016-08-11 18:50:20 +00:00
}
2016-07-29 14:18:17 +00:00
}
}
2016-11-16 19:15:42 +00:00
return nil
2016-07-29 14:18:17 +00:00
}
2016-10-30 13:48:49 +00:00
if specKeywords != nil {
// If we didn't actually change the set of keywords, we can just use specKeywords.
2022-04-04 20:06:48 +00:00
if c . String ( "use-keywords" ) == "" && c . String ( "add-keywords" ) == "" {
2016-10-30 13:48:49 +00:00
currentKeywords = specKeywords
}
for _ , keyword := range currentKeywords {
// As always, time is a special case.
// TODO: Fix that.
2016-11-18 00:47:31 +00:00
if ( keyword == "time" && mtree . InKeywordSlice ( "tar_time" , specKeywords ) ) || ( keyword == "tar_time" && mtree . InKeywordSlice ( "time" , specKeywords ) ) {
2016-10-30 13:48:49 +00:00
continue
}
}
}
// -p and -T are mutually exclusive
2022-04-04 20:06:48 +00:00
if c . String ( "path" ) != "" && c . String ( "tar" ) != "" {
2016-11-16 19:15:42 +00:00
return fmt . Errorf ( "options -T and -p are mutually exclusive" )
2016-10-30 13:48:49 +00:00
}
2016-03-24 20:31:36 +00:00
// -p <path>
2016-07-21 17:40:48 +00:00
var rootPath = "."
2022-04-04 20:06:48 +00:00
if c . String ( "path" ) != "" {
rootPath = c . String ( "path" )
2016-03-24 20:31:36 +00:00
}
2016-11-18 03:43:02 +00:00
excludes := [ ] mtree . ExcludeFunc { }
// -d
2022-04-04 20:06:48 +00:00
if c . Bool ( "directory-only" ) {
2016-11-18 03:43:02 +00:00
excludes = append ( excludes , mtree . ExcludeNonDirectories )
}
2016-08-02 19:57:51 +00:00
// -u
// Failing early here. Processing is done below.
2022-04-04 20:06:48 +00:00
if c . Bool ( "update-attributes" ) && c . String ( "tar" ) != "" {
2016-08-02 19:57:51 +00:00
return fmt . Errorf ( "ERROR: -u can not be used with -T" )
}
2016-06-28 20:40:35 +00:00
// -T <tar file>
2022-04-04 20:06:48 +00:00
if c . String ( "tar" ) != "" {
2016-08-11 18:07:07 +00:00
var input io . Reader
2022-04-04 20:06:48 +00:00
if c . String ( "tar" ) == "-" {
2016-08-11 18:07:07 +00:00
input = os . Stdin
} else {
2022-04-04 20:06:48 +00:00
fh , err := os . Open ( c . String ( "tar" ) )
2016-08-11 18:07:07 +00:00
if err != nil {
2016-11-16 19:15:42 +00:00
return err
2016-08-11 18:07:07 +00:00
}
defer fh . Close ( )
input = fh
2016-06-28 20:40:35 +00:00
}
2016-11-18 03:43:02 +00:00
ts := mtree . NewTarStreamer ( input , excludes , currentKeywords )
2016-06-28 20:40:35 +00:00
if _ , err := io . Copy ( ioutil . Discard , ts ) ; err != nil && err != io . EOF {
2016-11-16 19:15:42 +00:00
return err
2016-06-28 20:40:35 +00:00
}
if err := ts . Close ( ) ; err != nil {
2016-11-16 19:15:42 +00:00
return err
2016-06-28 20:40:35 +00:00
}
2016-08-11 18:07:07 +00:00
var err error
2016-10-30 13:48:49 +00:00
stateDh , err = ts . Hierarchy ( )
if err != nil {
2016-11-16 19:15:42 +00:00
return err
2016-10-30 13:48:49 +00:00
}
2022-04-04 21:43:17 +00:00
} else if len ( c . StringSlice ( "file" ) ) > 1 {
// load this second hierarchy file provided
fh , err := os . Open ( c . StringSlice ( "file" ) [ 1 ] )
if err != nil {
return err
}
stateDh , err = mtree . ParseSpec ( fh )
fh . Close ( )
if err != nil {
return err
}
2016-10-30 13:48:49 +00:00
} else {
// with a root directory
2016-11-18 07:53:26 +00:00
stateDh , err = mtree . Walk ( rootPath , excludes , currentKeywords , nil )
2016-03-24 20:31:36 +00:00
if err != nil {
2016-11-16 19:15:42 +00:00
return err
2016-03-24 20:31:36 +00:00
}
2016-06-28 20:40:35 +00:00
}
2016-07-26 18:21:44 +00:00
2016-08-02 19:57:51 +00:00
// -u
2022-04-04 20:06:48 +00:00
if c . Bool ( "update-attributes" ) && stateDh != nil {
2016-08-02 19:57:51 +00:00
// -u
// this comes before the next case, intentionally.
2017-06-26 19:12:31 +00:00
result , err := mtree . Update ( rootPath , specDh , mtree . DefaultUpdateKeywords , nil )
if err != nil {
return err
}
2022-04-04 02:34:48 +00:00
if len ( result ) > 0 {
2017-06-26 19:12:31 +00:00
fmt . Printf ( "%#v\n" , result )
}
2016-08-02 19:57:51 +00:00
2017-06-26 19:12:31 +00:00
var res [ ] mtree . InodeDelta
// only check the keywords that we just updated
res , err = mtree . Check ( rootPath , specDh , mtree . DefaultUpdateKeywords , nil )
2016-08-02 19:57:51 +00:00
if err != nil {
return err
}
2017-06-26 19:12:31 +00:00
if res != nil {
out := formatFunc ( res )
if _ , err := os . Stdout . Write ( [ ] byte ( out ) ) ; err != nil {
return err
}
2016-08-02 19:57:51 +00:00
2017-06-26 19:12:31 +00:00
// TODO: This should be a flag. Allowing files to be added and
// removed and still returning "it's all good" is simply
// unsafe IMO.
for _ , diff := range res {
if diff . Type ( ) == mtree . Modified {
2022-04-04 02:29:36 +00:00
return fmt . Errorf ( "manifest validation failed" )
2017-06-26 19:12:31 +00:00
}
}
2016-08-02 19:57:51 +00:00
}
2017-06-26 19:12:31 +00:00
2016-08-02 19:57:51 +00:00
return nil
}
2016-06-28 20:40:35 +00:00
// -c
2022-04-04 20:06:48 +00:00
if c . Bool ( "create" ) {
2016-10-30 13:48:49 +00:00
fh := os . Stdout
2022-04-04 20:25:30 +00:00
if len ( c . StringSlice ( "file" ) ) > 0 {
fh , err = os . Create ( c . StringSlice ( "file" ) [ 0 ] )
2016-06-28 20:40:35 +00:00
if err != nil {
2016-11-16 19:15:42 +00:00
return err
2016-06-28 20:40:35 +00:00
}
}
2016-10-30 13:48:49 +00:00
// output stateDh
stateDh . WriteTo ( fh )
2016-11-16 19:15:42 +00:00
return nil
2016-10-30 13:48:49 +00:00
}
2017-01-20 18:13:59 +00:00
// no spec manifest has been provided yet, so look for it on stdin
if specDh == nil {
// load the hierarchy
specDh , err = mtree . ParseSpec ( os . Stdin )
if err != nil {
return err
}
// We can't check against more fields than in the specKeywords list, so
// currentKeywords can only have a subset of specKeywords.
specKeywords = specDh . UsedKeywords ( )
}
2016-10-30 13:48:49 +00:00
// This is a validation.
if specDh != nil && stateDh != nil {
var res [ ] mtree . InodeDelta
res , err = mtree . Compare ( specDh , stateDh , currentKeywords )
2016-03-24 20:31:36 +00:00
if err != nil {
2016-11-16 19:15:42 +00:00
return err
2016-03-24 20:31:36 +00:00
}
2016-10-30 13:48:49 +00:00
if res != nil {
2022-04-04 20:06:48 +00:00
if isTarSpec ( specDh ) || c . String ( "tar" ) != "" {
2016-11-10 01:41:20 +00:00
res = filterMissingKeywords ( res )
}
2016-10-30 13:48:49 +00:00
2016-07-20 22:08:24 +00:00
out := formatFunc ( res )
if _ , err := os . Stdout . Write ( [ ] byte ( out ) ) ; err != nil {
2016-11-16 19:15:42 +00:00
return err
2016-04-05 15:44:55 +00:00
}
2017-01-14 07:01:21 +00:00
// TODO: This should be a flag. Allowing files to be added and
// removed and still returning "it's all good" is simply
// unsafe IMO.
for _ , diff := range res {
if diff . Type ( ) == mtree . Modified {
2022-04-04 02:29:36 +00:00
return fmt . Errorf ( "manifest validation failed" )
2017-01-14 07:01:21 +00:00
}
}
2016-07-22 22:01:54 +00:00
}
2016-07-26 00:35:38 +00:00
} else {
2016-11-16 19:15:42 +00:00
return fmt . Errorf ( "neither validating or creating a manifest. Please provide additional arguments" )
}
return nil
}
var formats = map [ string ] func ( [ ] mtree . InodeDelta ) string {
// Outputs the errors in the BSD format.
"bsd" : func ( d [ ] mtree . InodeDelta ) string {
var buffer bytes . Buffer
for _ , delta := range d {
2017-01-14 07:01:21 +00:00
fmt . Fprintln ( & buffer , delta )
2016-11-16 19:15:42 +00:00
}
return buffer . String ( )
} ,
// Outputs the full result struct in JSON.
"json" : func ( d [ ] mtree . InodeDelta ) string {
var buffer bytes . Buffer
if err := json . NewEncoder ( & buffer ) . Encode ( d ) ; err != nil {
panic ( err )
}
return buffer . String ( )
} ,
// Outputs only the paths which failed to validate.
"path" : func ( d [ ] mtree . InodeDelta ) string {
var buffer bytes . Buffer
for _ , delta := range d {
if delta . Type ( ) == mtree . Modified {
fmt . Fprintln ( & buffer , delta . Path ( ) )
}
}
return buffer . String ( )
} ,
}
// isDirEntry returns wheter an mtree.Entry describes a directory.
func isDirEntry ( e mtree . Entry ) bool {
for _ , kw := range e . Keywords {
kv := mtree . KeyVal ( kw )
if kv . Keyword ( ) == "type" {
return kv . Value ( ) == "dir"
}
2016-03-24 20:31:36 +00:00
}
2016-11-16 19:15:42 +00:00
// Shouldn't be reached.
return false
}
// filterMissingKeywords is a fairly annoying hack to get around the fact that
// tar archive manifest generation has certain unsolveable problems regarding
// certain keywords. For example, the size=... keyword cannot be implemented
// for directories in a tar archive (which causes Missing errors for that
// keyword).
//
// This function just removes all instances of Missing errors for keywords.
// This makes certain assumptions about the type of issues tar archives have.
// Only call this on tar archive manifest comparisons.
func filterMissingKeywords ( diffs [ ] mtree . InodeDelta ) [ ] mtree . InodeDelta {
newDiffs := [ ] mtree . InodeDelta { }
loop :
for _ , diff := range diffs {
if diff . Type ( ) == mtree . Modified {
// We only apply this filtering to directories.
// NOTE: This will probably break if someone drops the size keyword.
if isDirEntry ( * diff . Old ( ) ) || isDirEntry ( * diff . New ( ) ) {
// If this applies to '.' then we just filter everything
// (meaning we remove this entry). This is because note all tar
// archives include a '.' entry. Which makes checking this not
// practical.
if diff . Path ( ) == "." {
continue
}
// Only filter out the size keyword.
// NOTE: This currently takes advantage of the fact the
// diff.Diff() returns the actual slice to diff.keys.
keys := diff . Diff ( )
for idx , k := range keys {
// Delete the key if it's "size". Unfortunately in Go you
// can't delete from a slice without reassigning it. So we
// just overwrite it with the last value.
if k . Name ( ) == "size" {
if len ( keys ) < 2 {
continue loop
}
keys [ idx ] = keys [ len ( keys ) - 1 ]
}
}
}
}
// If we got here, append to the new set.
newDiffs = append ( newDiffs , diff )
}
return newDiffs
}
// isTarSpec returns whether the spec provided came from the tar generator.
// This takes advantage of an unsolveable problem in tar generation.
func isTarSpec ( spec * mtree . DirectoryHierarchy ) bool {
// Find a directory and check whether it's missing size=...
// NOTE: This will definitely break if someone drops the size=... keyword.
for _ , e := range spec . Entries {
if ! isDirEntry ( e ) {
continue
}
for _ , kw := range e . Keywords {
kv := mtree . KeyVal ( kw )
if kv . Keyword ( ) == "size" {
return false
}
}
return true
}
// Should never be reached.
return false
2016-03-24 20:31:36 +00:00
}
2016-11-18 00:47:31 +00:00
func splitKeywordsArg ( str string ) [ ] mtree . Keyword {
keywords := [ ] mtree . Keyword { }
2016-11-16 20:42:53 +00:00
for _ , kw := range strings . Fields ( strings . Replace ( str , "," , " " , - 1 ) ) {
keywords = append ( keywords , mtree . KeywordSynonym ( kw ) )
}
return keywords
2016-03-24 20:31:36 +00:00
}