hayateasdf's blog

Unity,C#, javascript,C++,python,batなど

Google Calendarから祝日を取得

環境

  • VS Code

  • Python 3.6.2

    • pip install

OAuth認証でGoogle Calendar APIに接続する(python 編)

quickstart.pyを実行し、カレンダー利用の許可。
このコードはholiday.pyにリネームする。

GoogleカレンダーAPIを使って、日本の祝日を取得する

コードの下記のAPI実行の部分を利用する。
holiday.pyに記述。

Pythonで「指定した年と月」の「すべての日付」を表示する関数をつくってみた

上記の方法で1年分のカレンダーを作成する。
新規でtest.py作成

holiday.py

from __future__ import print_function
import httplib2
import os

from apiclient import discovery
from oauth2client import client
from oauth2client import tools
from oauth2client.file import Storage

from datetime import date

try:
    import argparse
    flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args()
except ImportError:
    flags = None

# If modifying these scopes, delete your previously saved credentials
# at ~/.credentials/calendar-python-quickstart.json
SCOPES = 'https://www.googleapis.com/auth/calendar.readonly'
CLIENT_SECRET_FILE = 'client_secret.json'
APPLICATION_NAME = 'Google Calendar API Python Quickstart'


def _get_credentials():
    """Gets valid user credentials from storage.

    If nothing has been stored, or if the stored credentials are invalid,
    the OAuth2 flow is completed to obtain the new credentials.

    Returns:
        Credentials, the obtained credential.
    """
    home_dir = os.path.expanduser('~')
    credential_dir = os.path.join(home_dir, '.credentials')
    if not os.path.exists(credential_dir):
        os.makedirs(credential_dir)
    credential_path = os.path.join(credential_dir,
                                   'calendar-python-quickstart.json')

    store = Storage(credential_path)
    credentials = store.get()
    if not credentials or credentials.invalid:
        flow = client.flow_from_clientsecrets(CLIENT_SECRET_FILE, SCOPES)
        flow.user_agent = APPLICATION_NAME
        if flags:
            credentials = tools.run_flow(flow, store, flags)
        else: # Needed only for compatibility with Python 2.6
            credentials = tools.run(flow, store)
        print('Storing credentials to ' + credential_path)
    return credentials

def get_holiday_events(year):
    """Shows basic usage of the Google Calendar API.

    Creates a Google Calendar API service object and outputs a list of the next
    10 events on the user's calendar.
    """
    credentials = _get_credentials()
    http = credentials.authorize(httplib2.Http())
    service = discovery.build('calendar', 'v3', http=http)

    calendar_id = 'ja.japanese#holiday@group.v.calendar.google.com'
    calendar_min = date(year=year, month=1, day=1).isoformat() + 'T00:00:00.000000Z'
    calendar_max = date(year=year, month=12, day=31).isoformat() + 'T00:00:00.000000Z'

    event_results = service.events().list(
      calendarId = calendar_id,
      timeMin = calendar_min,
      timeMax = calendar_max,
      maxResults = 50,
      singleEvents = True,
      orderBy = "startTime"
    ).execute()

    events = event_results.get('items', [])
    # for event in events:
    #   print("%s\t%s" % (event["start"]["date"], event["summary"]))
    
    return events;

test.py

import calendar
import holiday
from datetime import date

def get_year_days(year, month):
  month_days = [i+1 for i in range(calendar.monthrange(year, month)[1])]
  days = list(map(lambda day: "{0:02d}/{1:02d}/{2:02d}".format(year, month, day), month_days))
  return days

def main(year):
  months = [i+1 for i in range(12)]
  year_days = list(map(lambda month: get_year_days(year, month), months))

  events = holiday.get_holiday_events(year)
  holiday_dict = dict(
        (event["start"]["date"].replace("-", "/"), event["summary"])
        for event in events
      )

  for month in year_days:
    for day in month:
      if day in holiday_dict:
        print(day + " " + holiday_dict[day])
      else:
        print(day)

if __name__ == '__main__':
  main(2017)

