Skip to content

Commit

Permalink
Merge pull request #2 from persiancal/master
Browse files Browse the repository at this point in the history
Update Repo
  • Loading branch information
Goudarz authored Oct 9, 2019
2 parents f562856 + f48f9c8 commit a9eb3e0
Show file tree
Hide file tree
Showing 47 changed files with 2,357 additions and 1,792 deletions.
21 changes: 16 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,34 @@ generate-hijri: $(ROOT)/cmd/thetool/thetool
mkdir -p $(ROOT)/dist
$(ROOT)/cmd/thetool/thetool -dir $(ROOT)/hijri generate -dist $(ROOT)/dist

generate: generate-hijri generate-jalali
generate-gregorian: $(ROOT)/cmd/thetool/thetool
mkdir -p $(ROOT)/dist
$(ROOT)/cmd/thetool/thetool -dir $(ROOT)/gregorian generate -dist $(ROOT)/dist

generate: generate-hijri generate-jalali generate-gregorian
# Make sure update something in dist, since travis looks for changes, and if the build only contains new file
# skips the deploy
date > $(ROOT)/dist/.build_at

validate-jalali: $(ROOT)/cmd/thetool/thetool
$(ROOT)/cmd/thetool/thetool -dir $(ROOT)/jalali validate
$(ROOT)/cmd/thetool/thetool -dir $(ROOT)/jalali validate-links -ignore
$(ROOT)/cmd/thetool/thetool -dir $(ROOT)/jalali validate-links

validate-hirir: $(ROOT)/cmd/thetool/thetool
validate-hijri: $(ROOT)/cmd/thetool/thetool
$(ROOT)/cmd/thetool/thetool -dir $(ROOT)/hijri validate
$(ROOT)/cmd/thetool/thetool -dir $(ROOT)/hijri validate-links -ignore
$(ROOT)/cmd/thetool/thetool -dir $(ROOT)/hijri validate-links

validate: validate-hirir validate-jalali
validate-gregorian: $(ROOT)/cmd/thetool/thetool
$(ROOT)/cmd/thetool/thetool -dir $(ROOT)/gregorian validate
$(ROOT)/cmd/thetool/thetool -dir $(ROOT)/gregorian validate-links

validate: validate-hijri validate-jalali validate-gregorian

reorder-jalali: $(ROOT)/cmd/thetool/thetool
$(ROOT)/cmd/thetool/thetool -dir $(ROOT)/jalali reorder -output - > $(ROOT)/jalali.yaml

reorder-hijri: $(ROOT)/cmd/thetool/thetool
$(ROOT)/cmd/thetool/thetool -dir $(ROOT)/hijri reorder -output - > $(ROOT)/hijri.yaml

reorder-gregorian: $(ROOT)/cmd/thetool/thetool
$(ROOT)/cmd/thetool/thetool -dir $(ROOT)/gregorian reorder -output - > $(ROOT)/gregorian.yaml
36 changes: 29 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,38 @@
<div dir="rtl">

# دیتابیس رویدادهای رسمی تقویم
# رویدادهای تقویم ایران

