hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

package_builder.go (6017B)

    1 // Copyright 2020 The Hugo Authors. All rights reserved.
    2 //
    3 // Licensed under the Apache License, Version 2.0 (the "License");
    4 // you may not use this file except in compliance with the License.
    5 // You may obtain a copy of the License at
    6 // http://www.apache.org/licenses/LICENSE-2.0
    7 //
    8 // Unless required by applicable law or agreed to in writing, software
    9 // distributed under the License is distributed on an "AS IS" BASIS,
   10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   11 // See the License for the specific language governing permissions and
   12 // limitations under the License.
   13 
   14 package npm
   15 
   16 import (
   17 	"bytes"
   18 	"encoding/json"
   19 	"fmt"
   20 	"io"
   21 	"strings"
   22 
   23 	"github.com/gohugoio/hugo/common/hugio"
   24 
   25 	"github.com/gohugoio/hugo/hugofs/files"
   26 
   27 	"github.com/gohugoio/hugo/hugofs"
   28 	"github.com/spf13/afero"
   29 
   30 	"github.com/gohugoio/hugo/common/maps"
   31 
   32 	"github.com/gohugoio/hugo/helpers"
   33 )
   34 
   35 const (
   36 	dependenciesKey    = "dependencies"
   37 	devDependenciesKey = "devDependencies"
   38 
   39 	packageJSONName = "package.json"
   40 
   41 	packageJSONTemplate = `{
   42   "name": "%s",
   43   "version": "%s"
   44 }`
   45 )
   46 
   47 func Pack(fs afero.Fs, fis []hugofs.FileMetaInfo) error {
   48 	var b *packageBuilder
   49 
   50 	// Have a package.hugo.json?
   51 	fi, err := fs.Stat(files.FilenamePackageHugoJSON)
   52 	if err != nil {
   53 		// Have a package.json?
   54 		fi, err = fs.Stat(packageJSONName)
   55 		if err == nil {
   56 			// Preserve the original in package.hugo.json.
   57 			if err = hugio.CopyFile(fs, packageJSONName, files.FilenamePackageHugoJSON); err != nil {
   58 				return fmt.Errorf("npm pack: failed to copy package file: %w", err)
   59 			}
   60 		} else {
   61 			// Create one.
   62 			name := "project"
   63 			// Use the Hugo site's folder name as the default name.
   64 			// The owner can change it later.
   65 			rfi, err := fs.Stat("")
   66 			if err == nil {
   67 				name = rfi.Name()
   68 			}
   69 			packageJSONContent := fmt.Sprintf(packageJSONTemplate, name, "0.1.0")
   70 			if err = afero.WriteFile(fs, files.FilenamePackageHugoJSON, []byte(packageJSONContent), 0666); err != nil {
   71 				return err
   72 			}
   73 			fi, err = fs.Stat(files.FilenamePackageHugoJSON)
   74 			if err != nil {
   75 				return err
   76 			}
   77 		}
   78 	}
   79 
   80 	meta := fi.(hugofs.FileMetaInfo).Meta()
   81 	masterFilename := meta.Filename
   82 	f, err := meta.Open()
   83 	if err != nil {
   84 		return fmt.Errorf("npm pack: failed to open package file: %w", err)
   85 	}
   86 	b = newPackageBuilder(meta.Module, f)
   87 	f.Close()
   88 
   89 	for _, fi := range fis {
   90 		if fi.IsDir() {
   91 			// We only care about the files in the root.
   92 			continue
   93 		}
   94 
   95 		if fi.Name() != files.FilenamePackageHugoJSON {
   96 			continue
   97 		}
   98 
   99 		meta := fi.(hugofs.FileMetaInfo).Meta()
  100 
  101 		if meta.Filename == masterFilename {
  102 			continue
  103 		}
  104 
  105 		f, err := meta.Open()
  106 		if err != nil {
  107 			return fmt.Errorf("npm pack: failed to open package file: %w", err)
  108 		}
  109 		b.Add(meta.Module, f)
  110 		f.Close()
  111 	}
  112 
  113 	if b.Err() != nil {
  114 		return fmt.Errorf("npm pack: failed to build: %w", b.Err())
  115 	}
  116 
  117 	// Replace the dependencies in the original template with the merged set.
  118 	b.originalPackageJSON[dependenciesKey] = b.dependencies
  119 	b.originalPackageJSON[devDependenciesKey] = b.devDependencies
  120 	var commentsm map[string]any
  121 	comments, found := b.originalPackageJSON["comments"]
  122 	if found {
  123 		commentsm = maps.ToStringMap(comments)
  124 	} else {
  125 		commentsm = make(map[string]any)
  126 	}
  127 	commentsm[dependenciesKey] = b.dependenciesComments
  128 	commentsm[devDependenciesKey] = b.devDependenciesComments
  129 	b.originalPackageJSON["comments"] = commentsm
  130 
  131 	// Write it out to the project package.json
  132 	packageJSONData := new(bytes.Buffer)
  133 	encoder := json.NewEncoder(packageJSONData)
  134 	encoder.SetEscapeHTML(false)
  135 	encoder.SetIndent("", strings.Repeat(" ", 2))
  136 	if err := encoder.Encode(b.originalPackageJSON); err != nil {
  137 		return fmt.Errorf("npm pack: failed to marshal JSON: %w", err)
  138 	}
  139 
  140 	if err := afero.WriteFile(fs, packageJSONName, packageJSONData.Bytes(), 0666); err != nil {
  141 		return fmt.Errorf("npm pack: failed to write package.json: %w", err)
  142 	}
  143 
  144 	return nil
  145 }
  146 
  147 func newPackageBuilder(source string, first io.Reader) *packageBuilder {
  148 	b := &packageBuilder{
  149 		devDependencies:         make(map[string]any),
  150 		devDependenciesComments: make(map[string]any),
  151 		dependencies:            make(map[string]any),
  152 		dependenciesComments:    make(map[string]any),
  153 	}
  154 
  155 	m := b.unmarshal(first)
  156 	if b.err != nil {
  157 		return b
  158 	}
  159 
  160 	b.addm(source, m)
  161 	b.originalPackageJSON = m
  162 
  163 	return b
  164 }
  165 
  166 type packageBuilder struct {
  167 	err error
  168 
  169 	// The original package.hugo.json.
  170 	originalPackageJSON map[string]any
  171 
  172 	devDependencies         map[string]any
  173 	devDependenciesComments map[string]any
  174 	dependencies            map[string]any
  175 	dependenciesComments    map[string]any
  176 }
  177 
  178 func (b *packageBuilder) Add(source string, r io.Reader) *packageBuilder {
  179 	if b.err != nil {
  180 		return b
  181 	}
  182 
  183 	m := b.unmarshal(r)
  184 	if b.err != nil {
  185 		return b
  186 	}
  187 
  188 	b.addm(source, m)
  189 
  190 	return b
  191 }
  192 
  193 func (b *packageBuilder) addm(source string, m map[string]any) {
  194 	if source == "" {
  195 		source = "project"
  196 	}
  197 
  198 	// The version selection is currently very simple.
  199 	// We may consider minimal version selection or something
  200 	// after testing this out.
  201 	//
  202 	// But for now, the first version string for a given dependency wins.
  203 	// These packages will be added by order of import (project, module1, module2...),
  204 	// so that should at least give the project control over the situation.
  205 	if devDeps, found := m[devDependenciesKey]; found {
  206 		mm := maps.ToStringMapString(devDeps)
  207 		for k, v := range mm {
  208 			if _, added := b.devDependencies[k]; !added {
  209 				b.devDependencies[k] = v
  210 				b.devDependenciesComments[k] = source
  211 			}
  212 		}
  213 	}
  214 
  215 	if deps, found := m[dependenciesKey]; found {
  216 		mm := maps.ToStringMapString(deps)
  217 		for k, v := range mm {
  218 			if _, added := b.dependencies[k]; !added {
  219 				b.dependencies[k] = v
  220 				b.dependenciesComments[k] = source
  221 			}
  222 		}
  223 	}
  224 }
  225 
  226 func (b *packageBuilder) unmarshal(r io.Reader) map[string]any {
  227 	m := make(map[string]any)
  228 	err := json.Unmarshal(helpers.ReaderToBytes(r), &m)
  229 	if err != nil {
  230 		b.err = err
  231 	}
  232 	return m
  233 }
  234 
  235 func (b *packageBuilder) Err() error {
  236 	return b.err
  237 }