結果

2017/01/01 元日
2017/01/02 元日 振替休日
2017/01/03
2017/01/04
2017/01/05
2017/01/06
2017/01/07
2017/01/08
2017/01/09 成人の日
2017/01/10
2017/01/11
2017/01/12
...
2017/12/23 天皇誕生日
2017/12/24
2017/12/25
2017/12/26
2017/12/27
2017/12/28
2017/12/29
2017/12/30
2017/12/31

css gridレイアウト 1.

参考

CSS Grid Layout を極める!(基礎編)
A Complete Guide to Grid

目的

  • display: grid;を使ってみる
  • grid-template-rows, grid-template-columnsでの画面サイズ設定
  • grid-template-areasでのgrid-area画面レイアウト

See the Pen CSS display:grid; 1 by tikyu (@tikyuu) on CodePen.

Unity Xcode アーカイブ、 ipa作成自動化

UnityでXcodeからipa出力までの各設定の自動化をやってみた。

できた。

※ 勘で書いた部分もあるので注意。

参考

XcodeAPIの使い方
CIなどを使用するときに必要な値を確認する方法
UnityプロジェクトをJenkinsを使用してXCode8対応した
Unityでbashを利用する   xcodebuildでipa作成 -exportOptionsPlist対応版

流れ

  • 1 exportOptions.plist作成、DEVELOPMENT_TEAMの10桁のコードを確認。
  • 2 下記コードのvar ipa_plist = "path/to/exportOptions.plist";の部分を作成したplistのパスに変更。 pbx.SetBuildProperty(targetGuid, "DEVELOPMENT_TEAM", "xxxxxxxxxx");のxの部分に10桁のコードを設定。
  • 3 iOSビルド後に、OnPostprocessBuildが呼ばれる
  • 4 ProjectSettingでXcodeのプロジェクトを書き換える
  • 5 PlistSettingでplistを書き換える
  • 6 AutomationCreatorで、automation.commandという自動化用bashスクリプトを作成し、chmod -xで実行権限を付与。
  • 7 生成されたプロジェクト内のautomation.commandをダブルクリックで実行
  • 8 hoge.xcarchiveとhoge.ipaが作成される

XcodeProjectUpdater.cs

#if UNITY_IOS
using UnityEngine;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEditor.iOS.Xcode;
using System.IO;
using System.Collections;
using System.Diagnostics;

// http://kan-kikuchi.hatenablog.com/entry/XcodeAPI
public class XcodeProjectUpdater
{
    [PostProcessBuild(100)] // layer 100 小さい方が先に実行
    static void OnPostprocessBuild(BuildTarget buildTarget, string path)
    {
        if (buildTarget != BuildTarget.iOS) return;
        UnityEngine.Debug.Log ("xcode project update onpostprocess!");

        ProjectSetting (path);
        PlistSetting (path);

        AutomationCreator (path, "hoge");
    }

    static void AutomationCreator(string path, string output_name)
    {

        var cmd_cd = "cd `dirname $0`";

        var camera_capture_path = Path.Combine (path, "Classes/Unity/CameraCapture.mm");
        var cmd_camera_reset = string.Format ("sed -i -e 's/!UNITY_TVOS/0/g' \"{0}\"", camera_capture_path);

        var xcodeproj_path = Path.Combine (path, "Unity-iPhone.xcodeproj");
        output_name = Path.Combine (path, output_name);
        var cmd_archive = string.Format("xcodebuild -project \"{0}\" -scheme \"Unity-iPhone\" archive -archivePath \"{1}\"",
            xcodeproj_path,
            output_name
        );

        // http://qiita.com/roworks/items/7ef12acabf9679561d84
        var ipa_plist = "path/to/exportOptions.plist";
        var cmd_ipa = string.Format("xcodebuild -exportArchive -archivePath \"{0}.xcarchive\" -exportPath \"{0}.ipa\" -exportOptionsPlist \"{1}\"",
            output_name,
            ipa_plist
        );

        var output_path = Path.Combine (path, "automation.command");
        using(var sw = new StreamWriter(output_path))
        {
            sw.WriteLine (cmd_cd);
            sw.WriteLine (cmd_camera_reset);
            sw.WriteLine (cmd_archive);
            sw.WriteLine (cmd_ipa);
        }

        BashProcess ("chmod +x " + output_path);
    }

