おまじないの綴り方

spelling of a logical spell

ajax通信のライブラリを使いつつ、直列に複数のPOSTを送る

まとめ

  • async/awaitを使うと関数から返ってきたPromiseがresolveされるまで処理を止めることができる
  • ajax通信で通信終了まで待機するには、ajax通信を行う部分をreturn new Promiseでくるみ、通信が終わったらresolveする
  • ajax通信の呼び出し元はawaitしておく

基本形

※ サンプルではajax通信用にsuperagentを使用

https://visionmedia.github.io/superagent/

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8"/>
  <!-- superagentのimport -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/superagent/3.8.3/superagent.min.js"></script>
</head>
<body>
  <button onclick="postAll()">post</button>
  
  <script>
    //asyncを先頭につけることでawaitが使えるようになる
    //awaitは戻り値(Promise)がresolveされるまで処理を止めて待機する
    async function postAll(){

      await post()
      await post()

    }

    function post(){

      return new Promise(function(resolve, reject){

        var request = window.superagent
        url = "post先のURL"
        parameter = {
          //postするパラメータ
        }

        request.post(url)
        .send(parameter)
        .end(function(err, res){
          //通信が終わったらresolveする
          resolve()
         })
      })
    }

  </script>

</body>
</html>

変形: ループに適用

postAll()の中身を下記のようにする

    async function postAll(){
      for(var i = 0; i < 10; i++){
        await post()
      }
    }

おまけ: 簡易的な並列度の調整

5こずつ、10こずつなど全部ではないものの複数のjobをまとめて投げたいときもあるので、 一度にpostする量を変数でコントロールできるようにする

※ 全部一気に投げて良いならPromise.Allなどを使ったほうが楽

    async function postAll(){

      //同時に処理するjobの数をコントロールする変数
      concurrency = 10; 

     //一番最後のjobも実行されることを保証したいので、少し多めにforを回す
     //(もっときれいな書き方がありそう)
      for(var i = 0; i < num_job + concurrency; i+=concurrency){

        //post()が返すpromise型の変数を格納する配列
        //この中にjobを追加していく。
        promises = []
        for(var j = 0; j < concurrency; j++){
          job_id = i + j
          //すこし多めにforを回しているため余計なif文がある
          if(job_id < num_job){
            promises.push(post())
          }
        }

        //promisesに入っているjobが全て完了するまで待機
        for(var j in promises){
          await promises[j]
        }
      }
    }

bash on windowsにawscli入れようとしたらyaml.hがないと怒られた

やること

sudo apt-get install libyaml-dev

経緯

bash on windows環境でawscliを入れるために下記コマンドを叩いたら、yaml.hがないのでコンパイルできない的なエラーで怒られた

sudo pip3 install awscli

参考のサイトによると「libyaml-dev」を入れてからawscli入れればエラーが出ないとのことだったので、上記apt-getを叩いてからもう一度pip3 installしたらエラーが出なくなった。

感想

すごいバッドノウハウ感あるけど副作用ないんだろうか。。。?
とりあえずちゃんと動いてそうなのでしばらく触ってみる

参考

www.pradeepadiga.me

boto3 + DynamoDBでリストに要素を追加・削除

目標

dynamoDBに入っているリスト要素に対して、追加・削除を行う

環境

コード

import boto3
import decimal
import json
from boto3.dynamodb.conditions import Key, Attr

# DB設定
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('table')

# Helper class to convert a DynamoDB item to JSON.
# AWSのドキュメントにあったもの。これがないとnumber <-> floatの変換周りでエラーになる
class DecimalEncoder(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, decimal.Decimal):
            if o % 1 > 0:
                return float(o)
            else:
                return int(o)
        return super(DecimalEncoder, self).default(o)

