|  12-16-2020, 02:06 PM | #106 | 
| Grand Sorcerer            Posts: 12,525 Karma: 8065948 Join Date: Jan 2010 Location: Notts, England Device: Kobo Libra 2 | 
			
			@capink: a random thought. I think that you can get a usable form of action branching if you add an option to run an action only if a template returns a value. You would add this option as another column in the chain links table. Doing this would permit creating a chain where actions are run only if they make sense for the current book. You could simplify things by requiring use of a zero-parameter stored template. That would ensure you only need a name in the table, and as a side effect would make it easier for the user to test the template. You could go a bit further and add a check for what the template returns (another column?). If the template returns that value then the action is run. If it does not, the action isn't. That would permit using the same stored template in multiple places. | 
|   |   | 
|  12-16-2020, 03:49 PM | #107 | |
| Wizard            Posts: 1,216 Karma: 1995558 Join Date: Aug 2015 Device: Kindle | Quote: 
 The current code even have an instance variable in the chain_loop called step_length (default is 1), that is used by the chain loop to jump to the next action (and is ofcourse reset to default after each jump). I was not sure whether this would be useful or not so I deferred it. This bit about a separate column for a check is not clear to me. | |
|   |   | 
|  12-16-2020, 05:04 PM | #108 | 
| Grand Sorcerer            Posts: 12,525 Karma: 8065948 Join Date: Jan 2010 Location: Notts, England Device: Kobo Libra 2 | 
			
			The idea is to be able to express the following pseudo-code: Code: if template() == 'a' then do action 1 fi; if template() == 'b' then do action 2 fi; if template() == 'c' then do action 3 fi; Code: v = template_action('foo()')
if v == X then do action 1 fi
if v == X then do action 2 fi
if v == Y then do action 3 fiCode: v = template1_action('foo()')
if v == X then do action 1 fi
if v == X then do action 2 fi
if v == Y then do action 3 fi
v = template1_action('bar()')
if v == Z then do action 4 fi
if v == W then do action 5 fi
 | 
|   |   | 
|  12-16-2020, 09:29 PM | #109 | 
| Wizard            Posts: 1,026 Karma: 500000 Join Date: Jun 2015 Device: Rocketbook, kobo aura h2o, kobo forma, kobo libra color | 
			
			While being able to branch would be very powerful, and calling different chains from the end of the branching chain would work and is something I might use, I'd still rather be able to feed a calculated constant down a chain. I'm thinking something along the lines of maybe code in a module that sets a variable in a special way, and then be able to use that variable in a template where a constant would go in a form in the chain (like, a tag field for instance). | 
|   |   | 
|  12-16-2020, 11:01 PM | #110 | |
| Wizard            Posts: 1,216 Karma: 1995558 Join Date: Aug 2015 Device: Kindle | Quote: 
 Actually I was thinking of not (just) using templates on book metadata, since mi objects can accept extra information I can do something like this: Code: device_uuid = get_device_uuid()
