docx renamer Word文書のファイル名を タイトル+最終更新日

多様なサブディレクトリに置かれたワード文書を、このスクリプトを起動した地点に移動させ、かつ
ファイル名を、
"元の名前.docx"
から
"元の名前_メタデータに書かれたタイトル名_最終更新日.docx"
に変更するスクリプトです。
この操作は、非可逆に、大量に操作するので、元の文書はバックアップを取って様子を確認してから使ってみてください。

// ワード文書名に、タイトルと変更日のメタタグを追加する。
// 変更例 : 文書13.docx --> 文書13-Groovyでお掃除-20120117.docx

startDir = "."

new File(startDir).eachFileRecurse { file ->
  if (file.isFile() && file.name.endsWith("docx")){
     new AntBuilder().unzip(src: file, dest:'/OUTPUT') {
        patternset {
           include(name: '**/core.xml')
        }
        mapper(type: 'flatten')
     }
     println file
     docxcore = new groovy.util.XmlParser().parse("/OUTPUT/core.xml")
     title =  docxcore.toString().replaceAll(/(.*title\[attributes\=\{\}; value=\[)([^\]]*)(\]\], \{http:\/\/purl.*)/)  {m0,m1,m2,m3->m2 }
     title = title.tr(':/ ','___')

     modified =  docxcore.toString().replaceAll(/(.*\}modified\[attributes\=\{\{http.*W3CDTF\}; .*value\=\[)(20[^\]]*)(Z\]\]\].*)/) {m0,m1,m2,m3->m2 }
     modified = modified.replaceAll(/(.*)(T..:..:..)/) {m0,m1,m2 -> m1}
     modified = modified.replaceAll(/(\d+)\-(\d+)\-(\d+)/) {m0,m1,m2,m3 -> m1+m2+m3 }

     targetName= file.name.replaceAll('\\.docx','') +'-'+ title +'-'+modified +'.docx'
     println file.name + " renamed to " + targetName
     file.renameTo(new File((targetName )))  
  }
}

自分のPCにある写真をPicasa webに丸ごとアップ

自分のPCにあるjpg写真をサブディレクトリーの階層ごと丸ごとアップロードします。
G* Advent Calendar 2011 17日目のネタです。  #gadvent2011
特にGroovyの新技術を使うこともなく、あくまて日用の小道具として、目的志向でGroovyを活用したいと思います。

picasaのwebストレージも Google+に参加するとほぼ容量無制限となるので、

公開するかどうかは、後でアクセス権の設定の時に考えるとして自分のPCの写真を全てPicasaに保存します。
目的は洪水対策ですね、自分の家のPCが全壊してもアルバムは保全したいと思います。

前提として使うアプリケーションは Gladinet Cloud Desktopです。
これによりPicasa web サービスがドライブとしてPCにマウントして見ることができます。
したがって動作OSはWindowsです。

Picasa webサービズがドライブとして見えているのだから、
ここに一気に xcopyrobocopy で一気にコピーできるかというと、Picasa web には制約があって、サブディレクトリーは一段までです。
例えば
c:/趣味/つり/伊豆/船1.jpg
という深いディレクリーにある画像ファイルはそのままでは送れません、それを
c:¥趣味_つり_伊豆¥舟1.jpg
という風にディレクトリー名をひとつの長いものに変えて送る必要があります。
ここでは特にPicasaWebサービスAPIとかは使っていなのでPicasa以外の似たようなサービスにも適用できると思います。

以下のスクリプトは、自分の持っているjpgをまるごと複製するので、動かす頻度がとても少ないのでハードコードしまくりです。

コピー先のPicasaディレクトリーは、ルートが z:/GooglePicasa という設定になっている前提です。
またこのスクリプト自体は、複製元の写真の配置してある場所のルートで起動します。
sleep で相当遅くなってますが、これは Gladinet のフリー版は、一日あたりの転送数に、制限がある為です
まったりPCをつけっぱなしにて動かすことが前提です。
多分Gladinet側の問題たと思いますが、何やら動作が不安定です。
量的にGladinetに負荷をかけすぎているのかもしれません。