# 本体
def lambda_handler(event, context):

    ##### 要素の削除

    # リストのうち削除したい要素番号を定義しておく
    index = 0

    res = table.update_item(
        Key={
            # アップデートするレコードの条件
            # 例: idカラム(プライマリキー)=1のとき
            'id': 1,
        },
        # Remove命令でリストから要素を削除する。
        # 基本形: REMOVE カラム名[要素番号]
        # ExpressionAttributeValuesでの要素番号指定は(何故か)できないので、削除する要素番号が可変の場合は無理やり渡している
        UpdateExpression="REMOVE #attribute[" + str(index) + "]",
        ExpressionAttributeNames= {
            # UpadateExpressionで使っている#attributeを置き換える
            # リスト要素の名前、今回はtarget_listだと仮定する
            '#attribute': "target_list"
        },
        # DB操作に直接影響しないので好みに合わせて
        ReturnValues="UPDATED_NEW"
    )

    ##### 要素の追加

    # 追加する要素には、数値・文字列・バイナリが指定可能
    # boto経由だと、変数の型が考慮されるようなので注意(そのかわりN,S,Bの指定はいらない?)
    append_value = 0

    res = table.update_item(
        Key={
            # 例: idカラム(プライマリキー)=1のとき
            'id': 1,
        },
        # list_appendでlist同士をつなぎ、SET命令で新しいリストを代入する
        # 基本形: SET カラム名 = list_append(リスト1, リスト2)
        # list同士の結合命令なので、追加要素が一つしかなくてもリストの形で渡す
        # ExpressionAttributeValuesが使えるのでこちらを使ったほうがよさそう
        UpdateExpression="SET #attribute = list_append(#attribute, :val)",
        ExpressionAttributeNames= {
            # UpadateExpressionで使っている#attributeを置き換える
            # リスト要素の名前、今回はtarget_listだと仮定する
            '#attribute': "target_list"
        },
        ExpressionAttributeValues={
            # UpadateExpressionで使っている:valを実際の値で置き換える
            ':val': [append_value]
        },
        # DB操作に直接影響しないので好みに合わせて
        ReturnValues="UPDATED_NEW"
    )

備考

Number Setに対して同じことをやろうとするときはADD命令を使うらしいが、うまく行かなかった。 botoで実現可能なのだろうか。。。

jsで簡易マインドマップエディタを作る

できたもの

f:id:miyatsuki_yatsuki:20171217202012p:plain

できること

  • textarea内に記載された箇条書きに従ってマインドマップ(SVG)をリアルタイムに生成する
  • 依存ライブラリがjqueryだけなので、ブラウザさえあれば特別なソフトなしで使用可能

できないこと

実装上のポイント

  • jsから動的にSVG要素を作るときは、createElementではなくcreatElementNSを使う
  • SVGで自動改行するテキストボックスを作るにはforeignObject要素の子要素としてdivを配置
    • その際foreignObjectはSVG要素の直下に置く。
    • foreignObject配下にdivを作るときもcreateElementNSを使っておいたほうがよさそう
var svgNS = "http://www.w3.org/2000/svg";
var htmlNS = "http://www.w3.org/1999/xhtml"

var foreignElement = document.createElementNS(svgNS, "foreignObject");
foreignElement.setAttribute("width", nodeWidth);
foreignElement.setAttribute("height", nodeHeight); 
foreignElement.setAttribute("x", x);
foreignElement.setAttribute("y", y);

var innerElement = document.createElementNS(htmlNS, "div");
innerElement.innerText = text;

document.getElementById("map").appendChild(foreignElement);
foreignElement.appendChild(innerElement);

コード

<html>
  <head>
    <script type="text/javascript" src="jquery-3.2.1.min.js"></script>    
  </head>

  <style>

  </style>

  <body>
<div>
    <div style="float:left">
        <textarea id="text" name="text" rows="20" cols="40"></textarea>
    </div>
    <div style="float:left;">
      <svg id="map" width="700" height="500"></svg>
    </div>
