Last active
August 29, 2015 14:02
-
-
Save colinta/212dc1858bf86c05c5b1 to your computer and use it in GitHub Desktop.
Skip to `generate.rb` to see the good stuff, and the output follows.
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
class AndroidTag | |
attr_accessor :is_root | |
def initialize(tag, *args, &block) | |
@buffer = [] | |
@attrs = {} | |
@raw_tag = tag.to_s | |
if rename = AndroidXml.tags[tag.to_s][:rename] | |
@tag = rename | |
else | |
@tag = tag.to_s.gsub('_', '-') | |
end | |
args.each do |arg| | |
if arg.is_a?(Hash) | |
@attrs.merge!(arg) | |
else | |
raise ArgumentError.new("Unknown argument #{arg.inspect} in #{self.class}#new") | |
end | |
end | |
@generate = block | |
end | |
def method_missing(method_name, *args, &block) | |
tag = AndroidTag.new(method_name, *args, &block) | |
@buffer << tag | |
tag | |
end | |
def attrs(whitespace) | |
attrs = {} | |
if is_root | |
attrs.merge!(AndroidXml.root_tag[:defaults]) | |
end | |
attrs.merge!(AndroidXml.tags[@raw_tag][:defaults]) | |
attrs.merge!(@attrs) | |
output = '' | |
is_first = true | |
attrs.each do |key, value| | |
next if value.nil? | |
key = key.to_s | |
if AndroidXml.tags[@tag][:attrs].key?(key) | |
xml_key = AndroidXml.tags[@tag][:attrs][key] | |
elsif key.to_s.include?(':') | |
xml_key = key.to_s | |
else | |
xml_key = "android:#{key}" | |
end | |
if is_first | |
output << " #{xml_key}=\"#{value}\"" | |
is_first = false | |
else | |
output << "\n#{whitespace}#{xml_key}=\"#{value}\"" | |
end | |
end | |
output | |
end | |
def generate(tab='') | |
whitespace = "#{tab} #{' ' * @tag.length}" | |
output = "#{tab}<#{@tag}#{attrs(whitespace)}" | |
if @generate | |
inside = generate_block(tab + AndroidXml.tab) | |
if !inside || inside.strip.empty? | |
output << " />\n" | |
else | |
output << ">" | |
if inside.lstrip.start_with?('<') | |
output << "\n" << inside << "#{tab}</#{@tag}>\n" | |
else | |
output << inside << "</#{@tag}>\n" | |
end | |
end | |
else | |
output <<" />\n" | |
end | |
output | |
end | |
def generate_block(tab='') | |
return @block_output if @block_output | |
output = '' | |
if @generate | |
text = instance_exec(&@generate) | |
@buffer.each do |tag| | |
output << tag.generate(tab) | |
end | |
if text.is_a?(String) | |
output << text | |
end | |
end | |
@block_output = output | |
end | |
def to_s | |
generate | |
end | |
def to_ary | |
[to_s] | |
end | |
def out | |
puts to_s | |
end | |
end | |
class AndroidXml < AndroidTag | |
ROOT = '__root_node_with_a_long_special_name__' | |
class << self | |
attr_writer :tab | |
def files | |
@files ||= [] | |
end | |
def write_all | |
files.each do |xml_file| | |
File.open(xml_file.filename, 'w') do |f| | |
f.write(xml_file.to_s) | |
end | |
puts "✓ #{xml_file.filename}" | |
end | |
end | |
def output_all | |
files.each do |xml_file| | |
xml_file.out | |
end | |
end | |
def tags | |
@tags ||= Hash.new do |hash, key| | |
hash[key] = { | |
attrs: {}, | |
defaults: {}, | |
rename: nil, | |
} | |
end | |
end | |
def tab | |
@tab ||= ' ' | |
end | |
def setup(&block) | |
instance_exec(&block) | |
end | |
def root_tag | |
tags[AndroidXml::ROOT] | |
end | |
def tag(name, &block) | |
context_was = @context | |
if name.is_a?(Hash) | |
@context = nil | |
name.each do |shortcut, tag_name| | |
raise "There can be only one key-value pair" if @context | |
@context = shortcut.to_s | |
tags[@context][:rename] = tag_name.to_s | |
end | |
else | |
@context = name.to_s | |
end | |
yield | |
@context = context_was | |
end | |
def rename(attrs) | |
if attrs.is_a?(Hash) | |
attrs.each do |attr_name, attr_rename| | |
tags[@context][:attrs][attr_name.to_s] = attr_rename.to_s | |
end | |
else | |
tags[@context][:attrs][attrs.to_s] = attrs.to_s | |
end | |
end | |
def defaults(attrs) | |
tags[@context][:defaults].merge!(attrs) | |
end | |
def root(&block) | |
tag(ROOT, &block) | |
end | |
end | |
attr :filename | |
def initialize(filename, &block) | |
@filename = filename | |
super(nil, &block) | |
AndroidXml.files << self | |
end | |
def method_missing(method_name, *args, &block) | |
raise "There can be only one (new: #{method_name}, old: #{@root})" if @root | |
@root = super | |
@root.is_root = true | |
@root | |
end | |
def generate | |
output = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" | |
output << "<!-- Do not edit this file. It was generated by AndroidXml -->\n" | |
output << generate_block | |
end | |
def out | |
puts "---- filename: #{@filename} ----" | |
super | |
puts "---------------#{'-' * @filename.to_s.length}-----" | |
end | |
end |
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
require_relative './android_xml' | |
AndroidXml.setup do | |
# assign the xmlns to all root nodes | |
root do | |
defaults 'xmlns:android' => 'http://schemas.android.com/apk/res/android' | |
end | |
tag :resources do | |
# disable the xmlns attribute on the resource node | |
defaults 'xmlns:android' => nil | |
end | |
tag :string do | |
rename :name # suppress 'android:' prefix | |
end | |
tag :manifest do | |
rename :package | |
end | |
# creates a "tag shortcut" | |
tag :main_action => 'action' do | |
defaults name: 'android.intent.action.MAIN' | |
end | |
tag :launcher_category => 'category' do | |
defaults name: 'android.intent.category.LAUNCHER' | |
end | |
end | |
AndroidXml.new('AndroidManifest.xml') do | |
manifest package: 'com.colinta.MyFirstAndroidApp', versionCode: 1, versionName: 1.0 do | |
uses_sdk minSdkVersion: 11 | |
application label: '@string/app_name', icon: '@drawable/ic_launcher' do | |
activity name: 'MainActivity', label: '@string/app_name' do | |
intent_filter do | |
main_action | |
launcher_category | |
end | |
end | |
activity name: 'DisplayMessageActivity', label: '@string/app_name', parentActivityName: 'com.colinta.MyFirstAndroidApp.MainActivity' | |
end | |
end | |
end | |
AndroidXml.new('res/values/strings.xml') do | |
resources do | |
string(name: 'app_name') { 'Harroo' } | |
string(name: 'hello') { 'Well, Harroo!' } | |
string(name: 'portrait') { 'portrait' } | |
string(name: 'landscape') { 'landscape' } | |
string(name: 'button_send') { 'Send' } | |
string(name: 'action_search') { 'Search' } | |
string(name: 'action_settings') { 'Settings' } | |
string(name: 'title_activity_main') { 'MainActivity' } | |
end | |
end | |
AndroidXml.new('res/menu/main_activity_actions.xml') do | |
menu do | |
item id: '@+id/action_search', | |
icon: '@drawable/ic_action_search', | |
title: '@string/action_search', | |
showAsAction: "ifRoom" | |
item id: '@+id/action_settings', | |
title: '@string/action_settings', | |
showAsAction: "never" | |
end | |
end | |
# AndroidXml.write_all | |
AndroidXml.output_all | |
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
---- filename: AndroidManifest.xml ---- | |
<?xml version="1.0" encoding="utf-8"?> | |
<!-- Do not edit this file. It was generated by AndroidXml --> | |
<manifest xmlns:android="http://schemas.android.com/apk/res/android" | |
package="com.colinta.MyFirstAndroidApp" | |
android:versionCode="1" | |
android:versionName="1.0"> | |
<uses-sdk android:minSdkVersion="11" /> | |
<application android:label="@string/app_name" | |
android:icon="@drawable/ic_launcher"> | |
<activity android:name="MainActivity" | |
android:label="@string/app_name"> | |
<intent-filter> | |
<action android:name="android.intent.action.MAIN" /> | |
<category android:name="android.intent.category.LAUNCHER" /> | |
</intent-filter> | |
</activity> | |
<activity android:name="DisplayMessageActivity" | |
android:label="@string/app_name" | |
android:parentActivityName="com.colinta.MyFirstAndroidApp.MainActivity" /> | |
</application> | |
</manifest> | |
--------------------------------------- | |
---- filename: res/values/strings.xml ---- | |
<?xml version="1.0" encoding="utf-8"?> | |
<!-- Do not edit this file. It was generated by AndroidXml --> | |
<resources> | |
<string name="app_name">Harroo</string> | |
<string name="hello">Well, Harroo!</string> | |
<string name="portrait">portrait</string> | |
<string name="landscape">landscape</string> | |
<string name="button_send">Send</string> | |
<string name="action_search">Search</string> | |
<string name="action_settings">Settings</string> | |
<string name="title_activity_main">MainActivity</string> | |
</resources> | |
------------------------------------------ | |
---- filename: res/menu/main_activity_actions.xml ---- | |
<?xml version="1.0" encoding="utf-8"?> | |
<!-- Do not edit this file. It was generated by AndroidXml --> | |
<menu xmlns:android="http://schemas.android.com/apk/res/android"> | |
<item android:id="@+id/action_search" | |
android:icon="@drawable/ic_action_search" | |
android:title="@string/action_search" | |
android:showAsAction="ifRoom" /> | |
<item android:id="@+id/action_settings" | |
android:title="@string/action_settings" | |
android:showAsAction="never" /> | |
</menu> | |
------------------------------------------------------ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
And yes, this will probably be my first rm-android gem, once the beta is out :-)