هدف از این مخزن، جمع آوری اطلاعات مربوط به رویدادهای تقویم با محوریت رویدادهای مرتبط با اقوام پارسی زبان است. در حال حاضر، به دنبال دیتا نیستم، و بیشتر هدف این است که یک ساختار مناسب قابل انعطاف به وجود بیاید که جوابگوی مشکلات زیر باشد :
[![Build Status](https://travis-ci.org/persiancal/cal-events.svg?branch=master)](https://travis-ci.org/persiancal/cal-events)

هدف این مخزن، جمع‌آوری و به روز نگه‌داشتن رویدادهای مهم در تقویم کشورهای پارسی زبان است.

- چند زبانه بودن
- افزودن اطلاعات اضافه به یک رویداد به صورت نامحدود مثل URL
- بدون وابستگی به زبان برنامه‌نویسی یا سیستم‌عامل خاص
## مهم یعنی چه؟

معیار اینکه یک رویداد مهم است یا نیست و باید اینجا اضافه شود یا نه، ویکی‌پدیاست. ما تنها این رویدادها را جمع‌آوری میکنیم، تعیین اینکه آیا یک رویداد به اندازه کافی سرشناس هست که به این مخزن اضافه شود یا نه برمبنای ویکی‌پدیاست، نه تصمیم شخصی یا گروهی افرادی که این مخزن را مدیریت میکنند.

## چگونه همکاری کنم

برای افزودن یک رویداد یا اصلاح یک رویداد موجود میتوانید یا یک [گزارش اشکال](https://github.com/persiancal/cal-events/issues/new/choose) پر کنید یا که یک [درخواست ادغام](https://github.com/persiancal/cal-events/pulls) ایجاد کنید.

فایلهای موجود در پوشه [docs](https://github.com/persiancal/cal-events/tree/master/docs) را برای توضیح ساختار ببینید

برای اینکه در نهایت این مخزن وابسته به من نباشد، آنرا به یک Github Organization منتقل میکنم و مطمئن میشویم که افرادی به جز من با دسترسی کامل به مخزن در دسترس باشند. (تا مثلا اتفاقی که برای `jcal` پیش آمد برای این مخزن پیش نیاید، همچنین هدف افزودن مخازن مرتبط به این گروه هم هست)
## چگونه از این مخزن دربرنامه‌ام استفاده کنم؟

این متن صرفا برای معرفی پروژه نوشته شده و به زودی اصلاح و تکمیل میشود.
فایلهای موجود در برنچ `master` برای استفاده در برنامه شما *نیست*. فایل‌های نتیجه در برنچ [gh-pages](https://github.com/persiancal/cal-events/tree/gh-pages) هستند و فایلهای برنچ مستر سورس فایل نهایی ساخته شده در برنچ `gh-pages` است. اگر شما می‌خواهید که از این رویدادها استفاده کنید، لطفا این برنچ را ببینید.[1]

## من این رویدادها را با فرمت XXX نیاز دارم

در حال حاضر فرمتهای «خروجی» پشتیبانی شده ‍`yaml` و ‍`json` هستند. اگر فرمت دیگری مدنظر شماست میتوانید آن‌را به ابزار ما اضافه کنید (این ابزار با `Go` نوشته شده است) یا اگر نمی‌توانید یک [درخواست](https://github.com/persiancal/cal-events/issues/new) جدید ایجاد کنید.

## چرا Yaml!

برای ورودی اطلاعات (و نه خروجی)‌ما `yaml` را انتخاب کردیم به دلایل زیر :

- این فرمت در مقایسه با `json` یا `xml` برای افراد غیر برنامه‌نویس (کسانی که کامپیوتری نیستند) خواناتر است
- فرمت `json` را میتوان به هر صورتی نوشت. برای برنامه، چندان فرقی نمیکند، ولی برای فایلی که قرار است توسط انسان نگهداری شود مناسب نیست. در صورتی که `yaml` حتی تو رفتگی را هم اجبار میکند
- در نهایت اینکه این ساختار در خروجی تاثیری ندارد و در خروجی همچنان همه فرمتهایی که لازم هستند ایجاد میشوند



[1] به زودی با کمک قابلیت انتشار گیت‌هاب، یک وب سایت استاتیک با لینک دانلود برای رویدادها ایجاد میشود.
</div>
27 changes: 16 additions & 11 deletions cmd/thetool/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ type Event struct {
Title map[string]string `json:"title,omitempty" yaml:"title,omitempty"`
Description map[string]string `json:"description,omitempty" yaml:"description,omitempty"`
Year int `json:"year,omitempty" yaml:"year,omitempty"`
Discontinue int `json:"discontinue,omitempty" yaml:"discontinue,omitempty"`
Month int `json:"month" yaml:"month"`
Day int `json:"day" yaml:"day"`
Calendar map[string][]string `json:"calendar" yaml:"calendar"`
Calendar []string `json:"calendar,omitempty" yaml:"calendar,omitempty"`
Holiday map[string][]string `json:"holiday,omitempty" yaml:",omitempty"`
Sources []string `json:"sources,omitempty" yaml:"sources,omitempty"`
}
Expand All @@ -40,19 +41,20 @@ func (e *Event) CalculateKey(collection string) {
e.Key = hash.Sum32()
}

// Preset is the month structure validator
type Preset struct {
MonthsNormal []int `json:"normal,omitempty" yaml:"normal,omitempty"`
MonthsLeap []int `json:"leap,omitempty" yaml:"leap,omitempty"`
MonthsName []map[string]string `json:"name,omitempty" yaml:"name,omitempty"`
// Months is the month structure validator
type Months struct {
Normal []int `json:"normal,omitempty" yaml:"normal,omitempty"`
Leap []int `json:"leap,omitempty" yaml:"leap,omitempty"`
Name []map[string]string `json:"name,omitempty" yaml:"name,omitempty"`
}

// File is the single file
type File struct {
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Countries []string `json:"countries,omitempty" yaml:"countries,omitempty"`
Months *Preset `json:"months,omitempty" yaml:"months,omitempty"`
Events []Event `json:"events,omitempty" yaml:"events,omitempty"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Countries []string `json:"countries,omitempty" yaml:"countries,omitempty"`
Calendars []map[string]string `json:"calendars,omitempty" yaml:"calendars,omitempty"`
Months *Months `json:"months,omitempty" yaml:"months,omitempty"`
Events []Event `json:"events,omitempty" yaml:"events,omitempty"`
}

func (f *File) Len() int {
Expand Down Expand Up @@ -80,6 +82,10 @@ func (f *File) Merge(new *File) {
f.Months = new.Months
}

if len(f.Calendars) == 0 {
f.Calendars = new.Calendars
}

f.Countries = append(f.Countries, new.Countries...)
f.Events = append(f.Events, new.Events...)
}
Expand Down Expand Up @@ -125,7 +131,6 @@ func loadFolder(folder string) (*File, error) {
if err != nil {
return nil, err
}

res.Merge(f)
}

Expand Down
50 changes: 27 additions & 23 deletions cmd/thetool/links.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,37 @@ var (
ignore *bool
)

func checkLink(wg *sync.WaitGroup, in chan string, out chan error) {
defer wg.Done()
client := http.Client{
Timeout: time.Second,
func checkSingle(client *http.Client, lnk string) error {
u, err := url.Parse(lnk)
if err != nil {
return fmt.Errorf("parse %q failed with err %w", lnk, err)
}
for lnk := range in {
u, err := url.Parse(lnk)
if err != nil {
out <- fmt.Errorf("parse %q failed with err %w", lnk, err)
continue
}

req, err := http.NewRequest("GET", u.String(), nil)
if err != nil {
out <- fmt.Errorf("create request for %q failed with err %w", lnk, err)
continue
}
req, err := http.NewRequest("GET", u.String(), nil)
if err != nil {
return fmt.Errorf("create request for %q failed with err %w", lnk, err)
}

rsp, err := client.Do(req)
if err != nil {
out <- fmt.Errorf("fetch %q failed with err %w", lnk, err)
continue
}
rsp, err := client.Do(req)
if err != nil {
return fmt.Errorf("fetch %q failed with err %w", lnk, err)
}

if rsp.StatusCode != http.StatusOK {
return fmt.Errorf("fetch %q failed with status code %d", lnk, rsp.StatusCode)
}

return nil
}

if rsp.StatusCode != http.StatusOK {
out <- fmt.Errorf("fetch %q failed with status code %d", lnk, rsp.StatusCode)
continue
func checkLink(wg *sync.WaitGroup, in chan string, out chan error) {
defer wg.Done()
client := &http.Client{
Timeout: time.Second * 10,
}
for lnk := range in {
if err := checkSingle(client, lnk); err != nil {
out <- err
}
}
}
Expand Down
29 changes: 29 additions & 0 deletions cmd/thetool/links_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package main

import (
"net/http"
"testing"
"time"

"github.com/stretchr/testify/assert"
)

func TestCheckLink(t *testing.T) {
fixtures := map[string]bool{
"https://fa.wikipedia.org/wiki/صفحهٔ_اصلی": true,
"https://fa.wikipedia.org/wiki/سیقفبغلعاتهنخمثسقیفبغلعاتهنمک۴۵۶۷غعهخحیبلاذتد": false,
}

client := &http.Client{
Timeout: time.Second,
}

for lnk, valid := range fixtures {
err := checkSingle(client, lnk)
if valid {
assert.NoError(t, err)
} else {
assert.Error(t, err)
}
}
}
4 changes: 2 additions & 2 deletions cmd/thetool/split.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ import (
var target *string

func split(cmd *command, fl *File) error {
ev := make([][]Event, len(fl.Months.MonthsNormal))
ev := make([][]Event, len(fl.Months.Normal))
for i := range fl.Events {
ev[fl.Events[i].Month-1] = append(ev[fl.Events[i].Month-1], fl.Events[i])
}

for i := range ev {
name := strings.ToLower(filepath.Join(*target, fmt.Sprintf("%02d-%s.yml", i+1, fl.Months.MonthsName[i]["en_US"])))
name := strings.ToLower(filepath.Join(*target, fmt.Sprintf("%02d-%s.yml", i+1, fl.Months.Name[i]["en_US"])))
b, err := yaml.Marshal(File{
Events: ev[i],
})
Expand Down
47 changes: 36 additions & 11 deletions cmd/thetool/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ func textValidator(s string, rs ...interface{}) error {
}

func validate(cmd *command, fl *File) error {
if err := validateEventCalendar(fl); err != nil {
return fmt.Errorf("validate calendars failed: %w", err)
}

if err := validateEventContent(fl.Events, fl.Months, fl.Countries); err != nil {
return fmt.Errorf("validate events failed: %w", err)
}
Expand All @@ -55,7 +59,7 @@ func isValidCountry(c string, list []string) bool {
return false
}

func validateEventContent(ev []Event, p *Preset, countries []string) error {
func validateEventContent(ev []Event, p *Months, countries []string) error {
for i := range ev {
if ev[i].Key != 0 {
return fmt.Errorf("the Key should not be in the input file")
Expand All @@ -74,10 +78,8 @@ func validateEventContent(ev []Event, p *Preset, countries []string) error {
}

for l, t := range ev[i].Calendar {
for idx, r := range t {
if err := textValidator(r, ev[i].PartialKey, "calendar", l, idx); err != nil {
return err
}
if err := textValidator(t, ev[i].PartialKey, "calendar", l, t); err != nil {
return err
}
}

Expand All @@ -93,22 +95,26 @@ func validateEventContent(ev []Event, p *Preset, countries []string) error {
return fmt.Errorf("the partial key %q is invalid, only lower english chars, _ and numbers are allowed ([a-z0-9_])", ev[i].PartialKey)
}

if ev[i].Month <= 0 || ev[i].Month > len(p.MonthsNormal) {
return fmt.Errorf("invalid month on key %d", i)
if ev[i].Month <= 0 || ev[i].Month > len(p.Normal) {
return fmt.Errorf("invalid month on key %q", ev[i].PartialKey)
}

max := p.MonthsNormal[ev[i].Month-1]
if leap := p.MonthsLeap[ev[i].Month-1]; leap > max {
max := p.Normal[ev[i].Month-1]
if leap := p.Leap[ev[i].Month-1]; leap > max {
max = leap
}

if ev[i].Day <= 0 || ev[i].Day > max {
return fmt.Errorf("invalid day on key %d", i)
return fmt.Errorf("invalid day on key %q", ev[i].PartialKey)
}

if ev[i].Discontinue != 0 && ev[i].Discontinue < ev[i].Year {
return fmt.Errorf("discontinue before year is not allowed %q", ev[i].PartialKey)
}

for country := range ev[i].Holiday {
if !isValidCountry(country, countries) {
return fmt.Errorf("country is invalid: %q in key %d", country, i)
return fmt.Errorf("country is invalid: %q in key %q", country, ev[i].PartialKey)
}
}
}
Expand Down Expand Up @@ -138,6 +144,25 @@ func validateEventOrder(ev []Event) error {
return nil
}

func validateEventCalendar(fl *File) error {
for _, ev := range fl.Events {
if len(ev.Calendar) == 0 {
return fmt.Errorf("event %q has no calendar", ev.PartialKey)
}
middleLoop:
for _, c := range ev.Calendar {
for _, cv := range fl.Calendars {
if cv["en_US"] == c {
continue middleLoop
}
}
return fmt.Errorf("calendar %q for event %q is invalid", c, ev.PartialKey)
}
}

return nil
}

func init() {
cmd := newCommand("validate", "validate the input yaml file", validate)
registerCommand(cmd)
Expand Down
35 changes: 30 additions & 5 deletions cmd/thetool/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,10 @@ func TestValidateEventsContent(t *testing.T) {
},
}

p := &Preset{
MonthsNormal: []int{31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29},
MonthsLeap: []int{31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 30},
MonthsName: nil,
p := &Months{
Normal: []int{31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29},
Leap: []int{31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 30},
Name: nil,
}

for i := range fixtures {
Expand Down Expand Up @@ -130,9 +130,34 @@ func TestTextValidator(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := textValidator(tt.args.s,tt.args.r); (err != nil) != tt.wantErr {
if err := textValidator(tt.args.s, tt.args.r); (err != nil) != tt.wantErr {
t.Errorf("textValidator() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

func TestCalendarValidator(t *testing.T) {
fl := &File{
Calendars: []map[string]string{
{"en_US": "Test1"},
{"en_US": "Test2"},
},
Events: []Event{
{
PartialKey: "valid",
Calendar: []string{"Test1", "Test2"},
},
},
}

assert.NoError(t, validateEventCalendar(fl))

fl.Events[0].Calendar = []string{}

assert.Error(t, validateEventCalendar(fl))

fl.Events[0].Calendar = []string{"INVALID"}

assert.Error(t, validateEventCalendar(fl))
}
Loading

0 comments on commit a9eb3e0

Please sign in to comment.