How do I embed icons into text within Squib, the Ruby gem? - ruby

I added this block to my deck.rb:
text(str: 'Gain 1 :tribute:') do |embed|
embed.svg key: ':tribute:', file: 'tribute.svg'
end
However, this puts "Gain 1 [my icon here]" into the top left of every card, but not where the card text says "Gain 1 tribute."
If I add this line, in an attempt to make it specify the "Ability" column in my .csv file:
%w(Ability).each do |key|
Then I get an error message:
"Syntax error, unexpected end-of-input, expecting keyword_end."
What do I need to add to my deck.rb, exactly, in order to make it use the tribute.svg icon wherever cards within the Ability column have the text, "Gain 1 tribute"?
Here's my current deck.rb:
require 'squib'
require 'game_icons'
Squib::Deck.new(cards: 4, layout: %w(hand.yml layout.yml)) do
background color: '#FFFFFF'
data = csv file: 'country.csv'
png file: data['Art'], layout: 'Art'
%w(Title Ability Quote Type Subtype).each do |key|
text str: data[key], layout: key, markup: true
end
%w(Tribute Power Dominion).each do |key|
svg file: "#{key.downcase}.svg", layout: "#{key}Icon"
text str: data[key], layout: key
end
text(str: 'Gain 1 :tribute:', x: 275, y: 745) do |embed|
embed.svg key: ':tribute:', file: 'tribute.svg'
end
save_png prefix: 'country_'
end

The text method needs to have x and y specified. Like this:
text(str: 'Gain 1 :tribute:', x: 300, y: 500) do |embed|
embed.svg key: ':tribute:', file: 'tribute.svg'
end
As for the syntax error, every do needs an end, because you're defining a block. Although that part seems unrelated to the first part of your question. The snipped %w(Ability).each seems silly to me because that's just iterating over a 1-element array.

Related

How can I use the ruamel.yaml rtsc mode?