</div>

    <script type="text/javascript">

    var svgNS = "http://www.w3.org/2000/svg";
    var htmlNS = "http://www.w3.org/1999/xhtml"

    var inputText = "";
    $("#text").keyup(function(){
        if(inputText != $("#text").val())
        {
            inputText = $("#text").val()
            drawMap(inputText);
        }
    })

    var nodeWidth = 100;
    var nodeHeight = 50;
    var xMargin = 50;
    var yMargin = 20;

    function drawMap(text)
    {
        $("#map").empty();

        console.log(text)
        nodeArray = parseText(text);
        nodeArray = decideNodePosition(nodeArray);
        console.log(nodeArray);

        for(var i = 0; i < nodeArray.length; i++)
        {
            drawSingleNode(nodeArray[i].text, nodeArray[i].x, nodeArray[i].y)
            var children = nodeArray[i].children;
            for(var j = 0; j < children.length; j++)
            {
                connectNodes(nodeArray[i], children[j]);                
            }
        }
    }

    function decideNodePosition(nodeArray)
    {
        var leafCounter = 0;

        for(var i = 0; i < nodeArray.length; i++)
        {
            var level = nodeArray[i].level;
            nodeArray[i]["x"] = level*(nodeWidth + xMargin)

            if(nodeArray[i].children.length == 0)
            {
                nodeArray[i]["y"] = leafCounter*(nodeHeight + yMargin)
                leafCounter++;
            }
        }

        for(var i = nodeArray.length - 1; i >= 0; i--)
        {
            if(nodeArray[i].children.length > 0)
            {
                var y = 0;
                var middleNodeID = Math.floor(nodeArray[i].children.length/2);

                if(nodeArray[i].children.length % 2 == 1)
                {
                    y = nodeArray[i].children[middleNodeID].y;
                }
                else
                {
                    var y1 = nodeArray[i].children[middleNodeID].y;
                    var y2 = nodeArray[i].children[middleNodeID - 1].y;
                    y = (y1 + y2)/2;
                }

                nodeArray[i]["y"] = y;
            }
        }

        return nodeArray
    }

    function drawSingleNode(text, x, y)
    {
        var rectElement = document.createElementNS(svgNS, "rect");
        rectElement.setAttribute("width", nodeWidth);
        rectElement.setAttribute("height", nodeHeight);
        rectElement.setAttribute("x", x);
        rectElement.setAttribute("y", y);
        rectElement.setAttribute("fill", "white");
        rectElement.setAttribute("stroke", "black");
        var foreignElement = document.createElementNS(svgNS, "foreignObject");
        foreignElement.setAttribute("width", nodeWidth);
        foreignElement.setAttribute("height", nodeHeight); 
        foreignElement.setAttribute("x", x);
        foreignElement.setAttribute("y", y);
        var innerElement = document.createElementNS(htmlNS, "div");
        innerElement.innerText = text;
        innerElement.style["padding"] = "0 5 0 5";

        document.getElementById("map").appendChild(rectElement);
        document.getElementById("map").appendChild(foreignElement);
        foreignElement.appendChild(innerElement);
    }

    function connectNodes(fromNode, toNode)
    {
        var lineElement = document.createElementNS(svgNS, "line");
        lineElement.setAttribute("x1", fromNode.x + nodeWidth);
        lineElement.setAttribute("y1", fromNode.y + nodeHeight/2);
        lineElement.setAttribute("x2", toNode.x);
        lineElement.setAttribute("y2", toNode.y + nodeHeight/2);
        lineElement.setAttribute("stroke", "black");

        document.getElementById("map").appendChild(lineElement);
    }


    function parseText(text)
    {
        textArray = text.split("\n")
        mapObject = new Object();
        nodeArray = [];

        levelArray = [];
        levelArray[0] = null;

        for(var i = 0; i < textArray.length; i++)
        {
            var level = 0;
            var text = textArray[i];

            for(var j = 0; j < text.length; j++)
            {
                if(text.startsWith("*"))
                {
                    text = text.substring(1);
                    level++;
                }
                else
                {
                    break;
                }
            }

            text = text.trim();

            var node = {id: i.toString(), text: text, level: level, parent: levelArray[level]}
            nodeArray.push(node)
            levelArray[level + 1] = node;
        }
        
        childrenMap = {};

        for(var i = nodeArray.length - 1; i >= 0; i--)
        {
            if(nodeArray[i].id in childrenMap)
            {
                nodeArray[i]["children"] = childrenMap[nodeArray[i].id];
            }
            else
            {
                nodeArray[i]["children"] = [];                
            }

            if(nodeArray[i].parent != null)
            {
                if(!(nodeArray[i].parent.id in childrenMap))
                {
                    childrenMap[nodeArray[i].parent.id] = []
                }

                childrenMap[nodeArray[i].parent.id].push(nodeArray[i]);
            }
        }

        return nodeArray;
    }

    </script>

  </body>


</html>

excel vbaで画像の位置を取得する

目標

アクティブシート内の図形/画像の中心位置を座標(ピクセル単位)で取得する

やり方

状況