def  targetDrive="z:\\GooglePiasa\\"  // Gladinet で設定したPicasa Webサービスのroot directory

File.metaClass.copy = { String destName -> 
  if(delegate.isFile()){
    new File(destName).withOutputStream{ out ->
      out.write delegate.readBytes()
    }
  }
}

new File(".").eachFileRecurse { file -> 
  if(file.isFile() && file.name.endsWith('.jpg')){
    curName = file.getPath().replaceAll(/.\\(.*)/) {m0,m1 -> m1}
      newName = curName.reverse().replaceFirst("\\\\", "----").reverse()
      newName = newName.replaceAll("\\\\", "_")
      newName = newName.replaceAll("----", "\\\\")
      newFile = new File(URLDecoder.decode(newName, "UTF-8"))
      println "現行ファイル名: "+curName
      println "新規ファイル名: "+newName
      
      subDir = newName.toString().replaceAll(/\\.*$/,"")
      if (subDir && subDir.length() +2< newName.length() ) { 
        targetDir = new File(targetDrive+subDir)
          if (!targetDir.exists()) {
             println "Subdir作成:" + targetDrive+subDir
             new File(targetDrive+subDir).mkdir();    //make folders   
             Thread.sleep(1000*60)
          }
      }
      new File(curName).copy(targetDrive+newName)
      Thread.sleep(1000*60)
  }
}

すみません、いまいち安定動作していません、うーん。

固定ハッシュタグ複数定点観測用追跡器作りました

[Groovy][Twitter] 固定ハッシュタグ複数定点観測用追跡器作りました。

#devsumi とか、#jawsug とか、ずっと追跡し続けたい固定ハッシュタグは、いくつかあるものです。
自分のPDAバイスそのものでハッシュタグを追跡すると、途中を見落とししたり、電池切れで追えなくなったりします。
このような問題があるため、できれば追跡そのものは自宅のマシンで無人で行い、その結果を随時PDAで閲覧する方式の
方が便利が良いと思います。また、デブサミのような大規模なセミナーだと複数のハッシュタグを追わないと追いつかないケースもあり、
複数のハッシュタグを同時に記録し続けたいこともあります。
そんな目的のために複数固定ハッシュタグ追跡器作りました。
複数のハッシュタグを追跡し続けてログを付けます。


準備:
TAGS.txt という、事前に追跡しておきたい、お好みのハッシュタグテーブルを作ります、1件1行です。
実は、登録する単語は、ハッシュタグである必要はなく、また日本語でもOKです。TwitterはStream APIで検索する場合は、ハッシュタグのみが可能です、逆に通常の検索の場合は、日本語でも何でも探せますが、その代わり、即時性がありません、スピードと多様性はトレードオフですが、ここでは、記録性を優先するので、通常の検索で追っています、従って5分から10分程度の遅延を伴います。

動かし方: TAGS.txt と同じディレクトリーで HashTagChaser.groovyを動かします。
すると#jggug.log といった名前のログファイルが作られます。

この結果をPDAでコンファレンス会場などで閲覧すればOKです。
で、この結果を外からどうやって見るかですが、簡単なことですが単にDropboxディレクトリー内で動かすだけです。
巨人の肩の上に立つ訳です。
Dropboxの閲覧はPCでもiPhoneでも可能です。
Groovyをインスコするのはイヤという人のために、独立したjar版も作っておきました。
HashTagChaser.jar
こちらは、Javaだけあれば単独で動きます。


また、Dropboxのアカウントをお持ちでない人は、よかったらこちらからアカウント取得してください。
http://db.tt/8BiuAAb
招待ありでアカウントを作成すると、紹介者と招待された人の双方に、初期値の2Gに、プラスで250MB の容量追加が与えられます。招待者の私(nemo10)も250MBメリットがあります。