I've been working on creating a YAML re-formatter based on ruamel.yaml (which you can see here).
I'm currently using version 0.17.20.
Cleaning up comments and whitespace has been difficult. I want to:
ensure there is only one space before the # for EOL comments
align full line comments with the key or item immediately following
remove duplicate blank lines so there is at most one blank line
To get closer to achieving that, I have a custom Emitter class where I extend write_comment to adjust the comments just before writing with super().write_comment(...). However, the Emitter does not know about which key or item comes next because comments are generally attached as post comments.
As I've studied the ruamel.yaml code to figure out how to do this, I found the rtsc mode (Round Trip Split Comments) which looks fantastic because it separates EOLComment, BlankLineComment and FullLineComment instead of lumping them together.
From what I can tell, the Parser and Scanner have been adjusted to capture the comments. So, loading is (mostly?) implemented with this "NEWCMNT" implementation. But Emitter.write_comment expects CommentToken instead of comment line numbers, so dumping does not work yet.
If I update my Emitter.write_comment method, is that enough to finish dumping? Or what else might be necessary? In one of my tries, I ran into a sys.exit in ScannedComments.assign_eol() - what else is needed to finish that?
PS: I wouldn't normally ask how to collaborate on StackOverflow, but this is not a bug report or a feature request, and I'm trying/failing to use a new (undocumented) feature, so I'm filing this here instead of sourceforge.
rtsc is work in progress cq work started but unfinished. It's internals will almost certainly change.
Two of the three points you indicate can relatively easy be implemented:
set the column of each comment to 0 ( by recursively going over a loaded data structure similar to here ) if the column is before the position of the end of the value on a line, you'll get one space between the value and the column
at the same time doing the recursion in the previous point. Take each comment value and do something like:
value = '\n'.join(line.strip() for line in value.splitlines()
while '\n\n\n' in value:
value = value.replace('\n\n\n', '\n\n')
The indentation to the following element is difficult, depends on the
data structure etc. Given that these are full line comments, I suggest
you do some postprocessing of the YAML document you generate:
find a full line comment, gather full line comments until next line is
not full line comment (i.e. some "real" YAML). Since full line comments
are in column[0] if the previous stuff is applied, you don't have to
track if you are in a (multi-line) literal or folded scalar string where
one of the lines happens to start with #
determine number of spaces
before real YAML and apply these to the full line comments.
import sys
import ruamel.yaml
yaml_str = """\
# the following is a example YAML doc
a:
- b: 42
# collapse multiple empty lines
c: |
# this is not a comment
it is the first line of a block style literal scalar
processing this gobbles a newline which doesn't go into a comment
# that is unless you have a (dedented) comment directly following
d: 42 # and some non-full line comment
e: # another one
# and some more comments to align
f: glitter in the dark near the Tannhäuser gate
"""
def redo_comments(d):
def do_one(comment):
if not comment:
return
comment.column = 0
value = '\n'.join(line.strip() for line in comment.value.splitlines()) + '\n'
while '\n\n\n' in value:
value = value.replace('\n\n\n', '\n\n')
comment.value = value
def do_values(v):
for x in v:
for comment in x:
do_one(comment)
def do_loc(v):
if v is None:
return
do_one(v[0])
if not v[1]:
return
for comment in v[1]:
do_one(comment)
if isinstance(d, dict):
do_loc(d.ca.comment)
do_values(d.ca.items.values())
for val in d.values():
redo_comments(val)
elif isinstance(d, list):
do_values(d.ca.items.values())
for elem in d:
redo_comments(elem)
def realign_full_line_comments(s):
res = []
buf = []
for line in s.splitlines(True):
if not buf:
if line and line[0] == '#':
buf.append(line)
else:
res.append(line)
else:
if line[0] in '#\n':
buf.append(line)
else:
# YAML line, determine indent
count = 0
while line[count] == ' ':
count += 1
if count > len(line):
break # superfluous?
indent = ' ' * count
for cline in buf:
if cline[0] == '\n': # empty
res.append(cline)
else:
res.append(indent + cline)
buf = []
res.append(line)
return ''.join(res)
yaml = ruamel.yaml.YAML()
# yaml.indent(mapping=4, sequence=4, offset=2)
# yaml.preserve_quotes = True
data = yaml.load(yaml_str)
redo_comments(data)
yaml.dump(data, sys.stdout, transform=realign_full_line_comments)
which gives:
# the following is a example YAML doc
a:
- b: 42
# collapse multiple empty lines
c: |
# this is not a comment
it is the first line of a block style literal scalar
processing this gobbles a newline which doesn't go into a comment
# that is unless you have a (dedented) comment directly following
d: 42 # and some non-full line comment
e: # another one
# and some more comments to align
f: glitter in the dark near the Tannhäuser gate

Combine remote images to one image using ruby-vips

I had a template image and need to append on that a specific images on X , Y positions. Is there any equivalent to that function in rmagick
ImageList.new("https://365psd.com/images/istock/previews/8479/84796157-football-field-template-with-goal-on-top-view.jpg")
and draw on that other images and generate one image.
You can read and write URIs in ruby-vips like this:
#!/usr/bin/ruby
require "vips"
require "down"
def new_from_uri(uri)
byte_source = Down.open uri
source = Vips::SourceCustom.new
source.on_read do |length|
puts "reading #{length} bytes from #{uri} ..."
byte_source.read length
end
source.on_seek do |offset, whence|
puts "seeking to #{offset}, #{whence} in #{uri}"
byte_source.seek(offset, whence)
end
return Vips::Image.new_from_source source, "", access: :sequential
end
a = new_from_uri "https://upload.wikimedia.org/wikipedia/commons/a/a6/Big_Ben_Clock_Face.jpg"
b = new_from_uri "https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png"
out = a.composite b, "over", x: 100, y: 100
out.write_to_file "x.jpg"
If you watch the console output you can see it loading the two source images and interleaving the pixels. It makes this output:
The docs on Vips::Source have more details.

YAML deserializer with position information?

Does anyone know of a YAML deserializer that can provide position information for the constructed objects?
I know how to deserialize a YAML file into a Java object. Simple instructions on http://yamlbeans.sourceforge.net/.
However, I want to do some algorithmic validation on the deserialized object and report error back to the user pointing to the position in the YAML that cause the error.
Example:
=========YAML file==========
name: Nathan Sweet
age: 28
address: 4011 16th Ave S
=======JAVA class======
public class Contact {
public String name;
public int age;
public String address;
}
Imagine if I want to first load the yaml into Contact class and then validate the address against some repository and error back if its invalid. Something like:
'Line 3 Column 9: The address does not match valid entry in the database'
The problem is, currently there is no way to get the position inside a deserialized object from YAML.
Anyone know a solution to this issue?
Most YAML parsers, if they keep any information about positions around they drop it while constructing the language native objects.
In ruamel.yaml ¹, I keep more information around because I want to be able to round-trip with minimal loss of original layout (e.g. keeping comments and key order in mappings).
I don't keep information on individual key-value pairs, but I do on the "upper-left" position of a mapping². Because of the kept order of the mapping items you can give some rather nice feedback. Given an input file:
- name: anthon
age: 53
adres: Rijn en Schiekade 105
- name: Nathan Sweet
age: 28
address: 4011 16th Ave S
And a program that you call with the input file as argument:
#! /usr/bin/env python2.7
# coding: utf-8
# http://stackoverflow.com/questions/30677517/yaml-deserializer-with-position-information?noredirect=1#comment49491314_30677517
import sys
import ruamel.yaml
up_arrow = '↑'
def key_error(key, value, line, col, error, e='E'):
print('E[{}:{}]: {}'.format(line, col, error))
print('{}{}: {}'.format(' '*col, key, value))
print('{}{}'.format(' '*(col), up_arrow))
print('---')
def value_error(key, value, line, col, error, e='E'):
val_col = col + len(key) + 2
print('{}[{}:{}]: {}'.format(e, line, val_col, error))
print('{}{}: {}'.format(' '*col, key, value))
print('{}{}'.format(' '*(val_col), up_arrow))
print('---')
def value_warning(key, value, line, col, error):
value_error(key, value, line, col, error, e='W')
class Contact(object):
def __init__(self, vals):
for offset, k in enumerate(vals):
self.check(k, vals[k], vals.lc.line+offset, vals.lc.col)
for k in ['name', 'address', 'age']:
if k not in vals:
print('K[{}:{}]: {}'.format(
vals.lc.line+offset, vals.lc.col, "missing key: "+k
))
print('---')
def check(self, key, value, line, col):
if key == 'name':
if value[0].lower() == value[0]:
value_error(key, value, line, col,
'value should start with uppercase')
elif key == 'age':
if value < 50:
value_warning(key, value, line, col,
'probably too young for knowing ALGOL 60')
elif key == 'address':
pass
else:
key_error(key, value, line, col,
"unexpected key")
data = ruamel.yaml.load(open(sys.argv[1]), Loader=ruamel.yaml.RoundTripLoader)
for x in data:
contact = Contact(x)
giving you E(rrors), W(arnings) and K(eys missing):
E[0:8]: value should start with uppercase
name: anthon
↑
---
E[2:2]: unexpected key
adres: Rijn en Schiekade 105
↑
---
K[2:2]: missing key: address
---
W[4:7]: probably too young for knowing ALGOL 60
age: 28
↑
---
Which you should be able to parser in a calling program in any language to give feedback. The check method of course need adjusting to your requirements. This is not as nice as being to do that in the language the rest of your application is in, but it might be better than nothing.
In my experience handling the above format is certainly simpler than extending an existing (open source) YAML parser.
¹ Disclaimer: I am the author of that package
² I want to use that kind of information at some point to preserve spurious newlines, inserted for readability
In python, you can readily write custom Dumper/Loader objects and use them to load (or dump) your yaml code. You can have these objects track the file/line info:
import yaml
from collections import OrderedDict
class YamlOrderedDict(OrderedDict):
"""
An OrderedDict that was loaded from a yaml file, and is annotated
with file/line info for reporting about errors in the source file
"""
def _annotate(self, node):
self._key_locs = {}
self._value_locs = {}
nodeiter = node.value.__iter__()
for key in self:
subnode = nodeiter.next()
self._key_locs[key] = subnode[0].start_mark.name + ':' + \
str(subnode[0].start_mark.line+1)
self._value_locs[key] = subnode[1].start_mark.name + ':' + \
str(subnode[1].start_mark.line+1)
def key_loc(self, key):
try:
return self._key_locs[key]
except AttributeError, KeyError:
return ''
def value_loc(self, key):
try:
return self._value_locs[key]
except AttributeError, KeyError:
return ''
# Use YamlOrderedDict objects for yaml maps instead of normal dict
yaml.add_representer(OrderedDict, lambda dumper, data:
dumper.represent_dict(data.iteritems()))
yaml.add_representer(YamlOrderedDict, lambda dumper, data:
dumper.represent_dict(data.iteritems()))
def _load_YamlOrderedDict(loader, node):
rv = YamlOrderedDict(loader.construct_pairs(node))
rv._annotate(node)
return rv
yaml.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, _load_YamlOrderedDict)
Now when you read a yaml file, any mapping objects will be read as a YamlOrderedDict, which allows looking up the file location of keys in the mapping object. You can also add an iterator method like:
def iter_with_lines(self):
for key, val in self.items():
yield (key, val, self.key_loc(key))
...and now you can write a loop like:
for key,value,location in obj.iter_with_lines():
# iterate through the key/value pairs in a YamlOrderedDict, with
# the source file location

