purge history since May 2016
This commit is contained in:
commit
a5bf332911
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
finstr
|
||||||
|
.gitdist
|
||||||
|
|
18
License
Normal file
18
License
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
finstr: A bullshit-free image gallery generator.
|
||||||
|
Copyright (C) 2016 Alexander Weinhold
|
||||||
|
|
||||||
|
Unless explicitly stated otherwise, the following applies:
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
29
Readme.md
Normal file
29
Readme.md
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
finstr
|
||||||
|
======
|
||||||
|
|
||||||
|
finstr is a bullshit-free image gallery generator. It produces pages according to a template and groups images by tag or month. Releases can be found at [releases.gutmet.org](https://releases.gutmet.org).
|
||||||
|
|
||||||
|
build
|
||||||
|
=======
|
||||||
|
|
||||||
|
If you want to build the executable yourself, you need go1.11+ with module support. Checkout the repository and run 'go build cmd/finstr.go'.
|
||||||
|
|
||||||
|
quick start
|
||||||
|
=====
|
||||||
|
|
||||||
|
Put the executable somewhere in your path, create a folder for your gallery, then
|
||||||
|
|
||||||
|
```
|
||||||
|
> cd YOUR_FOLDER
|
||||||
|
> finstr init
|
||||||
|
```
|
||||||
|
|
||||||
|
Put some .jpgs inside the img folder. Tag them by creating files with the same name but .tags suffix. Each line is a tag, then
|
||||||
|
|
||||||
|
```
|
||||||
|
> finstr gen
|
||||||
|
> cd pages
|
||||||
|
> firefox index.html
|
||||||
|
```
|
||||||
|
|
||||||
|
finstr has also been integrated into the static site generator [wombat](/wombat/about).
|
21
cmd/finstr.go
Normal file
21
cmd/finstr.go
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"git.gutmet.org/finstr.git"
|
||||||
|
"git.gutmet.org/finstr.git/initer"
|
||||||
|
"git.gutmet.org/goutil.git"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
commands := []goutil.Command{
|
||||||
|
goutil.NewCommandWithFlags("init", initer.Command, "initialize finstr directory"),
|
||||||
|
goutil.NewCommandWithFlags("gen", finstr.Command, "generate gallery"),
|
||||||
|
}
|
||||||
|
err := goutil.Execute(commands)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
40
crop/crop.go
Normal file
40
crop/crop.go
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
package crop
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"git.gutmet.org/goutil.git"
|
||||||
|
"image"
|
||||||
|
)
|
||||||
|
|
||||||
|
func boundInt(i, min, max int) int {
|
||||||
|
return goutil.IntMin(goutil.IntMax(i, min), max)
|
||||||
|
}
|
||||||
|
|
||||||
|
func bound(p image.Point, bounds image.Rectangle) image.Point {
|
||||||
|
min := bounds.Min
|
||||||
|
max := bounds.Max
|
||||||
|
return image.Point{X: boundInt(p.X, min.X, max.X), Y: boundInt(p.Y, min.Y, max.Y)}
|
||||||
|
}
|
||||||
|
|
||||||
|
type subImageSupport interface {
|
||||||
|
SubImage(rec image.Rectangle) image.Image
|
||||||
|
}
|
||||||
|
|
||||||
|
func Crop(img image.Image, topleft image.Point, w int, h int) (image.Image, error) {
|
||||||
|
bounds := img.Bounds()
|
||||||
|
topleft = bound(topleft, bounds)
|
||||||
|
bottomright := bound(image.Point{X: topleft.X + w, Y: topleft.Y + h}, bounds)
|
||||||
|
croparea := image.Rect(topleft.X, topleft.Y, bottomright.X, bottomright.Y)
|
||||||
|
if timg, ok := img.(subImageSupport); ok {
|
||||||
|
return timg.SubImage(croparea), nil
|
||||||
|
} else {
|
||||||
|
return nil, errors.New("Crop: image format does not support 'SubImage' method (cropping not supported)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CropCenter(img image.Image, w int, h int) (image.Image, error) {
|
||||||
|
bounds := img.Bounds()
|
||||||
|
center := image.Point{X: bounds.Min.X + bounds.Dx()/2, Y: bounds.Min.Y + bounds.Dy()/2}
|
||||||
|
topleft := image.Point{X: center.X - w/2, Y: center.Y - h/2}
|
||||||
|
return Crop(img, topleft, w, h)
|
||||||
|
}
|
426
finstr.go
Normal file
426
finstr.go
Normal file
|
@ -0,0 +1,426 @@
|
||||||
|
package finstr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"git.gutmet.org/finstr.git/crop"
|
||||||
|
"git.gutmet.org/finstr.git/initer"
|
||||||
|
"git.gutmet.org/go-resize.git"
|
||||||
|
"git.gutmet.org/goutil.git"
|
||||||
|
goimg "image"
|
||||||
|
"image/jpeg"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var dir string
|
||||||
|
var outdir string
|
||||||
|
var t *template.Template
|
||||||
|
var suffix string
|
||||||
|
var artsy bool
|
||||||
|
|
||||||
|
type link struct {
|
||||||
|
Path string
|
||||||
|
Name string
|
||||||
|
Space bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type image struct {
|
||||||
|
FilenameMedium string
|
||||||
|
FilenameSmall string
|
||||||
|
}
|
||||||
|
|
||||||
|
type page struct {
|
||||||
|
Title string
|
||||||
|
Links []link
|
||||||
|
Images []image
|
||||||
|
Back link
|
||||||
|
Artsy bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func replaceFirst(s string, search string, replace string) string {
|
||||||
|
return strings.Replace(s, search, replace, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*-------------------------*/
|
||||||
|
|
||||||
|
type calendarMonth struct {
|
||||||
|
year int
|
||||||
|
month int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m calendarMonth) String() string {
|
||||||
|
return fmt.Sprintf("%04d %v", m.year, time.Month(m.month).String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m calendarMonth) laterThan(m2 calendarMonth) bool {
|
||||||
|
if m.year == m2.year {
|
||||||
|
return m.month > m2.month
|
||||||
|
} else {
|
||||||
|
return m.year > m2.year
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func byMonthDesc(m []calendarMonth) func(int, int) bool {
|
||||||
|
return func(i, j int) bool { return m[i].laterThan(m[j]) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/*-------------------------*/
|
||||||
|
|
||||||
|
type photo struct {
|
||||||
|
filename string
|
||||||
|
modified calendarMonth
|
||||||
|
tags []string
|
||||||
|
}
|
||||||
|
|
||||||
|
const dirImg = "img"
|
||||||
|
|
||||||
|
func dirOrig() string {
|
||||||
|
return filepath.Join(dir, dirImg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func dirMedium() string {
|
||||||
|
return filepath.Join(outdir, "img_m")
|
||||||
|
}
|
||||||
|
|
||||||
|
func dirSmall() string {
|
||||||
|
return filepath.Join(outdir, "img_k")
|
||||||
|
}
|
||||||
|
|
||||||
|
func dirPages() string {
|
||||||
|
return filepath.Join(outdir, "pages")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *photo) srcLocation(folder string) string {
|
||||||
|
return replaceFirst(p.filename, dirOrig(), filepath.Base(folder))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *photo) filenameMedium() string {
|
||||||
|
return p.srcLocation(dirMedium())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *photo) filenameSmall() string {
|
||||||
|
return p.srcLocation(dirSmall())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *photo) img() image {
|
||||||
|
img := image{}
|
||||||
|
img.FilenameMedium = p.filenameMedium()
|
||||||
|
img.FilenameSmall = p.filenameSmall()
|
||||||
|
return img
|
||||||
|
}
|
||||||
|
|
||||||
|
func byModified(p []photo) func(int, int) bool {
|
||||||
|
return func(i, j int) bool {
|
||||||
|
if p[i].modified == p[j].modified {
|
||||||
|
return p[i].filename > p[j].filename
|
||||||
|
} else {
|
||||||
|
return p[i].modified.laterThan(p[j].modified)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*-------------------------*/
|
||||||
|
|
||||||
|
func filename(s string) string {
|
||||||
|
return url.QueryEscape(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func filenameDate(date calendarMonth) string {
|
||||||
|
return fmt.Sprintf("%04d%02d", date.year, date.month)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*-------------------------*/
|
||||||
|
|
||||||
|
func (p *page) String() string {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
p.Artsy = artsy
|
||||||
|
err := t.Execute(buf, p)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *page) write(name string) {
|
||||||
|
goutil.OptPanic(goutil.WriteFile(filepath.Join(dirPages(), name+suffix), p.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func singlePage(arg *pageArg, photos []photo) {
|
||||||
|
sort.Slice(photos, byModified(photos))
|
||||||
|
img := make([]image, 0)
|
||||||
|
for _, p := range photos {
|
||||||
|
img = append(img, p.img())
|
||||||
|
}
|
||||||
|
p := &page{Images: img, Title: arg.title(), Back: arg.back()}
|
||||||
|
p.write(arg.filename())
|
||||||
|
}
|
||||||
|
|
||||||
|
/*-------------------------*/
|
||||||
|
|
||||||
|
type pageArg struct {
|
||||||
|
tag string
|
||||||
|
date calendarMonth
|
||||||
|
}
|
||||||
|
|
||||||
|
func (arg *pageArg) filename() string {
|
||||||
|
if arg.tag != "" {
|
||||||
|
return filename(arg.tag)
|
||||||
|
} else {
|
||||||
|
return filenameDate(arg.date)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (arg *pageArg) title() string {
|
||||||
|
if arg.tag != "" {
|
||||||
|
return arg.tag
|
||||||
|
} else {
|
||||||
|
return arg.date.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (arg *pageArg) back() link {
|
||||||
|
if arg.tag != "" {
|
||||||
|
return link{Path: "ByTag.html", Name: "Back"}
|
||||||
|
} else {
|
||||||
|
return link{Path: "ByDate.html", Name: "Back"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*-------------------------*/
|
||||||
|
|
||||||
|
type generator struct {
|
||||||
|
photos []photo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *generator) byTag() {
|
||||||
|
l := make([]link, 0)
|
||||||
|
photosWithTag := make(map[string][]photo)
|
||||||
|
for _, photo := range g.photos {
|
||||||
|
for _, tag := range photo.tags {
|
||||||
|
photosWithTag[tag] = append(photosWithTag[tag], photo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tags := make([]string, 0)
|
||||||
|
for tag := range photosWithTag {
|
||||||
|
tags = append(tags, tag)
|
||||||
|
}
|
||||||
|
sort.Sort(sort.StringSlice(tags))
|
||||||
|
for _, tag := range tags {
|
||||||
|
photos := photosWithTag[tag]
|
||||||
|
l = append(l, link{Path: filename(tag) + ".html", Name: tag})
|
||||||
|
args := &pageArg{tag: tag}
|
||||||
|
singlePage(args, photos)
|
||||||
|
}
|
||||||
|
back := link{Path: "index.html", Name: "Back"}
|
||||||
|
p := &page{Links: l, Title: "photos by tag", Back: back}
|
||||||
|
p.write("ByTag")
|
||||||
|
}
|
||||||
|
|
||||||
|
func keys(m map[calendarMonth][]photo) []calendarMonth {
|
||||||
|
keys := make([]calendarMonth, 0)
|
||||||
|
for k := range m {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *generator) byDate() {
|
||||||
|
l := make([]link, 0)
|
||||||
|
photosByMonth := make(map[calendarMonth][]photo)
|
||||||
|
for _, photo := range g.photos {
|
||||||
|
month := photo.modified
|
||||||
|
photosByMonth[month] = append(photosByMonth[month], photo)
|
||||||
|
}
|
||||||
|
months := keys(photosByMonth)
|
||||||
|
sort.Slice(months, byMonthDesc(months))
|
||||||
|
for _, month := range months {
|
||||||
|
photos := photosByMonth[month]
|
||||||
|
l = append(l, link{Path: filenameDate(month) + ".html", Name: month.String(), Space: true})
|
||||||
|
arg := &pageArg{date: month}
|
||||||
|
singlePage(arg, photos)
|
||||||
|
}
|
||||||
|
back := link{Path: "index.html", Name: "Back"}
|
||||||
|
p := page{Links: l, Title: "photos by date", Back: back}
|
||||||
|
p.write("ByDate")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *generator) index() {
|
||||||
|
byDate := link{Path: "ByDate.html", Name: "By Date", Space: true}
|
||||||
|
byTag := link{Path: "ByTag.html", Name: "By Tag"}
|
||||||
|
l := []link{byDate, byTag}
|
||||||
|
back := link{Path: "/", Name: "Home"}
|
||||||
|
p := &page{Links: l, Title: "photos", Back: back}
|
||||||
|
p.write("index")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *generator) generate() {
|
||||||
|
photoFiles := goutil.ListFilesExt(dirOrig(), ".jpg")
|
||||||
|
tagFiles := goutil.ListFilesExt(dirOrig(), ".tags")
|
||||||
|
photos := make(map[string]*photo)
|
||||||
|
|
||||||
|
for _, photoFile := range photoFiles {
|
||||||
|
name := goutil.TrimExt(path.Base(photoFile))
|
||||||
|
stat, err := os.Stat(photoFile)
|
||||||
|
goutil.OptPanic(err)
|
||||||
|
modified := stat.ModTime()
|
||||||
|
month := calendarMonth{year: modified.Year(), month: int(modified.Month())}
|
||||||
|
photos[name] = &photo{filename: photoFile, modified: month}
|
||||||
|
}
|
||||||
|
for _, tagFile := range tagFiles {
|
||||||
|
name := goutil.TrimExt(path.Base(tagFile))
|
||||||
|
tagString, err := goutil.ReadFile(tagFile)
|
||||||
|
goutil.OptPanic(err)
|
||||||
|
tags := strings.Split(tagString, "\n")
|
||||||
|
for _, tag := range tags {
|
||||||
|
if _, ok := photos[name]; !ok {
|
||||||
|
panic("No matching photo file for " + tagFile)
|
||||||
|
}
|
||||||
|
if tag = strings.TrimSpace(tag); tag != "" {
|
||||||
|
photos[name].tags = append(photos[name].tags, tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, v := range photos {
|
||||||
|
g.photos = append(g.photos, *v)
|
||||||
|
}
|
||||||
|
g.byDate()
|
||||||
|
g.byTag()
|
||||||
|
g.index()
|
||||||
|
}
|
||||||
|
|
||||||
|
func resizeAll(src string, dest string, w uint, h uint) {
|
||||||
|
thumbnail := (w == h)
|
||||||
|
rsz := func(w uint, h uint, img goimg.Image) goimg.Image { return resize.Resize(w, h, img, resize.Lanczos3) }
|
||||||
|
files := goutil.ListFilesExt(src, ".jpg")
|
||||||
|
for _, f := range files {
|
||||||
|
outfile := filepath.Join(dest, strings.TrimPrefix(f, src))
|
||||||
|
if goutil.PathExists(outfile) {
|
||||||
|
fmt.Println(f + " already converted")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Println("Converting " + f)
|
||||||
|
file, err := os.Open(f)
|
||||||
|
goutil.OptPanic(err)
|
||||||
|
img, err := jpeg.Decode(file)
|
||||||
|
goutil.OptPanic(err)
|
||||||
|
file.Close()
|
||||||
|
var m goimg.Image
|
||||||
|
if thumbnail {
|
||||||
|
bounds := img.Bounds()
|
||||||
|
if bounds.Dx() < bounds.Dy() {
|
||||||
|
m = rsz(w, 0, img)
|
||||||
|
} else {
|
||||||
|
m = rsz(0, h, img)
|
||||||
|
}
|
||||||
|
if !artsy {
|
||||||
|
m, err = crop.CropCenter(m, int(w), int(h))
|
||||||
|
goutil.OptPanic(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m = rsz(w, h, img)
|
||||||
|
}
|
||||||
|
out, err := os.Create(outfile)
|
||||||
|
defer out.Close()
|
||||||
|
goutil.OptPanic(err)
|
||||||
|
goutil.OptPanic(jpeg.Encode(out, m, &jpeg.Options{Quality: 95}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func boundSize() {
|
||||||
|
fmt.Println("medium images")
|
||||||
|
fmt.Println("-------------")
|
||||||
|
resizeAll(dirOrig(), dirMedium(), 0, 1080)
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeThumbnails() {
|
||||||
|
fmt.Println("thumbnails")
|
||||||
|
fmt.Println("----------")
|
||||||
|
var size uint
|
||||||
|
if artsy {
|
||||||
|
size = 300
|
||||||
|
} else {
|
||||||
|
size = 200
|
||||||
|
}
|
||||||
|
resizeAll(dirMedium(), dirSmall(), size, size)
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Flags struct {
|
||||||
|
InDir string
|
||||||
|
OutDir string
|
||||||
|
Suffix string
|
||||||
|
Clean bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func ensureFolders(clean bool) {
|
||||||
|
outfolders := []string{dirMedium(), dirSmall(), dirPages()}
|
||||||
|
for _, d := range outfolders {
|
||||||
|
if clean {
|
||||||
|
goutil.OptPanic(os.RemoveAll(d))
|
||||||
|
}
|
||||||
|
goutil.OptPanic(os.MkdirAll(d, 0755))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func readStyle() {
|
||||||
|
style, err := goutil.ReadFile(initer.InitFlag(dir))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if style == "artsy" {
|
||||||
|
artsy = true
|
||||||
|
} else {
|
||||||
|
artsy = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Generate(fl Flags) (e error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
e = errors.New(fmt.Sprint("finstr: ", r))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
dir = fl.InDir
|
||||||
|
if fl.InDir != "" && fl.OutDir == "" {
|
||||||
|
outdir = fl.InDir
|
||||||
|
} else {
|
||||||
|
outdir = fl.OutDir
|
||||||
|
}
|
||||||
|
suffix = fl.Suffix
|
||||||
|
initer.InitializedOrDie(dir)
|
||||||
|
ensureFolders(fl.Clean)
|
||||||
|
readStyle()
|
||||||
|
boundSize()
|
||||||
|
makeThumbnails()
|
||||||
|
pageTemplate, err := goutil.ReadFile(initer.TemplateFile(dir))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t = template.Must(template.New("site").Parse(pageTemplate))
|
||||||
|
gen := &generator{}
|
||||||
|
gen.generate()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Command() (goutil.CommandFlagsInit, goutil.CommandFunc) {
|
||||||
|
f := Flags{}
|
||||||
|
flagsInit := func(s *flag.FlagSet) {
|
||||||
|
s.StringVar(&f.InDir, "in", "", "use 'in' directory as input instead of current working directory")
|
||||||
|
s.StringVar(&f.OutDir, "out", "", "use 'out' directory as output instead of 'in' directory")
|
||||||
|
s.StringVar(&f.Suffix, "suffix", ".html", "file extension of generated pages")
|
||||||
|
s.BoolVar(&f.Clean, "clean", false, "remove old files in output directory before generating (use with caution)")
|
||||||
|
}
|
||||||
|
return flagsInit, func([]string) error {
|
||||||
|
return Generate(f)
|
||||||
|
}
|
||||||
|
}
|
6
go.mod
Normal file
6
go.mod
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
module git.gutmet.org/finstr.git
|
||||||
|
|
||||||
|
require (
|
||||||
|
git.gutmet.org/go-resize.git v0.0.0-20180221191011-83c6a9932646
|
||||||
|
git.gutmet.org/goutil.git v0.0.0-20181222191234-18f9120c8c1e
|
||||||
|
)
|
131
initer/initer.go
Normal file
131
initer/initer.go
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
package initer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"git.gutmet.org/goutil.git"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
const htmlTemplate = `<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>{{.Title}}</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Open Sans,Clear Sans,Verdana,Helvetica,Arial;
|
||||||
|
font-size : 12pt;
|
||||||
|
max-width: 850px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
background-color : black;
|
||||||
|
color: #E8E8E8;
|
||||||
|
text-align:justify;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color : #0D0;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
color : red;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
div.gallery {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>{{.Title}}</h1>
|
||||||
|
<div class="gallery">
|
||||||
|
|
||||||
|
{{if .Links}}{{range $index, $link := .Links}}<a href="{{$link.Path}}">{{$link.Name}}</a><br>{{if $link.Space}}<br>{{end}}{{end}}{{end}}
|
||||||
|
|
||||||
|
{{if .Images}}{{range $index, $img := .Images}}<a href="../{{$img.FilenameMedium}}"><img src="../{{$img.FilenameSmall}}" /></a>{{if $.Artsy}}<br><br>{{end}}{{end}}{{end}}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<br><br>
|
||||||
|
<a href="{{.Back.Path}}">{{.Back.Name}}</a>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
|
||||||
|
const mdTemplate = `---
|
||||||
|
title: {{.Title}}
|
||||||
|
---
|
||||||
|
{{if .Links}}{{range $index, $link := .Links}}<a href="{{$link.Path}}">{{$link.Name}}</a><br>{{if $link.Space}}<br>{{end}}{{end}}{{end}}
|
||||||
|
|
||||||
|
{{if .Images}}{{range $index, $img := .Images}}<a href="../{{$img.FilenameMedium}}"><img src="../{{$img.FilenameSmall}}" /></a>{{if $.Artsy}}<br><br>{{end}}{{end}}{{end}}
|
||||||
|
<br><br>
|
||||||
|
<a href="{{.Back.Path}}">{{.Back.Name}}</a>
|
||||||
|
`
|
||||||
|
|
||||||
|
type IniterFlags struct {
|
||||||
|
Dir string
|
||||||
|
Markdown bool
|
||||||
|
Artsy bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitializedOrDie(dir string) {
|
||||||
|
paths := []string{imgFolder(dir), InitFlag(dir), TemplateFile(dir)}
|
||||||
|
for _, p := range paths {
|
||||||
|
if !goutil.PathExists(p) {
|
||||||
|
panic("Not a finstr folder? Did not find " + p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func imgFolder(dir string) string {
|
||||||
|
return filepath.Join(dir, "img")
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitFlag(dir string) string {
|
||||||
|
return filepath.Join(dir, ".finstr")
|
||||||
|
}
|
||||||
|
|
||||||
|
func readme(dir string) string {
|
||||||
|
return filepath.Join(imgFolder(dir), "Readme")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TemplateFile(dir string) string {
|
||||||
|
return filepath.Join(dir, "template")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Init(fl IniterFlags) error {
|
||||||
|
dir := fl.Dir
|
||||||
|
err := os.MkdirAll(imgFolder(dir), 0755)
|
||||||
|
var style string
|
||||||
|
if fl.Artsy {
|
||||||
|
style = "artsy"
|
||||||
|
} else {
|
||||||
|
style = "square"
|
||||||
|
}
|
||||||
|
var template string
|
||||||
|
if fl.Markdown {
|
||||||
|
template = mdTemplate
|
||||||
|
} else {
|
||||||
|
template = htmlTemplate
|
||||||
|
}
|
||||||
|
err = goutil.OptDo(err, func() error { return goutil.WriteFile(InitFlag(dir), style) })
|
||||||
|
err = goutil.OptDo(err, func() error { return goutil.WriteFile(readme(dir), "Put your .jpgs and .tags here") })
|
||||||
|
err = goutil.OptDo(err, func() error { return goutil.WriteFile(TemplateFile(dir), template) })
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func Command() (goutil.CommandFlagsInit, goutil.CommandFunc) {
|
||||||
|
f := IniterFlags{}
|
||||||
|
flagsInit := func(s *flag.FlagSet) {
|
||||||
|
s.BoolVar(&f.Markdown, "markdown", false, "init with Markdown template instead of HTML")
|
||||||
|
s.StringVar(&f.Dir, "dir", "", "initialize 'dir' instead of current working directory")
|
||||||
|
s.BoolVar(&f.Artsy, "artsy", false, "mark gallery to generate thumbnails with original ratio instead of square")
|
||||||
|
}
|
||||||
|
return flagsInit, func([]string) error {
|
||||||
|
return Init(f)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user