ちょっと疑問に思ったことを調べた

echo の n オプションがスクリプト書くと反映されていなかった。なので、ちょっと調べた

% which echo
echo: shell built-in command
% cat > test.sh && chmod u+x test.sh && ./test.sh
which echo
/bin/echo

そもそも違うechoが実行されていたようだ。
ちなみに

% echo ${SHELL}
/usr/local/bin/zsh

である。
結果としてbuilt-inのechoは-nが使える。

#! /usr/local/bin/zsh
# test.sh with zsh
echo -n test
echo test
% ./test.sh
testtest

でもshだと

#! /bin/sh
# test.sh with sh
echo -n test
echo test
% ./test.sh
-n test
test

kivyのログを出力するTextInputをつくってみた

タイトルではわかりにくいけど、kivyのログを反映するTextInputをつくってみたが正しいのではないかと思う。

方法

今回は、kivyのLoggerにStreamHandlerを追加して反映させることにした。委譲のAdapterっぽい感じでio.TextIOBaseを継承するTextInputIOなるものをつくり、必要なものを実装していった。

テスト

import unittest

from kivy.uix.textinput import TextInput

from app.TextInputIO import TextInputIO


class TextInputIOTestCase(unittest.TestCase):
    """
    Test for TextInputIO class
    """

    def setUp(self):
        self.text_input = TextInput()

    def test_as_context_manager(self):
        with TextInputIO(self.text_input) as f:
            pass

    def test_open_close(self):
        f = TextInputIO(self.text_input)
        f.close()

    def test_write(self):
        f = TextInputIO(self.text_input)
        f.write('TESTING TEXT INPUT\n')
        f.close()

    def test_non_readable(self):
        f = TextInputIO(self.text_input)
        self.assertRaises(OSError, f.read)
        f.close()

    def test_non_seekable(self):
        f = TextInputIO(self.text_input)
        self.assertRaises(OSError,f.seek)
        f.close()

    def test_isatty(self):
        f = TextInputIO(self.text_input)
        self.assertEqual(f.isatty(), False)
        f.close()

こんな感じで書いた。

実装

from io import TextIOBase


class TextInputIO(TextIOBase):
    """
    IO for Logger. Using TextInput as file.
    """
    def __init__(self,text_input):
        super().__init__()
        self.text_input = text_input

    def isatty(self):
        return False

    def readable(self):
        return False

    def writable(self):
        return True

    def seekable(self):
        return False

    def write(self, s):
        self.text_input.text += s

    def writelines(self, lines):
        if not isinstance(lines,list):
            raise TypeError('list')
        for s in lines:
            if not isinstance(s,str):
                raise TypeError('str')
            self.write(s)

    def close(self):
        super().close()
        self.text_input = None

バッファとかないしどうしようかなと思ったりしてた。テストに書いたように__enter__と__exit__を継承してないけど、コンテキストマネージャとして動いた。多分、親のを呼び出してるんだと思う。

使ってみた

from kivy import Logger
from kivy.app import App
from kivy.uix.button import Button
import logging
from logging import StreamHandler

from kivy.uix.gridlayout import GridLayout
from kivy.uix.textinput import TextInput

from app.TextInputIO import TextInputIO

logger = Logger
logger.setLevel(logging.DEBUG)

text_input = TextInput()
text_input_io = TextInputIO(text_input)
sh = StreamHandler(text_input_io)
fh = logging.FileHandler('app.log')
logger.addHandler(fh)


def pressed(view):
    logger.info('Application: Button was pressed!!')


class MainScreen(GridLayout):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.cols = 1
        b = Button(text="Button")
        b.bind(on_press=pressed)
        self.add_widget(b)
        self.add_widget(text_input)
        logger.addHandler(sh)


class TestApp(App):
    def build(self):
        return MainScreen()


if __name__ == '__main__':
    TestApp().run()

テスト実行用にこんなの書いた。最初、別のロガーを使っていたのでグローバルなところにロガーの設定があるけど気にしないでほしい。

f:id:b1u3:20180621190844p:plain

そこそこ動いた。多分問題点として、スレッドセーフじゃないと思う。あとTextInputを追加する前にloggerにハンドラとして追加すると強制終了する。追加は遅延させないとダメかなと。したがって、最初のGLとかのログメッセージは反映されない。あと、最初のカラムのINFOとかが出なかった。logging.BASIC_FORMATをハンドラに設定しても解析されなかった。

やってみて思ったこと

まだまだ調べることが多そうだなって思った。最近ブログを更新する気がだいぶ出てきたので、この調子でやっていきたいなぁって。

Room使ってみた

Room Persistence Library 使ってみた。

Room Persistence Libraryとはなんぞ

The Room persistence library provides an abstraction layer over SQLite to allow fluent database access while harnessing the full power of SQLite.

https://developer.android.com/topic/libraries/architecture/room

とある。Androidのデータベースアクセスを抽象化したライブラリーっぽい。個人的にはかなり抽象化されているので作らなきゃいけないものも少し多いなっていうイメージだった。

データベースのテストを書く