    static void ProjectSetting(string path)
    {
        var pbxProjPath = PBXProject.GetPBXProjectPath (path);
        var pbx = new PBXProject ();
        pbx.ReadFromString (File.ReadAllText (pbxProjPath));

        var targetGuid = pbx.TargetGuidByName (PBXProject.GetUnityTargetName ());

        pbx.SetBuildProperty (targetGuid, "ENABLE_BITCODE", "NO");
        pbx.SetBuildProperty (targetGuid, "ARCHS", "arm64");
        pbx.SetBuildProperty (targetGuid, "VALID_ARCHS", "arm64");
        pbx.SetBuildProperty (targetGuid, "IPHONEOS_DEPLOYMENT_TARGET", "9.0");

        // http://qiita.com/Akira_Kido_N/items/25ffd68cade64ecade2e
        // http://qiita.com/altemina/items/c4747f26615792495a97
        pbx.SetBuildProperty(targetGuid, "DEVELOPMENT_TEAM", "xxxxxxxxxx"); // 10桁のコード

        File.WriteAllText (pbxProjPath, pbx.WriteToString ());
    }
    static void PlistSetting(string path)
    {
        var pListPath = Path.Combine (path, "Info.plist");
        var plist = new PlistDocument ();
        plist.ReadFromFile (pListPath);

        #region ローカライズ 日本語
        plist.root.SetString ("CFBundleDevelopmentRegion", "ja_JP");
        // plist.root.CreateDict ("CFBundleLocalizations");
        var locallization = plist.root.CreateArray ("CFBundleLocalizations");
        locallization.AddString ("ja");
        #endregion

        plist.WriteToFile (pListPath);
    }

    static bool isWorking = false;
    // http://smartgames.hatenablog.com/entry/2016/06/21/230714
    static void BashProcess(string cmd)
    {
        if (isWorking) { 
            UnityEngine.Debug.Log ("working.");
            return;
        }
        isWorking = true;

        var p = new Process ();
        p.StartInfo.FileName = "/bin/bash";
        p.StartInfo.Arguments = "-c \" " + cmd + " \"";
        p.StartInfo.UseShellExecute = false;
        p.StartInfo.RedirectStandardOutput = true;
        p.Start ();

        var output = p.StandardOutput.ReadToEnd ();
        p.WaitForExit ();
        p.Close ();

        isWorking = false;
        UnityEngine.Debug.Log (output);
    }
}

#endif // UNITY_IOS

unity YamlDotNet for Unityを使いやすくするオレオレスクリプト

http://qiita.com/asus4/items/bac121c34cd3169116c0

上記の記事を見てもらいたい。 わたしが言いたいことも同じで、YamlDotNetを使っていると型キャストがめんどくさい。

ので、上記記事のJsonNodeの作りを丸パクリしてYamlNode用のYamlInstant(即席yaml)を作成しました。

using System.Collections;
using System.Collections.Generic;
using System;
using YamlDotNet;
using YamlDotNet.RepresentationModel;

// 即席Yaml
public class YamlInstant : IEnumerable<YamlInstant>, IDisposable
{
    YamlNode obj;
    public YamlInstant(YamlNode obj) { this.obj = obj; }
    public void Dispose() { obj = null; }

