Last active
September 13, 2023 07:16
-
-
Save fum1h1ro/dee2570aa81b27e009a9bde1009e0ca1 to your computer and use it in GitHub Desktop.
タイムスタンプじゃなくファイルのハッシュで変更を検知するツール
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
ヘルプ | |
$ ruby sync_assets.rb | |
ビルド&同期 | |
$ ruby sync_assets.rb build -v | |
クリーン(ハッシュを記録しているフォルダを消す) | |
$ ruby sync_assets.rb clean |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/env ruby | |
require 'thor' | |
require 'pathname' | |
require 'digest/md5' | |
require 'fileutils' | |
require 'open3' | |
require 'rake' | |
def assert(expr, msg="") | |
unless expr | |
if msg.empty? | |
raise RuntimeError.new() | |
else | |
raise RuntimeError.new(msg) | |
end | |
end | |
end | |
class Task | |
attr_reader :src, :dst | |
@@image_exts = 'png|jpg'.split('|').map { |x| ".#{x}" } | |
def initialize(app, src, dst) | |
@app = app | |
@src = src | |
@dst = dst | |
end | |
def src_dir | |
File.dirname(@src) | |
end | |
def dst_dir | |
File.dirname(@dst) | |
end | |
def image? | |
not @@image_exts.find { |x| x == File.extname(src) }.nil? | |
end | |
def spine? | |
'.spine' == File.extname(src) | |
end | |
def cp | |
@app.file_cp(@src, @dst) | |
end | |
end | |
class SyncTask < Task | |
def resize_cp(scale, suffix="") | |
assert image?, "#{src} is not image" | |
dir = Pathname(File.dirname(@dst)) | |
fname = File.basename(@dst, '.*') | |
ext = File.extname(@dst) | |
@app.resize_cp(@src, dir.join("#{fname}#{suffix}#{ext}").to_s, scale * 100) | |
end | |
end | |
class ConvTask < Task | |
def system(cmd) | |
@app.system(cmd) | |
end | |
end | |
class SyncPair | |
def initialize(app, src_dir, dst_pattern, block) | |
@app = app | |
assert Dir.exists?(src_dir), "#{src_dir} was not found" | |
@targets = [] | |
src_files = Dir.glob("#{src_dir}/*.*") | |
if dst_pattern.include?('%') | |
src_files.each do |src_file| | |
@targets << [src_file, src_file.pathmap(dst_pattern)] | |
end | |
else | |
dst_dir = dst_pattern | |
src_files.each do |src_file| | |
sfn = File.basename(src_file) | |
@targets << [src_file, "#{dst_dir}/#{sfn}"] | |
end | |
end | |
@block = block | |
end | |
def execute(force) | |
@targets.each do |t| | |
src = t[0] | |
dst = t[1] | |
src_old_hash = @app.get_src_file_hash(src) | |
src_hash = @app.calc_file_hash(src) | |
dst_hash = @app.calc_file_hash(dst) | |
dst_exists = File.exists?(dst) | |
if force or src_old_hash.nil? or src_old_hash != src_hash or not dst_exists or src_hash != dst_hash | |
if @app.verbose? | |
print "sync: #{src} -> #{dst}\n" | |
print " src old hash: #{src_old_hash}\n" unless src_old_hash.nil? | |
print " src hash: #{src_hash}\n" unless src_hash.nil? | |
print " dst hash: #{dst_hash}\n" unless dst_hash.nil? | |
print " #{dst} was not found.\n" unless dst_exists | |
end | |
if @block != nil | |
@block.call(SyncTask.new(@app, src, dst)) | |
else | |
SyncTask.new(@app, src, dst).cp | |
end | |
@app.set_src_file_hash(src, src_hash) | |
end | |
end | |
end | |
end | |
class ConvPair | |
def initialize(app, src, dst_pattern, block=nil) | |
@app = app | |
assert !block.nil?, "no block" | |
assert File.exists?(src), "#{src} was not found" | |
@src = src | |
if dst_pattern.include?('%') | |
@dst = src.pathmap(dst_pattern) | |
else | |
@dst = dst | |
end | |
@block = block | |
end | |
def execute(force) | |
src_old_hash = @app.get_src_file_hash(@src) | |
src_hash = @app.calc_file_hash(@src) | |
dst_exists = File.exists?(@dst) | |
if force or src_old_hash.nil? or src_old_hash != src_hash or not dst_exists | |
if @app.verbose? | |
print "conv: #{@src} -> #{@dst}\n" | |
print " hash:#{src_old_hash} #{src_old_hash != src_hash ? '!=' : '=='} #{src_hash}\n" if not src_old_hash.nil? and not src_hash.nil? | |
print " #{@dst} was not found.\n" unless dst_exists | |
end | |
@app.mkdir_if(File.dirname(@dst)) | |
@block.call(ConvTask.new(@app, @src, @dst)) | |
@app.set_src_file_hash(@src, src_hash) | |
end | |
end | |
end | |
class Application | |
@@imagemagick_path = '/usr/local/bin/convert' | |
def initialize(dry: false, verbose: false) | |
@hash = Digest::MD5.new | |
@pwd = Dir.pwd | |
@config = "#{@pwd}/SyncAssets" | |
@tmp = "#{@pwd}/.sync_assets" | |
@options = { | |
:v => verbose, | |
:n => dry, | |
} | |
@syncs = [] | |
@convs = [] | |
@has_imagemagick = File.exists?(@@imagemagick_path) | |
end | |
def verbose? | |
@options[:v] | |
end | |
def dry? | |
@options[:n] | |
end | |
def fileutils_options | |
{ | |
:verbose => verbose?, | |
:noop => dry?, | |
} | |
end | |
def get_src_file_hash(filename) | |
hash = @hash.reset.update(File.expand_path(filename)) | |
path = "#{@tmp}/#{hash}" | |
if File.exists?(path) | |
File.open(path).read | |
else | |
nil | |
end | |
end | |
def set_src_file_hash(filename, filehash) | |
unless @options[:n] | |
hash = @hash.reset.update(File.expand_path(filename)).to_s | |
path = "#{@tmp}/#{hash}" | |
File.open(path, "w+").print(filehash) | |
end | |
end | |
def calc_file_hash(filename) | |
if File.exists?(filename) | |
@hash.reset.file(filename).to_s | |
else | |
nil | |
end | |
end | |
def command_clean | |
$stdout.print("Cleaning temporary data.\n") if verbose? | |
FileUtils.rm_rf(@tmp, **fileutils_options) | |
end | |
def command_build(force) | |
mkdir_if(@tmp) | |
if File.exists?(@config) | |
load @config | |
end | |
@syncs.each do |sync| | |
sync.execute(force) | |
end | |
@convs.each do |conv| | |
conv.execute(force) | |
end | |
end | |
def create_sync(src, dst, block) | |
@syncs << SyncPair.new(self, src, dst, block) | |
end | |
def create_conv(src, dst, block) | |
@convs << ConvPair.new(self, src, dst, block) | |
end | |
def mkdir_if(dir) | |
FileUtils.mkdir_p(dir, **fileutils_options) unless Dir.exists?(dir) | |
end | |
def file_cp(src, dst) | |
mkdir_if(File.dirname(dst)) | |
FileUtils.cp(src, dst, **fileutils_options) | |
end | |
def resize_cp(src, dst, scale) | |
assert @has_imagemagick, 'no imagemagick' | |
mkdir_if(File.dirname(dst)) | |
cmd = "#{@@imagemagick_path} \"#{src}\" -resize #{scale}% \"#{dst}\"" | |
system(cmd) | |
end | |
def system(cmd) | |
print "exec: #{cmd}\n" if verbose? | |
unless dry? | |
o, e, s = Open3.capture3(cmd) | |
raise RuntimeError.new(e) unless s.success? | |
return o | |
end | |
end | |
end | |
module AppCommand | |
@@app = nil | |
class << self | |
def app | |
@@app | |
end | |
def app=(a) | |
@@app = a | |
end | |
end | |
def sync(src, dst, &block) | |
AppCommand.app.create_sync(src, dst, block) | |
end | |
def conv(src, dst, &block) | |
AppCommand.app.create_conv(src, dst, block) | |
end | |
end | |
self.extend AppCommand | |
class CommandLineInterface < Thor | |
#default_command :build | |
class_option 'dry-run', :aliases => '-n', :type => :boolean, :default => false | |
class_option 'verbose', :aliases => '-v', :type => :boolean, :default => false | |
desc "clean", "clean temporary data" | |
def clean() | |
AppCommand.app = Application.new(verbose: @options[:verbose], dry: @options['dry-run']) | |
AppCommand.app.command_clean | |
end | |
desc "build [-f|--force]", "build & sync assets" | |
method_option :force, :aliases => '-f', :type => :boolean, :default => false, :desc => "Force" | |
def build() | |
AppCommand.app = Application.new(verbose: @options[:verbose], dry: @options['dry-run']) | |
AppCommand.app.command_build(options[:force]) | |
end | |
end | |
CommandLineInterface.start(ARGV) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
DROPBOX_ROOT = File.expand_path("~/Hoge Dropbox") + "/hoge" | |
PROJECT_ROOT = "Unity" | |
ASSETS_ROOT = "#{PROJECT_ROOT}/Assets" | |
ADDRESSABLE_RESOURCES_ROOT = "#{ASSETS_ROOT}/AddressableResources" | |
REFERENCED_RESOURCES_ROOT = "#{ASSETS_ROOT}/ReferencedResources" | |
SPRITES_ROOT = "#{REFERENCED_RESOURCES_ROOT}/Sprites" | |
BACKGROUND_ROOT = "#{REFERENCED_RESOURCES_ROOT}/Background" | |
SPINE_CHARACTER_ROOT = "#{REFERENCED_RESOURCES_ROOT}/SpineResources/Characters" | |
SPINE_EFFECT_ROOT = "#{REFERENCED_RESOURCES_ROOT}/SpineResources/Effects" | |
SPINE_MAP_ROOT = "#{REFERENCED_RESOURCES_ROOT}/SpineMapResources" | |
SPINE_EXPORTER = "ruby Tools/spine_exporter.rb" | |
SOUND_ROOT = "#{ADDRESSABLE_RESOURCES_ROOT}/Sound" | |
# 左のフォルダの中のファイルを右のフォルダへ同期(ハッシュチェックあり) | |
sync File.expand_path("~/Documents/GitHub/hoge-config"), "#{PROJECT_ROOT}/Config" do |s| | |
s.cp | |
end | |
# 上と同じだけどコピー先のファイル名をpathmapしてくれる | |
sync "#{DROPBOX_ROOT}/LargeImages", "#{ADDRESSABLE_RESOURCES_ROOT}/LargeImages/%f.bytes" do |s| | |
s.cp | |
end | |
Dir.glob("#{DROPBOX_ROOT}/BG/**/").each do |src| | |
dir = File.basename(src) | |
next if dir.start_with?('_') | |
dst = src.gsub("#{DROPBOX_ROOT}/BG", "#{BACKGROUND_ROOT}") | |
sync src, dst do |s| | |
s.cp | |
# 画像ファイルは拡縮してコピーできる | |
[4, 8].each do |x| | |
s.resize_cp(1.0 / x, "@#{x}x") | |
end | |
end | |
end | |
# コンバート | |
Dir.glob("#{DROPBOX_ROOT}/SpineData/character/*/*.spine").each do |src| | |
conv src, "#{SPINE_CHARACTER_ROOT}/%n/%n.json" do |c| | |
c.system("#{SPINE_EXPORTER} \"#{c.src}\" -o \"#{c.dst_dir}\" -e \"Settings/hoge.export.json\"") | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment