Initial commit.

master
Carl Kittelberger 2017-02-09 15:13:03 +01:00
commit fa2662369b
Signed by: icedream
GPG Key ID: C1D30A06E6490C14
23 changed files with 1014 additions and 0 deletions

18
.editorconfig Normal file
View File

@ -0,0 +1,18 @@
root = true
[*]
indent_style = tab
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = false
insert_final_newline = true
[*.json]
indent_style = space
indent_size = 2
insert_final_newline = false
[*.{yaml,yml}]
indent_style = space
indent_size = 4
insert_final_newline = true

24
.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
workreportmgr
*.exe
*.test

49
export/latex/export.go Normal file
View File

@ -0,0 +1,49 @@
package latex
import (
"io"
"text/template"
"github.com/nicksnyder/go-i18n/i18n"
"git.dekart811.net/icedream/workreportmgr/project"
)
// TexMarker represents information about a TeX document to be read in by the
// respective compiler. It contains information for example about which TeX
// variant to use.
type TexMarker struct {
Program string
}
// Exporter provides functionality to export a workreports project to a LaTeX
// file.
type Exporter struct {
Locale string
Inputs []string
Marker TexMarker
}
// Export generates LaTeX code from the given project and writes it to the given
// writer.
func (e *Exporter) Export(prj *project.Project, w io.Writer) (err error) {
T, err := i18n.Tfunc(e.Locale)
if err != nil {
return
}
exportTemplate.Funcs(template.FuncMap{
"T": T,
})
data := struct {
Project *project.Project
TexMarker TexMarker
TexInputs []string
}{
Project: prj,
TexInputs: e.Inputs,
TexMarker: e.Marker,
}
return exportTemplate.Execute(w, data)
}

6
export/latex/init.go Normal file
View File

@ -0,0 +1,6 @@
package latex
func init() {
initLocalization()
initTemplate()
}

View File

@ -0,0 +1,29 @@
package latex
import (
"log"
"github.com/nicksnyder/go-i18n/i18n"
)
//go:generate go-bindata -pkg latex -o localization_assets.go localization/
func initLocalization() {
log.Println("Initializing localization for LaTeX export...")
files, err := AssetDir("localization")
if err != nil {
log.Fatal("Failed to browse embedded asset directory.", err)
panic(err)
}
for _, file := range files {
if localizationBytes, err := Asset("localization/" + file); err != nil {
log.Fatal("Failed to read localization file.", err)
panic(err)
} else if err := i18n.ParseTranslationFileBytes(file, localizationBytes); err != nil {
log.Fatal("Failed to parse localization file.", err)
panic(err)
}
}
}

View File

@ -0,0 +1,54 @@
[
{
"id": "calendar_week",
"translation": "Kalenderwoche"
},
{
"id": "calendar_week_short",
"translation": "KW"
},
{
"id": "department",
"translation": "Abteilung"
},
{
"id": "instructor",
"translation": "Ausbilder/in"
},
{
"id": "legal_representative",
"translation": "Gesetzlicher Vertreter"
},
{
"id": "name",
"translation": "Name"
},
{
"id": "no_school_periods_this_week",
"translation": "Hat in dieser Woche noch nicht stattgefunden."
},
{
"id": "operational_activities",
"translation": "Betriebliche Tätigkeiten"
},
{
"id": "operational_instruction",
"translation": "Betrieblicher Unterricht"
},
{
"id": "professional_school",
"translation": "Berufsschule"
},
{
"id": "proof_of_education",
"translation": "Ausbildungsnachweis Nr. {{.Count}}"
},
{
"id": "trainee",
"translation": "Auszubildende/r"
},
{
"id": "time_period",
"translation": "Zeitraum"
}
]

View File

@ -0,0 +1 @@
[]

View File

@ -0,0 +1,54 @@
[
{
"id": "calendar_week",
"translation": "Calendar week"
},
{
"id": "calendar_week_short",
"translation": "CW"
},
{
"id": "department",
"translation": "Department"
},
{
"id": "instructor",
"translation": "Instructor"
},
{
"id": "legal_representative",
"translation": "Legal representative"
},
{
"id": "name",
"translation": "Name"
},
{
"id": "no_school_periods_this_week",
"translation": "No school periods this week."
},
{
"id": "operational_activities",
"translation": "Operational activities"
},
{
"id": "operational_instruction",
"translation": "Operational instruction"
},
{
"id": "professional_school",
"translation": "Professional school"
},
{
"id": "proof_of_education",
"translation": "Proof of Education No. {{.Count}}"
},
{
"id": "trainee",
"translation": "Trainee"
},
{
"id": "time_period",
"translation": "Time period"
}
]

View File

@ -0,0 +1 @@
[]

View File

@ -0,0 +1,306 @@
// Code generated by go-bindata.
// sources:
// localization/de-de.all.json
// localization/de-de.untranslated.json
// localization/en-us.all.json
// localization/en-us.untranslated.json
// DO NOT EDIT!
package latex
import (
"bytes"
"compress/gzip"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"
)
func bindataRead(data []byte, name string) ([]byte, error) {
gz, err := gzip.NewReader(bytes.NewBuffer(data))
if err != nil {
return nil, fmt.Errorf("Read %q: %v", name, err)
}
var buf bytes.Buffer
_, err = io.Copy(&buf, gz)
clErr := gz.Close()
if err != nil {
return nil, fmt.Errorf("Read %q: %v", name, err)
}
if clErr != nil {
return nil, err
}
return buf.Bytes(), nil
}
type asset struct {
bytes []byte
info os.FileInfo
}
type bindataFileInfo struct {
name string
size int64
mode os.FileMode
modTime time.Time
}
func (fi bindataFileInfo) Name() string {
return fi.name
}
func (fi bindataFileInfo) Size() int64 {
return fi.size
}
func (fi bindataFileInfo) Mode() os.FileMode {
return fi.mode
}
func (fi bindataFileInfo) ModTime() time.Time {
return fi.modTime
}
func (fi bindataFileInfo) IsDir() bool {
return false
}
func (fi bindataFileInfo) Sys() interface{} {
return nil
}
var _localizationDeDeAllJson = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x84\xd2\xc1\x6a\xdb\x40\x10\x06\xe0\x73\xfc\x14\x83\xce\xc1\xb9\xf7\x96\xf6\xd0\x42\x21\xa7\xb6\x81\x96\x22\xd6\xab\x5f\xd2\x90\xf5\xac\x98\x99\x8d\x21\xc6\x6f\xd3\x37\xe9\x8b\x15\xc9\x31\xb4\x45\xeb\x9c\x77\xff\x6f\xa5\x99\xff\xc7\x86\xe8\xb8\x21\x22\x6a\xb8\x6b\xde\x51\x13\x43\x82\x74\x41\xdb\x03\xf0\xd4\xdc\x9e\x8f\x5c\x83\x58\x0a\xce\x59\xe6\x3b\x9f\x97\x3b\xd0\x43\x8e\x23\x9a\x0d\xd1\xe9\xf6\x2a\xd3\xda\x98\xd5\x6b\xd8\xe3\xba\xd0\x61\x0a\xea\x7b\x48\x2d\x78\xbf\x73\x70\x2a\x32\xac\xe7\x59\xcc\xb5\x44\xcf\x5a\xcb\x17\xdb\x71\xea\xa0\x77\x2c\xeb\x44\xc2\x10\x52\xab\x98\x14\x06\xf1\xe0\xfc\x8c\x0a\xf6\x11\x06\x7f\x49\x1c\x47\x28\x7d\x83\xba\xc2\xa1\xeb\xac\x84\x7d\x8d\x79\x98\x8f\xd6\x43\xb9\xb5\x38\xe6\x9c\xda\x09\xca\xb9\xb3\xd6\x47\xb6\x6b\x5b\xfa\x14\x9c\x58\xa8\x63\x18\x94\x1e\xe7\x5d\x91\xe4\x38\x92\x70\x1c\x9d\xcc\x83\xfb\x80\xbe\x48\x07\xd9\xae\x3f\x9a\x27\xe8\xe2\x85\xd4\x86\xe8\xfc\xcc\xce\xb0\xca\x7b\xef\xe1\xca\xd8\x2d\x33\xa0\x2f\xbf\x7f\x39\x0f\x4f\x60\x47\x65\xb8\x7f\xdb\x97\x5d\xcd\xd2\xdb\xb8\xd2\x57\x71\xa8\xce\xbf\xb1\x6e\x4f\x9a\x7b\x98\x9d\xf1\xf3\xd8\xaa\xae\x96\xde\x2c\x8e\x25\x55\x06\x3f\x69\xce\x7d\x9b\xfb\x16\x5d\x89\xe1\xca\x27\xbe\xf6\xa9\xc8\x60\x12\xe2\x78\x00\x1b\x3d\xe8\x96\x8e\xc7\xed\x87\x5c\xc4\x4f\xa7\xf5\x07\x5c\x03\x0b\x6a\x8d\xb8\x2f\xf6\x52\x96\x9e\x4a\x87\xbb\x7f\x1b\x75\x73\x11\x78\x8f\xd7\x5a\x2c\xca\xcd\xff\xc8\x77\xb0\x6b\x28\xfb\x25\xbd\xf9\xf9\x27\x00\x00\xff\xff\x2b\xa3\xd3\x89\xf5\x03\x00\x00")
func localizationDeDeAllJsonBytes() ([]byte, error) {
return bindataRead(
_localizationDeDeAllJson,
"localization/de-de.all.json",
)
}
func localizationDeDeAllJson() (*asset, error) {
bytes, err := localizationDeDeAllJsonBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "localization/de-de.all.json", size: 1013, mode: os.FileMode(436), modTime: time.Unix(1486647823, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _localizationDeDeUntranslatedJson = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x8a\x8e\x05\x04\x00\x00\xff\xff\x29\xbb\x4c\x0d\x02\x00\x00\x00")
func localizationDeDeUntranslatedJsonBytes() ([]byte, error) {
return bindataRead(
_localizationDeDeUntranslatedJson,
"localization/de-de.untranslated.json",
)
}
func localizationDeDeUntranslatedJson() (*asset, error) {
bytes, err := localizationDeDeUntranslatedJsonBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "localization/de-de.untranslated.json", size: 2, mode: os.FileMode(436), modTime: time.Unix(1479981513, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _localizationEnUsAllJson = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x84\x92\xc1\x4a\xc3\x40\x10\x86\xcf\xcd\x53\x0c\x39\x97\x3e\x80\xd7\xea\x41\x90\xda\x83\xe0\x41\x64\x19\x92\x89\x1d\x4c\x76\xc2\xec\xb4\x1e\x4a\xdf\x5d\x92\x6c\x69\xd0\xdd\xf4\x3c\xdf\xf7\xb5\xc9\x9f\x8f\x02\xe0\x5c\x00\x00\x94\x5c\x97\x0f\x50\x56\xd8\x92\xaf\x51\xdd\x0f\xd1\x77\xb9\x9e\x4e\xa6\xe8\x43\x8b\xc6\xe2\x07\x66\x1b\x19\x18\x99\x02\xe0\xb2\x5e\xcc\xb8\x70\x10\xb5\x5c\xec\x3d\x5d\xa8\xa9\x47\xb5\x8e\x7c\x4e\x7c\xbc\x01\xc9\x00\xfb\x60\x7a\xac\x4c\x34\x13\x78\xbe\x01\xc9\x40\x4b\x5f\xd8\x3a\xa5\x5e\x29\x90\x37\x34\x3e\x51\x26\xf5\x32\xa0\xf0\x07\x4d\x46\x3d\x76\xb9\xc8\x6e\x38\xa5\x25\x71\xa1\x3a\x88\xb4\xae\x27\x65\xa9\x83\xb3\x03\x87\xa5\x89\x76\x02\x93\x01\xd1\x80\xc1\x18\x07\xdb\xa4\x7f\x43\x7a\xd2\x51\xc7\xd6\x61\x65\x7c\x62\x63\x0a\x99\xfc\xeb\x0d\x86\x19\x7c\x37\x7c\xdd\x64\xc8\xdc\x2f\xcf\xe9\x64\xba\x57\x69\x28\x84\xa9\x3d\x3d\x6f\x26\xbb\x9f\x91\xf1\xcd\x64\x93\xd2\x38\x69\x1c\xd5\xc7\x0a\x17\xfe\xe8\x7e\x00\x41\x1a\x78\xba\x82\xb0\x93\x0d\x9c\xcf\x9b\xad\x1c\xbd\x5d\x2e\xe9\xbe\x29\xb2\xa7\xdc\x27\xf0\x16\xaf\x73\x75\x75\x35\xb9\xa3\xb8\xff\x68\xaf\xfe\xc9\xdc\x51\x9c\x7b\x0c\x14\x9f\xbf\x01\x00\x00\xff\xff\x24\x12\x73\xee\xde\x03\x00\x00")
func localizationEnUsAllJsonBytes() ([]byte, error) {
return bindataRead(
_localizationEnUsAllJson,
"localization/en-us.all.json",
)
}
func localizationEnUsAllJson() (*asset, error) {
bytes, err := localizationEnUsAllJsonBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "localization/en-us.all.json", size: 990, mode: os.FileMode(436), modTime: time.Unix(1486647790, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _localizationEnUsUntranslatedJson = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x8a\x8e\x05\x04\x00\x00\xff\xff\x29\xbb\x4c\x0d\x02\x00\x00\x00")
func localizationEnUsUntranslatedJsonBytes() ([]byte, error) {
return bindataRead(
_localizationEnUsUntranslatedJson,
"localization/en-us.untranslated.json",
)
}
func localizationEnUsUntranslatedJson() (*asset, error) {
bytes, err := localizationEnUsUntranslatedJsonBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "localization/en-us.untranslated.json", size: 2, mode: os.FileMode(436), modTime: time.Unix(1479981513, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
// Asset loads and returns the asset for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
func Asset(name string) ([]byte, error) {
cannonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[cannonicalName]; ok {
a, err := f()
if err != nil {
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
}
return a.bytes, nil
}
return nil, fmt.Errorf("Asset %s not found", name)
}
// MustAsset is like Asset but panics when Asset would return an error.
// It simplifies safe initialization of global variables.
func MustAsset(name string) []byte {
a, err := Asset(name)
if err != nil {
panic("asset: Asset(" + name + "): " + err.Error())
}
return a
}
// AssetInfo loads and returns the asset info for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
func AssetInfo(name string) (os.FileInfo, error) {
cannonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[cannonicalName]; ok {
a, err := f()
if err != nil {
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
}
return a.info, nil
}
return nil, fmt.Errorf("AssetInfo %s not found", name)
}
// AssetNames returns the names of the assets.
func AssetNames() []string {
names := make([]string, 0, len(_bindata))
for name := range _bindata {
names = append(names, name)
}
return names
}
// _bindata is a table, holding each asset generator, mapped to its name.
var _bindata = map[string]func() (*asset, error){
"localization/de-de.all.json": localizationDeDeAllJson,
"localization/de-de.untranslated.json": localizationDeDeUntranslatedJson,
"localization/en-us.all.json": localizationEnUsAllJson,
"localization/en-us.untranslated.json": localizationEnUsUntranslatedJson,
}
// AssetDir returns the file names below a certain
// directory embedded in the file by go-bindata.
// For example if you run go-bindata on data/... and data contains the
// following hierarchy:
// data/
// foo.txt
// img/
// a.png
// b.png
// then AssetDir("data") would return []string{"foo.txt", "img"}
// AssetDir("data/img") would return []string{"a.png", "b.png"}
// AssetDir("foo.txt") and AssetDir("notexist") would return an error
// AssetDir("") will return []string{"data"}.
func AssetDir(name string) ([]string, error) {
node := _bintree
if len(name) != 0 {
cannonicalName := strings.Replace(name, "\\", "/", -1)
pathList := strings.Split(cannonicalName, "/")
for _, p := range pathList {
node = node.Children[p]
if node == nil {
return nil, fmt.Errorf("Asset %s not found", name)
}
}
}
if node.Func != nil {
return nil, fmt.Errorf("Asset %s not found", name)
}
rv := make([]string, 0, len(node.Children))
for childName := range node.Children {
rv = append(rv, childName)
}
return rv, nil
}
type bintree struct {
Func func() (*asset, error)
Children map[string]*bintree
}
var _bintree = &bintree{nil, map[string]*bintree{
"localization": &bintree{nil, map[string]*bintree{
"de-de.all.json": &bintree{localizationDeDeAllJson, map[string]*bintree{}},
"de-de.untranslated.json": &bintree{localizationDeDeUntranslatedJson, map[string]*bintree{}},
"en-us.all.json": &bintree{localizationEnUsAllJson, map[string]*bintree{}},
"en-us.untranslated.json": &bintree{localizationEnUsUntranslatedJson, map[string]*bintree{}},
}},
}}
// RestoreAsset restores an asset under the given directory
func RestoreAsset(dir, name string) error {
data, err := Asset(name)
if err != nil {
return err
}
info, err := AssetInfo(name)
if err != nil {
return err
}
err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
if err != nil {
return err
}
err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
if err != nil {
return err
}
err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
if err != nil {
return err
}
return nil
}
// RestoreAssets restores an asset under the given directory recursively
func RestoreAssets(dir, name string) error {
children, err := AssetDir(name)
// File
if err != nil {
return RestoreAsset(dir, name)
}
// Dir
for _, child := range children {
err = RestoreAssets(dir, filepath.Join(name, child))
if err != nil {
return err
}
}
return nil
}
func _filePath(dir, name string) string {
cannonicalName := strings.Replace(name, "\\", "/", -1)
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
}

View File

@ -0,0 +1,5 @@
package stringutil
type converter interface {
Process(text string) string
}

View File

@ -0,0 +1,19 @@
package stringutil
import "regexp"
type simpleRegexConverter struct {
regex *regexp.Regexp
replacement string
}
func newSimpleRegexConverter(regexStr string, replacementStr string) *simpleRegexConverter {
return &simpleRegexConverter{
regex: regexp.MustCompile(regexStr),
replacement: replacementStr,
}
}
func (conv *simpleRegexConverter) Process(text string) string {
return conv.regex.ReplaceAllString(text, conv.replacement)
}

View File

@ -0,0 +1,19 @@
package stringutil
import "strings"
type stringConverter struct {
old string
replacement string
}
func newStringConverter(old string, replacementStr string) *stringConverter {
return &stringConverter{
old: old,
replacement: replacementStr,
}
}
func (conv *stringConverter) Process(text string) string {
return strings.Replace(text, conv.old, conv.replacement, -1)
}

View File

@ -0,0 +1,28 @@
package stringutil
var replacements = []converter{
newStringConverter("{", "\\{"),
newStringConverter("}", "\\}"),
newStringConverter("\\", "\\textbackslash{}"),
newStringConverter("&", "\\&"),
newStringConverter("%", "\\%"),
newStringConverter("$", "\\$"),
newStringConverter("#", "\\#"),
newStringConverter("_", "\\_"),
newStringConverter("~", "\\textasciitilde{}"),
newStringConverter("^", "\\textasciicircum{}"),
newStringConverter("ß", "\\ss{}"),
newSimpleRegexConverter(`"([^"]+)"`, `\enquote{$1}`),
}
// TexEscape modifies a string so it can be safely places in a LaTeX file
// without causing any errors due to special characters.
func TexEscape(s string) string {
for _, replacer := range replacements {
s = replacer.Process(s)
}
return s
}

152
export/latex/template.go Normal file
View File

@ -0,0 +1,152 @@
package latex
import (
"log"
"os"
"text/template"
"git.dekart811.net/icedream/workreportmgr/export/latex/stringutil"
"git.dekart811.net/icedream/workreportmgr/project"
"github.com/jinzhu/now"
"github.com/nicksnyder/go-i18n/i18n"
)
const dateFormat = "02.01.2006" // TODO - localize!
var exportTemplate = template.Must(template.
New("latex_export").
Funcs(template.FuncMap{
"T": i18n.IdentityTfunc,
"escape": stringutil.TexEscape,
"add": func(a, b int) int {
return a + b
},
"beginofweek": func(date project.Date) project.Date {
return project.Date{now.New(date.Time).BeginningOfWeek()}
},
"endofweek": func(date project.Date) project.Date {
return project.Date{now.New(date.Time).EndOfWeek()}
},
}).
Delims("<", ">").
Parse(`% !TeX
<- with .TexMarker.Program> program = <.><end>
\documentclass[11pt,a4paper,oneside]{article}
\usepackage{fancyhdr}
\usepackage{tabularx}
\usepackage[left=2cm,right=2cm,top=2cm,bottom=6cm,includeheadfoot]{geometry}
\usepackage{csquotes}
\usepackage{fontspec}
\usepackage{ifluatex}
\ifluatex
\else
\usepackage[utf8]{inputenc}
\fi
\pagestyle{fancy}
\fancyhf{}
\setlength{\headheight}{2cm}
\newcommand{\Name}{<.Project.Name>}
\newcommand{\Department}{<.Project.Department>}
\newcommand{\wrSigningField}[0]{
\begin{tabularx}{\textwidth}{| X | X | X |}
\hline
<T "trainee"> &
<T "legal_representative"> &
<T "instructor"> \\[2cm]
\hline
\end{tabularx}
}
\newenvironment{weeklyreport}{
}{
\fancyfoot[C]{\wrSigningField}
}
\newcommand{\preweeklyreporthead}[3]{}
\newcommand{\weeklyreporthead}[3]{
\preweeklyreporthead{#1}{#2}{#3}
\fancyhead[R]{
\begin{tabularx}{8cm}{rl}
\textbf{<T "name">}: & \Name \\
\textbf{<T "department">}: & \Department \\
\textbf{<T "time_period">}: & #2 - #3 \\
\end{tabularx}
}
\fancyfoot{}
\newpage
\setcounter{section}{#1}
\setcounter{subsection}{0}
\section*{<T "proof_of_education" "#1">}
\addcontentsline{toc}{section}{<T "proof_of_education" "#1">}
}
\newcommand{\weeklyreportsection}[1]{
\subsection*{#1}
}
<with .TexInputs>
<range .>
\input{<.>}
<end>
<end>
\begin{document}
\tableofcontents
<range $index, $week := .Project.Weeks>
\begin{weeklyreport}
\weeklyreporthead{<add $index 1>}{<beginofweek $week.Date>}{<endofweek $week.Date>}
\weeklyreportsection{<T "operational_activities">}
<with $week.WorkActivities>
\begin{itemize}
<range .>
\item <. | escape>
<end>
\end{itemize}
<end>
\weeklyreportsection{<T "operational_instruction">}
<$week.WorkActivityDetails | escape>
\weeklyreportsection{<T "professional_school">}
<if eq (len $week.Periods) 0>
<T "no_school_periods_this_week" | escape>
<else>
\begin{itemize}
<range $week.Periods>
\item{
<- .Subject | escape ->
<- with .Topics ->:
<range $index, $topic := . ->
<- if ne $index 0 ->, <end -><- $topic | escape ->
<- end ->
<- end ->
}
<end>
\end{itemize}
<end>
\end{weeklyreport}
<end>
\end{document}
`))
func initTemplate() {
log.Println("Initializing template for LaTeX export...")
var err error
defer func() {
if err != nil {
log.Fatal("Failed at initializing template.", err)
os.Exit(0xFF)
}
}()
}

105
main.go Normal file
View File

@ -0,0 +1,105 @@
package main
import (
"log"
"os"
"git.dekart811.net/icedream/workreportmgr/export/latex"
"git.dekart811.net/icedream/workreportmgr/project"
"github.com/alecthomas/kingpin"
)
var (
cli = kingpin.New("workreport-manager", "Manage all of your workreports through an easy to read and manage format.")
flagProjectFile = cli.Flag("file", "Defines the project filename from which to parse all workreports.").Short('f').Default("workreports.yml").File()
flagVerbose = cli.Flag("verbose", "Print extra information about what's happening.").Short('v').Bool()
flagLocale = cli.Flag("locale", "The locale to use for exports.").Default("en-US").String()
cmdInit = cli.Command("init", "Creates a new workreports project file.")
cmdAdd = cli.Command("add", "Add new information to the workreport.")
cmdAddActivity = cmdAdd.Command("activity", "Adds a new work activity to the workreport for the current date.")
cmdAddPeriod = cmdAdd.Command("period", "Adds a new school period to the workreport for the current date.")
cmdAddActivityDetails = cmdAdd.Command("details", "Sets the detail information for a work activity for the current date.")
cmdAddActivityDetailsFlagInput = cmdAddActivityDetails.Flag("input", "The file from which to read the information. Defaults to stdin.").Short('i').Default(os.Stdin.Name()).ExistingFile()
cmdExportToLatex = cli.Command("latex", "Exports the current workreport project to Latex.")
cmdExportToLatexFlagOutput = cmdExportToLatex.Flag("output", "The file to write the generated code to. Defaults to stdout.").Short('o').Default(os.Stdout.Name()).String()
cmdExportToLatexFlagInput = cmdExportToLatex.Flag("input", "Use this to write additional \\input statements in the resulting LaTeX file.").Short('i').Strings()
cmdExportToLatexFlagProgram = cmdExportToLatex.Flag("program", "The LaTeX compiler program to use, for example \"lualatex\". Leave empty if you don't know what to put here.").Short('p').String()
version = "master"
currentProject *project.Project
)
func main() {
cli.Version(version)
if *flagVerbose {
log.Printf("%s v%s", cli.Name, version)
}
command := kingpin.MustParse(cli.Parse(os.Args[1:]))
switch command {
case cmdInit.FullCommand():
log.Println("Not yet implemented") // TODO
case cmdAddActivity.FullCommand():
log.Println("Not yet implemented") // TODO
case cmdAddActivityDetails.FullCommand():
log.Println("Not yet implemented") // TODO
case cmdAddPeriod.FullCommand():
log.Println("Not yet implemented") // TODO
case cmdExportToLatex.FullCommand():
parseProject()
if *flagVerbose {
log.Println("Going to export to Latex file...")
}
w, err := os.Create(*cmdExportToLatexFlagOutput)
if err != nil {
log.Print("Failed to create output file.")
os.Exit(1)
}
log.Println("Created output file.")
defer w.Close()
exporter := new(latex.Exporter)
if flagLocale != nil {
exporter.Locale = *flagLocale
}
if cmdExportToLatexFlagInput != nil {
exporter.Inputs = *cmdExportToLatexFlagInput
}
if cmdExportToLatexFlagProgram != nil {
exporter.Marker.Program = *cmdExportToLatexFlagProgram
}
log.Println("Now exporting...")
if err := exporter.Export(currentProject, w); err != nil {
log.Print("Failed to generate LaTeX output:", err)
os.Exit(1)
}
log.Println("Done.")
default:
log.Println("Unknown command.")
}
}
func parseProject() {
var err error
defer func() {
if err != nil {
log.Print("Failed to parse project file:", err)
os.Exit(1)
}
}()
currentProject, err = project.DecodeFromFile(*flagProjectFile)
if *flagVerbose {
log.Println("Parsed project file successfully.")
log.Printf("Resulting project: %+v", currentProject)
}
}

31
project/date.go Normal file
View File

@ -0,0 +1,31 @@
package project
import (
"fmt"
"strings"
"time"
)
const dateFormat = "2006-01-02"
// Date is a wrapper around time.Time with a different UnmarshalJSON
// implementation that parses just date information.
type Date struct {
time.Time
}
// UnmarshalJSON takes the given buffer and translates it into a proper
// time.Time object.
func (t *Date) UnmarshalJSON(buf []byte) (err error) {
tt, err := time.Parse(dateFormat, strings.Trim(string(buf), `"`))
if err != nil {
return
}
t.Time = tt
return nil
}
func (t Date) String() string {
// TODO - Localize date!
return fmt.Sprintf("%02d.%02d.%04d", t.Time.Day(), t.Time.Month(), t.Time.Year())
}

31
project/date_test.go Normal file
View File

@ -0,0 +1,31 @@
package project_test
import (
"testing"
"time"
"git.dekart811.net/icedream/workreportmgr/project"
"github.com/stretchr/testify/require"
)
func TestDate_String(t *testing.T) {
date := project.Date{time.Date(2016, 8, 22, 11, 22, 33, 97, time.Local)}
require.Equal(t, "22.08.2016", date.String())
}
func TestDate_UnmarshalJSON_Quotes(t *testing.T) {
date := project.Date{}
require.Nil(t, date.UnmarshalJSON([]byte(`"2016-08-22"`)))
require.Equal(t, 2016, date.Time.Year())
require.Equal(t, time.Month(8), date.Time.Month())
require.Equal(t, 22, date.Time.Day())
}
func TestDate_UnmarshalJSON_NoQuotes(t *testing.T) {
date := project.Date{}
require.Nil(t, date.UnmarshalJSON([]byte(`2016-08-22`)))
require.Equal(t, 2016, date.Time.Year())
require.Equal(t, time.Month(8), date.Time.Month())
require.Equal(t, 22, date.Time.Day())
}

View File

@ -0,0 +1,16 @@
package project
import (
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestDate_dateFormat(t *testing.T) {
date, err := time.Parse(dateFormat, "2013-04-05")
require.Nil(t, err)
require.Equal(t, 2013, date.Year())
require.Equal(t, time.Month(4), date.Month())
require.Equal(t, 5, date.Day())
}

40
project/file.go Normal file
View File

@ -0,0 +1,40 @@
package project
import (
"io/ioutil"
"os"
"github.com/ghodss/yaml"
)
// Marshal takes the data from this Project instance and turns it into a YAML
// text that is easy to read and can easily be decoded with Unmarshal or Decode.
func (prj Project) Marshal() ([]byte, error) {
return yaml.Marshal(prj)
}
// Unmarshal decodes project information from a given YAML text blob into this
// Project instance.
func (prj *Project) Unmarshal(yamlData []byte) error {
return yaml.Unmarshal(yamlData, prj)
}
// Decode decodes project information from a given YAML text blob into a new
// instance of Project.
func Decode(yamlData []byte) (prj *Project, err error) {
prj = new(Project)
if err = prj.Unmarshal(yamlData); err != nil {
prj = nil
}
return
}
// DecodeFromFile decodes project information from a given file into a new
// instance of Project.
func DecodeFromFile(file *os.File) (prj *Project, err error) {
contents, err := ioutil.ReadAll(file)
if err != nil {
return
}
return Decode(contents)
}

10
project/format.go Normal file
View File

@ -0,0 +1,10 @@
package project
// Project represents the root structure of a project file.
type Project struct {
Name string `json:"Name"`
Department string `json:"Department"`
Begin Date `json:"Begin"`
End Date `json:"End"`
Weeks []Week `json:"Weeks"`
}

7
project/period.go Normal file
View File

@ -0,0 +1,7 @@
package project
// Period represents a school period in the weekly report.
type Period struct {
Subject string `json:"Subject"`
Topics []string `json:"Topics"`
}

9
project/week.go Normal file
View File

@ -0,0 +1,9 @@
package project
// Week represents a week in the report.
type Week struct {
Date Date
WorkActivities []string `json:"Operational activities"`
WorkActivityDetails string `json:"Operational instructions"`
Periods []Period `json:"Professional school"`
}