TAGS.txt ファイルの例 (単なる単語でもOK)

#jggug
#javareading
#devsumi
アイマス

それぞれのタグ名に応じて #jggug.log とか #javareading.log などのログが時々進んでいきます。

実行コード HashTagChaser.groovy 
>|groovy|
import static groovy.util.GroovyCollections.combinations
import groovy.util.Eval
import groovyx.gpars.GParsExecutorsPool

// 検索単語エンコード
String.metaClass.encodeIt = { ->
	return URLEncoder.encode(delegate,"UTF-8").replaceAll(/\.log/,'')
}
assert "ツイッター.log".encodeIt() == "%E3%83%84%E3%82%A4%E3%83%83%E3%82%BF%E3%83%BC"

// 発言番号を取得
String.metaClass.getNum = { ->
	if (delegate ==~/.*tag.*search.*/) {
		//println "発言番号取得 " + delegate.replaceAll(/.*:([0-9]+)/, '$1')
		return delegate.replaceAll(/.*:([0-9]+)/, '$1')
	} else {
		return delegate
	}
}
assert "tag:search.twitter.com,2005:16060673193".getNum() == "16060673193"

// 発言IDを取得
String.metaClass.getID = { ->
	if (delegate ==~/.*\(.*\)/) {
		//println "ID取得 " + delegate.replaceAll(/([^ ]+) \(.*/, '$1')
		return delegate.replaceAll(/([^ ]+) \(.*/, '$1')
	} else {
		return delegate
	}
}
assert "nemo_kaz (kazuo nemoto)".getID() == "nemo_kaz"

// 日本語チェック
String.metaClass.isKana = { ->
	if (delegate ==~/.*[あ-んア-ン].*/) {
		return "OK"
	} else {
		return "NG"
	}
}
assert "tag:search.twitter.com,2005:16060673193".getNum() == "16060673193"

//////////////////////////////////////

def loggers=[]
def file = new File("TAGS.txt")

file.eachLine{logname -> loggers.add (new Agent(logname+".log")) }

for (;;){
	try {
		loggers.each {clazz -> clazz.logIt() }
		Thread.sleep(1000*60*8)
	} catch (Exception e) { println "Exception"+e }
}

class Agent{
	String url
	File fhandle
	def counter
	def logname
	Agent(String input) {
		logname=input
		fhandle = new File(input)
		//fhandle.write("aaa\n")
	}
	def logIt() {
		def feed = null
		print " "
		def  atom2 = ("http://search.twitter.com/search.atom?q="+logname.encodeIt()).toURL().text
		feed = new XmlSlurper().parseText(atom2)
		feed.entry.collect{it}.reverse().each {
			if (counter < it.id.toString().getNum().toLong()) {
				counter = it.id.toString().getNum().toLong()
				if (it.title =~ /^RT/) {} else {
					println "■"+it.title +"  "+it.author[0].name.toString().getID() 
					fhandle.append("■"+it.title +"  "+it.author[0].name.toString().getID()+"\n" )
				} //RTは割愛
			} else {
				Thread.sleep(1000*5)
			}

		}//each
	}//logIt()
} //class Agent

Twitterの特定キーワードを追跡し続けるスクリプト

TwitterのStream APIを使うと、簡単に特定ハッシュタグがリアルタイムに
追跡できるわけですが、その代わり、漢字をStream APIで追跡することはできません。
ハッシュタグが漢字であったり、ハッシュタグ以外も追跡したい場合は、通常の検索機能を使うと、追跡できますが、今度は逆にStreamではないので、動きが緩慢になります。
漢字は追えないとあきらめてしまえばかまいませんが、やはり汎用な物を持っておきたいので作っておきました。
即応性の要求される、2時間程度のコンファレンスの追跡はStream APIが良いわけですが、
複数日にわたるイベントの追跡は検索APIを使ってもかまわないといえます。
このような仕様なので使いどころがないと思っていましたが、ちょうどJavaOneが開催されていて、複数日ずーっとそれを追いたい訳なので、#javaonejp のハッシュタグで追跡し続けています。
記録性の為なのでテキスト出力の方が便利かなと思っています。

@Grab('net.homeip.yusuke:twitter4j:[2.0,)')
import groovy.xml.Namespace

// 発言番号を取得
String.metaClass.getNum = { ->
	if (delegate ==~/.*tag.*search.*/) {
		//println "発言番号取得 " + delegate.replaceAll(/.*:([0-9]+)/, '$1')
		return delegate.replaceAll(/.*:([0-9]+)/, '$1')
	} else {
		return delegate
	}
}
assert "tag:search.twitter.com,2005:16060673193".getNum() == "16060673193"

// 発言IDを取得
String.metaClass.getID = { ->
	if (delegate ==~/.*\(.*\)/) {
		//println "ID取得 " + delegate.replaceAll(/([^ ]+) \(.*/, '$1')
		return delegate.replaceAll(/([^ ]+) \(.*/, '$1')
	} else {
		return delegate
	}
}
assert "nemo_kaz (kazuo nemoto)".getID() == "nemo_kaz"

// 検索単語エンコード
String.metaClass.encodeIt = { ->
	return URLEncoder.encode(delegate,"UTF-8")
}
assert "ツイッター".encodeIt() == "%E3%83%84%E3%82%A4%E3%83%83%E3%82%BF%E3%83%BC"

// main
if (args.length == 0) {println "検索単語を指定してください"; return }
		
Long counter=1
for (;;){
	def feed=null
	def atom2 = ("http://search.twitter.com/search.atom?q="+args[0].encodeIt()).toURL().text
	feed = new XmlSlurper().parseText(atom2)
	feed.entry.collect{it}.reverse().each {
		if (counter < it.id.toString().getNum().toLong()) {
			counter = it.id.toString().getNum().toLong()
			println "■"+it.title +"  "+it.author[0].name.toString().getID() 
			//println it.link[0]
			//println "class="+it.published[0].toString()
		} else {
			Thread.sleep(1000*90)
		}
	}
}

動作イメージ


■スピーカーが来ない.... #javaonejp skrb
■[CodeGeneration] @Lazyをフィールドにつけると、初めてアクセスするときに初期化するようなアクセサを生成してくれる。 #javaonejp nobeans
■[CodeGeneration] @Lazyで展開される実装はdouble-checked-lockingっぽい。スレッドセーフか?というツッコミに、多分Yes、といってたけど、Java5以降はね、という条件が必要だと思った。 #javaonejp nobeans
■[CodeGeneration]後はアノテーションによるAST変換の例をいくつか紹介してから、DbCのGroovy実装であるGContractsの紹介。アノテーションクロージャで事前条件と事後条件を指定すると実行前後でチェックする実装をAST変換で生成。 #javaonejp nobeans
■[CodeGeneration]次は、FindBugsのGroovy版的なCodeNarc。発見できるバグの種類は前者が200以上なのに対して、60いくつと少なめだけど、CodeNarcはAST変換でバグチェックを実現してるんだよ、と。 #javaonejp nobeans
■[CodeGeneration]Groovy標準付属のツールで、ASTツリーをGUI表示できる。その構造の見方の説明と、AST変換をするためのASTTransformationの実装の仕方の説明。 #javaonejp nobeans
■[CodeGeneratin]AST変換といっても文法的には万能じゃなくて、Groovyとして正しい文法でなければいけない。文法さえ正しければ、その意味は自由に書き換えられる。文法NGならSyntaxErrorが発生してAST変換まで到達しない。 #javaonejp nobeans
■そろそろModular&Jigsawが始まりそう。 #javaonejp nobeans
■@skrb ですよねー!だからこそ貴重な機会を大事にしたいです。 #javaonejp kawamnv
■セッションがはじまるまでウトウトしていた。いつのまにか部屋がいっぱいになっていた。このセッションもffull #javaonejp skrb

ソースコード同士の類似性評価ツールを作ってみた

Q: あなたはテストエンジニアです、ここにテストしなくてはならないソースが大量にあります。
しかし、よくみるとコピペメソッドによる類似コードが多数あり、ロジックを共有してるコードが大量にあります。従って、全てのコードをテストする必要はなさそうです。
そこで、ここで類似コードに関しては、代表的ないくつかをテストするという抽出テストにしたいと思いました、ここで、どうやって選別しますか?


A:類似したコードを群として束ね、類似性の高いコードはテストを割愛します。
類似性検出方法は下記の手段を用います。


A B C D のコードがあった時。

ひとつめは

  1. Aのコードを2回足しあわせたものを作ります。 A+A
  2. 次にそれを圧縮します。 zip(A+A)
  3. そして、それをファイルサイズで割った値を算出します。 zip(A+A)/size(A+A)=f(AA)

ふたつめは

  1. AとBのコードを足しあわせたファイルを作ります。 A+B
  2. 次にそれを圧縮します。 zip(A+B)
  3. そして、それをファイルサイズで割った値を算出します。 zip(A+B)/size(A+B)=f(AB)

ここで
f(AB)/f(AA)の値が1に近いとBはAとはあまり違わないものであるといえます。
もしも
f(AB)/f(AA)>1 の場合、1より大きい程、BはAとは異なったものであるといえます。

ここでzip()はrunlength方を使用していると想定しています。
runlength方は、ファイル内で同一パターンを見つけると、前出と同じ、といった表記に置換して圧縮する一般的な圧縮手法です。

動かし方
ソースコードの置いてあるディレクトリーの元の部分でこのスクリプトを動かすと、その下にある
コードの全てに対して、コード間の距離を評価します。

課題
実はすごく遅いです。当たり前、毎回圧縮しているのだから、ファイルIOをもっと減らしてマルチスレッドに
しないと実用的にはならない予感がします。
本来テスト対象コードが多すぎるから絞り込みたいという動機があるので、
処理速度がもっと速くないと使えないでしょう。

// ソースコード間の違いを評価するツール
List codes=[]
// ここでは 拡張子がjavaのコードを再帰的に収集します。
new File(".").eachFileRecurse { file -> 
	if(file.isFile() && file.name.endsWith("java")) { 
		name = file.getPath() //+file.getName()
		codes.add(file.getPath())
	}
}
println "検査対象コード"
println codes
println "ソース間距離: (100より大きいほど、二つのファイルは異なっている。"

num = 1
for (x in (0..codes.size-1)) {
	for (y in (0..codes.size-1)) {
		if (1 /* x!=y*/) {
			print "["+num++ +"]"+codes.getAt(y)+" "+codes.getAt(x)+"\t"
			isUnique(codes.getAt(x), codes.getAt(y))
		}
	}		
}

def isUnique(code1, code2) {
	File output0 = new File("doubled.txt")
	output0.write("")
	new File(code1).eachLine { line1 ->
		//println line1
		output0.append(line1)
	}
	new File(code1).eachLine { line1 ->
		//println line1
		output0.append(line1)
	}

	File output = new File("merged.txt")
	output.write("")
	new File(code1).eachLine { line1 ->
		//println line1
		output.append(line1)
	}
	new File(code2).eachLine { line2 ->
		//println line2
		output.append(line2)
	}

	proc = "cmd /c gzip -f doubled.txt".execute()
	proc.waitFor()

	proc = "cmd /c gzip -f merged.txt".execute()
	proc.waitFor()

	def zaa = new File("doubled.txt.gz").size() 
	def zab = new File("merged.txt.gz").size() 
	def a   = new File(code1).size()
	def b   = new File(code2).size()
	println " 類似性 ="+( (zab/(a+b))/(zaa/(a+a)))*100
}

実行イメージは例えば以下のようになります。
抽出方法とか、計算の効率化とか、もう少し改善したいです。

                                                                                                                                    • -

検査対象コード
[.\a.java, .\b.java, .\SOURCE1\a.java, .\SOURCE1\b.java]
ソース間距離: (100より大きいほど、二つのファイルは異なっている。
[1].\a.java .\a.java 類似性 =99.9551166900
[2].\b.java .\a.java 類似性 =37.2159692800
[3].\SOURCE1\a.java .\a.java 類似性 =192.5814740900
[4].\SOURCE1\b.java .\a.java 類似性 =103.5254833100
[5].\a.java .\b.java 類似性 =152.3449010600
[6].\b.java .\b.java 類似性 =99.9632623700
[7].\SOURCE1\a.java .\b.java 類似性 =178.8919630900
[8].\SOURCE1\b.java .\b.java 類似性 =279.0446589500
[9].\a.java .\SOURCE1\a.java 類似性 =27.5365924800
[10].\b.java .\SOURCE1\a.java 類似性 =6.2432073200
[11].\SOURCE1\a.java .\SOURCE1\a.java 類似性 =97.5609756100
[12].\SOURCE1\b.java .\SOURCE1\a.java 類似性 =12.7184763200
[13].\a.java .\SOURCE1\b.java 類似性 =133.4013067100
[14].\b.java .\SOURCE1\b.java 類似性 =87.9460498600
[15].\SOURCE1\a.java .\SOURCE1\b.java 類似性 =115.114421600
[16].\SOURCE1\b.java .\SOURCE1\b.java 類似性 =99.9902913200

「並行プログラミングの原理から実践まで」5章の補助資料書きました。

 

 

「並行プログラミングの原理から実践まで」を社内勉強会で、輪読しています。

今回、第5章を担当しました。

解説資料のすべては下記URLから原板がダウンロードできます。

改変、再配布は自由とのこと。

Index of /acafinal/ACA Self Learn

 

わかりにくい部分に、日本語でコメントを追記して使用しました。


このパワーポイントの資料は、本より後から作られたものらしく、本とは構成が違っているので、併読すると、かえって混乱する可能性もあります。

jsr166y forkjoinを理解する前提として、java.util.concurrent をあらためて深掘りして理解しておく必要があると思います。



ドライブ毎の空き容量を表示する。

小さいけど実用になるGroovyコードのメモ。

その昔、DOSで動く FREE.COMというツールがありました。
各ドライブ事の残量を示してくれました。大きなファイルを書き出したりする前に
最も大きな空きスペースのドライブを確認するときなどに便利でした。
ファイル名は "FREE.COM" などと言う、おいしすぎる名前なので、
もはやネット検索でも、再発見はできそうにない名前ですので、
記憶していたイメージにもとづきGroovyで再開発してみました。
Windows専用になります。
(drive in "C".."Z") という拡張for文がちょっと便利ですね。

動作イメージ

drv [  空き ]G [  総計 ]G [Free]%
C:\ [   49.0]G [  110.7]G [44.3]%
D:\ [   16.0]G [   99.8]G [16.1]%
Z:\ [    0.3]G [    0.3]G [100.0]%

ソース

println "drv [  free ]G [ total ]G [free]%"
for (drive in "C".."Z") {
	file = new File("${drive}:/") 
	BigDecimal free = file.getFreeSpace()/100000000
	BigDecimal total= file.getTotalSpace()/100000000
	if (total >0) {
		print file
		BigDecimal vaca = free/total*100
		printf (" [%7.1f]G [%7.1f]G [%2.1f]",free,total,vaca)
		println "%"
	}
}