今回はEntityとして、名前とid(自動インクリメント)を持つEntityをかいてみた。テストの時はinMemoryのデータベースを書いた方がいいらしい。

import android.arch.persistence.room.Room;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertThat;

@RunWith(AndroidJUnit4.class)
public class SimpleEntityTest {
    private EntryDao mEntryDao;
    private AppDatabase mDb;

    @Before
    public void createDb() {
        Context context = InstrumentationRegistry.getTargetContext();
        mDb = Room.inMemoryDatabaseBuilder(context,AppDatabase.class).build();
        mEntryDao = mDb.entryDao();
    }

    @After
    public void closeDb(){
        mDb.close();
    }

    @Test
    public void writeAndReadOneEntry(){
        Entry entry = new Entry();
        entry.setName("Test1");
        mEntryDao.insertAll(entry);
        Entry gotEntry = mEntryDao.getEntryByName("Test1");
        assertThat(gotEntry,equalTo(entry));

    }
}

Entity

EntryっていうEntityを書いた。

import android.arch.persistence.room.ColumnInfo;
import android.arch.persistence.room.Entity;
import android.arch.persistence.room.PrimaryKey;

import java.util.Objects;


@Entity(tableName = "entries")
public class Entry {

    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "entry_id")
    private long eid;

    @ColumnInfo(name = "name")
    private String name;


    public void setName(String name) {
        this.name = name;
    }

    public long getEid() {
        return eid;
    }

    public void setEid(long eid) {
        this.eid = eid;
    }

    public String getName() {
        return name;
    }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Entry entry = (Entry) o;
        return Objects.equals(name, entry.name);
    }

    @Override
    public int hashCode() {

        return Objects.hash(eid, name);
    }
}

equalsをOverrideしないとTestのequalToで等しくならないので、nameだけ同じなら同じってことにした。primary keyのautoGenerate=trueでオートインクリメントできるらしい。

Dao

DaoはEntityのインターフェースとなる部分。ここに動作を書く。

import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Delete;
import android.arch.persistence.room.Insert;
import android.arch.persistence.room.Query;

import java.util.List;

@Dao
public interface EntryDao {
    @Query("SELECT * FROM entries")
    List<Entry> getAll();

    @Query("SELECT * FROM entries WHERE name LIKE :name LIMIT 1")
    Entry getEntryByName(String name);

    @Insert
    void insertAll(Entry... entries);

    @Delete
    void delete(Entry entry);
}

DaoアノテーションでDaoであることを示す。Queryアノテーションで実行するQueryを指定する。

Database

import android.arch.persistence.room.Database;
import android.arch.persistence.room.RoomDatabase;

@Database(entities = {Entry.class},version = 1)
public abstract class AppDatabase extends RoomDatabase {
    public abstract EntryDao entryDao();
}

データベース。RoomDatabaseを継承したものをつくる。

テストの実行

これでテストはパスした。
エンティティにDateの属性を付けようとしていろいろ調べたけど、jack(java android compiler kit)の警告が出たりして、時間がたりなかったのでやめた。utilのDate使うよりOffsetDateTime(jave8)を使うのがいいらしい。Dateはタイムゾーンを考慮しないので。でも、OffsetDateTimeを入れるとjackとソースコンパティビリティの警告出るんだよなぁ。build.gradleでjack enabled=trueを入れてcompileOptionを消せばいいんだけどなんだかなぁという感じだった(加藤恵)。あとでちゃんとやってみたい。

その他

python書きてぇなつってjavaandroid書いてる。kivyとpySerialでpic18f14k50でシリアル通信するデスクトップアプリ書こうかなって隙間時間はkivy調べてる。さっさとandroid書き終わりたいなぁ。ぜんぜんhackしてないけど許してにゃん。

AndroidのExpandableListViewとRecyclerView

結論からして相性が良くない。requestLayoutとか入れて実験してみたけど、できなかった。

やりたかったこと

RecyclerView>CardView>ExpandableListViewっていうのをやってコストを下げたかった。

やってみた

そのまま実装してみるとだめだったので、RecyclerView>ExpandableListViewをやってみたらこの時点でダメだった。f:id:b1u3:20180616211355p:plain
wrap_contentがうまくいかない。Android developers見てみたらwrap_contentはダメだぞって書いてあった。ExpandableListViewのGroupViewのonClickにRecyclerViewのrequestLayout()を入れてもダメだった(実装が悪い?)

妥協案

愚直にLinearLayoutにぶち込んだ。この分ならRelativeでも大丈夫な気がする。
f:id:b1u3:20180616211730p:plain

AndroidのFragmentのreplace

最近またAndroidやってます。ノリで書いてたら詰まったのでちょっとしたメモです。

動的なFragment

Google developersに載ってるようなタグを使ってxmlを使うと動的にFragmentを差し替えることができない。

解決法

googleの画面を1:2に配分してそれぞれに動的なフラグメントを割り当てたいとき、googleのコードのを適当なViewGroupに変更する。つまり、

<?xml version="1.0" encoding="utf-8"?>
<!-- activity_main.xml -->
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    android:id="@+id/fragment_container"
    >
    <RelativeLayout
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:id="@+id/fragment_list_container"
            />
    <RelativeLayout
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="2"
            android:id="@+id/fragment_content_container"
            />