    public YamlInstant this [YamlNode key] { get { return new YamlInstant(toMap()[key]); } }
    public YamlScalarNode toValue() { 
        if (obj is YamlScalarNode) {
            return (YamlScalarNode)obj; 
        } else {
            throw new Exception("YamlScalarNode != " + obj.GetType().Name);
        }
    }
    public YamlMappingNode toMap() { 
        if (obj is YamlMappingNode) {
            return (YamlMappingNode)obj; 
        } else {
            throw new Exception("YamlMappingNode != " + obj.GetType().Name);
        }
    }
    public YamlSequenceNode toList() {
        if (obj is YamlSequenceNode) {
            return (YamlSequenceNode)obj;
        } else {
            throw new Exception("YamlSequenceNode != " + obj.GetType().Name);
        }
   }
    public string Value { get { return toValue().Value; } }
    public IEnumerator<YamlInstant> GetEnumerator()
    {
        var list = toList();
        foreach (YamlMappingNode o in list)
        {
            yield return new YamlInstant(o);
        }
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

使用例

AnimationCurveのプリセットにペナーイージングを加えるから.curves ファイルをお借りして.curvesファイル(yaml)から 直で値を引っ張ってきてcurveを生成する。

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using YamlDotNet;
using YamlDotNet.RepresentationModel;

public class YamlTest : MonoBehaviour {
    public List<AnimationCurve> curve = new List<AnimationCurve>();

    void Start () {
        var path = "Assets/Editor/EasingCurves.curves";
        var yaml = new YamlStream();
        using (var file = new StreamReader(path, System.Text.Encoding.UTF8))
        {
            yaml.Load(file);
        }

        var yi = new YamlInstant(yaml.Documents[0].RootNode);
        var presets = yi["MonoBehaviour"]["m_Presets"];

        foreach (var item in presets)
        {
            var name = item["m_Name"].Value;
            Debug.Log("name: " + name);

            var ary = item["m_Curve"]["m_Curve"];
            var keys = new List<Keyframe>();
            foreach (var data in ary)
            {
                var time = TryFloat(data["time"].Value);
                var value = TryFloat(data["value"].Value);
                var inslope = TryFloat(data["inSlope"].Value);
                var outslope = TryFloat(data["outSlope"].Value);

                keys.Add(new Keyframe(time, value, inslope, outslope));
            }
            curve.Add(new AnimationCurve(keys.ToArray()));
        }

    }
    public static float TryFloat(string s)
    {
        var f = -1f;
        float.TryParse(s, out f);
        return f;
    }
}

Unity uGUI ボタンを自作 & 拡張する

ButtonExtension.cs

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Events;
using UnityEngine.EventSystems;

[RequireComponent(typeof(Image))]
public class ButtonExtension : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IPointerEnterHandler, IPointerExitHandler
{
    [Header("押した瞬間")]
    public UnityEvent eventDown;
    [Header("離した瞬間")]
    public UnityEvent eventUp;
    [Header("押されている状態で入った瞬間")]
    public UnityEvent eventEnter;
    [Header("押されている状態で離れた瞬間")]
    public UnityEvent eventExit;


    bool isPush = false;
    bool isEnter = false;
    Image image;

    void Start()
    {
        #region test
        image = GetComponent<Image>();

        eventDown.AddListener(() => image.color = new Color(1f, 0f, 0f));
        eventUp.AddListener(() => image.color = new Color(1f, 1f, 1f));
        eventEnter.AddListener(() => image.color = new Color(1f, 0f, 0f));
        eventExit.AddListener(() => image.color = new Color(1f, 1f, 1f));
        #endregion
    }

    void OnEnable()
    {
        isPush = false;
        isEnter = false;
    }

    #region event
    public void OnPointerDown(PointerEventData eventData)
    {
        isPush = true;

        if (eventDown != null) eventDown.Invoke();
    }
    public void OnPointerUp(PointerEventData eventData)
    {
        isPush = false;
        if (!isEnter) return;  // オブジェクト内のみ有効

        if (eventUp != null) eventUp.Invoke();
    }
    public void OnPointerEnter(PointerEventData eventData)
    {
        isEnter = true;
        if (!isPush) return;

        if (eventEnter != null) eventEnter.Invoke();
    }
    public void OnPointerExit(PointerEventData eventData)
    {
        isEnter = false;
        if (!isPush) return;

        if (eventExit != null) eventExit.Invoke();
    }
    #endregion
}