以下のようにシート内に図形や図が幾つか設置されており、その中心座標を取得することを考えます

f:id:miyatsuki_yatsuki:20170702160430p:plain

コード

中心座標を直接取得することはできないので、左(上)の座標+幅(高さ)/2することで中心座標を取得します

Sub TopAndLeftSamp1()
    Dim Sh As Shape
    
    For Each Sh In ActiveSheet.Shapes  '---アクティブシート全ての図形に対し
      Debug.Print Sh.Name & ":" & (Sh.Left + Sh.Width / 2) & "," & (Sh.Top + Sh.Height / 2)
    Next Sh
    
End Sub

結果

Rectangle 1:118.125,87
Rectangle 2:346.125,226.5
Rectangle 3:204.375,243.75
Rectangle 4:437.625,96.75
Rectangle 5:475.875,374.25
Picture 7:597.3749,225.3749

結果は 図形名(英語) : x座標, y座標 の形式で出力されています。

図の正方形/長方形1は正方形/長方形2より右下にありますが、x, yともにRectangle2のほうがRectangle1より大きい値を示しているので問題なく取得できていそうです。

参考

Excel VBA を学ぶなら moug モーグ | 即効テクニック | 図形の位置を設定する

xargs + wget で ファイルリストにあるurlをすべて落として名前をつける

目標

  • 事前に用意したurlリストに記述されたURLをまとめてダウンロードする
  • 落としたファイルの名前を元々のURLにファイル名として使えない文字が入っていると辛いので、保存先の名前を少し置き換える

それぞれ単独ならオプション1つだけで実現できるのに、同時に実現しようとすると急に辛くなる

参考: 単独で動かす場合

事前に用意したurlリストに対してまとめて落とす

wget -i ファイル名
例: wget -i urlList.txt

ファイル名を設定する方法

wget -O 保存先ファイル名 URL
例: wget -O おまじないの綴り方.html http://spell-spell.hatenablog.com/entry/2017/03/25/175513

やり方

検証環境

bash on windows Linux 4.4.0-43-Microsoft #1-Microsoft Wed Dec 31 14:42:53 PST 2014 x86_64 x86_64 x86_64 GNU/Linux

準備

wget -i で使えるような普通のurlではなく、urlの一部からなる保存ファイル名を記載したリストを作る

例:自分のブログをgoogleで調べたときの結果を調べることを想定。 https://www.google.co.jp/search?q=検索クエリ というURLで検索できるので、今回は検索クエリの部分をファイル名にすることにする。 以下のようにファイル名を並べたファイルをfile_name.txtとして保存する。

おまじないの綴り方
うらがみずむ

コード

xargs -a file -I{} wget -O {}.html URL_prefix/{}
例: xargs -a file_name.txt -I{} wget -O {}.html https://www.google.co.jp/search?q={}

説明

xargs

xargsは標準入力として受け取った文字列を任意のコマンドの引数として渡すことができるコマンド。 以下の例ではecho “abc”| によって受け取った"abc"という文字列をxargsの後ろのechoに渡している

$ echo "abc" | xargs echo
abc
$

汎用性が極めて高いコマンドなので理解するのがやや難しいですが、ネット上の情報も多いので、他のサイトも調べてみるといいかもしれません。

aオプション

パイプ等で標準入力を受け取る代わりに、ファイルから読み取った文字列を引数として渡せるオプション。

I(大文字のアイ)オプション

直後に指定した文字列を置換用の文字列して指定できるオプション。 例えば以下の様に使用する。

$ (例)echo "abc" | xargs -I{} wget {}.html
$ (置換後)wget abc.html

例はabcという文字列をxargs経由でwgetに渡した例。-I{}によりxargsで渡ってきたabcが{}という文字列と置き換わるので、結局この例はwget abc.htmlというコマンドと等価になる。

wgetとの連携

xargs -I{} により、wgetコマンドの部分に記述されている{}の部分はxargsから渡された文字列に置き換わる。 そのため、file_name.txtに"おまじないの綴り方"と"うらがみずむ"が記録されている場合、

xargs -a file_name.txt -I{} wget -O {}.html https://www.google.co.jp/search?q={}

を実行すると"おまじないの綴り方"と"うらがみずむ"が文字列としてwgetに渡され、{}が置き換わるので、