</LinearLayout>
/*
MainActivity.java
*/
package com.b1u3dev.fragmentandlayout;

import android.os.Bundle;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;


public class MainActivity extends AppCompatActivity {
    private FragmentManager mFragmentManager;
    private String className=MainActivity.class.getSimpleName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mFragmentManager=getSupportFragmentManager();
        mFragmentManager.beginTransaction().add(R.id.fragment_content_container,new ContentFragment()).commit();
        mFragmentManager.beginTransaction().add(R.id.fragment_list_container,new ListFragment()).commit();

    }

 
    public void onClick(View view){
        Log.d(className,"Fragment Size:"+mFragmentManager.getFragments().size());
        switch (view.getId()){
            case R.id.content_button:
                Log.d(className,"content_button was clicked");
                mFragmentManager.beginTransaction().replace(R.id.fragment_content_container,new SecondContentFragment()).commit();
                break;
            case R.id.list_button:
                Log.d(className,"list_button was clicked");
                break;
        }

    }
}

こんな感じで。
xxxFragmentは全部FragmentのサブクラスでonCreateViewをオーバーライドしてinflateしてる。
f:id:b1u3:20180615030417p:plain

その他のファイル

package com.b1u3dev.fragmentandlayout;
/*
ContentFragment.java
 */

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class ContentFragment extends Fragment {
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

        return inflater.inflate(R.layout.fragment_content,container,false);
    }
}
package com.b1u3dev.fragmentandlayout;
/*
ListFragment.java
 */

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class ListFragment extends Fragment {
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_list,container,false);
    }
}
package com.b1u3dev.fragmentandlayout;
/*
SecondContentFragment.java
 */

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class SecondContentFragment extends Fragment {
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_content_second,container,false);
    }
}
<?xml version="1.0" encoding="utf-8"?>
<!-- fragment_content.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">
    <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Test Content"/>
    <Button
            android:id="@+id/content_button"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Content Fragment Button"
            android:onClick="onClick"/>

</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<!-- fragment_content_second.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">
    <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="SECOND FRAGMENT CONTENT BUTTON"/>
    <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="SECOND FRAGMENT CONTENT BUTTON"/>
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<!-- fragment_list.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                  android:orientation="vertical"
                  android:layout_width="match_parent"
                  android:layout_height="match_parent">
        <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="List Activity"/>
        <Button
                android:id="@+id/list_button"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="List Activity Button"
                android:onClick="onClick"/>

</LinearLayout>

あ、ボタンの名前修正するの忘れてた。

java 10.0.1 と Intelli J Idea

Intelli J Idea と java 10.0.1 と Android用の 8 の共存やってみた。萌黄えもなので。
普通に入れて設定したら

could not determine java version using executable ...

というのがsync failedと一緒に出てきた。
OS X の方はbrewでgradleをupgradeして、Intelli J のpreference>Build Tools>gradleからUse local gradle distributionにチェック。からのGradle homeに適切なlibexecを登録する。
Windowsの方は設定はsettingsから。ちなみに OS Xの方はdefault settingsから設定できない。

pythonのエントリーポイント

経緯

割と唐突にpythonソースコードを読んでみたくなったので、ちょっと覗いてみた。

今回の目的

インタープリターのエントリーポイントが見つかるまで。

探す

エントリーポイントっていうのはmain関数(UNIX/Linux)とwmain関数(windows)のことをここでは指す。_startではないです。pythonのcpythonリポジトリを最初に見ると、いろいろディレクトリがある。ひとつひとつ見たり、単語検索かけるのはあまり勉強味がないので、ある程度の推測とかを駆使して見つけていった。はじめにそのリポジトリを見るとインストール方法とかが載ってるREADME.rstが下部に表示されている。
UNIX系だと

./configure
make
make test
sudo make install

のような一般的なインストール方法になっている。

./configure

いうのは同ディレクトリ内のMakefile.pre.inというファイルをもとにした環境に適したmakefileを出力するスクリプト。これはautoconfというAutotoolsという一連の開発ツールの一つによって出力されたファイル。2万行ちかくあった。Makefile.pre.inを見れば、主な流れがわかる。

Makefile.pre.inを見てみる。最初の方には変数とか定数の定義が書かれている。これはmakefileの構文に従う。後半にmekefile特有のルールとかターゲットとかが書かれている。基本的にはallターゲットが実行されるので、all:ではじまる部分を見る。この部分は見てみると@で囲まれた単語があるので、ここは./configureで置換される部分。よって、ここだけではわからんみたいな。
読み進めていくと、コメントで

#Build the interpreter

の行があるので、インタープリターをビルドしてるんだなってわかる。んで、直後に$(BUILDPYTHON)っていうのがあるので、これがビルドされるプログラムの名前なのかなみたいな。だからもう少しみる。Programs/python.oというのが必要らしい。なんか重要っぽいなって思ったのでPrograms/python.cを見てみるとmainがあった。

結論

Programs/python.cにあるよ。