cmeta1 = {'datatype': 'text', 'is_multiple': {}, 'name': '#device_uuid'}
mi.set_user_metadata('#device_uuid', cmeta1)
mi.set('#device_uuid', device_uuid)Code: {#device_uuid}By the way, the above technique is already used in the plugin. Any action that implements an update_metadata(self, mi) method, is called on every mi object and given the chance to modify it. I have a custom action that inserts a calculated file path, that is not stored in calibre, into the mi object as a #path_to_file field, this field is used by the template feature of the open with action to open the file/folder. Since the file(s) location changes from time to time, I have it mapped into a automatically updated json file (uuid to location mapping). Finally, when fetching mi object in case we have multiple books selected, which one do we use? the current index? would the metadata for that one book provide value for conditional execution of actions? @compurandom: See whether the above point about update_metadata() method satisfies your needs, also the code snippet above it on how to insert arbitrary fields into mi objects | |
|   |   | 
|  12-17-2020, 02:04 AM | #111 | 
| Hedge Wizard            Posts: 802 Karma: 19999999 Join Date: May 2011 Location: UK/Philippines Device: Kobo Touch, Nook Simple | 
				
				Problem Installing Plugin
			 
			
			Hi I spotted the plugin so thought I would have a look at it. I downloaded the file but it will not install. I get the following error. I have Windows 10 Enterprise, Calibre 5.4. Spoiler: 
 | 
|   |   | 
|  12-17-2020, 06:43 AM | #112 | ||||
| Grand Sorcerer            Posts: 12,525 Karma: 8065948 Join Date: Jan 2010 Location: Notts, England Device: Kobo Libra 2 | Quote: 
 Another safer way to insert data: implement "globals" in the formatter. I would extend the formatter to accept a dictionary of name:value pairs. A template would access this dict using a new function, perhaps globals(), that acts similarly to the existing arguments() function, copying the globals to local variables. Example: in your plugin you call the formatter with something like Code: dict = {'a': 2, 'b': 'some string', 'e':'not used'}Code: globals(a, b='no string', c='default') In addition to the above, you define a new built-in action to set the value of a global. It would be almost identical to "Single Field Edit". That along with the branching idea might solve @compurandom's problem because he could use branching to set variables he can use in templates. In fact, he might not need branching. There might be a way to combine the globals dict with branching. For example, branching could test the global variable '_branch_condition_'. Another way would be to use two columns, one for the global name and one for the value. Quote: 
 Regarding extra information, perhaps things like the chain name, the action name, the action comment, the value controlling branching, and action-specific data such as the file path you mention below. FWIW: I think automatically-added globals should have a distinctive name to avoid collisions, perhaps one that begins and ends with '_'. Quote: 
 Quote: 
 | ||||
|   |   | 
|  12-17-2020, 10:53 AM | #113 | 
| Wizard            Posts: 1,216 Karma: 1995558 Join Date: Aug 2015 Device: Kindle | |
|   |   | 
|  12-17-2020, 11:05 AM | #114 | ||
| Wizard            Posts: 1,216 Karma: 1995558 Join Date: Aug 2015 Device: Kindle | Quote: 
  . Just to be sure we are on the same page, I will define a global dict for each chain, which have some predefined values and also allows the user to insert his own arbitrary variables Code: self.global = {
    _chain_name: ....,
    _action_name: ....,
    user_var1: ......,
    ......
}The user can access this using a template like this: Code: program: globals('user_var1')Code: template_output = SafeFormat().safe_format(template, mi, TEMPLATE_ERROR, mi, chain_loop.global) One problem here is that since this is a name/value pair, to test conditions you should have two corresponding columns, one for name and another for value. I much prefer that it is one column whose value is tested against a predefined name in the global dict e.g. _condition. Adding multiple columns is confusing, one column with keywords is more accessible IMO. I am open to feedback from others on this. Edit: striked out since it would not work with one column. Quote: 
 I think a table widget with two columns is more suitable since number and names of variables are not preset. Somewhat similar to table widgets used for chains and actions, with ability to add/remove rows (except for protected name like _chain_name). I never use devices with calibre (non rootable kindle, only email one book at a time since collections are useless), so my knowledge here is very limited. I only went for device_uuid as way to prevent name clashes if the user has two similar devices. I will defer to others decide whether a device_uuid is needed. Last edited by capink; 12-17-2020 at 11:35 AM. | ||
|   |   | 
|  12-17-2020, 12:01 PM | #115 | ||||
| Grand Sorcerer            Posts: 12,525 Karma: 8065948 Join Date: Jan 2010 Location: Notts, England Device: Kobo Libra 2 | Quote: 
 Quote: 
 Note that Code: program: globals(user_var1='some_default') Code: {somefield:'globals(user_var1);'rest of template''}Quote: 
 Code:     def safe_format(self, fmt, kwargs, error_value, book,
                    column_name=None, template_cache=None,
                    strip_results=True, template_functions=None,
                    global_vars={}):Code: template_output = SafeFormat().safe_format(template, mi, TEMPLATE_ERROR,
                                           mi, global_vars=chain_loop.global)Quote: 
 If you run from source, attached is a version of calibre.utils.formatter.py that implements globals. For fun, it also implements for loops. Example: Code: for v in expression:
    statement [; statement]*
rofEDIT: I will submit the formatter changes to Kovid if you think they work for you. EDIT 2: Replaced the attachment. I had left a print statement in the original. EDIT 3: Removed the attachment now that the files are in the official calibre source. Last edited by chaley; 12-19-2020 at 06:06 AM. Reason: Removed the attachment | ||||
|   |   | 
|  12-17-2020, 01:07 PM | #116 | ||
| Wizard            Posts: 1,216 Karma: 1995558 Join Date: Aug 2015 Device: Kindle | Quote: 
  . I will test tonight as soon I have the time. First mission is to remove the update_metadata and replace with template globals. Quote: 
 That is something I wondered about. Will certainly useful for a lot people  . | ||
|   |   | 
|  12-17-2020, 02:50 PM | #117 | 
| Custom User Title            Posts: 11,351 Karma: 79528341 Join Date: Oct 2018 Location: Canada Device: Kobo Libra H2O, formerly Aura HD |  | 
|   |   | 
|  12-17-2020, 02:58 PM | #118 | 
| Wizard            Posts: 1,216 Karma: 1995558 Join Date: Aug 2015 Device: Kindle | 
			
			OK, I am testing this now using calibre-debug -e script.py Code: def test():
    from calibre.library import db as DB
    from calibre.ebooks.metadata.book.formatter import SafeFormat
    db = DB().new_api
    book_id = list(db.all_book_ids())[0]
    mi = db.get_proxy_metadata(book_id)
    TEMPLATE_ERROR = 'TEMPLATE_ERROR: '
    my_globals = {'path_to_file': '/home/user', 'b': 2}
    template = "program: globals(path_to_file)"
    template_output = SafeFormat().safe_format(template, mi, TEMPLATE_ERROR, mi, global_vars=my_globals)
    print('debug: template_output: ({})'.format(template_output))
        
test()Code: debug: template_output: () | 
|   |   | 
|  12-17-2020, 03:01 PM | #119 | 
| Grand Sorcerer            Posts: 12,525 Karma: 8065948 Join Date: Jan 2010 Location: Notts, England Device: Kobo Libra 2 | 
			
			Your template "program: globals(path_to_file)" didn't do anything with the variable it set. Try "program: globals(path_to_file); path_to_file", which will  return the value of the variable (I hope). EDIT: I suppose I could have "globals" return the last variable it set instead of the empty string but I am not convinced that is a good idea. | 
|   |   | 
|  12-17-2020, 03:15 PM | #120 | 
| Wizard            Posts: 1,216 Karma: 1995558 Join Date: Aug 2015 Device: Kindle | 
			
			Thanks, it is working now. It works for me either way. It is your call.
		 | 
|   |   | 
|  | 
| 
 | 
|  Similar Threads | ||||
| Thread | Thread Starter | Forum | Replies | Last Post | 
| Action Chains Resources | capink | Plugins | 80 | 09-18-2025 04:45 AM | 
| [Editor Plugin] Editor Chains | capink | Plugins | 106 | 06-17-2025 05:36 PM | 
| [GUI Plugin] Noosfere_util, a companion plugin to noosfere DB | lrpirlet | Plugins | 2 | 08-18-2022 03:15 PM | 
| [GUI Plugin] Save Virtual Libraries To Column (GUI) | chaley | Plugins | 14 | 04-04-2021 05:25 AM |