wget -O おまじないの綴り方.html https://www.google.co.jp/search?q=おまじないの綴り方
wget -O うらがみずむ.html https://www.google.co.jp/search?q=うらがみずむ

が順番に実行され、本来ならsearch?q=おまじないの綴り方.htmlというファイル名で保存されるところが、おまじないの綴り方.htmlとして保存される。

注意点

  • 上記の様にgoogleの検索結果に対してwgetを書ける場合はユーザーエージェントの偽装を別途行う必要がある
  • bash on windows環境で上記を動かす場合は、file_name.txtの改行コードをLFにしないと動かない

マウスの位置によって表示位置を変えるツールチップをjsとCSSで実装する

目標

f:id:miyatsuki_yatsuki:20170325174822g:plain

前フリ

spell-spell.hatenablog.com

spell-spell.hatenablog.com

考え方

  • 画面内のマウスの位置に応じてツールチップの表示位置を変える
  • マウスが画面左上よりにいたら、ツールチップは右下方向へ、左下なら右上へといったように変えていく

コード

<html>
  <style>

  <!--ツールチップの見た目-->
  span#tooltip{
    position: absolute;   <!--座標で表示位置を直接コントロールできるようにする-->
    z-index: 10; <!--他の要素より前に表示されるように-->
    visibility: hidden; <!--最初は隠しておく-->
    padding: 0 5px;
    background-color: #FFF;
  }

 <!--ツールチップを表示させる部分の見た目。わかればなんでもいい-->
  div {
    background: #abcdef;
    width:300px;
    height:300px;
  }
  </style>

<html>
  <style>
  span#tooltip{
    position: absolute;
    z-index: 10;
    visibility: hidden;
    padding: 0 5px;
    background-color: #FFF;
  }

  div {
    background: #abcdef;
    width:300px;
    height:300px;
  }
  </style>

  <body>
    <span id="tooltip">hoge</span>
    <div onmousemove="move()" onmouseover="over()" onmouseout="out()"></div>
    <br>
    <br>
    <div onmousemove="move()" onmouseover="over()" onmouseout="out()"></div>

    <script type="text/javascript">
      function over()
      {
        var tooltip = document.getElementById("tooltip");
        tooltip.style.visibility = "visible";
      }

      function out()
      {
        var tooltip = document.getElementById("tooltip");
        tooltip.style.visibility = "hidden";
      }

      function move(){
        var tooltip = document.getElementById("tooltip")

        //前々回のバージョン:特にマウス位置は考慮しない
        //var xPos = event.pageX + 10; 
        //var yPos = event.pageY - 10;

        //前回のバージョン:ツールチップが隠れてしまわないように適切な座標を選択する
        var xPos = Math.min(event.pageX + 10, window.pageXOffset + window.innerWidth - tooltip.getBoundingClientRect().width - 10);
        var yPos = Math.min(event.pageY - 20, window.pageYOffset + window.innerHeight - tooltip.getBoundingClientRect().height -20);

        var xPos = 0;
        if(event.pageX < window.innerWidth/2) //マウスカーソル位置が画面の左半分側かどうか
        {
          xPos = event.pageX + 10; //左半分にいたらカーソル位置の右側にツールチップを表示
        }
        else
        {
          xPos = event.pageX - tooltip.getBoundingClientRect().width - 10; //右側にいたらカーソルの右側にツールチップを表示
        }

        var yPos = 0;
        if(event.pageY < window.innerHeight/2) //マウスカーソル位置が画面の上半分かどうか
        {
          yPos = event.pageY + 10 //上半分にいたらカーソル位置の下にツールチップを表示
        }
        else
        {
          yPos = event.pageY - tooltip.getBoundingClientRect().height - 10; //下半分にいたらカーソル位置の上にツールチップを表示
        }


        tooltip.style.top = yPos + "px";
        tooltip.style.left = xPos + "px";
        tooltip.innerHTML = "xPos:" + xPos + "<br/>" + "yPos:" + yPos;
      }
    </script>
  </body>
</html>

問題点

ツールチップの内容が、画面サイズに対して一定以上大きい場合、結局ツールチップが隠れたり、重なってしまったりする問題は起きうる

ただ、これ以上はツールチップの位置よりも中身や表示サイズなどを工夫したほうが良さそう