ちょっと疑問に思ったことを調べた
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()
テスト実行用にこんなの書いた。最初、別のロガーを使っていたのでグローバルなところにロガーの設定があるけど気にしないでほしい。
そこそこ動いた。多分問題点として、スレッドセーフじゃないと思う。あと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); }
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を消せばいいんだけどなんだかなぁという感じだった(加藤恵)。あとでちゃんとやってみたい。
AndroidのExpandableListViewとRecyclerView
結論からして相性が良くない。requestLayoutとか入れて実験してみたけど、できなかった。
やりたかったこと
RecyclerView>CardView>ExpandableListViewっていうのをやってコストを下げたかった。
やってみた
そのまま実装してみるとだめだったので、RecyclerView>ExpandableListViewをやってみたらこの時点でダメだった。
wrap_contentがうまくいかない。Android developers見てみたらwrap_contentはダメだぞって書いてあった。ExpandableListViewのGroupViewのonClickにRecyclerViewのrequestLayout()を入れてもダメだった(実装が悪い?)
妥協案
愚直にLinearLayoutにぶち込んだ。この分ならRelativeでも大丈夫な気がする。
AndroidのFragmentのreplace
最近またAndroidやってます。ノリで書いてたら詰まったのでちょっとしたメモです。
解決法
googleの画面を1:2に配分してそれぞれに動的なフラグメントを割り当てたいとき、googleのコードの
<?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してる。
その他のファイル
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のエントリーポイント
今回の目的
インタープリターのエントリーポイントが見つかるまで。
探す
エントリーポイントっていうのは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にあるよ。