Sublime Text - Goto line and column

Currently, the Go to line shortcut (CTRL+G in windows/linux) only allows to navigate to a specific line.
It would be nice to optionally allow the column number to be specified after comma, e.g.
:30,11 to go to line 30, column 11
Is there any plugin or custom script to achieve this?
Update 3
This is now part of Sublime Text 3 starting in build number 3080:
Goto Anything supports :line:col syntax in addition to :line
For example, you can use :30:11 to go to line 30, column 11.
Update 1 - outdated
I just realized you've tagged this as sublime-text-3 and I'm using 2. It may work for you, but I haven't tested in 3.
Update 2 - outdated
Added some sanity checks and some modifications to GotoRowCol.py
Created github repo sublimetext2-GotoRowCol
Forked and submitted a pull request to commit addition to package_control_channel
Edit 3: all requirements of the package_control repo have been met. this package is now available in the package repository in the application ( install -> GotoRowCol to install ).
I too would like this feature. There's probably a better way to distribute this but I haven't really invested a lot of time into it. I read through some plugin dev tutorial really quick, and used some other plugin code to patch this thing together.
Select the menu option Tools -> New Plugin
A new example template will open up. Paste this into the template:
import sublime, sublime_plugin
class PromptGotoRowColCommand(sublime_plugin.WindowCommand):
def run(self, automatic = True):
self.window.show_input_panel(
'Enter a row and a column',
'1 1',
self.gotoRowCol,
None,
None
)
pass
def gotoRowCol(self, text):
try:
(row, col) = map(str, text.split(" "))
if self.window.active_view():
self.window.active_view().run_command(
"goto_row_col",
{"row": row, "col": col}
)
except ValueError:
pass
class GotoRowColCommand(sublime_plugin.TextCommand):
def run(self, edit, row, col):
print("INFO: Input: " + str({"row": row, "col": col}))
# rows and columns are zero based, so subtract 1
# convert text to int
(row, col) = (int(row) - 1, int(col) - 1)
if row > -1 and col > -1:
# col may be greater than the row length
col = min(col, len(self.view.substr(self.view.full_line(self.view.text_point(row, 0))))-1)
print("INFO: Calculated: " + str({"row": row, "col": col})) # r1.01 (->)
self.view.sel().clear()
self.view.sel().add(sublime.Region(self.view.text_point(row, col)))
self.view.show(self.view.text_point(row, col))
else:
print("ERROR: row or col are less than zero") # r1.01 (->)
Save the file. When the "Save As" dialog opens, it should be in the the Sublime Text 2\Packages\User\ directory. Navigate up one level to and create the folder Sublime Text 2\Packages\GotoRowCol\ and save the file with the name GotoRowCol.py.
Create a new file in the same directory Sublime Text 2\Packages\GotoRowCol\GotoRowCol.sublime-commands and open GotoRowCol.sublime-commands in sublime text. Paste this into the file:
[
{
"caption": "GotoRowCol",
"command": "prompt_goto_row_col"
}
]
Save the file. This should register the GotoRowCol plugin in the sublime text system. To use it, hit ctrl + shift + p then type GotoRowCol and hit ENTER. A prompt will show up at the bottom of the sublime text window with two number prepopulated, the first one is the row you want to go to, the second one is the column. Enter the values you desire, then hit ENTER.
I know this is a complex operation, but it's what I have right now and is working for me.

How to write some value to a text file in ruby based on position

I need some help is some unique solution. I have a text file in which I have to replace some value based on some position. This is not a big file and will always contain 5 lines with fixed number of length in all the lines at any given time. But I have to specficaly replace soem text in some position only. Further, i can also put in some text in required position and replace that text with required value every time. I am not sure how to implement this solution. I have given the example below.
Line 1 - 00000 This Is Me 12345 trying
Line 2 - 23456 This is line 2 987654
Line 3 - This is 345678 line 3 67890
Consider the above is the file I have to use to replace some values. Like in line 1, I have to replace '00000' with '11111' and in line 2, I have to replace 'This' with 'Line' or any require four digit text. The position will always remain the same in text file.
I have a solution which works but this is for reading the file based on position and not for writing. Can someone please give a solution similarly for wrtiting aswell based on position
Solution for reading the file based on position :
def read_var file, line_nr, vbegin, vend
IO.readlines(file)[line_nr][vbegin..vend]
end
puts read_var("read_var_from_file.txt", 0, 1, 3) #line 0, beginning at 1, ending at 3
#=>308
puts read_var("read_var_from_file.txt", 1, 3, 6)
#=>8522
I have also tried this solution for writing. This works but I need it to work based on position or based on text present in the specific line.
Explored solution to wirte to file :
open(Dir.pwd + '/Files/Try.txt', 'w') { |f|
f << "Four score\n"
f << "and seven\n"
f << "years ago\n"
}
I made you a working sample anagraj.
in_file = "in.txt"
out_file = "out.txt"
=begin
=>contents of file in.txt
00000 This Is Me 12345 trying
23456 This is line 2 987654
This is 345678 line 3 67890
=end
def replace_in_file in_file, out_file, shreds
File.open(out_file,"wb") do |file|
File.read(in_file).each_line.with_index do |line, index|
shreds.each do |shred|
if shred[:index]==index
line[shred[:begin]..shred[:end]]=shred[:replace]
end
end
file << line
end
end
end
shreds = [
{index:0, begin:0, end:4, replace:"11111"},
{index:1, begin:6, end:9, replace:"Line"}
]
replace_in_file in_file, out_file, shreds
=begin
=>contents of file out.txt
11111 This Is Me 12345 trying
23456 Line is line 2 987654
This is 345678 line 3 67890
=end

Resources